资讯动态

急性心肌梗死72小时死亡风险预测实战方案

发布时间:2026/10/14 13:54:39 来源:尧图企业网站定制
简介本资源是一份面向人工智能与医疗数据分析初学者及进阶学习者的机器学习实践项目聚焦急性心肌梗死AMI患者死亡风险的建模与预测解决临床辅助决策中的关键分类问题。压缩包共10个文件含4个核心Python脚本如xgb.py、TrainLightGBM.py用于模型训练与评估PreProecssOneHot.py和PreProcessInsert.py负责数据清洗与特征编码、3个CSV格式结构化数据集含插值处理后的训练数据与最终预测结果、2个SQL脚本GET_AMI.sql、GET_WBC.sql用于模拟医疗指标提取、1个Excel临床信息表AMI_info.xlsx整体大小5.56MB代码—数据—文档链条完整。已有170人学习下载资源提供从原始数据预处理、多模型对比XGBoost/LightGBM、特征工程到可解释性分析的全流程实现附带标准化目录结构与注释清晰的源码便于复现、调试与教学拓展。1. 为什么用机器学习预测急性心肌梗死死亡风险不是“加个模型就完事”临床上一个确诊为急性心肌梗死AMI的患者入院后前72小时是死亡风险最高的窗口期——但传统GRACE、TIMI等评分工具依赖人工录入的5–10个临床指标漏填、延迟填、主观判断偏差普遍存在更关键的是它们无法利用电子病历里埋着的“沉默数据”比如每15分钟一次的生命体征趋势、连续3小时下降的乳酸值、两次心电图ST段抬高幅度的微小变化率。而这个标题里的「基于机器学习的急性心肌梗死死亡风险预测.zip」本质是一套可落地的端到端方案它不只调用XGBoost或LightGBM跑个AUC而是从SQL Server中拉取真实住院流水、检验检查、护理记录三类结构化表用拉格朗日插值对缺失的生命体征做时序填充再用LightGBM构建72小时死亡概率回归模型并输出每个时间点的风险动态曲线。适合心内科医生想快速验证模型临床可用性、信息科工程师要对接HIS系统、以及研究生做医学AI课题——它不假设你有GPU集群也不要求你重写EMR接口所有代码和SQL脚本都打包在zip里解压即跑通最小闭环。2. 从SQL Server抽数据不是SELECT *而是三张表的精准拼接与字段对齐急性心肌梗死患者的死亡风险建模核心不是算法多炫而是数据能不能代表真实临床决策链。我们不用全量病历只锁定三张表admission入院主表、lab_results检验结果、vital_signs生命体征。这三张表在SQL Server中通常分属不同Schema且时间戳精度不一致有的存到秒有的只到日直接JOIN会丢掉80%以上的有效样本。下面这段SQL不是教你怎么写语法而是告诉你为什么必须这样写。2.1 拉取入院基础信息并打上“黄金72小时”时间锚点-- SQL Server 2016 兼容写法避免使用LAG/LEAD导致版本兼容问题 SELECT a.patient_id, a.admit_time, DATEADD(HOUR, 72, a.admit_time) AS death_window_end, a.age, a.sex, a.hypertension, a.diabetes, a.smoking_history, a.previous_MI FROM dbo.admission a WHERE a.diagnosis_code IN (I21.0, I21.1, I21.3) -- ICD-10 AMI主诊断码 AND a.admit_time 2019-01-01 AND a.admit_time 2023-12-31;注意这里用DATEADD(HOUR, 72, ...)而非admit_time 72:00:00是因为SQL Server对datetime类型加字符串存在隐式转换风险同时过滤ICD-10诊断码比模糊匹配“急性心梗”文本更可靠——某院HIS系统曾把“心梗待排”也归入该字段导致阳性率虚高12%。2.2 关联检验结果按时间窗口聚合而非简单LEFT JOINWITH lab_window AS ( SELECT l.patient_id, l.test_time, l.test_name, l.result_value, -- 将检验项目映射为标准字段名避免医院自定义编码混乱 CASE WHEN l.test_name IN (肌钙蛋白I, cTnI, Troponin I) THEN cTnI WHEN l.test_name IN (CK-MB, 肌酸激酶同工酶) THEN CK_MB WHEN l.test_name IN (肌酐, Cr, Creatinine) THEN creatinine ELSE NULL END AS standard_test_name FROM dbo.lab_results l WHERE l.test_time (SELECT MIN(admit_time) FROM dbo.admission) AND l.test_time (SELECT MAX(DATEADD(HOUR, 72, admit_time)) FROM dbo.admission) ) SELECT lw.patient_id, MAX(CASE WHEN lw.standard_test_name cTnI THEN lw.result_value END) AS cTnI_max, MAX(CASE WHEN lw.standard_test_name CK_MB THEN lw.result_value END) AS CK_MB_max, AVG(CASE WHEN lw.standard_test_name creatinine THEN lw.result_value END) AS creatinine_mean, COUNT(*) AS lab_count FROM lab_window lw GROUP BY lw.patient_id;逻辑说明不用LEFT JOIN admission ON patient_id因为单次检验可能对应多个入院记录如复诊必须先限定test_time在所有admit_time到admit_time72h范围内MAX()和AVG()不是为了统计学意义而是解决同一患者多次检测的“临床最终采纳值”问题——比如cTnI取峰值指南明确要求肌酐取均值反映肾功能基线standard_test_name映射表必须由临床医生确认某三甲医院曾发现检验科把“高敏肌钙蛋白T”误标为“hs-cTnT”但LIS系统导出字段名却是hs_ctnt导致模型误判为新指标。2.3 生命体征表处理为什么必须用拉格朗日插值而不是线性插值生命体征血压、心率、血氧在监护仪中是高频采集每5秒1次但HIS系统只存每15分钟汇总值且常因设备离线出现整段缺失如凌晨2–4点连续2小时无记录。线性插值会平滑掉关键拐点——比如心率从110bpm骤降到55bpm再回升线性拟合会画成一条缓坡而拉格朗日插值能保留局部极值形态。-- 在Python中完成插值SQL Server不原生支持高阶插值 -- 但SQL需先提取原始时间序列注意去重和排序 SELECT v.patient_id, v.record_time, v.heart_rate, v.systolic_bp, v.oxygen_saturation FROM dbo.vital_signs v WHERE v.record_time (SELECT MIN(admit_time) FROM dbo.admission) AND v.record_time (SELECT MAX(DATEADD(HOUR, 72, admit_time)) FROM dbo.admission) AND v.heart_rate IS NOT NULL ORDER BY v.patient_id, v.record_time;参数说明v.heart_rate IS NOT NULL必须显式过滤因为某些监护仪故障时会写入0值SQL Server默认不视0为NULLORDER BY不可省略——后续Python用scipy.interpolate.lagrange时输入x坐标时间戳转为秒级数值必须严格递增否则抛ValueError: x cannot contain duplicates实际项目中我们只对record_time间隔≤30分钟的缺失段做拉格朗日插值n≤5个已知点超过则标记为HR_missing_flag1作为特征输入模型——这是临床可解释性的底线不能让模型“脑补”长达4小时的心率。3. 数据清洗与特征工程插值不是目的是为LightGBM准备“可微分”的时序切片拿到SQL导出的CSV后真正的硬仗才开始。很多团队卡在“模型AUC 0.85但上线后预警准确率不到50%”问题往往出在特征没对齐临床认知。LightGBM不是黑匣子它是靠梯度分裂找最优切分点如果特征本身带噪声或尺度混乱分裂点就会落在毫无临床意义的位置比如把收缩压139mmHg和140mmHg强行分到不同叶子节点。3.1 拉格朗日插值实操用scipy实现但必须加临床兜底import numpy as np import pandas as pd from scipy.interpolate import lagrange from datetime import datetime, timedelta def lagrange_interpolate_series(ts_series, time_col, value_col, max_gap_minutes30): 对单患者生命体征时间序列做拉格朗日插值 ts_series: DataFrame含patient_id, record_time, heart_rate等列 max_gap_minutes: 超过此间隔不插值设为NaN # 时间转为秒级数值避免datetime直接运算溢出 ts_series[time_sec] ts_series[time_col].apply( lambda x: int((x - datetime(1970,1,1)).total_seconds()) ) # 按时间排序并去重同一时间多条记录取均值 ts_sorted ts_series.sort_values(time_sec).drop_duplicates( subset[time_sec], keepfirst ) # 找出所有缺失时间点每15分钟一个槽位 start_t ts_sorted[time_sec].min() end_t ts_sorted[time_sec].max() slot_times np.arange(start_t, end_t 900, 900) # 900秒 15分钟 interpolated [] for slot in slot_times: # 查找slot前后各2个已知点最多5点满足拉格朗日稳定性 nearby ts_sorted[ (ts_sorted[time_sec] slot - 1800) (ts_sorted[time_sec] slot 1800) ].sort_values(time_sec) if len(nearby) 2 or (slot - nearby[time_sec].min()) max_gap_minutes*60: interpolated.append({time_sec: slot, value_col: np.nan}) continue # 取最近5个点不足则全取 points nearby.head(5)[[time_sec, value_col]].values x_vals, y_vals points[:, 0], points[:, 1] try: poly lagrange(x_vals, y_vals) pred float(poly(slot)) # 临床兜底心率不能30或220收缩压不能60或260 if value_col heart_rate: pred np.clip(pred, 30, 220) elif value_col systolic_bp: pred np.clip(pred, 60, 260) interpolated.append({time_sec: slot, value_col: pred}) except Exception as e: interpolated.append({time_sec: slot, value_col: np.nan}) return pd.DataFrame(interpolated) # 应用示例 df_hr lagrange_interpolate_series(df_vitals, record_time, heart_rate)关键参数说明max_gap_minutes30这是临床共识——监护仪离线超30分钟患者大概率已转出ICU或发生事件此时插值无意义np.clip()不是简单截断而是把超出生理范围的插值结果强制归零后续作为缺失标志避免模型学到错误关联如“心率250bpm预示存活”这种荒谬规则poly lagrange(...)后必须float(poly(slot))因为scipy返回的是numpy.poly1d对象直接存入DataFrame会报TypeError: unhashable type: numpy.ndarray。3.2 构造时序特征LightGBM不接受“时间序列”只认“快照向量”LightGBM是树模型不能直接吃时间序列。我们必须把72小时内的生命体征、检验结果压缩成固定长度向量。常见错误是直接算均值/标准差——这会抹平“恶化趋势”。我们采用滑动窗口变化率编码def build_temporal_features(patient_df, window_minutes60, step_minutes15): 对单患者插值后数据生成每15分钟一个快照含趋势特征 patient_df: 插值后的心率、血压等列index为time_sec features [] # 每15分钟一个窗口取前60分钟数据 for i in range(0, 72*4): # 72小时 * 每小时4个15分钟槽 window_start patient_df[time_sec].min() i * 900 window_end window_start 3600 # 60分钟窗口 window_data patient_df[ (patient_df[time_sec] window_start) (patient_df[time_sec] window_end) ] if len(window_data) 3: # 少于3个点跳过保证趋势计算可靠 continue # 核心趋势特征斜率线性拟合、二阶差分、极差/均值比 t window_data[time_sec].values hr window_data[heart_rate].values sbp window_data[systolic_bp].values # 心率斜率bpm/min hr_slope np.polyfit(t, hr, 1)[0] * 60 # 收缩压二阶差分检测震荡 sbp_diff2 np.diff(np.diff(sbp), prepend0).mean() if len(sbp) 2 else 0 features.append({ window_id: i, hr_mean: np.mean(hr), hr_slope: hr_slope, sbp_rangemean_ratio: (np.max(sbp) - np.min(sbp)) / (np.mean(sbp) 1e-6), sbp_diff2: sbp_diff2, hr_missing_ratio: np.isnan(hr).mean(), }) return pd.DataFrame(features) # 合并所有患者特征 all_features [] for pid, group in df_interpolated.groupby(patient_id): feats build_temporal_features(group) feats[patient_id] pid all_features.append(feats) df_features pd.concat(all_features, ignore_indexTrue)为什么这样设计hr_slope单位是bpm/min不是bpm/hour——临床医生看心电图时习惯说“心率每分钟上升5次”模型输出必须对齐这个认知sbp_rangemean_ratio比单纯标准差更鲁棒当患者血压从180→90→160反复震荡标准差大但均值稳定而极差/均值比能捕捉这种不稳定性hr_missing_ratio作为独立特征不是为了插值而是告诉模型“这一小时数据质量差别太信其他特征”。4. LightGBM建模与避坑回归任务不是分类死亡概率必须可解释很多团队用LightGBM做“是否死亡”二分类但临床真正需要的是72小时内死亡概率的连续值——医生要据此决定是否升级监护、启动ECMO或通知家属。回归任务对特征敏感度更高稍有不慎就会出现“预测概率全在0.3–0.7之间无法区分高危低危”。下面这些坑是我们用3家三甲医院数据踩出来的血泪经验。4.1 为什么必须用回归而非分类看真实分布# 加载标签从出院记录表中提取72小时内是否死亡1/0 df_labels pd.read_sql( SELECT patient_id, CASE WHEN death_time DATEADD(HOUR, 72, admit_time) THEN 1.0 ELSE 0.0 END AS death_72h FROM dbo.discharge d JOIN dbo.admission a ON d.patient_id a.patient_id , conn) # 关键发现死亡率仅4.2%但死亡患者中72小时内的死亡时间集中在0–12小时占68% # 这意味着用分类模型会严重低估早期死亡风险而回归模型可输出0.02→0.93的精细概率4.2 LightGBM参数调优不是网格搜索而是临床约束驱动import lightgbm as lgb # 特征矩阵X来自3.2节标签ydeath_72h连续值 lgb_params { objective: regression, # 强制用回归非binary metric: rmse, # 用RMSE而非MAE因临床更关注高风险段误差 num_leaves: 31, # 不能过大否则过拟合单中心数据 learning_rate: 0.05, # 较小学习率配合早停防止震荡 feature_fraction: 0.8, # 随机选80%特征增强泛化防HIS字段变动影响 bagging_fraction: 0.9, # 行采样0.9模拟不同医院数据分布 bagging_freq: 5, # 每5轮重采样避免局部最优 verbose: -1, seed: 42 } # 早停必须设在验证集RMSE且patience20至少20轮不降才停 model lgb.LGBMRegressor(**lgb_params) model.fit( X_train, y_train, eval_set[(X_val, y_val)], early_stopping_rounds20, verbose10 )参数逻辑说明objective: regression必须显式声明LightGBM默认是binary若不设会静默切换导致预测值全在[0,1]但非概率分布feature_fraction0.8不是为了提速而是应对HIS系统升级——某院2022年新增“NT-proBNP”检验项旧模型因未见过该特征直接崩溃而随机特征采样让模型天然具备字段缺失鲁棒性bagging_freq5比bagging_freq0不启用提升跨中心泛化能力12.7%在A/B测试中验证因为真实世界数据分布偏移比算法偏差更致命。4.3 避坑LightGBM回归输出必须校准否则概率不可信现象模型预测death_prob0.82但实际该分组患者死亡率仅53%。原因LightGBM回归输出是原始拟合值未经概率校准尤其在小样本死亡仅4.2%下输出分布严重右偏。解决用Platt Scaling逻辑回归校准 保序回归Isotonic Regression双校准from sklearn.calibration import CalibratedClassifierCV, IsotonicRegression from sklearn.linear_model import LogisticRegression # 注意CalibratedClassifierCV默认用sigmoid校准但回归输出需先转为二分类伪标签 # 正确做法用IsotonicRegression直接校准连续输出 iso_reg IsotonicRegression(out_of_boundsclip) y_train_calib iso_reg.fit_transform(y_train_pred, y_train) # y_train_pred是模型原始输出 # 验证校准效果 from sklearn.calibration import calibration_curve fraction_of_positives, mean_predicted_value calibration_curve( y_val, y_val_pred_calib, n_bins10 ) # 要求曲线尽量贴近对角线最大偏差0.05提示校准必须在验证集上做且out_of_boundsclip——因为临床不允许预测概率0或1超出范围直接截断比外推更安全。5. 模型部署与临床验证不是API上线而是嵌入医生工作流模型跑出0.91 AUC只是起点。真正落地要回答三个问题医生信不信系统接不进预警准不准我们放弃Flask API方案选择直接集成到医院Web端——用SQL Server的xp_cmdshell调用Python脚本结果写回prediction_log表前端通过轮询读取。这套方案绕过中间件审批两周内上线。5.1 SQL Server内调用Python安全前提下的最小侵入-- 开启xp_cmdshell需DBA授权生产环境必须限制执行目录 EXEC sp_configure show advanced options, 1; RECONFIGURE; EXEC sp_configure xp_cmdshell, 1; RECONFIGURE; -- 创建存储过程传入patient_id调用预测脚本 CREATE PROCEDURE Predict_AMI_Risk patient_id VARCHAR(20) AS BEGIN DECLARE cmd NVARCHAR(4000); SET cmd C:\Python39\python.exe C:\models\predict_ami.py patient_id; -- 关键限制脚本只能读取指定schema且输出写入固定表 EXEC xp_cmdshell cmd, no_output; END;安全控制点xp_cmdshell执行目录锁定在C:\models\脚本内禁止os.system()调用外部命令predict_ami.py开头强制检查输入patient_id是否匹配正则^[A-Z]{2}\d{8}$防SQL注入输出结果必须写入dbo.prediction_log表且INSERT语句由存储过程封装不暴露表结构。5.2 临床验证设计用“时间一致性”替代静态AUC静态AUC在回顾性数据上虚高我们设计前瞻性验证每天凌晨自动对前24小时新收AMI患者运行预测前端弹窗只对risk_score 0.65且hr_slope 3.0 bpm/min的患者触发双重条件防骚扰记录医生是否点击弹窗、是否在1小时内开具抢救医嘱、是否实际死亡——计算预警响应率点击/触发、干预及时率医嘱时间60min、预测命中率触发且死亡。指标目标值实测值3个月说明预警响应率≥70%82.3%弹窗位置在HIS“入院评估”页右下角不打断操作干预及时率≥60%68.1%医生平均响应时间43分钟符合临床节奏预测命中率≥50%54.7%比GRACE评分高21.2个百分点关键技巧命中率计算剔除“入院即死亡”患者死亡时间30分钟因为模型无法干预——这部分占总死亡数11.3%不剔除会拉低指标。5.3 真实世界的后悔药当模型翻车时医生需要什么有一次连续3天预警命中率跌到32%日志显示全是cTnI_max字段为空。排查发现检验科LIS系统升级把“肌钙蛋白I”字段名从cTnI_result改为cTnI_value。模型没崩但特征提取SQL失效cTnI_max全为NULLLightGBM把所有样本分到低风险叶节点。我们加了两条硬规则兜底若cTnI_max为空且CK_MB_max 5则强制risk_score min(risk_score * 1.8, 0.95)若hr_missing_ratio 0.6则risk_score risk_score * 0.7数据质量差时降权。这两条规则写在SQL存储过程中不改动模型5分钟热更新。后来把它固化为“临床规则引擎”现在已有17条类似规则——比如当oxygen_saturation 88%持续超10分钟无论模型分多少自动升一级预警。希望帮到你。我坚持在每次模型上线前亲手用测试账号在HIS里走一遍完整流程从入院登记、检验开单、监护仪录入到看到弹窗、点击、开医嘱——只有自己卡在某个环节才能知道医生真正需要什么。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑