CMeKG_tools 知识图谱项目速成教程(13课共24H)

源码地址:GitHub - king-yyf/CMeKG_tools · GitHub

源码简介:

中文医学知识图谱CMeKG CMeKG(Chinese Medical Knowledge Graph)是利用自然语言处理与文本挖掘技术,基于大规模医学文本数据,以人机结合的方式研发的中文医学知识图谱。

CMeKG 中主要模型工具包括:医学文本分词,医学实体识别和医学关系抽取。这里是三种工具的代码、模型和使用方法。

0. 学习计划

课次 主题 预计学时
阶段一 · 地基 --- 5.5 小时
1 认识三大任务 1 小时
2 文本与标签 2 小时
3 神经网络速成(里程碑 ①) 2.5 小时
阶段二 · 原理 --- 6 小时
4 BERT 精讲 2.5 小时
5 LSTM 1.5 小时
6 CRF(里程碑 ②) 2 小时
阶段三 · 代码精读 --- 9.5 小时
7 utils.py 全解 2.5 小时
8 模型代码精读 2 小时
9 推理与训练脚本 2.5 小时
10 关系抽取专题(里程碑 ③) 2.5 小时
阶段四 · 实战 --- 6.5 小时
11 跑通实战 2 小时
12 仿写项目(上) 2 小时
13 仿写项目(下,毕业) 2.5 小时
合计 13 课 约 27.5 小时(含练习与答疑)

1. 第1课 认识三大任务

把整个项目想象成一座病历加工厂。

原料 = 原始病历文本(一串连着写的汉字),最终产品 = 知识图谱(一张巨大的医学知识网络)。厂里有三道关键工序,恰好就是代码中的三个程序。

1.1 工序一:分词------划边界

第一道工序拿到的原料是这样的:

复制代码
高血压病人不可食用阿莫西林等药物

机器看到这串字只会犯迷糊:哪里是一个词?哪里是词和词的分界?所以第一道工序负责把它切开:

复制代码
高血压 / 病人 / 不可 / 食用 / 阿莫西林 / 等 / 药物
  • 输入:一句连着写的中文
  • 输出:切开成词的一串
  • 代码 :medical_cws.py

为什么必须切?因为后面所有分析都建立在「哪些字构成一个词」上。举例:如果「高血压」被切成「高 / 血压」,机器看到的就是两个不同的碎片,后面全乱套。

医学文本尤其难切:专业词超长(「促肾上腺皮质激素释放激素」12 个字才是一个词)、缩写多(CT、MRI)、新药名不断出现。

1.2 工序二:实体识别------找东西、分类

句子切开后,第二道工序负责:找出句子里「重要的东西」,并说出它属于哪一类。

复制代码
高血压病人不可食用阿莫西林等药物  高血压 = 疾病        阿莫西林 = 药物

医学术语管这些「重要的东西」叫实体 。项目里定义了 9 种实体类别:

【疾病、症状、身体、药物、医疗程序、医疗设备、科室、微生物、检验项目】

  • 输入:一句中文
  • 输出 :像 {"疾病": "高血压", "药物": "阿莫西林"} 这样的「类别: 实体」清单
  • 代码 :medical_ner.py

和分词的区别在于:分词看的是「边界 在哪」,实体识别看的是「这块文本属于哪一类」。

1.3 工序三:关系抽取------连关系

前两道工序产出的是一个个孤立的东西。第三道工序回答最后一个问题:它们之间是什么关系?

复制代码
(高血压)------ 禁忌 ------(阿莫西林)高血压病人不可食用阿莫西林 → 三元组:(高血压, 禁忌, 阿莫西林)

术语叫SPO 三元组 :(主语 subject, 谓词 predicate, 宾语 object)。医学关系也是固定清单,predicate.json 里定义了 23 种:临床表现、病因、适应症、禁忌、不良反应......

  • 输入:一句中文
  • 输出:(主语, 谓词, 宾语) 的清单
  • 代码 :model_re/medical_re.py

1.4 拼装成图 + 为什么用专门的模型

1. 三道工序拼起来 = 知识图谱

继续用工厂比喻:前面三道工序各自产出半成品,把它们串成一条流水线,最终产品就出来了。看一个完整例子:

复制代码
原始文本:新型冠状病毒肺炎患者常见发热、咳嗽等症状
     │ ① 分词(划边界)
     ▼
新型冠状病毒肺炎 患者 常见 发热 、 咳嗽 等 症状
     │ ② 实体识别(找东西、分类)
     ▼
{疾病: 新型冠状病毒肺炎, 症状: 发热, 症状: 咳嗽}
     │ ③ 关系抽取(连关系)
     ▼
(新型冠状病毒肺炎, 临床表现, 发热)
(新型冠状病毒肺炎, 临床表现, 咳嗽)
     │
     ▼
知识图谱(一张网):
    发热 ◀──临床表现── 新型冠状病毒肺炎 ──临床表现──▶ 咳嗽

注意三件事:

  1. 每一步的产物,是下一步的原料------文本 → 词 → 实体 → 关系,层层提炼
  2. 实体变成图上的节点 (一个个圆圈),关系变成节点之间的连线(带箭头的边)
  3. 为什么叫「图」而不是「表格」?因为知识和知识是互相连接成网的:从「新型冠状病毒肺炎」这个节点出发,能顺着连线找到它的所有症状、药物、并发症......这就是知识图谱的用处(智能问答、辅助诊断,都是在这张网上"顺藤摸瓜")

2. 为什么医学文本要用"专门训练"的模型

机器要完成这三道工序,前提是它得学过中文------学习方式是"阅读大量文本"。那为什么不直接用通用模型,要专门做医学模型?

打个比方:

  • 通用模型 = 一个只读新闻和百科长大的人
  • 医学模型 = 一个读了几十万份医学文献长大的人

让这两人去读病历,后者顺得多。因为医学语言和日常语言差距很大:

  1. 专业术语日常几乎不出现:「嗜酸性粒细胞」「糖皮质激素」,新闻里见不到
  2. 新术语不断出现:新疾病、新药名,词典永远追不上
  3. 一词多义:「阴性」在日常是性格词,在医学里指检测结果

所以本项目三个模型的地基,都是一个用医学语料预训练过的 BERT(可以先把它理解成"读过海量医学文本的语言理解引擎")。它到底怎么"读"、怎么"懂",是阶段二(第 4 课)的主角。

1.5 本课小结

  • 三道工序:分词 = 划边界;实体识别 = 找东西、分类(9 类实体);关系抽取 = 连关系(SPO 三元组,23 种谓词)
  • 合起来:节点(实体)+ 边(关系)= 知识图谱
  • 医学文本难:术语多、新词多、含义特殊 → 需要医学预训练的 BERT 打底

1.6 课堂练习

第一题·对这句话做三道工序:

糖尿病患者应定期检查血糖,必要时注射胰岛素。

  1. 分词 :用 / 把它切开
  2. 找实体:每个实体属于 9 类中的哪一类?(提示:至少能找出 3 个)
  3. 写三元组 :写 1~2 个「(主语, 谓词, 宾语)」,谓词从 predicate.json 的 23 种里挑(提示:检查、治疗 都在清单里)

第二题·用自己的话(两三句即可):三道工序分别做什么?它们怎么拼出知识图谱?

参考答案:

第一题答案:
1.分词:糖尿病/患者/应/定期/检查/血糖/,/必要/时/注射/胰岛素/。
2.找实体:
糖尿病=疾病,胰岛素=药物,血糖-检验项目
3.写三元组
糖尿病-治疗-胰岛素,糖尿病-检查-血糖
第二题答案:
三个工序分别是:分词、找实体、关系抽取
分词是找实体的前提,先将自然语言按词语切分,找出边界;找实体是按照预设的实体类别,模型逐字判断属于哪个类别,找出重要的实体及其所属类别;关系抽取是抽取实体与实体之间的关系,最终形成多个实体之间的关系网,这就是知识图谱。

2. 第2课 文本与标签

2.1 项目文件分类

复制代码
CMeKG_tools-main/
│
├─ 【推理入口】用户使用的三个"工具"                      ← 你以后调用的就是它们
│   ├─ medical_cws.py        分词工具(类名 medical_seg)
│   ├─ medical_ner.py        实体识别工具(类名 medical_ner)
│   └─ model_re/medical_re.py  关系抽取工具
│
├─ 【模型定义】"大脑"的结构说明                            ← 工具内部靠它们干活
│   ├─ model_cws/   (bert_lstm_crf.py + crf.py)
│   ├─ model_ner/   (bert_lstm_crf.py + crf.py)
│   └─ model_re/    (medical_re.py)
│
├─ 【训练脚本】带着答案教模型                                  ← 第 9 课精读
│   ├─ train_cws.py
│   └─ train_ner.py
│
├─ 【公共工具】所有文件都借用的"扳手螺丝刀"              ← 第 7 课精读
│   └─ utils.py              读数据、读词表、算 P/R/F1
│
├─ 【配置常数】把"设置"集中放的小抽屉
│   ├─ cws_constant.py       分词的设置(标签表、超参数)
│   └─ ner_constant.py       实体识别的设置(标签表、超参数)
│
└─ 【数据文件】
    ├─ predicate.json        23 种"关系"的名单(RE 用)
    └─ train_example.json    关系抽取的训练样本示例

调用关系:推理入口 → 借用 utils 读数据 → 交给 model_ 里的大脑 → 按 _constant.py 的标签表翻译回中文。

比如用实体识别工具,medical_ner.py 会:先用 utils.py 把句子变成数字 → 送进 model_ner/ 的模型 → 模型吐出一串标签编号 → 按 ner_constant.py 的标签表把编号翻回"疾病""药物"等中文。

2.2 文本变数字:模型的"语言"

模型是数学函数,它不认识汉字,只认识数字。所以第一道工序永远是:把一句话变成一串整数编号。

2.2.1. vocab:一张"字 → 编号"的大字典

做法很朴素:预先准备一张表(词表,vocab),把每个字编个号。查表即可:

于是"糖尿病"就变成了 1004, 3682, 2917 这样的整数列表。为什么按「字」切分,不按「词」? 两个原因:

  • 中文没空格,切词本身就要靠算法,词表会不停遇到没见过的新词;
  • 医学领域新词极多("新型冠状病毒""奥密克戎"......),按字切分永远不"缺字",词表稳定可控。

所以在中文 NLP 里,"字"是最小单位。一句话 = 一串字 = 一串编号。

2.2.2 四个"特殊符号":混在字里的 VIP

词表里不全是汉字,还有 4 个有特殊职责的符号,你在 utils.py 和模型里会反复看到它们:

举个例子,"发热。" 进入模型前会变成:

复制代码
[CLS] 发 热 。 [SEP] [PAD] [PAD] [PAD] ...   ← 补齐到固定长度(如 450)
 ↓
101   2071 8832 511 102   0     0     0  ...

补齐长度是为了批处理:一批句子装进一个矩阵,长度必须一致,短句子剩下的位置塞 PAD。但模型不能把泡沫纸当内容,所以还要配一张掩码(mask):真字位置记 1,PAD 位置记 0------"这些位置是假的,别当真"。这个 mask 你在 train_ner.py 里见过,就是那个 masks 变量。

2.3 核心思路:模型在做"逐字贴标签"的比赛

回忆第 1 课:模型是逐字做判断题。既然是判断题,就得有标准答案 ------标准答案就是标签。任务不同,标签表就不同:

输入侧 输出侧(标签)
通用 [CLS] [SEP] [PAD] [UNK] ---
分词 CWS 字序列 S / B / M / E + <pad> <start> <eos> = 7 个
实体识别 NER 字序列 9 类 × B/M/E + o + 3 个特殊的 = 31 个

注意:输入侧有 4 个特殊符号,输出侧也有特殊标签,两边是对称的设计。下面逐一拆。

2.4 分词标签表:只要 7 个(打开 cws_constant.py 第 3 行)

复制代码
l2i_dic = {"S":0, "B":1, "M":2, "E":3, "<pad>":4, "<start>":5, "<eos>":6}

l2i_dic = label to index(标签→编号)。前 4 个是主角,描述的是"这个字在词里的位置":

  • S = Single:自成一个词
  • B = Begin:词的开头
  • M = Middle:词的中间
  • E = End:词的结尾

为什么 4 个就够?因为任何词的结构都是"开头 →(中间)→结尾":

词长 例子 标签
1 字 应 S
2 字 患者、注射 B E
3 字 糖尿病、胰岛素 B M E
4 字 阿司匹林 B M M E

所以一句话贴完标签,答案就出来了。例:

code.txt

复制代码
字:  糖  尿  病  患  者  应  注  射  胰  岛  素  。
标签:B   M   E   B   E   S   B   E   B   M   E  S

程序拿到这串标签,划词规则极简单:"S 单独一刀;从 B 划到 E"→ 糖尿病 / 患者 / 应 / 注射 / 胰岛素 / 。 ✓

后 3 个是工具标签------和输入侧的 PAD、CLS、SEP 一一对应:

  • <pad>:标签侧的填充位,对应输入里补的泡沫纸
  • <start> / <eos>:句子起点/终点。留个悬念 :标签表为什么要塞这两个?模型的线性层为什么输出 tagset_size+2 个分数而不是 tagset_size 个?这就是给它们留的位置,第 6 课 CRF 揭晓。

第 5 行 i2l_dic 是 l2i_dic 的反向表(编号→标签),模型吐出编号后靠它翻回字母,就是 utils.py 里 recover_label 用的那张表。

2.5 实体识别标签表:31 个的来历(打开 ner_constant.py 第 4-6 行)

分词标签只说"位置",不说"是什么"。NER 必须再叠一层类别 ,做法就是:类别代号 + - + 位置字母。按结构拆开看,31 个一目了然:

复制代码
o                       ← 1 个:非实体(o = Outside),句子里大部分字都是它
d-B d-M d-E             ← 疾病
s-B s-M s-E             ← 临床表现(工具输出时叫"症状")
b-B b-M b-E             ← 身体
e-B e-M e-E             ← 医疗设备
p-B p-M p-E             ← 医疗程序
m-B m-M m-E             ← 微生物类
k-B k-M k-E             ← 科室
i-B i-M i-E             ← 医学检验项目
y-B y-M y-E             ← 药物
<pad> <start> <eos>     ← 3 个工具标签(同分词,和 CRF 有关)

9 类 × 3 位置 + 1 个 o + 3 个工具 = 31 。ner_constant.py 第 24 行 tagset_size = len(l2i_dic) 就是 31------模型最后一层要输出 31 个分数,对应 31 种标签的可能。

同一句话用 NER 的表贴标签:

复制代码
字:  糖   尿   病   患  者  应  使  用  胰   岛   素   。
标签:d-B  d-M  d-E  o   o   o   o   o   y-B  y-M  y-E  o

对比分词版本(B M E / B E / S / B E / B M E / S),看出区别了吗:

  • 分词的 B、E 只管词边界 ;NER 的 d-B、y-E 同时管边界 + 类别;
  • NER 里"患者""使用"这些没被划进 9 类的字全部标 o;
  • 两个任务里 B 都是 Begin、M 都是 Middle------字母含义相同,只是 NER 在前面挂了类别代号。

2.6 对齐规则:一字一标签

最后一条铁律:输入 N 个字,模型就要吐 N 个标签,位置严格一一对应。这叫"序列标注"任务。打开训练数据文件(第 7、9 课会看)会发现每行的格式就是:

复制代码
糖	d-B
尿	d-M
病	d-E
患	o
(空行)          ← 空行表示一句话结束

一行字、一行标签,天然对齐------数据格式本身就在强制这条规则。

2.7 课堂练习

练习 1(标分词):给下面这句话逐字标 S/B/M/E 标签(记住:先自己在心里划词,再贴标签):

发烧时可用阿司匹林退热。

练习 2(查代码) :打开 ner_constant.py,写出"药物"类的全部标签及对应编号(提示:看 l2i_dic,数一数行号)。

练习 3(相当于验收):回答上半段留的两道思考题------

  1. 为什么句子开头加 [CLS]、结尾加 [SEP]?
  2. 补齐长度的 [PAD] 靠什么告诉模型"别当真"?

参考答案:

练习1:

发烧时可用阿司匹林退热。

BESBEBMMEBES

练习2: 每类实体占 3 个连续编号: "y-B": 25, "y-M": 26, "y-E": 27

练习3:

  • [SEP]:句子到此结束的记号
  • [CLS]:它不只是"起点标记",更是 BERT 预留的"摘要位"------BERT 规定,CLS 经过层层计算后的输出向量,代表整句话的总结。做"整句判断"的任务(如情感分类:这句话是正面还是负面)时,就只看 CLS 的输出。咱们项目是逐字判断(CWS/NER),虽然用的是每个字的输出,但 CLS 依然按这个约定带着全句信息。这条讲透在第 4 课《BERT 精讲》。

2.补齐长度的pad靠mask为0告诉模型别当真

3. 第3课 神经网络速成(里程碑 ①)

4. 第4课 BERT 精讲

5. 第5课 LSTM

6. 第6课 CRF(里程碑 ②)

7. 第7课 utils.py 全解

8. 第8课 模型代码精读

9. 第9课 推理与训练脚本

10. 第10课 关系抽取专题(里程碑 ③)

11. 第11课 跑通实战

12. 第12课 仿写项目(上)

13. 第13课 仿写项目(下,毕业)

相关推荐
悟乙己2 小时前
AFAC2026 赛题四金融长文本 Agent竞赛学习笔记:关联技术(二)
笔记·学习·金融
我送炭你添花2 小时前
ASA原理与电子维修实战:前言 一路修来,一路学习
运维·网络·学习
代码山河3 小时前
第一个Java程序:Hello World从零编写与运行
java·学习·架构·教程·面向对象·项目
aqiu1111114 小时前
蓝桥杯省一学习计划:每天 2 小时,12 周高效备赛
学习·职场和发展·蓝桥杯
Filthyfrank4 小时前
I2C vs I3C时序差异
学习
正经人_x4 小时前
学习日记59:PAVL
人工智能·学习·计算机视觉
外收内放5 小时前
Python基础语法练习题(64)
python·学习
努力努力再努力FFF5 小时前
题目:2026AI 证书选购指南:不同基础、不同诉求如何抉择
学习
只愿云淡风清5 小时前
正则表达式学习笔记-01 正则表达式的来源
笔记·学习·正则表达式