随机森林结合大模型处理医疗表格数据,实现高精度建模搭配大模型医学文本归因(19.5)
一、前言
做医疗数据分析、AI辅助诊断应该都会遇到两个非常棘手的问题:一是纯表格结构化病历数据,只用大模型直接训练精度拉胯,数值、检验指标、分级特征很难被大模型精准捕捉;二是传统机器学习像随机森林,分类准确率足够高,但永远是黑盒模型,医生拿到预测结果,完全看不懂模型凭哪些检验指标、病史特征判定患者高风险,没法用于临床诊疗落地。
很久以来两条技术路线一直是割裂的,容易只知其一不知其二的困境:
- 做结构化数据预测的工程师死磕树模型、传统机器学习,只追求指标精度;
- 做大模型应用的算法人员埋头微调模型、RAG,专攻文本生成、问答解读,二者很少结合落地。
但医疗场景有硬性合规要求,不仅要AI给出诊断分类结果,还必须输出符合医学规范、通俗易懂的归因解释,缺一不可。从业这么久了,很多问题找不到方案的时候,就只能自己尝试着结合突破局限,构建新的解决方案,今天就做个简单的总结和思路的整理,供大家参考;

二、基础概念
1. 医疗表格数据
1.1 定义与数据来源
医疗表格数据,指以二维表格形式存储、每条样本对应一位患者、每一列代表一项临床指标的结构化医疗数据,区别于CT影像、门诊病历自由文本、语音问诊这类非结构化医疗数据。
常见数据源包含三大类:
- 检验系统(LIS):血常规、肝肾功能、血糖血脂、肿瘤标志物等数值型指标,格式统一,全部为数字;
- 电子病历系统(EMR):年龄、性别、既往病史、手术史、吸烟饮酒史、疾病分级等分类离散特征;
- 随访数据库:复诊频次、并发症记录、用药时长、预后分级等有序类别特征。
单条患者样本会整合数十至上百项特征,比如糖尿病筛查数据集包含:年龄、BMI、空腹血糖、糖化血红蛋白、高血压病史、家族遗传史、甘油三酯、用药记录等多维度特征,最终建模目标是二分类/多分类任务,例如:是否确诊2型糖尿病、心脑血管疾病风险等级、肿瘤良恶性判定、术后并发症发生概率。
1.2 三大特征类型细分
表格内所有临床特征可划分为三类,不同特征适配随机森林的分裂逻辑,同时决定后续大模型归因解读逻辑:
- 连续数值特征:血糖、胆固醇、肌酐、年龄等连续变化数字,随机森林会自动寻找最优分割阈值;
- 无序分类特征:性别、血型、有无过敏史,无大小排序关系;
- 有序分类特征:疾病分期(Ⅰ/Ⅱ/Ⅲ 期)、疼痛等级、风险分级,存在固定强弱顺序。

1.3 纯表格医疗数据现存技术痛点
单独使用任意一类模型处理该数据,都会出现无法规避的短板,这也是我们需要随机森林与大模型融合的根本原因:
仅用原生大模型处理表格数据缺陷:
- 大模型原生适配文本序列输入,对纯数字表格缺乏天然建模能力。
- 若直接把表格转为文本投喂给模型,数值大小、特征权重、指标阈值会被模型忽略,特征之间的数值关联丢失,分类准确率大幅下滑;
- 同时大模型训练成本高,小样本医疗数据集极易出现过拟合。
仅用随机森林处理表格数据缺陷:
- 随机森林训练完成后具备极高分类精度,对数值、分类特征适配性极强,训练速度快、小样本容错性好,但属于典型黑盒模型。
- 虽然可以提取特征重要性数值,但只能输出一串数字权重,无法结合医学知识翻译成医生能看懂的专业自然语言,不满足医疗临床可解释合规要求;
- 无法自主结合医学常识补充解读,单纯权重数字无临床使用价值。
行业落地硬性约束:
- 医疗AI需满足卫健委可解释性规范,预测结果必须配套完整归因逻辑,单纯精度达标无法上线;
- 同时临床数据多为小样本、高缺失、特征多维度,单一模型很难同时兼顾精度、可解释、低成本训练三大需求。
2. 随机森林基础
2.1 基础定义
随机森林属于集成学习中Bagging 分支的树模型,由大量相互独立的决策树共同组成,最终分类结果由所有单棵决策树投票决定。简单通俗理解:把每一棵决策树比作一名单独的临床实习生,每一位实习生只观察部分患者、部分检验指标独立给出诊断结论,最后汇总所有实习生投票,得票最多的疾病判定为最终结果。
随机包含两层核心随机机制,也是它适配医疗数据的关键:
- 样本随机:每棵决策树训练时,从全部患者数据中有放回抽样,生成专属训练子集,规避单一样本分布带来的偏差;
- 特征随机:每一次树节点分裂时,仅随机选取部分临床指标寻找最优分割阈值,避免单一核心指标完全主导预测,提升模型泛化能力。
2.2 单棵决策树工作逻辑
单棵决策树是分层分支判断结构,以糖尿病二分类举例:
- 第一层分裂特征选择空腹血糖,设定阈值 7.0mmol/L;大于阈值进入左分支,小于进入右分支;
- 左分支继续选取糖化血红蛋白做二次分裂,右分支选取 BMI 指数分裂;
- 逐层分裂直至达到预设深度、样本数量阈值,叶子节点输出该分支下患者大概率患病或健康的判定。

单棵决策树缺陷明显:极易过拟合,单条异常检验数据就会完全改变分裂逻辑,单独使用精度不稳定;而随机森林集成上百棵独立决策树,抵消单树过拟合缺陷,稳定提升分类精度。
2.3 随机森林适配医疗的优势
- 无需复杂特征预处理:深度学习、大模型处理数值数据必须做标准化、归一化,随机森林对数值尺度不敏感,血糖、年龄、肿瘤标志物不用统一缩放,缺失值可通过简单填充直接适配,大幅降低医疗数据预处理工作量;
- 兼容全部三类临床特征:连续数值、有序或无序分类指标无需单独编码,原生支持离散特征输入,适配医院多类型混合检验表格;
- 小样本医疗数据容错性强:临床标注患者病例样本获取成本极高,很多疾病数据集仅数千条样本,随机森林不需要海量数据训练,少量标注即可达到理想分类准确率;
- 原生输出量化特征重要性:模型训练结束后,可直接输出每一项临床指标对最终分类结果的贡献权重,例如 “糖化血红蛋白权重0.32、空腹血糖权重0.28、BMI权重0.15”,这是后续大模型做归因解读的核心输入素材,是纯大模型不具备的量化输出能力。
2.4 随机森林单独使用的缺点
随机森林仅能输出量化数字权重,没有医学语义理解能力,无法自主解读权重背后的临床意义。例如输出“空腹血糖权重最高”,模型不能自动生成“患者空腹血糖超过临床诊断阈值,是判定糖尿病高风险的核心依据,结合糖化指标进一步佐证患病可能性”这类专业医学文本,必须依赖大模型承接量化数据完成自然语言转化。
3. 模型融合概述
3.1 大模型处理表格数据的短板
- 数值敏感度不足:大模型本质处理文本Token,数字会被拆解为字符编码,无法精准感知数值区间、临床诊断阈值。例如空腹血糖6.9和7.1,在临床中是健康与糖尿病临界区分线,纯大模型很难捕捉这种细微数值边界差异,分类预测精度远低于随机森林;
- 训练成本高、小样本易过拟合:若直接使用大模型做患者疾病分类任务,需要大量标注医疗病例微调,算力、数据、标注成本极高,基层医院无法落地;随机森林轻量化训练可完美弥补该缺陷;
- 无原生量化特征贡献输出:大模型只能输出文本结论,无法给出每一项指标的量化贡献度,归因解读缺少客观数字支撑,不符合医疗数据客观、可量化的审核标准。
3.2 融合架构的定义
随机森林+大模型医疗表格高精度分类归因框架,是一套分工明确的两级流水线AI系统:
- 第一级轻量化预测层:随机森林负责接收原始患者表格数据,完成数据训练、疾病分类预测、计算单样本特征贡献权重,输出标准化结构化数字结果;
- 第二级可解释生成层:大模型接收第一层全部量化输出数据,结合内置医学指南知识库,完成指标权重推理、风险逻辑归因,输出符合临床规范、可读性强的自然语言诊断解释报告。
两个模型各司其职,优势互补,规避单一模型精度不足、无法解释的双重痛点,专门针对结构化检验表格医疗数据设计,兼顾预测精度、落地成本、临床合规可解释三大核心需求。
3.3 双模型明确分工
- 随机森林只做“数值计算、分类预测、量化权重输出”,不参与任何文本生成、医学解读;
- 大模型只做“结构化数字转医学自然语言、逻辑归因推理、报告生成”,不参与原始表格数据的疾病分类预测;
二者数据流单向流转:原始表格→随机森林(输出结构化数字)→大模型(输出自然语言归因文本),无反向数据回流,架构简单稳定,实践落地难度极低。
3.4 解决的医疗行业核心问题
- 解决纯大模型表格分类精度不足问题:用树模型弥补大模型数值建模短板,保证疾病分类、风险分级预测准确率;
- 解决随机森林黑盒不可解释合规问题:用大模型专业语义能力翻译量化权重,生成临床可用归因说明,满足医疗AI监管可解释硬性要求;
- 降低落地算力与数据成本:随机森林轻量化训练无需高算力GPU,仅CPU即可完成;大模型仅做推理生成,无需大规模微调,小医院、基层医疗机构均可部署使用。
3.5 适配医疗业务场景范围
可覆盖绝大多数结构化表格医疗分类任务:慢性病筛查(糖尿病、高血压)、肿瘤良恶性判别、术后并发症风险预测、心脑血管风险分级、肝肾损伤筛查等所有以检验表格为数据源的二分类、多分类临床AI辅助诊断场景。
三、核心基础原理
1. 随机森林分裂原理
1.1 决策树分裂核心评判指标:基尼系数
随机森林每一次寻找最优临床特征分割阈值,依靠基尼系数评判分裂效果,基尼系数代表样本集合混乱程度,数值越低代表分裂后两类患者“患病、健康”区分越清晰。
- 通俗医疗举例:全部样本混合基尼系数0.5,若以空腹血糖7.0分割后,两侧子集合基尼系数降至0.1,代表该指标分割区分度极强,会被模型选为第一层分裂节点。
- 分裂计算逻辑:遍历每一项临床特征的全部数值阈值,计算分割前后基尼系数差值,差值最大的特征与阈值作为本次节点分裂标准。
针对三类医疗特征分裂处理方式:
- 连续数值指标:遍历全部数值,逐个计算分割后基尼系数,选取最优阈值;
- 无序分类指标:按类别分组计算基尼差值;
- 有序分级指标:按照分级顺序遍历分割点位。
详细参考文章:《构建AI智能体:七十、小树成林,聚沙成塔:随机森林与大模型的协同进化》
1.2 双重随机机制底层原理
Bootstrap样本抽样(样本随机):
采用有放回抽样,数据集共N条患者病例,每次抽取N条作为单棵决策树训练集,部分样本重复抽取、部分样本完全不参与该树训练,未抽取样本称为袋外数据,可用于模型泛化误差评估,医疗场景下无需额外划分验证集,降低样本损耗;
特征子空间随机选取(特征随机):
每次节点分裂,不使用全部30多个临床指标,随机抽取固定数量特征参与阈值筛选,避免某一项强相关指标(如肿瘤标志物)垄断全部分裂节点,提升模型对多维度指标的综合判断能力,防止局部检验指标异常造成整体预测偏差。
1.3 集成投票分类逻辑
所有决策树独立完成预测后,采用简单多数投票输出最终分类:
- 二分类任务:统计预测“患病”、“健康”树的数量,数量多的作为最终判定;
- 多分类风险分级:统计Ⅰ/Ⅱ/Ⅲ期各自得票数量,最高票数对应风险等级输出。
海量树集成带来的核心增益:单棵树容易被单条异常检验值干扰,上百棵独立树投票后,异常样本带来的偏差会被抵消,整体预测稳定性大幅提升,医疗数据存在大量缺失、异常检验值,该机制尤为重要。
2. 大模型语义推理原理
2.1 Transformer注意力核心作用
大模型底层依靠自注意力机制完成信息关联推理,当输入随机森林导出的患者指标、特征权重、分类结果结构化数据时,模型通过注意力权重区分信息优先级:
- 1. 高权重临床指标分配高注意力分数,优先调取对应的医学诊断指南;
- 2. 低权重次要指标分配低注意力分数,仅作为辅助佐证描述;
- 3. 分类预测结果(患病/高风险)作为全局引导信号,约束整体归因文本的核心基调。
简单举例:输入权重显示空腹血糖权重最高,注意力机制会重点关联《糖尿病诊疗指南》中血糖临界值相关文本,作为归因核心论据;次要 BMI 指标仅作为辅助补充描述。
2.2 医疗预训练知识库存储与调用逻辑
医疗大模型预训练阶段学习海量医学文本,将诊断标准、指标参考范围、疾病关联逻辑存储在模型参数中,形成可调用的隐式知识库,无需额外搭建向量数据库也可完成基础推理。
接收随机森林量化数据后的推理流程:

- 1. 识别输入数值对应的临床参考区间,例如空腹血糖7.2mmol/L超出健康参考3.9~6.1范围;
- 2. 匹配对应疾病诊断标准,判定该指标属于确诊核心依据;
- 3. 结合其他次要指标权重,整合多条临床证据链;
- 4. 按照医学书面语言逻辑组织完整归因文本。
2.3 结构化数字转文本适配逻辑
大模型原生输入为文本序列,随机森林输出的数组、表格数字无法直接输入,需要标准化中转格式化:将权重、患者指标、预测结果拼接为固定模板提示词Prompt,把结构化数字嵌入Prompt模板后再投喂模型推理。
标准 Prompt 模板示例:
“现有患者临床检验数据:{患者各项指标数值};随机森林模型预测分类:{患病/高风险};各指标贡献权重:{权重数组};请结合糖尿病临床诊疗指南,生成专业、通俗易懂的诊断归因解释,区分核心风险指标与次要辅助指标。”
模板固定化后,大模型稳定输出规范归因文本,不会出现输出格式混乱、逻辑缺失问题。
3. 两者协同的数据流转
3.1 全链路单向数据流规范
完整数据流固定三段式流转,不存在反向传输,降低系统耦合度:

- 1. 输入层:原始CSV格式患者医疗表格,包含特征列和分类标签列;
- 2. 中间计算层:随机森林模块,输出三类结构化数据:单样本每条指标数值、全局特征重要性权重、单样本预测概率与分类结果,统一转为JSON结构化格式;
- 3. 生成输出层:JSON结构化数据填充固定Prompt模板,转为文本输入大模型,输出纯自然语言归因报告文本。
3.2 中间结构化数据标准格式
随机森林输出中转JSON固定字段,作为大模型的输入源,标准化格式保证大模型稳定推理,字段包含:
- patient_index:患者样本编号
- all_clinical_indicators:字典,key为指标名称,value为对应检验数值
- feature_weight:字典,key为指标名称,value为0~1归一化权重
- predict_label:模型最终分类结果,文本描述,如“2 型糖尿病高风险”
- predict_prob:分类预测置信概率
固定字段结构后,Prompt模板可直接读取键值对,无需额外数据清洗,使得开发和落地成本极低。
3.3 模型能力互补逻辑
从底层建模能力维度对比二者互补性,这是融合架构成立的核心逻辑:
- 数值拟合能力:随机森林强,大模型弱 → 分配预测建模任务给树模型;
- 语义推理与专业知识:随机森林无,大模型强 → 分配归因解释任务给大模型;
- 量化权重输出:随机森林原生支持,大模型无法输出 → 由树模型提供客观数字依据;
- 自然语言生成:随机森林无文本输出能力,大模型原生适配 → 大模型负责解读输出;
- 训练算力需求:随机森林极低CPU即可,大模型微调算力极高 → 拆分训练与推理,节约硬件成本。

3.4 架构底层稳定性保障
分层解耦设计是底层稳定核心:两个模型完全独立模块,可单独迭代升级:
- 若临床新增检验指标,仅重新训练随机森林即可,大模型Prompt模板小幅修改适配,无需重训模型;
- 若需要更换医疗大模型基座,仅替换推理调用接口,随机森林模块完全不用改动;
- 预测与解释模块分离,可单独关闭大模型模块,仅保留随机森林做快速批量预测,适配不同业务场景需求。
四、完整业务流程
1. 医疗数据预处理流程
1.1 原始医疗数据噪声处理
医院导出的电子病历表格天然存在大量脏数据,无法直接送入随机森林训练,常见问题:
- 检验指标缺失:部分患者未完成某项检查,单元格为空;
- 异常极值:检验设备故障导致数值超出人体合理区间,如血糖200mmol/L;
- 分类特征文本不统一:“吸烟”、“长期吸烟”、“有吸烟史”同类特征多种文本写法;
- 多余无关字段:患者姓名、住院号、病床编号无预测价值,需剔除。
1.2 分步标准化预处理操作

- 1. 无关字段剔除:删除隐私标识、无关记录字段,仅保留临床预测相关特征与疾病标签;
- 2. 异常值过滤:设置各项生理指标人体合理区间,超出区间统一标记为缺失值;
- 3. 缺失值分类型填充规则
- 连续数值指标:同疾病分组均值填充(糖尿病患者血糖用糖尿病人群均值填充,区分健康人群);
- 分类特征:新增“未知”类别填充,随机森林原生支持离散类别输入,无需独热编码;
- 4. 分类特征统一标准化:文本描述统一映射为固定离散值,例如吸烟史统一转换为0=无、1=轻度、2=重度;
- 5. 数据集划分:按照7:3分割训练集、测试集,训练集送入随机森林训练,测试集用于评估分类精度。
2. 随机森林训练预测流程
2.1 医疗场景专属超参选择逻辑
针对小样本、多特征医疗表格数据,随机森林核心超参选型标准:
- n_estimators(决策树数量):100~300棵,医疗数据样本量偏小,过多树无精度提升,浪费算力;
- max_depth(单树最大深度):6~10,限制树深度避免过拟合,临床特征维度30多种,深度过高会记住训练集噪声;
- max_features(单节点随机选取特征数):总特征数平方根,兼顾分裂多样性与指标关联性;
- random_state 固定随机种子,保证医疗实验可复现,满足临床实验溯源要求。
2.2 模型训练完整步骤

- 1. 分离特征矩阵X与分类标签y;
- 2. 初始化医疗场景定制随机森林模型,填入上述超参;
- 3. 输入训练集数据完成训练,模型自动完成双重随机抽样、节点基尼分裂、集成投票学习;
- 4. 训练完成后提取全局特征重要性权重,保存为本地字典文件,后续可复用。
2.3 医疗分类模型精度评估指标
医疗任务不能仅依靠准确率评估,需同步计算四类核心指标,贴合临床诊断评价标准:
- 精确率Precision:模型判定患病的样本中,真实患病患者占比,规避误诊;
- 召回率Recall:全部真实患病患者中被模型检出比例,规避漏诊,医疗场景漏诊危害远大于误诊,召回率为核心指标;
- F1分数:精确率与召回率调和平均,综合评估模型整体性能;
- AUC-ROC曲线:二分类疾病风险区分能力,数值越接近1代表高低风险患者区分越清晰。
3. 大模型归因生成流程
3.1 推理生成归因文本完整逻辑

- 1. 大模型接收拼接完成的完整Prompt文本;
- 2. 自注意力机制优先读取高权重检验指标,匹配内置糖尿病诊疗标准;
- 3. 区分核心致病指标、次要影响指标、无明显关联指标;
- 4. 按照约束层要求分段生成归因报告,规避无依据主观判断,全部结论依托输入量化权重与检验数值;
- 5. 过滤不符合医学常识的错误推理,输出标准化解读文本。
3.2 批量自动化处理时序流程

- 1. 定时同步医院LIS/EMR系统导出的最新患者检验表格;
- 2. 自动执行数据清洗预处理脚本,生成标准化干净数据集;
- 3. 调用预训练完成的随机森林批量预测,每条样本生成结构化中转JSON;
- 4. 循环调用大模型推理接口,批量生成每位患者的自然语言归因报告;
- 5. 同步存储三类结果:患者原始指标、模型分类预测标签、大模型生成归因解读文本;
- 6. 对接医院前端系统,展示预测风险等级+可视化特征权重图+完整医学归因报告。
五、应用实践分析
1. 环境导入与全局配置
导入项目所需的全部依赖库,全局配置区包含随机种子、模型超参、大模型路径和临床Prompt模板
import os
import pandas as pd
import numpy as np
import json
import torch
import matplotlib.pyplot as plt
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, roc_auc_score, roc_curve, confusion_matrix
from transformers import AutoTokenizer, AutoModelForCausalLM
import warnings
warnings.filterwarnings("ignore")
os.environ["TRANSFORMERS_VERBOSITY"] = "error"
os.environ["TRANSFORMERS_NO_ADVISORY_WARNINGS"] = "1"
# ---- 全局配置区(医疗场景可自行修改) ----
RANDOM_SEED = 42 # 随机种子,保证实验可复现
RF_ESTIMATORS = 200 # 随机森林树数量
RF_MAX_DEPTH = 8 # 随机森林最大深度
LLM_MODEL_NAME = "/home/model/Qwen/Qwen-7B-Chat" # 开源医疗微调大模型基座路径
DISEASE_NAME = "2型糖尿病" # 当前筛查的疾病名称
PROMPT_TEMPLATE = """
你是专业内分泌临床医师,严格依据《中国2型糖尿病防治指南》完成解读,禁止无依据主观推断。
【患者全部临床检验指标】:{patient_indicators}
【随机森林AI模型预测结果】:{predict_result},预测置信概率:{predict_prob}
【各检验指标对本次风险判定的贡献权重(0~1,数值越高影响越大)】:{feature_weights}
输出要求:分两段,第一段阐述核心高权重致病指标归因,标注指标临床正常参考范围;
第二段说明次要辅助指标影响,语言通俗专业,总字数控制500字以内。
"""
2. 医疗数据生成与预处理
模拟真实场景生成糖尿病筛查结构化表格数据,含连续数值与分类特征,执行缺失值填充、特征/标签分离、训练集/测试集划分等标准化预处理流程。
# ---- 步骤1:模拟生成医疗结构化表格数据 ----
def generate_medical_data(sample_num=2000):
"""模拟生成糖尿病筛查患者表格数据集"""
np.random.seed(RANDOM_SEED)
data = {}
# 连续数值临床特征
data["age"] = np.random.randint(30, 75, sample_num) # 年龄30-75
data["bmi"] = np.round(np.random.normal(24, 3.5, sample_num), 1) # BMI指数
data["fast_glucose"] = np.round(np.random.normal(6.2, 1.8, sample_num), 1) # 空腹血糖
data["hba1c"] = np.round(np.random.normal(5.8, 1.2, sample_num), 1) # 糖化血红蛋白
data["triglyceride"] = np.round(np.random.normal(1.5, 0.6, sample_num), 1) # 甘油三酯
# 无序分类特征
data["gender"] = np.random.randint(0, 2, sample_num) # 0女 1男
data["smoke"] = np.random.randint(0, 3, sample_num) # 0无 1轻度 2重度吸烟
# 目标标签:1=确诊糖尿病高风险,0=健康低风险
label = np.zeros(sample_num)
# 模拟患病逻辑:血糖、糖化、BMI偏高则标记为1
risk_mask = (data["fast_glucose"] > 6.8) & (data["hba1c"] > 6.2)
label[risk_mask] = 1
data["diabetes_risk"] = label
df = pd.DataFrame(data)
return df
# 生成2000条模拟患者数据
medical_df = generate_medical_data(2000)
print("原始医疗表格数据集前5行:")
print(medical_df.head())
# ---- 步骤2:医疗数据标准化预处理 ----
def medical_data_preprocess(df):
"""医疗表格清洗预处理,适配随机森林训练"""
# 1.分离特征与分类标签
X = df.drop("diabetes_risk", axis=1)
y = df["diabetes_risk"]
# 2.医疗数据缺失值填充(模拟少量缺失)
X = X.mask(np.random.random(X.shape) < 0.03) # 随机制造3%缺失数据
for col in X.columns:
if X[col].dtype in [np.float64, np.int64]:
X[col] = X[col].fillna(X[col].mean())
# 3.7:3划分训练集、测试集
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, random_state=RANDOM_SEED, stratify=y
)
return X_train, X_test, y_train, y_test, X
X_train, X_test, y_train, y_test, full_X = medical_data_preprocess(medical_df)
3. 随机森林模型训练与精度评估
基于预处理后的训练集训练医疗定制随机森林分类器,提取特征重要性权重;在测试集上评估5项核心指标(准确率、精确率、召回率、F1、AUC), 同时输出预测标签和概率分布,供后续可视化与大模型推理使用。
# ---- 步骤3:训练医疗定制随机森林模型 ----
def train_medical_rf(X_train, y_train):
"""初始化并训练适配医疗表格的随机森林分类器"""
rf_model = RandomForestClassifier(
n_estimators=RF_ESTIMATORS,
max_depth=RF_MAX_DEPTH,
max_features="sqrt",
random_state=RANDOM_SEED,
n_jobs=-1 # 启用全部CPU核心加速训练
)
rf_model.fit(X_train, y_train)
# 提取全局归一化特征重要性权重
feature_names = X_train.columns.tolist()
feature_importance = rf_model.feature_importances_
weight_dict = dict(zip(feature_names, np.round(feature_importance, 4)))
return rf_model, weight_dict
rf_model, global_feature_weight = train_medical_rf(X_train, y_train)
print("\n各临床指标全局贡献权重:")
for k, v in global_feature_weight.items():
print(f"{k}: {v}")
# ---- 步骤4:模型精度评估(医疗专用指标) ----
def evaluate_rf_model(model, X_test, y_test):
y_pred = model.predict(X_test)
y_pred_proba = model.predict_proba(X_test)[:, 1]
acc = accuracy_score(y_test, y_pred)
prec = precision_score(y_test, y_pred)
recall = recall_score(y_test, y_pred)
f1 = f1_score(y_test, y_pred)
auc = roc_auc_score(y_test, y_pred_proba)
print("\n随机森林糖尿病分类模型测试集评估结果:")
print(f"准确率Accuracy:{np.round(acc,4)}")
print(f"精确率Precision:{np.round(prec,4)}")
print(f"召回率Recall:{np.round(recall,4)}")
print(f"F1分数:{np.round(f1,4)}")
print(f"AUC值:{np.round(auc,4)}")
return y_pred, y_pred_proba
test_pred, test_proba = evaluate_rf_model(rf_model, X_test, y_test)
4. 单患者结构化数据导出
从测试集中选取单例患者,提取其临床指标、模型预测结果和特征权重,封装为标准化JSON结构。该JSON既是可视化雷达图的数据源,也是后续拼接到大模型Prompt中的核心结构化输入。
# ---- 步骤5:单患者样本导出结构化中转JSON数据 ----
def get_patient_struct_data(model, weight_dict, sample_x, sample_index=0):
"""提取单患者指标、预测结果、权重,生成标准化JSON"""
patient_indicators = sample_x.iloc[sample_index].to_dict()
pred_label_num = model.predict(sample_x.iloc[[sample_index]])[0]
pred_prob = np.round(model.predict_proba(sample_x.iloc[[sample_index]])[0][int(pred_label_num)], 3)
pred_text = f"{DISEASE_NAME}高风险" if pred_label_num == 1 else "健康低风险"
struct_data = {
"patient_id": sample_index,
"indicators": patient_indicators,
"feature_weight": weight_dict,
"predict_text": pred_text,
"predict_prob": float(pred_prob)
}
return struct_data
# 选取测试集第1位患者作为示例样本
patient_json_data = get_patient_struct_data(rf_model, global_feature_weight, X_test, sample_index=0)
print("\n单患者结构化中转JSON数据:")
print(json.dumps(patient_json_data, ensure_ascii=False, indent=2))
5. 模型可视化辅助诊断
基于模型评估结果与单患者数据,绘制5张独立可视化图表,包括:特征权重柱状图、ROC曲线、混淆矩阵、风险概率分布、患者雷达图。每张图均标注实际数据与临床含义,可直接用于辅助诊断报告。
# ---- 步骤6:绘制特征权重可视化柱状图 ----
def plot_feature_weight_chart(weight_dict):
"""绘制临床指标贡献权重可视化图,配套归因报告展示"""
plt.rcParams["font.sans-serif"] = ["WenQuanYi Micro Hei"]
plt.rcParams["axes.unicode_minus"] = False
features = list(weight_dict.keys())
weights = list(weight_dict.values())
plt.figure(figsize=(10, 6))
bars = plt.bar(features, weights, color="#2E86AB")
plt.title("糖尿病筛查-临床指标特征贡献权重分布", fontsize=14)
plt.xlabel("检验指标名称", fontsize=12)
plt.ylabel("归一化贡献权重(0~1)", fontsize=12)
plt.xticks(rotation=30)
# 柱状图标注权重数值
for bar in bars:
height = bar.get_height()
plt.text(bar.get_x() + bar.get_width()/2., height,
f"{np.round(height,3)}", ha="center", va="bottom")
plt.tight_layout()
output_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "195.糖尿病筛查-临床指标特征贡献权重分布.png")
plt.savefig(output_path, dpi=150, bbox_inches="tight")
plt.show()
plot_feature_weight_chart(global_feature_weight)
# ---- 步骤7:辅助诊断可视化(4张独立图 + 数据标注) ----
BASE_IMG_DIR = os.path.dirname(os.path.abspath(__file__))
plt.rcParams["font.sans-serif"] = ["WenQuanYi Micro Hei"]
plt.rcParams["axes.unicode_minus"] = False
# ---- 图1:ROC曲线 ----
def plot_roc_chart(y_test, y_pred_proba):
"""ROC曲线:评估模型对高低风险患者的区分能力"""
fpr, tpr, thresholds = roc_curve(y_test, y_pred_proba)
auc_val = roc_auc_score(y_test, y_pred_proba)
fig, ax = plt.subplots(figsize=(11, 8))
ax.plot(fpr, tpr, color="#2E86AB", lw=2.5, label=f"随机森林 AUC = {auc_val:.4f}")
ax.plot([0, 1], [0, 1], color="#AAA", lw=1.5, linestyle="--", label="随机猜测基线")
ax.fill_between(fpr, tpr, alpha=0.12, color="#2E86AB")
# 标注最佳约登指数点
youden = tpr - fpr
best_idx = np.argmax(youden)
best_thresh = thresholds[best_idx]
ax.scatter(fpr[best_idx], tpr[best_idx], s=120, color="#E74C3C", zorder=5, edgecolors="white", lw=2)
ax.annotate(f"最佳阈值={best_thresh:.2f}\nTPR={tpr[best_idx]:.2f} FPR={fpr[best_idx]:.2f}",
xy=(fpr[best_idx], tpr[best_idx]),
xytext=(fpr[best_idx]+0.15, tpr[best_idx]-0.15),
fontsize=11, color="#E74C3C", fontweight="bold",
arrowprops=dict(arrowstyle="->", color="#E74C3C", lw=1.5))
# 结论文本
if auc_val >= 0.9:
quality = "优秀"
elif auc_val >= 0.8:
quality = "良好"
elif auc_val >= 0.7:
quality = "尚可"
else:
quality = "需优化"
n_test = len(y_test)
ax.text(0.98, 0.15, f"AUC={auc_val:.4f},模型区分能力:{quality}\n测试集{n_test}例,高风险{np.sum(y_test.values)}例,低风险{n_test-np.sum(y_test.values)}例",
transform=ax.transAxes, fontsize=11, ha="right", va="bottom",
bbox=dict(boxstyle="round,pad=0.4", facecolor="#FFF9C4", edgecolor="#F9A825", alpha=0.9))
ax.set_title("ROC曲线 — 糖尿病风险分类模型区分能力评估", fontsize=14, fontweight="bold")
ax.set_xlabel("假阳性率 (FPR) — 实际低风险被误判为高风险的比例", fontsize=11)
ax.set_ylabel("真阳性率 (TPR) — 实际高风险被正确识别的比例", fontsize=11)
ax.legend(fontsize=11, loc="lower right")
ax.grid(alpha=0.3)
plt.tight_layout()
plt.savefig(os.path.join(BASE_IMG_DIR, "195.1-ROC曲线.png"), dpi=150, bbox_inches="tight")
plt.show()
# ---- 图2:混淆矩阵热力图 ----
def plot_confusion_matrix_chart(y_test, y_pred):
"""混淆矩阵:展示真阳性/假阳性/假阴性/真阴性的具体分布"""
cm = confusion_matrix(y_test, y_pred)
tn, fp, fn, tp = cm.ravel()
fig, ax = plt.subplots(figsize=(10, 8))
im = ax.imshow(cm, cmap="Blues", interpolation="nearest")
labels_text = ["低风险(0)", "高风险(1)"]
ax.set_xticks([0, 1])
ax.set_xticklabels(labels_text, fontsize=12)
ax.set_yticks([0, 1])
ax.set_yticklabels(labels_text, fontsize=12)
ax.set_xlabel("预测标签", fontsize=12)
ax.set_ylabel("真实标签", fontsize=12)
# 格子内标注
cell_info = [[f"真阴性 TN={tn}", f"假阳性 FP={fp}"],
[f"假阴性 FN={fn}", f"真阳性 TP={tp}"]]
for i in range(2):
for j in range(2):
color = "white" if cm[i, j] > cm.max() / 2 else "black"
ax.text(j, i - 0.08, f"{cm[i, j]}", ha="center", va="center",
fontsize=22, fontweight="bold", color=color)
ax.text(j, i + 0.18, cell_info[i][j], ha="center", va="center",
fontsize=11, color=color, style="italic")
plt.colorbar(im, ax=ax, fraction=0.046)
# 底部结论
total = tn + fp + fn + tp
sensitivity = tp / (tp + fn) if (tp + fn) > 0 else 0
specificity = tn / (tn + fp) if (tn + fp) > 0 else 0
precision_val = tp / (tp + fp) if (tp + fp) > 0 else 0
conclusion = (f"测试集共{total}例 | 灵敏度(召回率)={sensitivity:.2%} | 特异度={specificity:.2%} | 阳性预测值(精确率)={precision_val:.2%}\n"
f"漏诊{fn}例(假阴性) — 误诊{fp}例(假阳性)")
ax.text(0.5, -0.15, conclusion, transform=ax.transAxes, fontsize=11, ha="center",
bbox=dict(boxstyle="round,pad=0.4", facecolor="#FFF9C4", edgecolor="#F9A825", alpha=0.9))
ax.set_title("混淆矩阵 — 糖尿病风险分类结果分布", fontsize=14, fontweight="bold")
plt.tight_layout()
plt.savefig(os.path.join(BASE_IMG_DIR, "195.2-混淆矩阵.png"), dpi=150, bbox_inches="tight")
plt.show()
# ---- 图3:风险概率分布直方图 ----
def plot_risk_distribution(y_test, y_pred_proba):
"""展示高低风险人群的预测概率分布,评估模型分离度"""
prob_neg = y_pred_proba[y_test.values == 0]
prob_pos = y_pred_proba[y_test.values == 1]
fig, ax = plt.subplots(figsize=(12, 7))
ax.hist(prob_neg, bins=30, alpha=0.65, color="#27AE60",
label=f"实际低风险(n={len(prob_neg)},均值={np.mean(prob_neg):.3f})", edgecolor="white")
ax.hist(prob_pos, bins=30, alpha=0.65, color="#E74C3C",
label=f"实际高风险(n={len(prob_pos)},均值={np.mean(prob_pos):.3f})", edgecolor="white")
ax.axvline(x=0.5, color="black", linestyle="--", lw=2, label="决策阈值0.5")
# 标注重叠区域
overlap_low = np.sum((prob_neg > 0.3) & (prob_neg < 0.7))
overlap_high = np.sum((prob_pos > 0.3) & (prob_pos < 0.7))
ax.axvspan(0.3, 0.7, alpha=0.08, color="#F39C12", label=f"模糊区间(0.3~0.7):低风险{overlap_low}例/高风险{overlap_high}例")
# 标注均值
ax.axvline(x=np.mean(prob_neg), color="#27AE60", linestyle=":", lw=1.8)
ax.axvline(x=np.mean(prob_pos), color="#E74C3C", linestyle=":", lw=1.8)
ax.text(np.mean(prob_neg), ax.get_ylim()[1]*0.9, f"低风险均值\n{np.mean(prob_neg):.3f}", fontsize=10, ha="center", color="#27AE60")
ax.text(np.mean(prob_pos), ax.get_ylim()[1]*0.85, f"高风险均值\n{np.mean(prob_pos):.3f}", fontsize=10, ha="center", color="#E74C3C")
# 结论
separation = np.mean(prob_pos) - np.mean(prob_neg)
quality = "优秀" if separation > 0.6 else "良好" if separation > 0.4 else "一般"
ax.text(0.02, 0.98, f"高低风险均值差={separation:.3f},分离度:{quality}",
transform=ax.transAxes, fontsize=11, ha="left", va="top",
bbox=dict(boxstyle="round,pad=0.4", facecolor="#FFF9C4", edgecolor="#F9A825", alpha=0.9))
ax.set_title("预测风险概率分布 — 高低风险人群分离度", fontsize=14, fontweight="bold")
ax.set_xlabel("预测为高风险的概率", fontsize=12)
ax.set_ylabel("样本数", fontsize=12)
ax.legend(fontsize=10, loc="center right")
plt.tight_layout()
plt.savefig(os.path.join(BASE_IMG_DIR, "195.3-风险概率分布.png"), dpi=150, bbox_inches="tight")
plt.show()
# ---- 图4:单患者雷达图 ----
def plot_patient_radar(patient_data):
"""单患者雷达图:各指标与临床正常范围对比,超限区域红色突出"""
indicator_names = {"age": "年龄", "bmi": "BMI", "fast_glucose": "空腹血糖",
"hba1c": "糖化血红蛋白", "triglyceride": "甘油三酯",
"gender": "性别", "smoke": "吸烟"}
# 临床正常参考范围
normal_ref = {"age": ("—", 75), "bmi": ("18.5~24", 28), "fast_glucose": ("3.9~6.1", 7.0),
"hba1c": ("4.0~6.0", 6.5), "triglyceride": ("0.56~1.70", 2.0),
"gender": ("—", 1), "smoke": ("0", 2)}
patient_vals = patient_data["indicators"]
radar_features = ["fast_glucose", "hba1c", "bmi", "triglyceride", "age", "smoke"]
radar_labels = [indicator_names[f] for f in radar_features]
# 归一化到0~1(相对正常上限)
normal_max = {k: v[1] for k, v in normal_ref.items()}
patient_norm = [min(float(patient_vals[f]) / normal_max[f], 1.5) / 1.5 for f in radar_features]
normal_norm = [1.0 / 1.5 for _ in radar_features]
# 标记超限
exceeded = [float(patient_vals[f]) > normal_max[f] for f in radar_features]
val_labels = [f"{patient_vals[f]}{'↑' if e else ''}" for f, e in zip(radar_features, exceeded)]
angles = np.linspace(0, 2 * np.pi, len(radar_features), endpoint=False).tolist()
patient_norm += patient_norm[:1]
normal_norm += normal_norm[:1]
angles += angles[:1]
fig, ax = plt.subplots(figsize=(11, 9), subplot_kw=dict(polar=True))
ax.plot(angles, patient_norm, "o-", color="#E74C3C", lw=2, markersize=7, label="患者指标")
ax.fill(angles, patient_norm, alpha=0.2, color="#E74C3C")
ax.plot(angles, normal_norm, "s--", color="#2E86AB", lw=2, label="临床正常上限")
ax.fill(angles, normal_norm, alpha=0.08, color="#2E86AB")
# 超限点标红放大
for i, is_exc in enumerate(exceeded):
if is_exc:
ax.scatter(angles[i], patient_norm[i], s=180, color="#E74C3C", edgecolors="black", lw=1.5, zorder=5)
ax.set_xticks(angles[:-1])
ax.set_xticklabels([f"{l}\n{v}" for l, v in zip(radar_labels, val_labels)], fontsize=11)
ax.set_ylim(0, 1.15)
# 结论文本
exc_count = sum(exceeded)
exc_names = "、".join([radar_labels[i] for i, e in enumerate(exceeded) if e])
conclusion = f"患者预测:{patient_data['predict_text']}(置信{patient_data['predict_prob']:.1%})\n{exc_count}项指标超正常上限:{exc_names if exc_names else '无'}"
ax.set_title(f"单患者临床指标雷达图\n{conclusion}", fontsize=13, fontweight="bold", pad=25)
ax.legend(loc="upper right", bbox_to_anchor=(1.28, 1.12), fontsize=11)
plt.tight_layout()
plt.savefig(os.path.join(BASE_IMG_DIR, "195.4-单患者雷达图.png"), dpi=150, bbox_inches="tight")
plt.show()
# 依次绘制4张独立图
plot_roc_chart(y_test, test_proba)
plot_confusion_matrix_chart(y_test, test_pred)
plot_risk_distribution(y_test, test_proba)
plot_patient_radar(patient_json_data)
6. 大模型Prompt构建与医学归因推理
将单患者结构化JSON数据填入临床Prompt模板,构建完整的推理输入;加载本地大模型Qwen-7B-Chat,执行自回归生成,输出包含指标归因与临床建议的自然语言解读报告。
# ---- 步骤8:拼接大模型推理Prompt模板 ----
def build_llm_prompt(struct_data):
indicators_text = json.dumps(struct_data["indicators"], ensure_ascii=False, indent=1)
weight_text = json.dumps(struct_data["feature_weight"], ensure_ascii=False, indent=1)
prompt = PROMPT_TEMPLATE.format(
patient_indicators=indicators_text,
predict_result=struct_data["predict_text"],
predict_prob=struct_data["predict_prob"],
feature_weights=weight_text
)
return prompt
llm_input_prompt = build_llm_prompt(patient_json_data)
print("\n输入大模型完整Prompt文本:")
print(llm_input_prompt)
# ---- 步骤9:加载医疗大模型并生成归因解读 ----
def load_medical_llm(model_name):
"""加载轻量化医疗大模型与分词器"""
tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
llm_model = AutoModelForCausalLM.from_pretrained(
model_name, torch_dtype=torch.bfloat16, device_map="auto", trust_remote_code=True
)
return tokenizer, llm_model
def generate_medical_explain(tokenizer, llm, prompt):
"""输入Prompt,生成医学归因解读文本"""
inputs = tokenizer(prompt, return_tensors="pt").to("cuda")
outputs = llm.generate(
**inputs, max_new_tokens=600, temperature=0.3, top_p=0.7
)
explain_text = tokenizer.decode(outputs[0], skip_special_tokens=True)
return explain_text
# 加载模型并生成归因(无GPU环境可注释本段,直接查看Prompt逻辑)
tokenizer, llm = load_medical_llm(LLM_MODEL_NAME)
medical_explain_report = generate_medical_explain(tokenizer, llm, llm_input_prompt)
print("\n=====大模型输出完整医学归因解读报告=====")
print(medical_explain_report)
7. 运行输出和图示结果

原始医疗表格数据集前5行:
age bmi fast_glucose hba1c triglyceride gender smoke diabetes_risk
0 68 23.6 6.1 3.8 0.9 1 1 0.0
1 58 23.9 6.1 6.4 1.0 0 2 0.0
2 44 26.3 4.3 6.5 1.3 0 0 0.0
3 72 22.2 4.6 5.5 1.5 1 0 0.0
4 37 25.3 4.4 6.2 0.1 1 0 0.0各临床指标全局贡献权重:
age: 0.0397
bmi: 0.0432
fast_glucose: 0.4221
hba1c: 0.4418
triglyceride: 0.0368
gender: 0.0078
smoke: 0.0088

随机森林糖尿病分类模型测试集评估结果:
准确率Accuracy:0.99
精确率Precision:1.0
召回率Recall:0.9211
F1分数:0.9589
AUC值:0.9967

单患者结构化中转JSON数据:
{
"patient_id": 0,
"indicators": {
"age": 42.0,
"bmi": 25.9,
"fast_glucose": 5.3,
"hba1c": 7.4,
"triglyceride": 1.7,
"gender": 1.0,
"smoke": 1.0
},
"feature_weight": {
"age": 0.0397,
"bmi": 0.0432,
"fast_glucose": 0.4221,
"hba1c": 0.4418,
"triglyceride": 0.0368,
"gender": 0.0078,
"smoke": 0.0088
},
"predict_text": "健康低风险",
"predict_prob": 0.945
}

输入大模型完整Prompt文本:
你是专业内分泌临床医师,严格依据《中国2型糖尿病防治指南》完成解读,禁止无依据主观推断。
【患者全部临床检验指标】:{
"age": 42.0,
"bmi": 25.9,
"fast_glucose": 5.3,
"hba1c": 7.4,
"triglyceride": 1.7,
"gender": 1.0,
"smoke": 1.0
}
【随机森林AI模型预测结果】:健康低风险,预测置信概率:0.945
【各检验指标对本次风险判定的贡献权重(0~1,数值越高影响越大)】:{
"age": 0.0397,
"bmi": 0.0432,
"fast_glucose": 0.4221,
"hba1c": 0.4418,
"triglyceride": 0.0368,
"gender": 0.0078,
"smoke": 0.0088
}
输出要求:分两段,第一段阐述核心高权重致病指标归因,标注指标临床正常参考范围;
第二段说明次要辅助指标影响,语言通俗专业,总字数控制500字以内。
Loading checkpoint shards: 100%|██████████████████████████████████| 8=====大模型输出完整医学归因解读报告=====
你是专业内分泌临床医师,严格依据《中国2型糖尿病防治指南》完成解读,禁止无依据主观推断。
【患者全部临床检验指标】:{
"age": 42.0,
"bmi": 25.9,
"fast_glucose": 5.3,
"hba1c": 7.4,
"triglyceride": 1.7,
"gender": 1.0,
"smoke": 1.0
}
【随机森林AI模型预测结果】:健康低风险,预测置信概率:0.945
【各检验指标对本次风险判定的贡献权重(0~1,数值越高影响越大)】:{
"age": 0.0397,
"bmi": 0.0432,
"fast_glucose": 0.4221,
"hba1c": 0.4418,
"triglyceride": 0.0368,
"gender": 0.0078,
"smoke": 0.0088
}
输出要求:分两段,第一段阐述核心高权重致病指标归因,标注指标临床正常参考范围;
第二段说明次要辅助指标影响,语言通俗专业,总字数控制500字以内。
根据你的报告,主要的高权重致病指标是血糖水平和HbA1c。这两个指标都超过了正常范围,表明你可能有糖尿病的风险。首先,血糖水平是指血液中的葡萄糖含量。正常情况下,空腹血糖应该在3.9-6.1mmol/L之间,餐后2小时血糖应该在小于7.8mmol/L。然而,你的血糖水平为5.3mmol/L,已经超过了正常范围。
其次,HbA1c是反映过去2-3个月平均血糖水平的指标。正常情况下,HbA1c应该在4%-6%之间。然而,你的HbA1c为7.4%,明显高于正常范围。
此外,你的BMI(体重指数)也较高,达到了25.9,这可能是导致血糖升高的原因之一。BMI超过25被认为是超重,而超重是糖尿病的一个重要危险因素。
至于其他辅助指标,如吸烟和血脂水平,虽然它们的影响相对较小,但也不能忽视。吸烟会增加心血管疾病的风险,而血脂异常也是糖尿病的重要并发症之一。
总的来说,你需要关注你的血糖、HbA1c、BMI以及吸烟和血脂水平等指标,保持良好的生活习惯,定期进行体检,以预防糖尿病的发生。同时,如果你有任何疑问或不适,建议及时就医咨询。
糖尿病风险结果分布图:

高低风险人群分离度:

六、总结
总的来说,单独用随机森林或是大模型处理医疗表格数据,都存在绕不开的短板:树模型算得准却像黑盒,一堆权重数字医生看不懂;大模型擅长说人话,但对检验数值、临界阈值不敏感,直接做疾病分类精度拉胯。二者搭配才是扬长避短的最优解,分工逻辑特别清晰:随机森林扛下结构化数据建模、预测、输出量化权重的活,大模型只负责翻译数字、结合医学指南生成通顺的归因解释,整套架构简单好落地,算力门槛很低。
做行业AI不能执着于单模型通吃,分层解耦才是落地关键,不用强行微调大模型做分类,拆分任务能省下大量数据、算力成本,还能满足医疗行业可解释、可溯源的合规要求。
更多推荐


所有评论(0)