第二章 生物医学文本挖掘基础:命名实体识别(NER)简介
做药物靶点关系抽取,第一步得先看懂文本里哪些词是“实体”。说白了,你得教会计算机认出“BRAF V600E”、“吉非替尼”、“非小细胞肺癌”这些词分别属于基因、药物还是疾病。
命名实体识别(NER)就是干这个的。它的任务是从非结构化文本中定位出命名实体,并分类到预定义的类别。在生物医学领域,我们通常关注基因/蛋白质、化学物/药物、疾病、症状、细胞系、物种、突变等类型。
我个人习惯把NER看作“文本中的地标识别”——你要画地图,总得先标出哪里是山、哪里是河、哪里是城市吧?NER就是给文本画地标的。
2.1 为什么生物医学NER这么难?
你不要拿通用NER(比如识别人名、地名)来对标生物医学NER。基因名字能长成什么样?你想想看:
- 别名爆炸:同一个基因可能有十几个名字。比如“EGFR”也叫“ErbB-1”、“HER1”、“c-erbB-1”。你写成“EGFR”还是“epidermal growth factor receptor”?模型全得认识。
- 跨界混淆:“BRAF”是个基因,但“B-RAF”是一种激酶抑制剂?还有“c-KIT”既是基因也是受体。有时候一个词在PubMed摘要里同时指代蛋白质和疾病名。
- 嵌套实体:“BRAF V600E突变”里,“V600E”是“突变实体”,嵌套在“BRAF”这个基因实体后面。模型需要能捕捉这种层级。
- 缩写与口语化表达:“5-FU”代表“5-氟尿嘧啶”,“Met”可能指“甲氨蝶呤”或“甲硫氨酸”。上下文决定一切。
我在项目里遇到过最头疼的情况:一篇文献里“Akt”出现了50次,其中48次是“蛋白激酶B”的别名,还有2次是某个化合物缩写。你用规则词典匹配?词典里根本没收录那个化合物缩写。后来我意识到,必须用上下文建模才能搞定这种歧义。
2.2 NER的主流方法演化
从最早的规则匹配到现在的大模型微调,NER走过了三个阶段。为了方便对比,我整理了一个表格:
| 阶段 | 典型方法 | 优点 | 缺点 |
|---|---|---|---|
| 规则词典 | 基于正则、词典(如UMLS) | 精准、可解释 | 覆盖差、维护成本高 |
| 统计机器学习 | CRF、HMM、BIO标注 | 泛化能力强 | 需要大量特征工程 |
| 深度学习 | BiLSTM-CRF、BERT-CRF | 端到端、上下文感知 | 计算资源高,小样本差 |
现在工业界主流是用预训练语言模型(比如BioBERT、PubMedBERT)做编码,然后接CRF层做序列标注。我自己最常用的是BioBERT + CRF,在化学物和疾病数据集上F1能到90%以上。
2.3 标注格式:BIO/BIESO
NER模型一般用序列标注。每个token被分配一个标签。最常用的是BIO格式:
- B- (Begin):实体的第一个词
- I- (Inside):实体的内部词
- O (Outside):非实体
还有更细的BIESO格式,增加了E(End)和S(Single)标签。对于药物靶点任务,我推荐用BIESO,因为论文摘要里经常出现“表皮生长因子受体”这样的长实体,结束位置准确很重要。
举个例子,句子“吉非替尼抑制EGFR活性”的BIO标注:
吉 B-药物
非 I-药物
替 I-药物
尼 I-药物
抑 O
制 O
E B-基因
G I-基因
F I-基因
R I-基因
活 O
性 O
为什么会这样标注?因为“吉非替尼”是一个药物实体(4个字),“EGFR”是一个基因实体(4个字母)。注意字母“E”对应汉字“E”是因为英文token化?这里展示中文场景,英文同理。实际上在数据集里,英文单词一般被拆成subword,但为了演示我用了整词。
2.4 实战:快速搭建一个BioBERT NER管道
这里我用HuggingFace Transformers + PyTorch给大家演示。假设我们已经有一个标注好的数据(BIO格式,每行一个token+标签,空行分隔句子)。
from transformers import AutoTokenizer, AutoModelForTokenClassification
import torch
# 加载BioBERT预训练模型(专门在PubMed上训练)
model_name = "dmis-lab/biobert-base-cased-v1.1"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForTokenClassification.from_pretrained(
model_name,
num_labels=9 # 假设有9个类别:B-药物、I-药物、B-基因、I-基因、B-疾病、I-疾病、B-突变、I-突变、O
)
# 单句推理
sentence = "Imatinib inhibits BCR-ABL activity."
inputs = tokenizer(sentence, return_tensors="pt")
outputs = model(**inputs)
logits = outputs.logits
predictions = torch.argmax(logits, dim=-1)[0]
# 解码标签
id2label = {0: "B-药物", 1: "I-药物", 2: "B-基因", 3: "I-基因", 4: "B-疾病", 5: "I-疾病", 6: "B-突变", 7: "I-突变", 8: "O"}
tokens = tokenizer.convert_ids_to_tokens(inputs["input_ids"][0])
for token, pred in zip(tokens, predictions):
print(f"{token}\t{id2label[pred.item()]}")
输出示例(忽略特殊标记[CLS]和[SEP]):
Imatinib B-药物
inhibits O
BC B-基因
##R I-基因
- I-基因
AB I-基因
##L I-基因
activity O
.
注意“BCR-ABL”被拆成了多个subword。这是BERT的WordPiece分词导致的,但没关系,因为我们用BIO标注,模型会自动学习边界。
2.5 评价指标:精确率、召回率、F1
NER的评价通常按实体级别(entity-level)计算。也就是看预测的实体span和类别是否完全匹配。常用的:
- 精确率(Precision):预测出的实体中有多少是正确的。
- 召回率(Recall):真实标注的实体中有多少被预测出来了。
- F1分数:两者的调和平均,综合指标。
解释一下“严格匹配”:只有当预测实体的起始位置和类别都与标注完全一致时,才算正确。比如真实是“EGFR”(位置5-8,类别基因),你预测“EGFR”没问题;但如果预测了“EGFR gene”(包含额外token),就算错了。
2.6 本章知识框架
下面这张图概括了生物医学NER的核心流程和关键概念。我画的是从输入句子到输出实体列表的完整路线:
嗯,流程大致就是这样。从一句话开始,经过分词、生物医学预训练模型编码,再到CRF解码,最后得到一个个实体。每个环节都可能引入错误,所以实际项目中我会在每步后做质控检查。
2.7 常用工具与数据集
如果你准备动手做生物医学NER,这里列几个我常用的资源:
| 资源 | 说明 | 适用场景 |
|---|---|---|
| BioBERT | 预训练于PubMed,生物医学SOTA | 通用生物医学NER |
| PubMedBERT | 微软发布,更充分预训练 | 药物-靶点相关论文摘要 |
| BC2GM | 基因/蛋白质标注数据集 | 基因命名实体评估 |
| BC5CDR | 化学物-疾病关系,含NER标注 | 药物-疾病NER/关系抽取 |
| JNLPBA | 生物医学实体标注(5类) | 多类型NER评测 |
好了,这一章我们把NER的基础过了一遍。记住:没有准确的NER,根本谈不上关系抽取。后面几章我会带你一步步搭建端到端的药物靶点关系抽取系统。现在,你可以先找一个PubMed摘要,手动标几个实体,感受一下标注的难处——你会理解为什么我们需要模型来做这件事。