搜索​​​​

清除过滤器
文章
Michael Lei · 五月 12, 2021

通过 ML 与 IntegratedML 运行一些 Covid-19 ICU 预测(第一部分)

关键字:IRIS, IntegratedML, 机器学习, Covid-19, Kaggle  ## 目的 最近,我注意到一个用于预测 Covid-19 患者是否将转入 ICU 的 [Kaggle 数据集](https://www.kaggle.com/S%C3%ADrio-Libanes/covid19/kernels)。 它是一个包含 1925 条病患记录的电子表格,其中有 231 列生命体征和观察结果,最后一列“ICU”为 1(表示是)或 0(表示否)。 任务是根据已知数据预测患者是否将转入 ICU。 这个数据集看起来是所谓的“传统 ML”任务的一个好例子。数据看上去数量合适,质量也相对合适。它可能更适合在 [IntegratedML 演示](https://github.com/intersystems-community/integratedml-demo-template)套件上直接应用,那么,基于普通 ML 管道与可能的 IntegratedML 方法进行快速测试,最简单的方法是什么?   ## 范围 我们将简要地运行一些常规 ML 步骤,如: * 数据 EDA  * 特征选择 * 模型选择 * 通过网格搜索调整模型参数 与 * 通过 SQL 实现的整合 ML 方法。 它通过 Docker-compose 等方式运行于 AWS Ubuntu 16.04 服务器。   ## 环境 我们将重复使用 [integredML-demo-template](https://openexchange.intersystems.com/package/integratedml-demo-template) 的 Docker 环境: ![](https://user-images.githubusercontent.com/8899513/85151307-a0d1f280-b221-11ea-81d8-f0e11ca45d4c.PNG) 以下 notebook 文件在“tf2jupyter”上运行,带有 IntegratedML 的 IRIS 在“irismlsrv”上运行。 Docker-compose 在 AWS Ubuntu 16.04 上运行。   ## 数据和任务 该数据集包含收集自 385 名患者的 1925 条记录,每名患者正好 5 条记录。 它共有 231 个列,其中有一列“ICU”是我们的训练和预测目标,其他 230 列都以某种方式用作输入。 ICU 具有二进制值 1 或 0。 除了 2 列看上去是分类字符串(在数据框架中显示为“对象”)外,其他所有列都是数值。 import numpy as np import pandas as pd from sklearn.impute import SimpleImputer import matplotlib.pyplot as plt from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report, roc_auc_score, roc_curve import seaborn as sns sns.set(style="whitegrid") import os for dirname, _, filenames in os.walk('./input'): for filename in filenames: print(os.path.join(dirname, filename)) ./input/datasets_605991_1272346_Kaggle_Sirio_Libanes_ICU_Prediction.xlsx   df = pd.read_excel("./input/datasets_605991_1272346_Kaggle_Sirio_Libanes_ICU_Prediction.xlsx") df   PATIENT_VISIT_IDENTIFIER AGE_ABOVE65 AGE_PERCENTIL GENDER DISEASE GROUPING 1 DISEASE GROUPING 2 DISEASE GROUPING 3 DISEASE GROUPING 4 DISEASE GROUPING 5 DISEASE GROUPING 6 ... TEMPERATURE_DIFF OXYGEN_SATURATION_DIFF BLOODPRESSURE_DIASTOLIC_DIFF_REL BLOODPRESSURE_SISTOLIC_DIFF_REL HEART_RATE_DIFF_REL RESPIRATORY_RATE_DIFF_REL TEMPERATURE_DIFF_REL OXYGEN_SATURATION_DIFF_REL WINDOW ICU 1 60th 0.0 0.0 0.0 0.0 1.0 1.0 ... -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 0-2 1 1 60th 0.0 0.0 0.0 0.0 1.0 1.0 ... -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 2-4 2 1 60th 0.0 0.0 0.0 0.0 1.0 1.0 ... NaN NaN NaN NaN NaN NaN NaN NaN 4-6 3 1 60th 0.0 0.0 0.0 0.0 1.0 1.0 ... -1.000000 -1.000000 NaN NaN NaN NaN -1.000000 -1.000000 6-12 4 1 60th 0.0 0.0 0.0 0.0 1.0 1.0 ... -0.238095 -0.818182 -0.389967 0.407558 -0.230462 0.096774 -0.242282 -0.814433 ABOVE_12 1 ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... 1920 384 50th 1 0.0 0.0 0.0 0.0 0.0 0.0 ... -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 0-2 1921 384 50th 1 0.0 0.0 0.0 0.0 0.0 0.0 ... -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 2-4 1922 384 50th 1 0.0 0.0 0.0 0.0 0.0 0.0 ... -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 4-6 1923 384 50th 1 0.0 0.0 0.0 0.0 0.0 0.0 ... -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 6-12 1924 384 50th 1 0.0 0.0 1.0 0.0 0.0 0.0 ... -0.547619 -0.838384 -0.701863 -0.585967 -0.763868 -0.612903 -0.551337 -0.835052 ABOVE_12 1925 行 × 231 列 df.dtypes PATIENT_VISIT_IDENTIFIER int64 AGE_ABOVE65 int64 AGE_PERCENTIL object GENDER int64 DISEASE GROUPING 1 float64 ... RESPIRATORY_RATE_DIFF_REL float64 TEMPERATURE_DIFF_REL float64 OXYGEN_SATURATION_DIFF_REL float64 WINDOW object ICU int64 Length: 231, dtype: object 当然,设计此问题及其方法有几个选择。 首先,第一个显而易见的选择是,这可以是一个基本的“二元分类”问题。 我们可以将全部 1925 条记录都视为“无状态”个体记录,不管它们是否来自同一患者。 如果我们将 ICU 和其他值都当作数值来处理,这也可以是一个“回归”问题。 当然还有其他可能的方法。 例如,我们可以有一个更深层的视角,即数据集有 385 个不同的短“时间序列”集,每个患者一个。 我们可以将整个数据集分解成 385 个单独的训练集/验证集/测试集,我们是否可以尝试 CNN 或 LSTM 等深度学习模型来捕获每个患者对应的每个集合中隐藏的“症状发展阶段或模式”? 可以的。 这样做的话,我们还可以通过各种方式应用一些数据增强,来丰富测试数据。 但那就是另一个话题了,不在本帖的讨论范围内。 在本帖中,我们将只测试所谓的“传统 ML”方法与 IntegratedML(一种 AutoML)方法的快速运行。    ## “传统”ML 方法? 与大多数现实案例相比,这是一个相对标准的数据集,除了缺少一些值,所以我们可以跳过特征工程部分,直接使用各个列作为特征。 那么,我们直接进入特征选择。 ### **插补缺失数据** 首先,确保所有缺失值都通过简单的插补来填充: df_cat = df.select_dtypes(include=['object']) df_numeric = df.select_dtypes(exclude=['object']) imp = SimpleImputer(missing_values=np.nan, strategy='mean') idf = pd.DataFrame(imp.fit_transform(df_numeric)) idf.columns = df_numeric.columns idf.index = df_numeric.index idf.isnull().sum() ###   ### **特征选择** 我们可以使用数据框架中内置的正态相关函数,来计算每个列的值与 ICU 的相关性。 #### 特征工程 - **相关性** {#featuring-engineering---correlation} idf.drop(["PATIENT_VISIT_IDENTIFIER"],1) idf = pd.concat([idf,df_cat ], axis=1) cor = idf.corr() cor_target = abs(cor["ICU"]) relevant_features = cor_target[cor_target>0.1] # correlation above 0.1 print(cor.shape, cor_target.shape, relevant_features.shape) #relevant_features.index #relevant_features.index.shape 这将列出 88 个特征,它们与 ICU 目标值的相关度大于 0.1。 这些列可以直接用作我们的模型输入 我还运行了其他几个在传统 ML 任务中常用的“特征选择方法”: #### 特征选择 - **卡方** {#feature-selection---Chi-squared} from sklearn.feature_selection import SelectKBest from sklearn.feature_selection import chi2 from sklearn.preprocessing import MinMaxScaler X_norm = MinMaxScaler().fit_transform(X) chi_selector = SelectKBest(chi2, k=88) chi_selector.fit(X_norm, y) chi_support = chi_selector.get_support() chi_feature = X.loc[:,chi_support].columns.tolist() print(str(len(chi_feature)), 'selected features', chi_feature) 88 selected features ['AGE_ABOVE65', 'GENDER', 'DISEASE GROUPING 1', ... ... 'P02_VENOUS_MIN', 'P02_VENOUS_MAX', ... ... RATURE_MAX', 'BLOODPRESSURE_DIASTOLIC_DIFF', ... ... 'TEMPERATURE_DIFF_REL', 'OXYGEN_SATURATION_DIFF_REL'] 特征选择 - **皮尔逊相关**  def cor_selector(X, y,num_feats): cor_list = [] feature_name = X.columns.tolist() # calculate the correlation with y for each feature for i in X.columns.tolist(): cor = np.corrcoef(X[i], y)[0, 1] cor_list.append(cor) # replace NaN with 0 cor_list = [0 if np.isnan(i) else i for i in cor_list] # feature name cor_feature = X.iloc[:,np.argsort(np.abs(cor_list))[-num_feats:]].columns.tolist() # feature selection? 0 for not select, 1 for select cor_support = [True if i in cor_feature else False for i in feature_name] return cor_support, cor_feature cor_support, cor_feature = cor_selector(X, y, 88) print(str(len(cor_feature)), 'selected features: ', cor_feature) 88 selected features: ['TEMPERATURE_MEAN', 'BLOODPRESSURE_DIASTOLIC_MAX', ... ... 'RESPIRATORY_RATE_DIFF', 'RESPIRATORY_RATE_MAX'] #### 特征选择 - **回归特征消除 (RFE)** {#feature-selection---Recursive-Feature-Elimination-(RFE)} from sklearn.feature_selection import RFE from sklearn.linear_model import LogisticRegression rfe_selector = RFE(estimator=LogisticRegression(), n_features_to_select=88, step=100, verbose=5) rfe_selector.fit(X_norm, y) rfe_support = rfe_selector.get_support() rfe_feature = X.loc[:,rfe_support].columns.tolist() print(str(len(rfe_feature)), 'selected features: ', rfe_feature) Fitting estimator with 127 features. 88 selected features: ['AGE_ABOVE65', 'GENDER', ... ... 'RESPIRATORY_RATE_DIFF_REL', 'TEMPERATURE_DIFF_REL'] 特征选择 - **Lasso** ffrom sklearn.feature_selection import SelectFromModel from sklearn.linear_model import LogisticRegression from sklearn.preprocessing import MinMaxScaler X_norm = MinMaxScaler().fit_transform(X) embeded_lr_selector = SelectFromModel(LogisticRegression(penalty="l2"), max_features=88) embeded_lr_selector.fit(X_norm, y) embeded_lr_support = embeded_lr_selector.get_support() embeded_lr_feature = X.loc[:,embeded_lr_support].columns.tolist() print(str(len(embeded_lr_feature)), 'selected features', embeded_lr_feature) 65 selected features ['AGE_ABOVE65', 'GENDER', ... ... 'RESPIRATORY_RATE_DIFF_REL', 'TEMPERATURE_DIFF_REL'] 特征选择 - **RF 基于树**:SelectFromModel from sklearn.feature_selection import SelectFromModel from sklearn.ensemble import RandomForestClassifier embeded_rf_selector = SelectFromModel(RandomForestClassifier(n_estimators=100), max_features=227) embeded_rf_selector.fit(X, y) embeded_rf_support = embeded_rf_selector.get_support() embeded_rf_feature = X.loc[:,embeded_rf_support].columns.tolist() print(str(len(embeded_rf_feature)), 'selected features', embeded_rf_feature) 48 selected features ['AGE_ABOVE65', 'GENDER', ... ... 'TEMPERATURE_DIFF_REL', 'OXYGEN_SATURATION_DIFF_REL'] #### 特征选择 - **LightGBM** 或 **XGBoost** {#feature-selection---LightGBM-or-XGBoost} from sklearn.feature_selection import SelectFromModel from lightgbm import LGBMClassifier lgbc=LGBMClassifier(n_estimators=500, learning_rate=0.05, num_leaves=32, colsample_bytree=0.2, reg_alpha=3, reg_lambda=1, min_split_gain=0.01, min_child_weight=40) embeded_lgb_selector = SelectFromModel(lgbc, max_features=128) embeded_lgb_selector.fit(X, y) embeded_lgb_support = embeded_lgb_selector.get_support() embeded_lgb_feature = X.loc[:,embeded_lgb_support].columns.tolist() print(str(len(embeded_lgb_feature)), 'selected features: ', embeded_lgb_feature) embeded_lgb_feature.index 56 selected features: ['AGE_ABOVE65', 'GENDER', 'HTN', ... ... 'TEMPERATURE_DIFF_REL', 'OXYGEN_SATURATION_DIFF_REL'] #### 特征选择 - **全部集成** {#feature-selection---Ensemble-them-all} feature_name = X.columns.tolist() # put all selection together feature_selection_df = pd.DataFrame({'Feature':feature_name, 'Pearson':cor_support, 'Chi-2':chi_support, 'RFE':rfe_support, 'Logistics':embeded_lr_support, 'Random Forest':embeded_rf_support, 'LightGBM':embeded_lgb_support}) # count the selected times for each feature feature_selection_df['Total'] = np.sum(feature_selection_df, axis=1) # display the top 100 num_feats = 227 feature_selection_df = feature_selection_df.sort_values(['Total','Feature'] , ascending=False) feature_selection_df.index = range(1, len(feature_selection_df)+1) feature_selection_df.head(num_feats) df_selected_columns = feature_selection_df.loc[(feature_selection_df['Total'] > 3)] df_selected_columns 我们可以列出通过至少 4 种方法选择的特征: ![](/sites/default/files/inline/images/images/image(810).png) ... ... ![](/sites/default/files/inline/images/images/image(812).png) 我们当然可以选择这 58 个特征。 同时,经验告诉我们,特征选择并不一定总是“民主投票”;更多时候,它可能特定于域问题,特定于数据,有时还特定于我们稍后将采用的 ML 模型或方法。 特征选择 - **第三方工具**  有广泛使用的行业工具和 AutoML 工具,例如 DataRobot 可以提供出色的自动特征选择: ![](/sites/default/files/inline/images/images/capture_feature.png) 从上面的 DataRobot 图表中,我们不难看出,各个 RespiratoryRate 和 BloodPressure 值是与 ICU 转入最相关的特征。    特征选择 - **最终选择** 在本例中,我进行了一些快速实验,发现 LightGBM 特征选择的结果实际上更好一点,所以我们只使用这种选择方法。   df_selected_columns = embeded_lgb_feature # better than ensembled selection dataS = pd.concat([idf[df_selected_columns],idf['ICU'], df_cat['WINDOW']],1) dataS.ICU.value_counts() print(dataS.shape) (1925, 58) 我们可以看到有 58 个特征被选中;不算太少,也不算太多;对于这个特定的单一目标二元分类问题,看起来是合适的数量。   ### **数据不平衡** plt.figure(figsize=(10,5)) count = sns.countplot(x = "ICU",data=data) count.set_xticklabels(["Not Admitted","Admitted"]) plt.xlabel("ICU Admission") plt.ylabel("Patient Count") plt.show() 这说明数据不平衡,只有 26% 的记录转入了 ICU。 这会影响到结果,因此我们可以考虑常规的数据平衡方法,例如 SMOTE 等。 这里,我们可以尝试其他各种 EDA,以相应了解各种数据分布。   ### **运行基本 LR 训练** Kaggle 网站上有一些不错的快速训练 notebook,我们可以根据自己选择的特征列来快速运行。 让我们从快速运行针对训练管道的 LR 分类器开始:   data2 = pd.concat([idf[df_selected_columns],idf['ICU'], df_cat['WINDOW']],1) data2.AGE_ABOVE65 = data2.AGE_ABOVE65.astype(int) data2.ICU = data2.ICU.astype(int) X2 = data2.drop("ICU",1) y2 = data2.ICU from sklearn.preprocessing import LabelEncoder label_encoder = LabelEncoder() X2.WINDOW = label_encoder.fit_transform(np.array(X2["WINDOW"].astype(str)).reshape((-1,))) confusion_matrix2 = pd.crosstab(y2_test, y2_hat, rownames=['Actual'], colnames=['Predicted']) sns.heatmap(confusion_matrix2, annot=True, fmt = 'g', cmap = 'Reds') print("ORIGINAL") print(classification_report(y_test, y_hat)) print("AUC = ",roc_auc_score(y_test, y_hat),'\n\n') print("LABEL ENCODING") print(classification_report(y2_test, y2_hat)) print("AUC = ",roc_auc_score(y2_test, y2_hat)) y2hat_probs = LR.predict_proba(X2_test) y2hat_probs = y2hat_probs[:, 1] fpr2, tpr2, _ = roc_curve(y2_test, y2hat_probs) plt.figure(figsize=(10,7)) plt.plot([0, 1], [0, 1], 'k--') plt.plot(fpr, tpr, label="Base") plt.plot(fpr2,tpr2,label="Label Encoded") plt.xlabel('False positive rate') plt.ylabel('True positive rate') plt.title('ROC curve') plt.legend(loc="best") plt.show() ORIGINAL precision recall f1-score support 0 0.88 0.94 0.91 171 1 0.76 0.57 0.65 54 accuracy 0.85 225 macro avg 0.82 0.76 0.78 225 weighted avg 0.85 0.85 0.85 225 AUC = 0.7577972709551657 LABEL ENCODING precision recall f1-score support 0 0.88 0.93 0.90 171 1 0.73 0.59 0.65 54 accuracy 0.85 225 macro avg 0.80 0.76 0.78 225 weighted avg 0.84 0.85 0.84 225 AUC = 0.7612085769980507          看起来它达到了 76% 的 AUC,准确率为 85%,但转入 ICU 的召回率只有 59% - 似乎有太多假负例。 这当然不理想 - 我们不希望错过患者记录的实际 ICU 风险。 因此,以下所有任务都将集中在如何通过降低 FN 来提高召回率的**目标**上,希望总体准确度有所平衡。 在前面的部分中,我们提到了不平衡的数据,所以第一本能通常是对测试集进行 Stratify(分层),然后使用 SMOTE 方法使其成为更平衡的数据集。 #stratify the test data, to make sure Train and Test data have the same ratio of 1:0 X3_train,X3_test,y3_train,y3_test = train_test_split(X2,y2,test_size=225/1925,random_state=42, stratify = y2, shuffle = True) <span> </span> # train and predict LR.fit(X3_train,y3_train) y3_hat = LR.predict(X3_test) #SMOTE the data to make ICU 1:0 a balanced distribution from imblearn.over_sampling import SMOTE sm = SMOTE(random_state = 42) X_train_res, y_train_res = sm.fit_sample(X3_train,y3_train.ravel()) LR.fit(X_train_res, y_train_res) y_res_hat = LR.predict(X3_test) #draw confusion matrix etc again confusion_matrix3 = pd.crosstab(y3_test, y_res_hat, rownames=['Actual'], colnames=['Predicted']) sns.heatmap(confusion_matrix3, annot=True, fmt = 'g', cmap="YlOrBr") print("LABEL ENCODING + STRATIFY") print(classification_report(y3_test, y3_hat)) print("AUC = ",roc_auc_score(y3_test, y3_hat),'\n\n') print("SMOTE") print(classification_report(y3_test, y_res_hat)) print("AUC = ",roc_auc_score(y3_test, y_res_hat)) y_res_hat_probs = LR.predict_proba(X3_test) y_res_hat_probs = y_res_hat_probs[:, 1] fpr_res, tpr_res, _ = roc_curve(y3_test, y_res_hat_probs) plt.figure(figsize=(10,10)) #And plot the ROC curve as before.   LABEL ENCODING + STRATIFY precision recall f1-score support 0 0.87 0.99 0.92 165 1 0.95 0.58 0.72 60 accuracy 0.88 225 macro avg 0.91 0.79 0.82 225 weighted avg 0.89 0.88 0.87 225 AUC = 0.7856060606060606 SMOTE precision recall f1-score support 0 0.91 0.88 0.89 165 1 0.69 0.75 0.72 60 accuracy 0.84 225 macro avg 0.80 0.81 0.81 225 weighted avg 0.85 0.84 0.85 225 AUC = 0.8143939393939393              所以对数据进行 STRATIFY 和 SMOT 处理似乎将召回率从 0.59 提高到 0.75,总体准确率为 0.84。 现在,按照传统 ML 的惯例来说,数据处理已大致完成,我们想知道在这种情况下最佳模型是什么;它们是否可以做得更好,我们能否尝试相对全面的比较?   ### **运行各种模型的训练比较**: 让我们评估一些常用的 ML 算法,并生成箱形图形式的比较结果仪表板: # compare algorithms from matplotlib import pyplot from sklearn.model_selection import train_test_split from sklearn.model_selection import cross_val_score from sklearn.model_selection import StratifiedKFold from sklearn.linear_model import LogisticRegression from sklearn.tree import DecisionTreeClassifier from sklearn.neighbors import KNeighborsClassifier from sklearn.discriminant_analysis import LinearDiscriminantAnalysis from sklearn.naive_bayes import GaussianNB from sklearn.svm import SVC #Import Random Forest Model from sklearn.ensemble import RandomForestClassifier from xgboost import XGBClassifier # List Algorithms together models = [] models.append(('LR', <strong>LogisticRegression</strong>(solver='liblinear', multi_class='ovr'))) models.append(('LDA', LinearDiscriminantAnalysis())) models.append(('KNN', <strong>KNeighborsClassifier</strong>())) models.append(('CART', <strong>DecisionTreeClassifier</strong>())) models.append(('NB', <strong>GaussianNB</strong>())) models.append(('SVM', <strong>SVC</strong>(gamma='auto'))) models.append(('RF', <strong>RandomForestClassifier</strong>(n_estimators=100))) models.append(('XGB', <strong>XGBClassifier</strong>())) #clf = XGBClassifier() # evaluate each model in turn results = [] names = [] for name, model in models: kfold = StratifiedKFold(n_splits=10, random_state=1) cv_results = cross_val_score(model, X_train_res, y_train_res, cv=kfold, scoring='f1') ## accuracy, precision,recall results.append(cv_results) names.append(name) print('%s: %f (%f)' % (name, cv_results.mean(), cv_results.std())) # Compare all model's performance. Question - would like to see a Integrated item on it? pyplot.figure(4, figsize=(12, 8)) pyplot.boxplot(results, labels=names) pyplot.title('Algorithm Comparison') pyplot.show() LR: 0.805390 (0.021905) LDA: 0.803804 (0.027671) KNN: 0.841824 (0.032945) CART: 0.845596 (0.053828) NB: 0.622540 (0.060390) SVM: 0.793754 (0.023050) RF: 0.896222 (0.033732) XGB: 0.907529 (0.040693) ![](/sites/default/files/inline/images/images/image-20200821155401-1.png) 上图看起来表明,XGB 分类器和随机森林分类器的 F1 分数好于其他模型。 让我们也比较一下它们在同一组标准化测试数据上的实际测试结果: import time from pandas import read_csv from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report from sklearn.metrics import confusion_matrix from sklearn.metrics import accuracy_score from sklearn.svm import SVC for name, model in models: print(name + ':\n\r') start = time.clock() model.fit(X_train_res, y_train_res) print("Train time for ", model, " ", time.clock() - start) predictions = model.predict(X3_test) #(X_validation) # Evaluate predictions print(accuracy_score(y3_test, predictions)) # Y_validation print(confusion_matrix(y3_test, predictions)) print(classification_report(y3_test, predictions)) LR: Train time for LogisticRegression(multi_class='ovr', solver='liblinear') 0.02814499999999498 0.8444444444444444 [[145 20] [ 15 45]] precision recall f1-score support 0 0.91 0.88 0.89 165 1 0.69 0.75 0.72 60 accuracy 0.84 225 macro avg 0.80 0.81 0.81 225 weighted avg 0.85 0.84 0.85 225 LDA: Train time for LinearDiscriminantAnalysis() 0.2280070000000194 0.8488888888888889 [[147 18] [ 16 44]] precision recall f1-score support 0 0.90 0.89 0.90 165 1 0.71 0.73 0.72 60 accuracy 0.85 225 macro avg 0.81 0.81 0.81 225 weighted avg 0.85 0.85 0.85 225 KNN: Train time for KNeighborsClassifier() 0.13023699999999394 0.8355555555555556 [[145 20] [ 17 43]] precision recall f1-score support 0 0.90 0.88 0.89 165 1 0.68 0.72 0.70 60 accuracy 0.84 225 macro avg 0.79 0.80 0.79 225 weighted avg 0.84 0.84 0.84 225 CART: Train time for DecisionTreeClassifier() 0.32616000000001577 0.8266666666666667 [[147 18] [ 21 39]] precision recall f1-score support 0 0.88 0.89 0.88 165 1 0.68 0.65 0.67 60 accuracy 0.83 225 macro avg 0.78 0.77 0.77 225 weighted avg 0.82 0.83 0.83 225 NB: Train time for GaussianNB() 0.0034229999999979555 0.8355555555555556 [[154 11] [ 26 34]] precision recall f1-score support 0 0.86 0.93 0.89 165 1 0.76 0.57 0.65 60 accuracy 0.84 225 macro avg 0.81 0.75 0.77 225 weighted avg 0.83 0.84 0.83 225 SVM: Train time for SVC(gamma='auto') 0.3596520000000112 0.8977777777777778 [[157 8] [ 15 45]] precision recall f1-score support 0 0.91 0.95 0.93 165 1 0.85 0.75 0.80 60 accuracy 0.90 225 macro avg 0.88 0.85 0.86 225 weighted avg 0.90 0.90 0.90 225 RF: Train time for RandomForestClassifier() 0.50123099999999 0.9066666666666666 [[158 7] [ 14 46]] precision recall f1-score support 0 0.92 0.96 0.94 165 1 0.87 0.77 0.81 60 accuracy 0.91 225 macro avg 0.89 0.86 0.88 225 weighted avg 0.91 0.91 0.90 225 XGB: Train time for XGBClassifier(base_score=0.5, booster='gbtree', colsample_bylevel=1, colsample_bynode=1, colsample_bytree=1, gamma=0, gpu_id=-1, importance_type='gain', interaction_constraints='', learning_rate=0.300000012, max_delta_step=0, max_depth=6, min_child_weight=1, missing=nan, monotone_constraints='()', n_estimators=100, n_jobs=0, num_parallel_tree=1, random_state=0, reg_alpha=0, reg_lambda=1, scale_pos_weight=1, subsample=1, tree_method='exact', validate_parameters=1, verbosity=None) 1.649520999999993 0.8844444444444445 [[155 10] [ 16 44]] precision recall f1-score support 0 0.91 0.94 0.92 165 1 0.81 0.73 0.77 60 accuracy 0.88 225 macro avg 0.86 0.84 0.85 225 weighted avg 0.88 0.88 0.88 225 结果显示,RF 实际上好于 XGB。 这意味着 XGB 可能在某种程度上有一点过度拟合。 RFC 结果也比 LR 略有改善。   ### **通过进一步的“通过网格搜索调整参数”运行所选模型** 现在假定我们选择了随机森林分类器作为模型。 我们可以对此模型执行进一步的网格搜索,以查看是否可以进一步提高结果的性能。 记住,在这种情况下,我们的目标仍然是优化召回率,这通过最大程度地减少患者可能遇到的 ICU 风险的假负例来实现,所以下面我们将“recall_score”来重新拟合网格搜索。 再次像往常一样使用 10 折交叉验证,因为上面的测试集总是设置为 2915 条记录中的 12% 左右。 from sklearn.model_selection import GridSearchCV # Create the parameter grid based on the results of random search param_grid = {'bootstrap': [True], 'ccp_alpha': [0.0], 'class_weight': [None], 'criterion': ['gini', 'entropy'], 'max_depth': [None], 'max_features': ['auto', 'log2'], 'max_leaf_nodes': [None], 'max_samples': [None], 'min_impurity_decrease': [0.0], 'min_impurity_split': [None], 'min_samples_leaf': [1, 2, 4], 'min_samples_split': [2, 4], 'min_weight_fraction_leaf': [0.0], 'n_estimators': [100, 125], #'n_jobs': [None], 'oob_score': [False], 'random_state': [None], #'verbose': 0, 'warm_start': [False] } #Fine-tune by confusion matrix from sklearn.metrics import roc_curve, precision_recall_curve, auc, make_scorer, recall_score, accuracy_score, precision_score, confusion_matrix scorers = { 'recall_score': make_scorer(recall_score), 'precision_score': make_scorer(precision_score), 'accuracy_score': make_scorer(accuracy_score) } # Create a based model rfc = RandomForestClassifier() # Instantiate the grid search model grid_search = GridSearchCV(estimator = rfc, param_grid = param_grid, scoring=scorers, refit='recall_score', cv = 10, n_jobs = -1, verbose = 2) train_features = X_train_res grid_search.fit(train_features, train_labels) rf_best_grid = grid_search.best_estimator_ rf_best_grid.fit(train_features, train_labels) rf_predictions = rf_best_grid.predict(X3_test) print(accuracy_score(y3_test, rf_predictions)) print(confusion_matrix(y3_test, rf_predictions)) print(classification_report(y3_test, rf_predictions)) 0.92 [[ 46 14] [ 4 161]] precision recall f1-score support 0 0.92 0.77 0.84 60 1 0.92 0.98 0.95 165 accuracy 0.92 225 macro avg 0.92 0.87 0.89 225 weighted avg 0.92 0.92 0.92 225 结果表明,网格搜索成功地将总体准确度提高了一些,同时保持 FN 不变。 我们同样也绘制 AUC 比较图: confusion_matrix4 = pd.crosstab(y3_test, rf_predictions, rownames=['Actual'], colnames=['Predicted']) sns.heatmap(confusion_matrix4, annot=True, fmt = 'g', cmap="YlOrBr") print("LABEL ENCODING + STRATIFY") print(classification_report(y3_test, 1-y3_hat)) print("AUC = ",roc_auc_score(y3_test, 1-y3_hat),'\n\n') print("SMOTE") print(classification_report(y3_test, 1-y_res_hat)) print("AUC = ",roc_auc_score(y3_test, 1-y_res_hat), '\n\n') print("SMOTE + LBG Selected Weights + RF Grid Search") print(classification_report(y3_test, rf_predictions)) print("AUC = ",roc_auc_score(y3_test, rf_predictions), '\n\n\n') y_res_hat_probs = LR.predict_proba(X3_test) y_res_hat_probs = y_res_hat_probs[:, 1] predictions_rf_probs = rf_best_grid.predict_proba(X3_test) #(X_validation) predictions_rf_probs = predictions_rf_probs[:, 1] fpr_res, tpr_res, _ = roc_curve(y3_test, 1-y_res_hat_probs) fpr_rf_res, tpr_rf_res, _ = roc_curve(y3_test, predictions_rf_probs) plt.figure(figsize=(10,10)) plt.plot([0, 1], [0, 1], 'k--') plt.plot(fpr, tpr, label="Base") plt.plot(fpr2,tpr2,label="Label Encoded") plt.plot(fpr3,tpr3,label="Stratify") plt.plot(fpr_res,tpr_res,label="SMOTE") plt.plot(fpr_rf_res,tpr_rf_res,label="SMOTE + RF GRID") plt.xlabel('False positive rate') plt.ylabel('True positive rate') plt.title('ROC curve') plt.legend(loc="best") plt.show() LABEL ENCODING + STRATIFY precision recall f1-score support 0 0.95 0.58 0.72 60 1 0.87 0.99 0.92 165 accuracy 0.88 225 macro avg 0.91 0.79 0.82 225 weighted avg 0.89 0.88 0.87 225 AUC = 0.7856060606060606 SMOTE precision recall f1-score support 0 0.69 0.75 0.72 60 1 0.91 0.88 0.89 165 accuracy 0.84 225 macro avg 0.80 0.81 0.81 225 weighted avg 0.85 0.84 0.85 225 AUC = 0.8143939393939394 SMOTE + LBG Selected Weights + RF Grid Search precision recall f1-score support 0 0.92 0.77 0.84 60 1 0.92 0.98 0.95 165 accuracy 0.92 225 macro avg 0.92 0.87 0.89 225 weighted avg 0.92 0.92 0.92 225 AUC = 0.8712121212121211       结果表明,经过算法比较和进一步的网格搜索后,我们将 AUC 从 78% 提高到 87%,总体准确度为 92%,召回率为 77%。   ### **“传统 ML”方法回顾** 那么,这个结果到底如何? 对于使用传统 ML 算法的基本手动处理是可以的。 在 Kaggle 竞争表中表现如何? 好吧,它不会出现在排行榜上。 我通过 DataRobot 当前的 AutoML 服务运行了原始数据集,在对排名前 43 的模型进行比较后,最好的结果是使用模型“具有无人监督学习功能的 XGB 树分类器”实现的相当于大约 90+% 的 AUC(有待使用同类数据进行进一步确认)。 如果真的想在 Kaggle 上具有竞争力,这可能是我们要考虑的底线模型。 我也会将最佳结果与模型的排行列表放在 github 中。 最后,对于特定于医护场所的现实案例,我的感觉是,我们还需要考虑具有一定程度自定义的深度学习方法,正如本贴的“数据和任务”部分所提到的。 当然,在现实情况下,在哪里收集高质量数据列也可能是一个前期问题。   ## IntegratedML 方法? 上文说明了所谓的传统 ML 流程,其中通常包括数据 EDA、特征工程、特征选择、模型选择和通过网格搜索进行性能优化等。 这是我目前能想到的最简单的适合此任务的方法,我们甚至还没有触及模型部署和服务管理生命周期 - 我们将在下一个帖子中探讨这些方面,研究如何利用 Flask/FastAPI/IRIS,并将这个基本的 ML 模型部署到 Covid-19 X-Ray 演示服务栈中。 现在,IRIS 有了 IntegratedML,它是一个优雅的 SQL 包装器,包装了 AutoML 的强大选项。 在第二部分中,我们可以研究如何以大为简化的流程来完成上述任务,这样我们就不必再为特征选择、模型选择和性能优化等问题而烦恼,同时可获得等效的 ML 结果来实现商业利益。 到这里,如果再塞入使用相同数据快速运行 integratedML 的内容,本帖可能太长了,无法在 10 分钟内读完,因此我将该内容移至[第二部分](https://community.intersystems.com/post/run-some-covid-19-icu-predictions-ml-vs-integratedml-part-i)。  
文章
Nicky Zhu · 十一月 15, 2021

关于信息平台/数据中台技术,你应该知道的八件事

查看原文 近日,国家卫健委统计信息中心发布了两则通知—— 2021年10月25日,国家卫健委统计信息中心发布《关于开展国家医疗健康信息互联互通标准化成熟度评测工作的通知》,这意味着新一年的评测工作开始启动。 2021年11月5日,国家卫健委统计信息中心发布了“关于2020年度国家医疗健康信息互联互通标准化成熟度测评结果(第二批)公示的通知”,公布了第二批10个区域和92家医院的测评结果。 这两则通知,再次将“互联互通”带到了医疗IT人的面前。而每每谈到互联互通,就不可避免地要谈到集成平台、信息平台和数据中台等项目建设问题,本文将从供应商选择、技术选型等从八个核心问题,浅谈关于平台和中台的那些事。 一、如何选择供应商? 如上图所示,如果我们把平台/中台项目的实施方称作解决方案提供商,那么每一家解决方案提供方背后还会有一家产品技术提供方解决方案,因为解决方案提供方往往需要借助成熟的产品来实现信息平台和数据中台项目,以聚焦所服务医院客户的具体需求,并加速实施效率,所以一个平台/中台供应链条相对比较长。也因此,医院/医疗集团需要花费更多的精力在产品和解决方案的组合中进行选型。选型的标准也成为许多信息中心或者CIO们关注的首要问题。 首先要考虑平台/中台解决方案提供方本身的品牌和实力:通常而言,选择全国性的解决方案提供方更安全一些,这类厂商的解决方案相对成熟、成功案例多,技术能力强,实施经验丰富;但是对于一些规模略小的医院而言,可能会顾虑这些厂商的客户太多,对本院的支持力度不够,或者是在该厂商在当地没有分公司,存在技术服务跟不上等问题,也可能会更倾向于初创企业或者本地解决方案提供方来做项目的集成或者实施,这两种选择都没有问题,关键是所选择的合作伙伴要值得信赖。 另外要考虑厂商背后的产品技术提供方:通常产品技术提供方不直接面向最终客户提供实施服务,而是通过本地合作伙伴向最终客户(即医院或者医疗集团)提供服务。但是作为解决方案的基础,该产品或者技术本身的先进性、可靠性以及未来的可扩展性都是需要重点衡量的因素。例如医院建设集成平台或者互联互通平台通常都会本着以评促建以评促用的目的,利用信息平台的建设契机,打通院内的信息和数据流程。此时,产品技术提供方有多少业务建模和流程整合的案例与经验也将在很大程度上影响项目的交付质量。 同样,医院也可以参考该产品技术提供方的行业积累、案例详情、服务承诺以及业界口碑等等。 总结一下,在选择方案时,需要考虑的实际上是产品本身的技术能力和对应的解决方案提供方的服务能力。因此,我们建议大家基于成熟的产品,选择能提供较好技术服务的解决方案提供方。如果产品并不成熟,那么即使解决方案提供方愿意常年提供驻场技术服务,也很难应对故障,也难以制订预案保障平台稳定运行。 二、技术路线的选择 在医疗行业进行业务和数据整合时,用户常常会需要在点对点集成模式、消息路由模式以及SOA架构模式进行技术决策。 事实上,从来就未曾出现过集成模式的最终解决方案。医院和医疗集团用某种特定的集成模式搭建自己的数字化高速公路时需要充分考虑该模式是否适合自己的场景,投入产出比是否符合自己的预期,以及是否能够充分利用该模式的优势。 举例而言,当医院考虑采用SOA架构时,需要考虑到遗留系统是否能够提供服务接口;在当前的业务运行条件下,是否能够承受由于接口的侵入式设计引入的风险,是否可能通过预案规避风险;以及医院是否已经或者将在平台投产前后具有实时数据分析的需求和技术储备。否则就将面临投入无法得到回报的质疑,甚至是规划无法落地的尴尬。 再看一个例子,如果要采用点对点模式集成,那么医院就需要考虑在平台投产可预见的周期(如3~5年)内,是否会面临跨部门跨系统数据利用需求快速增长的前景。如果有,那么,由于缺乏SOA架构能够提供的业务抽象和整合的能力,爆炸性增长的接口数量和数据整合需求会成为信息科难以应对的直接威胁。 正是由于集成模式的高度个性化,我们认为作为基础设施的集成平台类产品必须能够支持所有集成模式。一方面是满足各种不同类型的医院的需要,另一方面,医院也需要认识到,基础设施的建设从来都不是一蹴而就的“一锤子买卖”。您完全可以在集成需求数量较低时选用点对点模式快速投产,在需要进行流程和数据整合时应用SOA架构以获得企业全景视图的整合优势。而一个能够支持所有模式的产品才能赋能于客户,使之具备进行策略选择的优势。 三、开源策略的潜在风险 采用开源组件迅速获得能力,结合DevOps快速迭代开发是应对快速变化的市场环境和需求,进行产品化开发时的优先选择。在进行应用开发时,这样的策略通常有效。 然而优势与代价总是如影随形。借助开源组件的优势是能够快速获得能力,但开源组件的稳定性、可靠性和安全性则是每一个技术决策者都需要考察的关键风险,甚至开源组件许可证的更新都有可能为企业引入巨大的知识产权风险。 例如久负盛名的ElasticSearch,作为一款企业级搜索和分析引擎,它对于文本检索的能力和效率都有保障,被许多产品集成用于检索。但ElasticSearch及其依赖的其他组件已被检测出大量的安全性漏洞,例如可以引入中间人攻击的CVE-2021-22138,可以允许用户查看未授权敏感信息的CVE-2021-22147,以及可以允许用户通过ElasticSearch在服务器上运行任何OS指令的CVE-2014-3120等,风险列表每年都在更新。(风险列表可参见https://www.elastic.co/community/security)同样是ElasticSearch,在将开源授权更新为SSPL之后,如果业务用到了ElasticSearch并打包为可盈利产品,则ElasticSearch公司有权要求用户开放源码并收取费用。 因此,在使用大规模集成开源组件构建的产品时,医院需要评估产品技术提供方是否能够及时更新开源组件以获得安全性更新,并评估产品技术提供方是否能够及时跟踪和处理因授权变化会引入的法律风险。 集成平台、数据中台甚至是数据库这样的基础设施如果构建在大量的开源组件上,频繁的版本变动通常意味着组件集成风险的大幅升高,而跟踪和处理版本变更的技术和法务影响也将成为需要持续投入的持有成本。因此,一体化、完整知识产权的集成平台或数据中台产品在简化技术堆栈的同时也将大幅降低长期持有成本。对于医院用户来说,需要平衡评估一体化商业产品和开源集成产品的购买和持有成本,更需要考察产品技术提供方对开源组件的跟踪、更新和维护能力。 四、关注稳定与可靠性保障 核心业务系统、集成平台和数据中台这一类的关键系统,事关医院业务是否能持续运行,其运行稳定性与可靠性的重要意义不言而喻。基于主备、多活等冗余技术的平台高可用和灾备方案仍是为平台运行保驾护航的关键手段。 在市场上可见到的诸多产品中,有采用原生高可用方案的产品,也有集成第三方或开源技术高可用方案的产品。在这里,各位信息中心负责人或者技术决策者不得不考虑一个问题,即高可用方案的责任归属问题。 因此,即使一些非核心部件采用第三方技术,高可用方案也应采用产品原生技术。即使退一步来说,在没有原生高可用方案的情况下,您的解决方案提供方也应当承担起解决平台可用性和可靠性问题的技术服务角色。试想,当高可用方案失效或处于故障状态时,解决方案提供方采用了非原生高可用方案,届时难道能依赖开源社区的随机问答解决问题? 五、跨越技术门槛 医疗数字化进程与人工智能等目前的热点技术有很大不同,即必须基于当前业务。但由于医疗业务本身面临与新技术的融合,因此数字化进程也必须具备足够的灵活性,能够迅速应对业务过程的演化。而医疗业务流程或数据流程的演化,是需要业务专家、开发工程师、运维保障团队协作共同完成的,每一种角色都需要在平台上工作。因此,纯粹面向开发工程师的技术平台将无法有效应对业务流程本身的快速迭代。我们认为,一个成熟的集成平台/数据中台,需要为团队中不同角色的成员提供适合他们的开发/维护/测试工具,使各成员能以较低的成本各施所长。这些工具至少包括: · 图形化的流程、数据转换和业务规则建模工具,使得业务专家即使不了解业务组件的技术实现,也能利用平台上的组件搭建出适合医院的业务/数据流 · 专业的IDE和管理工具,供研发工程师扩展业务组件和对现有组件进行跟踪和组件级调优 · 监控和管理工具,供运维工程师监控平台运行的健康状况和性能,必要时对平台运行参数进行调优 六、集群的选择 我们理解一些工程师非常关心产品是否支持负载均衡。需要注意的是,对于现代的集成平台和数据中台而言,它们本身应当是由一系列的集群共同构成的分布式系统。 比如院内集成平台拥有基于Web的操作管理页面,运行API实现或数据流程的容器,有消息引擎,有数据库,因此,可以构成一个典型的由Web程序,API/应用服务器,消息引擎和数据库分层构建的分布式系统,而其中的每一层,都可以根据高可用与负载需求以不同目的的集群形态搭建出来。 以集成平台产品为例,通常,集成平台的Web管理程序由于并发操作的人很少,不需要单独进行集群化;而API/应用服务器层默认会采用高可用集群,对于业务量极大的用户,则可以采用负载均衡+高可用集群;数据库层同样如此,必要时还可以考虑部署读写分离+负载均衡+高可用集群;消息引擎则比较特别,如果不需要保障消息的先进先出特性,可以部署高可用和负载均衡集群,而对于需要保障消息处理时序的场景,则通常不能依赖负载均衡集群,或即使部署了负载均衡集群,也需要控制消息分规则,由单一实例处理这样的消息。 但是否采用及采用何种集群架构,则完全应当基于业务的实际需要和产品能力。举例而言,InterSystems产品可以支持负载均衡+高可用集群,还可以部署为读写分离+负载均衡+高可用集群,但通常我们并不会作为默认配置推荐给集成平台用户。原因在于,我们的产品在单台服务器上经性能测试可以达到20亿消息/天的处理效率。而根据我们对国内数百家三家医院的实际调查,即使在国内顶尖的三甲医院中,也未发现超过2千万消息/天的性能需求。因此,对于单体医院,高可用集群已足够使用。基于奥卡姆剃刀原理和成本控制的基本需求,负载均衡集群并无必要,反而会由于加大了架构的复杂性使持有、维护成本都大幅提升。而对于医疗集团客户,由于需要集成数十家三甲与二级医院,同时还需要控制单个服务器的成本,因此我们的一部分医疗集团客户部署了负载均衡+高可用集群并可进行弹性横向扩展。 当然,不同的产品有不同的性能指标,如果产品的本身性能表现无法支撑医院业务量,那么部署为负载均衡集群支撑医院的实际需求还是非常必要的。 七、技术服务保障 相比开源产品,基于商业产品搭建平台/中台解决方案最显著的附加价值主要来源于技术服务。无论是最终用户还是解决方案提供方,都能受益于产品提供方的技术服务。技术服务也是项目能否成功上线、持续稳定运行或者二次开发的重要保障,对于技术服务,需要考虑产品技术提供方是否能够提供下面的三种或者以上的方式: · 故障处理和技术支持:对于产品应用、二次开发的疑问,是否可获得技术支持资源以解决疑问?对于在产品运行过程中可能遭遇的软硬件故障,尤其是系统崩溃、宕机等高等级事件,是否能够获得直接的技术支持解决、定位和调查问题? · 产品培训:是否具备成体系的产品应用、二次开发和维护培训体系 · 在线课程 · 产品文档库 · 开发者社区:非工程师的客户往往不重视开发者社区的力量。实际上,作为可供全球开发者沟通的场所,在开发者社区往往能找到常见问题的解决方案,具体问题和场景的最佳实践,前瞻性技术研究等非常有价值的资料。 对于医院或是医疗集团客户来说,如果需要掌握信息平台或数据中台,能够达到自主维护、持续演进的目标,那么,无论是通过解决方案提供方还是通过产品技术提供方,都需要获得上述的多种技术服务支持。 八、选型中的常见问题 对于采用商业产品这一策略本身,需要经过大量的选型工作。产品技术提供方和解决方案提供商都会积极宣传自己的产品,而医院则需要对产品的特性,服务体系,性能表现,案例的代表性,综合实施效果等做出评估,方可得出对自己有利的评估结果。在这个过程中,客户往往还是需要综合运用多种手段,包括自行评估、走访典型案例和开展验证测试等手段,避免常见的一些问题,例如: · 技术的可执行性评估不足 例如对于仅支持消息引擎的集成平台,往往需要按照一种特定的消息类型进行通信,使系统间交互具备统一协议,并且系统都需要改造以接入消息引擎。这样的规划不能说不好,但医院的遗留系统能不能都配合平台进行改造或医院有没有足够的预算支撑改造项目落地,以及业务系统现场改造的风险,都会影响实施效果。因此需要切实评估和核实。 · 产品特性不能达到预期 例如对于具有ETL能力的产品,需要评估其对于大容量数据(例如初始化数据加载过程)进行批量采集、转换和落盘的处理效率,以便与借助简单的SQL JDBC连接逐条抽数和转换的SQL适配器相区别。由于两种模式在处理速度上通常有数量级的差别,如果使用SQL适配器模式,在大批量对数据进行ETL操作时将不可避免地遭遇瓶颈。 · 产品对主流技术的覆盖不全面 在现在的技术条件下,即使对同一类型的接口,也往往有多种技术选择。如产品不能提供对这些技术的覆盖,则用户需要投入额外的成本和风险完成接入。例如对常见的负载均衡方案而言,通常对于推模式接口(由外部调用触发的接口),例如SOAP WebService或者REST API,往往都能提供负载均衡;而对于拉模式接口(由产品自身自动触发),例如SQL扫描或一些CDC功能接口,则无法直接受益于负载均衡技术。假如实际业务中有大量需要通过SQL获取数据的接口,则负载均衡集群并没有多少意义。 再如SQL接口可以基于JDBC或ODBC连接,如果产品只能支持其中一种连接,那么对于遗留系统的接入能力将大打折扣。 · 缺乏技术验证过程和约束 对于架构和技术的落地,通常需要验证过程,用户才可能获得预期的效果。例如市场上存在对架构模式进行过度简化与概念偷换的现象。例如将SOA架构等价于ESB、将ESB的概念偷换为接口引擎、或将集成平台概念偷换为消息引擎,而在实施时更进一步地简化为接口的注册和连接,实际上变成了点对点模式。由于集成架构将影响未来3~5年的医院数字化转型过程中的难度和成本,点对点模式的后续实施成本将随接口的数量增加指数上升,导致后期的实施成本居高不下。 当然,充分了解到以上关于供应商选择与技术选型的8个问题,才是真正的互联互通建设的起点,更重要的是,医院信息负责人还需要真正读懂评测要求,并了解本院建设互联互通的整体目标以及医院管理层、临床业务部门等相关部门的不同述求,把这些目标与述求一一映射到平台/中台解决方案中,才是成功通关的秘籍。