← 返回项目

医疗大模型

[unisound 实习] CHIP 智能病历质控

[unisound 实习] CHIP 智能病历质控项目说明

1. 项目概述

本项目来源于 CHIP 智能病历质控赛题。赛题给定 19 个病历质控点,要求系统检查入院病历是否存在信息缺失、书写不规范、内容矛盾或诊断质量问题。

项目采用三阶段流程:

原始入院病历
    ↓
阶段一:病历理解与结构化
    ↓
阶段二:19项病历质控
    ↓
阶段三:Thinking模型复核
    ↓
病历级质控结果
    ↓
按record_id和rule_id展开
    ↓
SFT / DAPO训练数据

CHIP项目的核心产物不是一个直接训练的模型,而是一批经过结构化、质控和复核的病历质控数据。这些数据可以转换为SFT或DAPO格式,提供给下游 llm_cft-ms_swift 项目进行模型微调。

需要明确:19个质控点及其定义来自赛题,本项目负责实现这些任务并完成数据生产,不是自行设计了19项医疗标准。


2. 原始数据

2.1 原始数据格式

原始文件是一个JSON数组,一条记录对应一份入院病历:

{
  "record_id": "MED_QC_ADM_0001",
  "record_type": "ADM_NOTE",
  "emr_content": "主诉、现病史、既往史、检查和诊断等完整病历文本"
}

字段含义:

  • record_id:病例唯一标识;
  • record_type:病例类型,本项目主要是入院记录 ADM_NOTE;
  • emr_content:一整段半结构化病历文本。

2.2 emr_content包含的内容

emr_content通常包含:

  • 主诉;
  • 现病史;
  • 既往史;
  • 个人史;
  • 家族史;
  • 月经史、婚育史;
  • 体格检查;
  • 专科检查;
  • 辅助检查;
  • 入院诊断;
  • 初步诊断;
  • 入院主诊断、修正主诊断等可选内容。

数据中的姓名、时间、温度和血压等敏感信息可能使用 * 脱敏,缺失章节可能写成 None、空字符串或完全不出现。


3. 阶段一:病历理解与结构化

3.1 整体结构化

第一阶段调用32B医疗大模型,将 emr_content 拆分为字段明确的结构化JSON。至少要求以下7个字段存在:

主诉、现病史、既往史、专科检查、辅助检查、入院诊断、初步诊断

字段必须存在,但内容可以为空。例如原文没有专科检查时,应输出空字符串;没有诊断时,应输出空列表。

转换前:

{
  "record_id": "MED_QC_ADM_0001",
  "emr_content": "主诉:反复发热两周\n现病史:患者三天前出现发热……"
}

转换后:

{
  "record_id": "MED_QC_ADM_0001",
  "emr_content": "原始完整病历文本",
  "json": {
    "主诉": "反复发热两周",
    "现病史": "患者三天前出现发热……",
    "既往史": "否认高血压、糖尿病史……",
    "专科检查": "",
    "辅助检查": "胸部CT提示肺部结节……",
    "入院诊断": ["发热", "肾功能不全"],
    "初步诊断": ["发热", "血管炎待排"]
  }
}

原始 emr_content 继续保留,用于信息追溯和后续复核。

3.2 诊断字段统一

模型有时会把入院诊断或初步诊断输出为字符串。程序通过本地 ensure_list() 函数将它们统一为列表:

"发热" → ["发热"]

这一步使用本地程序,不调用LLM。

3.3 手术和操作实体抽取

结构化完成后,再调用两次LLM:

  1. 从现病史中抽取手术或医疗操作实体;
  2. 从既往史中抽取手术或医疗操作实体。

例如:

现病史:患者一月前行软性输尿管镜钬激光碎石术,术后留置输尿管内支架。

抽取结果:

[
  "软性输尿管镜钬激光碎石术",
  "留置输尿管内支架"
]

程序随后调用ICD编码服务,并对照《手术操作分类代码国家临床版2.0》判断实体是否属于手术:

{
  "现病史实体": [
    "软性输尿管镜钬激光碎石术 属于手术",
    "留置输尿管内支架 不属于手术"
  ],
  "既往史实体": []
}

这些实体主要用于判断现病史与既往史中的手术史是否矛盾。

3.4 结构化幻觉控制

大模型可能增加原文不存在的信息、改变否定关系或遗漏字段。合理的质量控制方式是,在模型输出JSON后执行硬编码检查:

  1. 检查JSON是否可以解析;
  2. 检查7个必要字段是否存在;
  3. 检查字段类型是否符合约定;
  4. 归一化空格、换行和标点后,检查字段内容能否回溯到原始 emr_content;
  5. 无法回溯的内容标记为疑似幻觉,重新抽取;重复失败则丢弃。

需要注意:原始仓库完成了字段存在性和诊断列表转换等处理,但“逐字段原文回溯并自动丢弃”的严格流程属于后续完善方案,不能把它描述成当前代码已经完整实现的功能。


4. 阶段二:19项病历质控

4.1 质控任务分类

19个质控点可以分为四类:

类型 数量 主要内容
信息完整性 7项 主诉、现病史、既往史和诊断信息是否缺失
病历规范性 6项 症状、检查、用药及诊断记录是否规范
内容一致性 4项 主诉、现病史和既往史是否前后矛盾
诊断质量 2项 诊断是否合理、是否仅使用症状名称
合计 19项 —

19项规则如下:

rule_id 检查内容
IC-ZS-01-V1 主诉是否包含病情信息和持续时间
EN-ZS-01-V1 主诉症状数量、长度和表达是否规范
IC-XB-01-V1 现病史是否记录入院前治疗经过
IC-XB-02-V1 是否记录与主诊断相关的阳性、阴性症状
IC-XB-03-V1 是否记录饮食、睡眠、大小便等一般情况
CO-XB-01-V1 主诉与现病史中的临床表现是否矛盾
CO-XB-02-V1 手术史等病史信息前后是否一致
CO-XB-03-V1 主诉与现病史是否相关、相符
CO-XB-04-V1 主诉与现病史中的时间是否一致
EN-XB-01-V1 主要症状特点和发展变化是否记录规范
EN-XB-02-V1 发病时间、诱因等是否记录规范
IC-JW-01-V1 慢性病、手术史等是否记录完整
EN-JW-01-V1 用药、手术情况是否记录规范
IC-ZK-01-V1 专科检查是否遗漏阳性体征
EN-FZ-01-V1 辅助检查是否包含时间、地点、项目和结果
DQ-RZ-01-V1 病历信息能否支持主要诊断
DQ-RZ-02-V1 诊断是否仅为症状或体征名称
IC-RZCB-01-V1 诊断列表是否存在遗漏
EN-RZCB-01-V1 诊断顺序是否规范

4.2 硬编码与LLM的分工

硬编码处理能够确定判断的特殊情况,例如:

  • 主诉、现病史、既往史或诊断为空;
  • 主诉长度超过20;
  • 专科检查或辅助检查为空时跳过对应规则;
  • 模型输出JSON格式是否合法。

其余情况由医疗LLM结合独立Prompt判断。正常字段都不为空时,19项规则都可能调用LLM,因此硬编码主要承担前置快速判断,LLM承担核心语义质控。

4.3 Prompt与模型输出

每个质控点都有独立Prompt。程序从结构化JSON中取出相关字段,填入Prompt后调用医疗LLM。

例如 CO-XB-04-V1 检查主诉与现病史时间是否一致:

判断规则:主诉与现病史中的发病时间应保持一致。

主诉:反复发热两周
现病史:患者三天前出现发热……

请输出:
{"right":"合格/不合格","evidence":"判断依据"}

模型返回:

{
  "right": "不合格",
  "evidence": "主诉记录发热两周,现病史记录三天前发热,时间不一致"
}

right表示当前规则是否合格,evidence表示不合格依据。合格时Evidence应为空。

4.4 problems字段

模型返回不合格后,程序将结果转换并加入 problems:

{
  "problems": [
    {
      "field": "history_present",
      "issue_type": "CONSISTENCY",
      "rule_id": "CO-XB-04-V1",
      "description": "主诉记录发热两周,现病史记录三天前发热,时间不一致"
    }
  ]
}

映射关系是:

right="不合格" → 加入problems
evidence        → description
当前质控规则     → rule_id、field、issue_type

规则没有出现在 problems 中,表示该规则被判断为合格。

第二阶段同时保存每个规则实际使用的Prompt,形成:

{
  "prompt": {
    "CO-XB-04-V1": "该质控点实际使用的完整Prompt"
  }
}

5. 阶段三:Thinking模型复核

第三阶段使用Thinking模型复核医疗LLM生成的质控问题。复核输入包括:

  • 当前 rule_id 和质控标准;
  • 相关病历字段;
  • 初次质控问题及 description;
  • 常见症状、慢性病和通俗用语等外部知识。

Thinking模型判断:

  • 质控结果正确:初次问题成立;
  • 质控结果不正确:初次问题可能是误报。

审核后,问题对象会增加:

{
  "rule_id": "CO-XB-04-V1",
  "description": "主诉与现病史时间不一致",
  "whether_to_check": "是",
  "check_res": "质控结果正确"
}

原始代码主要完成“复核和标记”,并没有真正从 problems 中删除 check_res=质控结果不正确 的问题。若要生产严格筛选后的训练数据,还需要增加过滤步骤。

Thinking模型适合复核复杂语义判断,但不能视为绝对金标,因为它仍然可能产生误判和遗漏。


6. CHIP最终病历级数据

经过三个阶段后,一份病例大致为:

{
  "record_id": "MED_QC_ADM_0001",
  "record_type": "ADM_NOTE",
  "emr_content": "原始完整病历文本",
  "json": {
    "主诉": "反复发热两周",
    "现病史": "患者三天前出现发热……",
    "既往史": "否认高血压、糖尿病史……",
    "专科检查": "",
    "辅助检查": "胸部CT提示肺部结节……",
    "入院诊断": ["发热", "肾功能不全"],
    "初步诊断": ["发热", "血管炎待排"]
  },
  "现病史实体": [],
  "既往史实体": [],
  "problems": [
    {
      "field": "history_present",
      "issue_type": "CONSISTENCY",
      "rule_id": "CO-XB-04-V1",
      "description": "主诉与现病史时间不一致",
      "whether_to_check": "是",
      "check_res": "质控结果正确"
    }
  ],
  "prompt": {
    "CO-XB-04-V1": "该质控点实际使用的完整Prompt"
  }
}

这仍然是病历级中间结果,还不是SFT或DAPO训练格式。


7. 从病历级结果到规则级训练数据

7.1 按规则展开

一份病历需要按19个 rule_id 展开:

一份病历 × 一个rule_id = 一条规则级训练样本

如果某规则出现在审核通过的 problems 中:

{
  "right": "不合格",
  "evidence": "对应problem中的description"
}

如果规则没有出现在 problems 中:

{
  "right": "合格",
  "evidence": ""
}

规则级通用数据为:

{
  "record_id": "MED_QC_ADM_0001",
  "rule_id": "CO-XB-04-V1",
  "prompt": "质控规则、输出要求和相关病历内容",
  "right": "不合格",
  "evidence": "主诉与现病史时间不一致"
}

Prompt内部包含当前规则需要的病历内容,通常不再单独保存完整 emr_content。

7.2 SFT格式

SFT把标准答案放进assistant消息:

{
  "messages": [
    {
      "role": "user",
      "content": "质控规则、输出要求和相关病历内容"
    },
    {
      "role": "assistant",
      "content": "{\"right\":\"不合格\",\"evidence\":\"主诉与现病史时间不一致\"}"
    }
  ],
  "record_id": "MED_QC_ADM_0001",
  "rule_id": "CO-XB-04-V1"
}

训练时模型直接模仿标准答案。

7.3 DAPO格式

DAPO只在 messages 中保留输入,把标准答案放在 solution 中:

{
  "messages": [
    {
      "role": "user",
      "content": "质控规则、输出要求和相关病历内容 /no_think"
    }
  ],
  "solution": "{\"right\":\"不合格\",\"evidence\":\"主诉与现病史时间不一致\"}",
  "thinking": "0",
  "record_id": "MED_QC_ADM_0001",
  "rule_id": "CO-XB-04-V1"
}

DAPO训练时,模型先生成多个答案,奖励函数再将生成结果与 solution 比较:

  • right直接比较是否一致;
  • evidence使用ROUGE-L计算文本重合程度;
  • 输出格式正确时获得额外格式奖励。

因此,同一批CHIP数据既可以转换成SFT,也可以转换成DAPO,区别主要是标准答案的位置和训练方式。


8. 实际数据结果

以下结果来自当前项目文件的实际统计,不是估算值。

8.1 原始病例与中间结果

数据文件 病例数 唯一record_id problems数量
data/train.json 300 300 924
data/train_更新_0929.json 300 300 912
data/test_B_gold.json 300 300 823
data/final_test_set.json 300 300 823
data/to_test_6(数据构建结果).json 300 300 924

to_test_6(数据构建结果).json虽然扩展名是 .json,实际内容是JSONL:每行保存一份结构化病例。

8.2 规则级训练数据

300份病例、19项任务理论上最多产生:

300 × 19 = 5,700条

实际下游数据为:

文件 样本数 病例数 规则数
to_train_compare.jsonl 5,280 300 19
720_to_train_dapo.jsonl 5,280 300 19

实际比理论最大值少420条,主要是部分规则在当前病例中不适用或被跳过。

8.3 标签分布

5,280条规则级数据中:

标签 数量 占比
合格 4,515 85.5%
不合格 765 14.5%
合计 5,280 100%

数据存在明显类别不均衡,训练和评测时不能只看Accuracy,还需要关注不合格类的Precision、Recall、F1以及分规则指标。

8.4 数据集划分

SFT和DAPO使用同一批5,280条规则级样本,只是格式不同。数据按 record_id 划分:

集合 样本数 病例数
Train 4,224 240
Validation 521 30
Test 535 30
合计 5,280 300

同一份病例产生的所有规则级样本只进入一个集合,避免相同病历文本同时出现在训练集和测试集中造成数据泄漏。


9. 项目总结

完整数据链路可以概括为:

300份原始入院病历
    ↓ 32B医疗LLM
结构化病历JSON
    ↓ 两次实体抽取 + ICD服务
手术/操作实体
    ↓ 硬编码前置判断 + 19个分规则Prompt
病历质控problems
    ↓ Thinking模型
质控结果复核标记
    ↓ 按record_id、rule_id展开
5,280条规则级数据
    ├── SFT格式
    └── DAPO格式

项目的主要价值包括:

  1. 将非结构化病历转换为统一字段;
  2. 将赛题规定的19项质控任务实现为可执行Prompt;
  3. 使用确定性规则处理明确问题,使用医疗LLM处理复杂语义判断;
  4. 使用Thinking模型复核初次质控结果;
  5. 将300份病例展开为5,280条规则级数据,并完成病历级隔离划分;
  6. 为下游SFT和DAPO微调提供统一数据来源。

当前代码仍存在配置字段缺失、合格结果可能向 problems 写入 None、复核后未实际删除误报以及绝对路径依赖等问题。如果需要将项目作为稳定的数据生产系统继续使用,应先修复这些工程问题,再重新运行并统计严格筛选后的最终数据。