Discharge Medication Recommendation System for Metabolic Diseases
Based on Chinese Electronic Medical Records (EMR) and GLM4-8B-Chat
CausalMed-GLM4 is a research-oriented system that leverages large language models (GLM4-8B-Chat) to generate personalized discharge medication recommendations for patients with metabolic diseases.
It integrates causal reasoning, reinforcement refinement, and Chinese EMR understanding.
训练数据集的token分布如下:
| 统计项 | 数值 |
|---|---|
| 样本总数 | 3602 |
| 平均token数 | 1531.57 |
| 最大token数 | 8095 |
| 最小token数 | 379 |
| 平均标签数 | 6.08 |
| 最少标签数 | 1 |
| 最多标签数 | 21 |
| 分位数 | token数量 | 标签数量 |
|---|---|---|
| P50 | 1422.00 | 6.00 |
| P80 | 1922.60 | 9.00 |
| P90 | 2247.90 | 11.00 |
| P95 | 2600.75 | 12.00 |
| P99 | 3715.92 | 15.00 |
测试数据集的token分布如下
| 统计项 | 数值 |
|---|---|
| 平均token数 | 1519.56 |
| 最大token数 | 6644 |
| 最小token数 | 456 |
| 分位数 | token数量 |
|---|---|
| P50 | 1415.00 |
| P80 | 1909.60 |
| P90 | 2232.80 |
| P95 | 2574.85 |
| P99 | 3628.44 |
从上表中可以观察到,大约80%样本的token数量都是小于1922.6的。所以,在我baseline训练的时候,我把max_seq_length设置成了2000,即也就是意味着有20%的训练样本会缺失上下文信息。而我目前手里面空闲的卡只有2张A6000 48GB, 所以max_seq_length=2000已经是极限,这还是在per_device_train_batch_size=1, gradient_accumulation_steps=2的前提下。再大就会OOM了。
而这里的痛点在于:
(1) max_seq_length=2000一定是有20%的样本缺失一部分上下文信息,模型遇到超过2000的测试样本的时候,泛化性能会下降。这里max_seq_length的理想值应该是4096左右(后续我会想办法,搞定它!比如用一下TP、SP或者是序列打包)
(2) 如果我想动一动模型,加大一点参数量或者是加大一点batch_size,那么就会OOM,算力上比较焦虑。
所以综上两点,我们训练的时候,序列必须是必须的。这里感谢Xtuner-V0.1架构,我们基于该架构的SP机制实现了我们的pipeline。序列大小设置为比较理想的4000
训练数据集中,药品标签出现的次数分布如下
| top20排名 | 药物名称 | 出现次数 |
|---|---|---|
| 1 | 阿托伐他汀钙片 | 1298 |
| 2 | 阿司匹林肠溶片 | 991 |
| 3 | 阿卡波糖 | 884 |
| 4 | 二甲双胍 | 609 |
| 5 | 碳酸钙片 | 517 |
| 6 | 甘精胰岛素 | 500 |
| 7 | 硝苯地平控释片 | 479 |
| 8 | 氨氯地平片 | 478 |
| 9 | 甲钴胺 | 470 |
| 10 | 门冬胰岛素 | 381 |
| 11 | 瑞巴派特片 | 374 |
| 12 | 依帕司他 | 373 |
| 13 | 雷贝拉唑 | 369 |
| 14 | 美托洛尔缓释片 | 338 |
| 15 | 兰索拉唑 | 307 |
| 16 | 阿法骨化醇 | 306 |
| 17 | 瑞舒伐他汀 | 296 |
| 18 | 氯吡格雷 | 286 |
| 19 | 银杏叶软胶囊 | 267 |
| 20 | 左甲状腺素钠片 | 253 |
| -top20排名 | 药物名称 | 出现次数 |
|---|---|---|
| 1 | 脑脉利颗粒 | 1 |
| 2 | 富马酸福伏诺拉生片 | 1 |
| 3 | 非奈利酮 | 1 |
| 4 | 拉坦前列素滴眼液 | 1 |
| 5 | 尼洛替尼 | 1 |
| 6 | 美西律片 | 1 |
| 7 | 甲硫咪唑 | 1 |
| 8 | 安脑丸 | 1 |
| 9 | 洛芬待因片 | 1 |
| 10 | 福辛普利 | 1 |
| 11 | 头孢克肟 | 1 |
| 12 | 硫必利片 | 1 |
| 13 | 孢克洛缓释片 | 1 |
| 14 | 干扰素凝胶 | 1 |
| 15 | 卡马西平 | 1 |
| 16 | 坦索罗欣胶囊 | 1 |
| 17 | 美金刚 | 1 |
| 18 | 艾曲波帕 | 1 |
| 19 | 艾拉莫德 | 1 |
| 20 | 参仙升脉口服液 | 1 |
头部类占比: 9.90%, 平均频次: 260.7 中部类占比: 80.03%, 平均频次: 14.3 尾部类占比: 10.07%, 平均频次: 1.0
也就是说,模型更倾向于预测中高频药物,低频药物梯度贡献小,几乎不被学习(这在医学推荐中特别危险,因为低频药物往往是特殊并发症或罕见病药物,临床意义反而更大)。如何解决这个问题?
数据的边际收益递减效应:当某个类别的数据样本非常多时,继续增加同类样本对模型性能的提升会越来越小(收益递减
头部类别的冗余信息: 重复样本会导致模型过度偏向该类(冗余信息)
标签共现: 在多标签任务中,一个样本可能同时属于多个标签,而且某些标签经常一起出现,这种“同时出现”的关系就叫 标签共现, 例如:一个病人的处方中可能同时包含:「二甲双胍」 + 「阿卡波糖」 + 「阿托伐他汀钙片」这几个标签往往是 一起出现的,说明它们之间存在共现关系(共现频率高)。为什么共现是个问题?因为如果模型学到:“只要出现二甲双胍,就预测阿卡波糖也出现”,那么模型会“偷懒”,不去真正理解样本内容,而是仅仅靠标签之间的统计关系预测,这样模型泛化性差。Distribution-balanced Loss(DB Loss)就是通过重新加权(rebalanced weighting)来减少这种“共现带来的偏差”,让模型更关注独立的标签特征。
此外,多标签场景中,一个包含多个标签的样本可能会被过度采样(常见长尾分布算法中的问题):指在重采样(resampling)时,如果你不加区分地对每个标签都按照“样本越少权重越大”的原则来采样,带有多个稀有标签的样本会被重复采样多次,导致训练集偏向它。举个例子: 假设有如下数据:样本 A [阿托伐他汀钙片, 阿司匹林] 样本 B [脑脉利颗粒, 美西律片]这里,“脑脉利颗粒”和“美西律片”都是非常稀有的标签(出现次数=1)。 如果你按单标签的平衡采样策略(1/频率)来重采样,那么: 样本 A 的两个标签都常见,采样概率小; 样本 B 的两个标签都极少见 → 各自都有很高的采样权重。 于是样本 B 会被采样两次高权重叠加,导致它被选中的概率远大于别的样本。 这就是“过度采样”——同一个样本因为有多个稀有标签而被采太多次。
简单的基础模型+分类层无法充分考虑药品之间的排斥问题,在实际应用场景中,如果预测的药物列表中含有相互排斥的药物是及其不安全的。
基于以上考虑,我们有必要在模型中加以限制,充分考虑药品之间的相互排斥性。
具体而言,我们基于这651个药品语料库,训练了一个分数矩阵A,A[i][j]的取值范围是0-1,0代表完全不排斥,1代表完全排斥。
共现矩阵形状: (586, 586) 平均共现次数: 0.44 最大共现次数: 621.00
TOTO:如何训练这个分数矩阵A呢?基于图谱还是基于什么来训呢?
药物 - 药物相互作用(DDI)建模
图正则化损失函数
1. 基于公开数据库(DrugBank、BioGRID 或中国 NMPA 数据库)构建 DDI 图。
2. 构建邻接矩阵A[651×651] → 若两种药物存在已知相互作用,矩阵元素为 1,否则为 0。
3. 为存在冲突的药物添加成对排斥约束:
def ddi_loss(y_pred, A, lambda_ddi=0.1):
y_pred = y_pred.sigmoid() # 将模型输出转换为概率
ddi_pairs = y_pred @ A @ y_pred.T # 计算推荐药物组合中的相互作用程度
return lambda_ddi * ddi_pairs.mean() # DDI损失(λ_ddi为权重超参数)总损失 = 二元交叉熵损失(BCE) + λ_ddi × DDI损失
输入字段:
{
"患者序号": 2,
"就诊标识": "2-1",
"性别": "女",
"出生日期": "1940-12",
"民族": "汉族",
"BMI": 27.3,
"就诊时间": "2015-03",
"诊疗过程描述": "门诊查尿酮体:+,白细胞:250/ul、镜下:7-9/Hp。入院后查:尿常规:白细胞+++/ul、尿白细胞62.25/HP↑,口服补液后复查尿常规白细胞及酮体(-)。馒头餐试验结果回报:糖化血红蛋白...... ",
"入院情况": "患者以\"烦渴、多饮、多尿5年,尿痛伴血糖控制不佳2个月。\"为主诉入院重要查体:T36.6℃,P76次/分,R22次/分,BP160/80mmHg......",
"现病史": "患者5年前无明显诱因出现烦渴、多饮、多尿症状,遂于某医院就诊,测空腹血糖16.7mmol/l,予患者二甲双胍、瑞格列奈、阿卡波糖片控制血糖...... ",
"既往史": "否认冠心病病史,否认有肝炎、结核、疟疾等传染病史,否认食物、药物过敏史,否认外伤、手术史,否认输血史,预防接种史不详。",
"主诉": "烦渴、多饮、多尿5年,尿痛伴血糖控制不佳2个月。",
"出院诊断": ["2型糖尿病", "糖尿病酮症", "泌尿系感染", "糖尿病大血管病变",...]
"出院带药列表": [ "阿卡波糖", "瑞格列奈", "瑞舒伐他汀", "替米沙坦", "氨氯地平片", "碳酸钙片" ]
}
我们的最终任务目标是预测"出院带药列表",即做一个多标签预测的任务。输入是所有字段信息的拼接(除了"出院带药列表")。
但是从以上数据来看,一个好的模型,应该可以依据患者基本信息和临床信息判断出"出院诊断".
所以,除了预测多标签分类以外,是否可以加一个LM任务?预测"出院诊断"?
此外,掩码机制是否可以基础信息+临床信息做一个双向注意力,而出院诊断那边做一个因果掩码呢?
如果4能够实现,且效果优秀的话,我们是否可以把分类分成2个阶段:
第一阶段,基础信息+临床信息的hidden的平均作为embedding,做分类?(这里取平均是因为什么呢?)
第二阶段:正常的最后一个token作为embedding,做分类(因为,最后一个token能包含前面所有的信息,因果掩码的机制)
训练集标签不完备的意思是:主办方给的药品列表里面有651种药,我们做的也是651分类。但实际上训练集中标签只有586个,也就是有65个类别得不到训练。
但是训练集里面一定包含这65个类别
换句话说,也就是训练未见过的标签,如何在测试集中表现的好呢?