拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于 AI-For-Beginners 课程实战命名实体识别(NER):从 BIO 标注到 LSTM/BERT 实现

基于 AI-For-Beginners 课程实战命名实体识别(NER):从 BIO 标注到 LSTM/BERT 实现
  • 教程
  • 人工智能
  • 机器学习
  • 深度学习

【免费下载链接】AI-For-Beginners

12 Weeks, 24 Lessons, AI for All!

项目地址:https://gitcode.com/GitHub_Trending/ai/AI-For-Beginners
点击查看免费下载

命名实体识别(NER)是自然语言处理中与文本分类并列的核心任务之一:它需要在句子中找出人名、地名、时间、化学式、疾病名等实体,并标注其类别。本指南以 AI-For-Beginners 课程19-NER一节为主线,先讲清楚"为什么需要 NER"(聊天机器人意图+参数抽取)与"N ER 的建模方式"(把 NER 视为令牌分类问题并用 BIO 标注),再结合课程自带的 NER-TF.ipynb 逐行拆解如何用 Keras 训练双向 LSTM 完成令牌分类,最后给出基于 PubMedBERT 的医疗实体识别进阶路线。读完本文,你将能够:把一个原始文本数据集转换为 BIO 编码,搭建并训练一个 NER 神经网络,并理解如何在真实医学文献语料上落地。

图为课程配图(Image by the author):用户话语 "What is the temperature in Moscow today?" 中,"Moscow"(location)、"today"(datetime)、"temperature"(measurement)被识别为实体并填入对应参数槽,从而让聊天机器人触发 Weather 意图。图片来源 bot-ner.png。

NER 为什么重要:聊天机器人与意图-槽位(Intent-Slot)范式

智能聊天机器人(类似 Amazon Alexa 或 Google Assistant)工作的核心方式是:对用户输入句子做文本分类,判断用户"想干什么",这个分类结果被称为intent(意图),它决定了机器人下一步该执行什么动作。

但仅有意图是不够的。用户在表达需求时往往附带参数,例如询问天气时可能指定地点或日期:

  • 句子:What is the temperature in Moscow today?
  • 意图:Weather(查询天气)
  • 需要填写的参数槽:地点 = Moscow、时间 = today、测量量 = temperature

机器人必须能把这些实体从句子中抽取出来,并在执行动作前填入对应的"参数槽(parameter slots)"。这正是 NER 的用武之地。

✅ 另一个典型场景是科学医学论文分析。在分析大量医学文献时,我们需要定位疾病名、药物名、化学物质等专有医学名词。少部分疾病可以通过子串搜索(substring search)简单抽取,但像化合物、药物名称这类复杂实体,子串匹配远远不够,必须依赖 NER 模型。

在课程仓库中,19-NER 课程的作业 正是要求用 NER 处理医疗语料——训练一个能识别"疾病(Disease)"和"化学物质(Chemical)"实体的模型。可见 NER 在真实业务中,既是"理解用户输入"的关键环节,也是"从非结构化文本中提取结构化知识"的核心工具。

NER 的本质:令牌分类(Token Classification)

NER 模型本质上是一个令牌分类模型(token classification model):对输入的每一个 token,我们需要判断它是否属于某个实体,以及属于哪一类实体。

以论文标题为例:

Tricuspid valve regurgitationandlithium carbonatetoxicityin a newborn infant.

其中的实体包括:

  • Tricuspid valve regurgitation(三尖瓣反流)——疾病(DIS)
  • Lithium carbonate(碳酸锂)——化学物质(CHEM)
  • Toxicity(毒性)——疾病(DIS)

BIO(IOB)标注:解决"跨 token 实体"与"相邻实体"

注意到两点:

  1. 一个实体可能跨越多个 token(如 "Tricuspid valve regurgitation" 由三个 token 组成);
  2. 两个实体可能紧挨着(如 "lithium carbonate" 与 "toxicity")。

因此,仅用一个实体类别标签无法区分"实体的开头 token"和"实体的后续 token"。课程采用业界标准的BIO tagging(即 IOB 标注,Inside–Outside–Beginning):

  • B-前缀:beginning,表示实体的第一个 token,如B-DIS;
  • I-前缀:inner,表示实体的后续/内部 token,如I-DIS;
  • O:表示所有其他(outside)不属于任何实体的 token。

对上述标题做 BIO 标注后,每个 token 对应的标签如下:

TokenTag
TricuspidB-DIS
valveI-DIS
regurgitationI-DIS
andO
lithiumB-CHEM
carbonateI-CHEM
toxicityB-DIS
inO
aO
newbornO
infantO
.O

为什么用"多对多"的 RNN 架构

令牌与类别之间是一一对应关系,因此我们可以训练一个**多对多(many-to-many)**的循环神经网络模型。课程文档引用了 Andrej Karpathy 的经典博文The Unreasonable Effectiveness of Recurrent Neural Networks中的示意图:图中最右侧的"many-to-many"架构,即输入序列与输出序列长度一致、每个时间步都产生一个输出的结构,正是 NER 令牌分类模型所对应的网络形态(原文参考图)。

简言之:输入是一个 token 序列,输出是等长的 tag 序列,网络在每个时间步对当前 token 做出分类决策。

用 Keras 训练 NER 模型:双层双向 LSTM 实战

课程文档指出:既然 NER 本质是令牌分类,我们完全可以复用前面章节熟悉的 RNN 技术;此时循环网络每个时间步(block)都会输出该 token 的类别 ID。课程为此提供了完整的实战 Notebook:NER-TF.ipynb,使用 TensorFlow/Keras 实现。

1. 数据准备:读取 Kaggle 实体标注语料

Notebook 使用的数据集是 Kaggle 上的Annotated Corpus for Named Entity Recognition(文件ner_dataset.csv,需先下载到当前目录)。读取并查看数据:

import pandas as pd from tensorflow import keras import numpy as np df = pd.read_csv('ner_dataset.csv', encoding='unicode-escape')

数据集的典型结构如下(每行一个 token,按句子分组):

Sentence #WordPOSTag
Sentence: 1ThousandsNNSO
NaNofINO
NaNdemonstratorsNNSO
NaNhaveVBPO

字段含义:Sentence #为句子编号(同一句内的后续行用 NaN 表示延续)、Word为词、POS为词性标注、Tag为 NER 标签(即我们要预测的目标)。

2. 构建词表与标签表

课程指出:为简单起见,构建词表时不考虑词频;而在真实项目中,建议使用 Keras 的向量化器(vectorizer)并限制词表大小。Notebook 中的做法如下:

tags = df.Tag.unique() # 所有 NER 标签(含 B-*/I-*/O) id2tag = dict(enumerate(tags)) # 标签 ID -> 标签名 tag2id = { v : k for k,v in id2tag.items() } vocab = set(df['Word'].apply(lambda x: x.lower())) id2word = { i+1 : v for i,v in enumerate(vocab) } vocab.add('<UNK>') # 预留未知词占位符 word2id = { v : k for k,v in id2word.items() }

注意这里id2word从 1 开始编号,为<UNK>(未知词)留出了位置,这样测试阶段遇到词表外的词也能正常向量化。

3. 按句重组:从"词-标签"表到"句子序列"

因为ner_dataset.csv是按行存储的,需要把属于同一句的 token 聚合为句子序列:

X, Y = [], [] s, t = [], [] for i, row in df[['Sentence #', 'Word', 'Tag']].iterrows(): if pd.isna(row['Sentence #']): s.append(row['Word']) t.append(row['Tag']) else: if len(s) > 0: # 上一句结束,保存 X.append(s) Y.append(t) s, t = [row['Word']], [row['Tag']] X.append(s) Y.append(t)

随后定义两个映射函数,把词序列和标签序列分别转为 ID 序列:

def vectorize(seq): return [word2id[x.lower()] for x in seq] def tagify(seq): return [tag2id[x] for x in seq] Xv = list(map(vectorize, X)) Yv = list(map(tagify, Y))

4. 序列填充(Padding):不同句子统一长度

句子长度不一,需要填充到同一长度。Notebook 使用pad_sequences,并指定padding='post'(在序列尾部补零):

X_data = keras.preprocessing.sequence.pad_sequences(Xv, padding='post') Y_data = keras.preprocessing.sequence.pad_sequences(Yv, padding='post')

注意:Y_data的填充值同样是 0。由于后面使用sparse_categorical_crossentropy作为损失函数,填充位置对应的标签 ID 0 会被计入损失——这在教学示例中是可接受的简化;在正式工程中通常需要借助 mask 机制排除 padding 位置。随后:

maxlen = X_data.shape[1] # 序列最大长度 num_tags = len(tags) # 标签类别数 vocab_size = len(vocab) # 词表大小

5. 搭建双层双向 LSTM:TimeDistributed 实现"多对多"

这是本节最核心的代码。为了对 LSTM 每个时间步的输出都施加一个稠密分类器,Keras 使用TimeDistributed包装Dense层,从而把同一个全连接层复制到序列的每一步(正是"多对多"架构的框架级实现):

model = keras.models.Sequential([ keras.layers.Embedding(vocab_size, 300, input_length=maxlen), keras.layers.Bidirectional(keras.layers.LSTM(units=100, activation='tanh', return_sequences=True)), keras.layers.Bidirectional(keras.layers.LSTM(units=100, activation='tanh', return_sequences=True)), keras.layers.TimeDistributed(keras.layers.Dense(num_tags, activation='softmax')) ]) model.compile(loss='sparse_categorical_crossentropy', optimizer='adam', metrics=['acc'])

要点解析(对应 Notebook 中的模型概要输出):

  • Embedding 层:词表大小 × 300 维的词向量,输入长度固定为maxlen。从源码结构看,词向量随训练一起学习;
  • 两层双向 LSTM:每层 100 个单元、tanh激活,return_sequences=True保证每个时间步都输出隐藏状态(这是做令牌分类的前提);双向(Bidirectional)使模型能同时利用每个 token 的前后文信息;
  • TimeDistributed + Dense:对序列每一步分别做 softmax 分类,输出维度为num_tags,即每个 token 在所有标签上的概率分布;
  • 损失函数:sparse_categorical_crossentropy(标签以整数 ID 给出)+ Adam 优化器,指标为准确率。

Notebook 特别提醒:这里显式指定了maxlen;如果希望网络支持变长序列,需要在定义网络时更精细地处理(例如不固定input_length并使用 mask)。

6. 训练与推理

训练一行即可完成:

model.fit(X_data, Y_data)

推理阶段,把输入句子分词后同样走word2id+pad_sequences(注意要用与训练一致的maxlen),再送入模型取 argmax 得到每个 token 的标签:

words = sent.lower().split() v = keras.preprocessing.sequence.pad_sequences( [[word2id[x] for x in words]], padding='post', maxlen=maxlen) pred = model.predict(v)

7. 局限性:为什么还要上 BERT?

Notebook 的结论部分给出明确指引:即使是简单的 LSTM 模型,在 NER 上也能得到合理的结果;但要想获得显著更好的效果,应使用大规模预训练语言模型(如 BERT),并用 Hugging Face Transformers 库对 BERT 做 NER 微调。这正是接下来课程作业的进阶方向。

进阶实战:基于 PubMedBERT 的医疗实体识别(课程作业)

课程的 Lab 作业说明 要求训练一个医疗术语 NER 模型,并给出了完整技术路线。

数据集:BC5CDR

作业使用BC5CDR 数据集(BioCreative V Track 3 CDR),其中包含来自 1500+ 篇论文的疾病(Disease)与化学物质(Chemical)标注实体。数据格式如下:

6794356|t|Tricuspid valve regurgitation and lithium carbonate toxicity in a newborn infant. 6794356|a|A newborn with massive tricuspid regurgitation, atrial flutter, congestive heart failure, and a high serum lithium level is described. ... 6794356 0 29 Tricuspid valve regurgitation Disease D014262 6794356 34 51 lithium carbonate Chemical D016651 6794356 52 60 toxicity Disease D064420 ...

数据说明(见 lab/README.md):

  • 前两行是论文标题(|t|)和摘要(|a|);
  • 随后每一行是一个实体:论文ID + 起始位置 + 结束位置 + 实体文本 + 实体类型 + 医学本体ID;
  • 起止位置是相对于"标题+摘要"整块文本的偏移量;
  • 除实体类型外,还给出了该实体在某个医学本体中的本体 ID(如D014262)。

你需要自行编写 Python 代码,把这种"位置标注"格式转换为前面介绍的BIO 编码。

模型选型:为什么用 PubMedBERT

作业明确指出:在 NLP 任务中,Transformer 架构,尤其是BERT 语言模型,效果远好于传统 RNN。预训练 BERT 已经理解语言的通用结构,可以用相对较小的数据集与计算成本,微调到具体任务。

由于应用场景是医疗领域,使用在医学文本上训练过的 BERT是更合理的选择。Microsoft Research 发布了在 PubMed 文献库上微调得到的PubMedBERT模型,社区将其托管在 Hugging Face 生态中。

微调代码:Hugging Face Transformers 三行起步

Hugging Face Transformers 是训练 Transformer 模型的事实标准库,其中包含社区维护的预训练模型库(含 PubMedBERT)。加载并使用的核心代码极其简洁:

model_name = "microsoft/BiomedNLP-PubMedBERT-base-uncased-abstract" classes = ... # number of classes: 2*entities+1 tokenizer = AutoTokenizer.from_pretrained(model_name) model = BertForTokenClassification.from_pretrained(model_name, classes)

参数说明(见 lab/README.md):

  • model_name:Hugging Face 上的 PubMedBERT 预训练权重标识;
  • classes:分类类别数,其计算公式为2 * 实体类别数 + 1(每个实体对应B-、I-两个标签,再加一个O标签)——这与前文 BIO 标注的类别体系完全一致;
  • tokenizer:用于把输入文本切分为 token(注意 BERT 使用子词(subword)分词,与单词级分词不同,因此把数据集转换为 BIO 格式时必须把 PubMedBERT 的 tokenization 纳入考量);
  • model:为令牌分类任务构建的 BERT 模型,输出classes个类别的分类结果。

作业还建议参考一段将标注数据按 BERT 分词对齐为 BIO 格式的 Python 代码片段作为灵感。

落地价值:把 NER 用在 COVID 文献语料上

作业的 Takeaway 部分强调:这个任务非常接近你在真实场景中可能遇到的需求——从大量自然语言文本中挖掘洞见。例如,可以把训练好的模型应用到 **COVID 相关论文数据集(CORD-19)**上,识别论文中的疾病、药物、化学物质等实体,再结合图分析等手段发现规律。课程的示例曾演示过对医学论文语料做实体识别后,利用共现网络(如实体-实体关系网络)得到疾病、化学物质与治疗手段之间的关联洞察,展示了 NER 在文献计量分析中的完整闭环价值。

小结与自测

课程文档的结论可以总结为一句:NER 模型就是令牌分类模型,它解决的是从文本中识别地点、人名、日期等实体这一 NLP 高频任务。掌握它需要三个层次:

  1. 概念层:理解 BIO/IOB 标注体系、多对多 RNN 架构与令牌分类的关系;
  2. 工程层:能够把位置式/逐词式标注数据转换为 BIO 序列,用 Keras 完成填充、Embedding + 双向 LSTM + TimeDistributed 分类的端到端训练;
  3. 进阶层:认识 BERT 类预训练模型(尤其领域预训练模型如 PubMedBERT)在 NER 上的压倒性优势,掌握 Hugging Face 微调范式。

课后练习(19-NER 课程作业)要求你:先按本课方式训练 LSTM 医疗实体识别模型,再改用 BERT(PubMedBERT)微调对比效果,最后换一个不同数据集验证模型的泛化能力。推荐进一步精读 Karpathy 的The Unreasonable Effectiveness of Recurrent Neural Networks博客及其 Further Reading 部分,深化对序列建模的理解。

  • 教程
  • 人工智能
  • 机器学习
  • 深度学习

【免费下载链接】AI-For-Beginners

12 Weeks, 24 Lessons, AI for All!

项目地址:https://gitcode.com/GitHub_Trending/ai/AI-For-Beginners
点击查看免费下载
上一篇:NVIDIA NeMo-Aligner:高效模型对齐的利器
下一篇:探索高级魔法:Advanced Alchemy 开源项目推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

返回列表