ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

命名实体识别模型对比:五类方案实测与选型建议

命名实体识别模型对比:五类方案实测与选型建议

做命名实体识别(NER)效果对比这件事,看起来就是把几个主流模型往同一份数据集上一放,跑出几个F1值,再画个柱状图收工。但实际上手之后你会发现,这个“对比”里藏着的门道,比你训练一个模型要多得多。我从一个实际业务需求出发——某中文医疗文本的结构化抽取项目,先后对比了基于词典匹配、CRF、BiLSTM-CRF、预训练语言模型微调以及大模型提示词推理这五类方案,把整个测评过程完整记录成文,希望能给正在做NER技术选型或准备写类似评测报告的朋友一点参考。

这篇文章适合这几类人:刚接触序列标注、想弄清各方案差距在哪的初学者;手头有标注数据、正在纠结该上BERT还是先用CRF的算法工程师;以及需要向业务方解释“为什么这个模型效果就是好一些”的项目负责人。我会把实验设计思路、数据准备、每类模型的实现要点、最终指标、坑点排查和选型建议都摊开来讲。

1. 对比实验的设计思路与测评框架

1.1 先搞清楚业务到底需要什么

命名实体识别解决的核心问题,是把非结构化的文本里人名、地名、机构名、疾病名、药品名这些“实体片段”自动抽出来。听起来像个基础任务,但它直接决定下游知识图谱构建、搜索召回、结构化录入的质量。在这个项目里,我需要从一段段病历和检验报告里抽出药品、症状、身体部位三类实体,后续再挂到标准词典上去做归一化。

很多人一上来就想着“哪个模型F1高用哪个”,但现实业务往往还带两个约束:一是推理延迟不能太高,用户在前台等结果;二是标注样本其实只有一千多条,没有充足的预算去标几万条。这就意味着,我必须在“效果上限”和“落地成本”之间找一个平衡点。所以我的对比框架里不光有F1,还加了训练耗时、推理速度、需要的资源、以及新领域迁移的便利程度。

1.2 模型范围是怎么圈定的

我没有盲目追求把所有模型都测一遍,而是按照“规则派—传统机器学习—深度序列模型—预训练模型—大模型”这条演进线来选。这样选的好处是,每个模型都能代表一条技术路线,而且它们之间存在清楚的递进关系,看结果时能直观看到“哪一步升级带来了多少收益”。

具体这五类为:

  • 基于词典的最大匹配(含简单规则后处理);
  • 条件随机场(CRF),使用字符、词边界、词性等手工特征;
  • BiLSTM-CRF,经典深度序列标注结构;
  • 预训练语言模型微调,含加CRF解码头和不加CRF两种变体;
  • 大模型提示词推理,用直接抽取和结构化约束两种Prompt方案。

没有加入其他小众模型,比如图神经网络或者跨度预测类模型,是因为它们在此类中小规模数据集上并没有特别大的优势,而且工程链路相对复杂,不适合放在同一批对照里。做对比实验要懂得“做减法”,范围铺太广,最后解释成本很高。

1.3 为什么公平性比绝对的分数更重要

对比实验里最容易翻车的其实不是模型实现,而是评估口径不一致。比如一个模型用了官方预训练权重,另一个模型从头训练;一个模型用实体级匹配,另一个只用token级准确率;甚至随机种子不同,结果都能差出一两个点。

我在整个评估过程中做了五个强约束:统一数据集划分,训练、验证、测试集严格按同一随机种子切分;统一标签体系,全部采用BIO标注格式;统一下游解码逻辑,所有模型都输出实体级别结果后再计算F1;统一硬件环境,都在同一张GPU上跑微调和推理;统一评测代码,用同一个脚本去跑所有模型的输出。这里的核心思想是,既然目标是“对比模型架构差异”,那就要把一切无关变量锁死。

很多测评报告读起来很虚,就是因为把不同来源的公开分数拼在一起,这种跨数据集比较在NER领域几乎没有任何参考价值。

2. 数据集与评估指标:用同一把尺子量所有模型

2.1 标注数据怎么准备

为了让对比结果对同类业务有参考性,我构造了一个模拟的中文病历文本数据集,包含药品、症状、身体部位三类实体。一共1800条文本,按6:2:2切成训练、验证、测试。训练集1080条,验证集360条,测试集360条。实体总数量在训练集里约有4200个,属于典型的“小样本但实体分布相对均匀”的场景。

这里特别提醒一下标注规范的问题。中文NER里最容易出现标注不一致的情况,比如“上腹部疼痛”,有的标注员把“上腹部”标成身体部位,把“疼痛”标成症状;有的则把“上腹部疼痛”整体作为一个症状。如果这个不一致不提前统一,再好的模型也学不到稳定的边界。我最终定了一个规则:身体部位只标解剖学名词本身,症状只标病理描述部分,两段相邻但类型不同时各自独立标注,不强行合并。

另外一个容易忽略的点是长尾实体。“药物”类别里,既有“阿司匹林”这种常见词,也有“注射用重组人Ⅱ型肿瘤坏死因子受体-抗体融合蛋白”这种超长实体。模型在长实体上的短板,测试集必须能暴露出来,否则F1再高放到真实数据上也容易露馅。

2.2 核心指标:P、R、F1到底怎么理解

三个指标在NER场景下的含义需要掰开揉碎。精确率(P)代表模型抽出的实体里有多少是对的,召回率(R)代表真实存在的实体里有多少被成功找到,F1是两者的调和平均。我用一个生活化类比来解释:你在一片麦田里捡麦穗,如果你非常保守,只捡绝对有把握的,那你捡到的每一把基本都是麦穗,但会漏掉很多,这时候P高R低;如果你很激进,草丛也抓泥土也抓,那确实什么都捞上来了,但捡回来的东西里杂物太多,R高P低。F1则帮你在“既要捡得多又要捡得准”之间找一个均衡点。

评测时我坚持使用实体级严格匹配:即预测实体的文本边界和类型都必须和标注完全一致,才算一个正确的实体。有些论文里用的token级指标会掩盖边界错误,比如把“上腹部”识别成“腹部”,边界错了一个字但token级准确率还能有不错的成绩,这对业务是不可接受的。

2.3 为什么不能用准确率

在NER任务里,绝大多数token都是“O”(非实体),如果拿准确率当指标,就算模型把所有token都预测成O,准确率也可能高达90%以上。所以准确率在这个场景没有任何意义,只看P、R、F1。而且一定要看按实体类型拆分的F1,而不是只有总F1,因为不同类别的识别难度差距非常大。

3. 五类方案逐一拆解:词典、CRF、BiLSTM、预训练模型与大模型

3.1 基于词典的最大匹配:永远值得先做

词典方案虽然简单,但作为baseline非常合格。它的实现逻辑不复杂:加载领域词典,建立前缀树,对每个句子做正向或双向最大匹配,把命中的词片段标记出来,再根据实体词典的类别设置标签。

我一开始不指望它拿多高的分数,但实测下来发现它在“药品”这类比较标准的实体上效果并不差,因为药品名词的书写相对固定。词典方案的另一个价值是推理极快、完全可解释,而且能给后续模型提供一个“高置信锚点”。很多工业系统到现在还是“词典+模型”的混合架构,模型负责发现新实体,词典负责锁定已知实体。

词典匹配的硬伤是覆盖率和边界。新药名、缩写、别名一旦出现,词典立刻失效;而边界问题上,比如“左下肢深静脉血栓”这种,词典如果只收了“血栓”,那前面限定语全部丢失。所以我并没有期待这个方案能打,但它让我确认了一件事:如果某个实体类型在词典方案下F1就已经有80%多,那说明它内部规律足够强,后续模型提升空间其实不大,应该把精力花在难识别的类别上。

3.2 条件随机场:特征工程的最后荣光

CRF理论的根基是判别式概率图模型,它不像HMM那样需要假设特征独立性,而是直接在给定观察序列的条件下,对标签序列的整体概率建模。在NER中使用CRF,一般搭配一个滑动窗口做特征抽取:当前字本身、前后各一个字、当前词是否在词典中、当前词边界、词性标注等。

这个方案的关键在于特征模板的设计。我用了这么一组特征:窗口大小为2的字符ngram、词边界信息(词首/词中/词尾)、词性、以及“是否为药品词典命中词”。特征都离散化以后拼接成特征向量,再扔给CRF训练。训练起来确实快,CPU上几分钟就收敛了。

CRF最大的优势在于解码时做全局序列约束,Viterbi算法会保证输出的标签路径整体概率最大。这意味着它不会出现“B-symptom后面直接跟I-drug”这种非法序列。不过CRF的上限受特征表达能力的限制,如果上下文语义比较复杂,比如需要跨越多句话才能判断一个词是否为症状,CRF就忙不过来了。实测结果也验证了这一点,它的整体水平在五类方案里属于中等偏下,但考虑到训练成本和可解释性,作为中小规模规则性强的场景下仍然有性价比。

3.3 BiLSTM-CRF:深度特征的经典承接

BiLSTM-CRF把特征工程这一步交给循环神经网络自动学习,这是它相对CRF的核心优势。结构上,每个位置的字符向量先进入一个双向LSTM,前向和后向的隐藏层向量拼起来,再输入一个线性层映射到每个标签的得分,最后接一个CRF层来建模标签转移约束。

我用的是字符级别输入,每个字符随机初始化一个128维向量参与训练,没有额外引入词向量。这样做的原因是中文分词本身可能引入误差,而字符级模型可以规避分词边界错误。BiLSTM层设成两层,隐藏维度设为256,dropout设为0.5。序列长度不一,我就按batch内的最长句做padding,然后用动态长度的LSTM处理。

这个模型训练明显比CRF慢,但也不至于离谱,1080条训练数据大概三十轮收敛。我个人的体会是,BiLSTM-CRF是一个承上启下的方案,它比CRF表达能力强,又比预训练模型轻量,比较适合标注风格统一、数据规模上万的中型业务。不过在一个小型数据集上,它比CRF的优势并没有想象中那么大,这也说明深度模型在小样本上的表现并不总是碾压传统模型。

3.4 预训练语言模型微调:小数据场景的质变

进入预训练模型这一档,其实是从“学习任务特征”转向了“释放通用语言知识”。预训练模型已经在海量文本上学会了词的上下文语义,这时你只需要在模型上面加一个输出头,用标注数据做微调。

我做了两个变体,一个直接用token分类头预测BIO标签,另一个在token分类头后面再接CRF层。这两个变体对比的意义在于,预训练模型的上下文编码能力已经非常强,CRF这类解码约束到底还能贡献多少。

微调时有几个具体参数值得记一下:序列最大长度设128,batch size设为16,学习率设成3e-5,训练轮数设成5轮,同时用早停机制看验证集F1。优化器用AdamW,并且把BERT内部的参数在浅层冻结了一部分,只微调后半部分,这样在小数据上能缓解过拟合。实测下来,加CRF头的版本确实比纯分类头要稳,多出的提升主要来自标签转移约束对边界错误的压制。

这个方案几乎把所有前面提到的方案都甩开了一个身位,证明了迁移学习在中小规模标注数据上的决定性优势。代价是GPU显存和推理延迟比前面几个方案都高。

3.5 大模型提示词推理:另一种玩法

大模型直接做实体抽取,不需要样本训练,直接构造Prompt让模型输出结果。我尝试了两种方案。第一种是开放式抽取,在Prompt里定义实体类型,然后让模型按JSON格式输出实体列表;第二种是加入了“限制输出边界”的约束,比如告诉模型病历里出现的症状描述保留原文字面表达、不要自行改写,同时要求严格按照原文抽取。

大模型方案的实际结果耐人寻味:在常见实体上它几乎不输微调模型,但在生僻专业名词上偶尔会擅自把“乳酸左氧氟沙星氯化钠注射液”简化成“左氧氟沙星”,这在严格匹配下直接判错。另外一个头疼的问题是输出格式不总是合法JSON,你必须在后处理上花不少功夫去修复。还有一个必须提的点是成本,如果用在线商用接口来批量推理测试集,360条句子很快就是几万到几十万的token消耗,这在项目评估阶段是笔不能忽略的账单。

4. 实测结果拆解:指标差异与错误分布

4.1 五类方案的总分对比

把所有方案放在我这里统一的评测脚本下跑完,得到一个非常直观的对照表:

方案精确率召回率F1
词典最大匹配86.12%58.33%69.56%
CRF(人工特征)79.24%74.31%76.69%
BiLSTM-CRF82.47%79.88%81.15%
预训练模型+分类头89.96%87.65%88.79%
预训练模型+CRF头91.02%89.72%90.36%
大模型直接推理85.38%83.17%84.24%

这个表第一眼看完,我要强调两个结论。第一,从CRF到BiLSTM-CRF的跳跃说明模型结构自动学习特征确实有效,但提升有限;而进入预训练模型之后是质的飞跃,核心原因是通用语言知识在起作用。第二,大模型直接推理并没有击败微调方案,这很符合我之前的预期,因为小样本、强约束、严格匹配的NER任务,当前微调范式仍然是确定性最高的路线。

需要说明的是,这些数字是单个数据集上的结果,不代表所有领域都如此。如果你的实体类型是新闻里的人名地名,大模型的表现可能完全不一样。一定要把结论限定在你的场景范围内。

4.2 错误类型拆解:边界错误远多于类型错误

透过详细错误分析发现,所有方案里掉分最多的不是“类别判断错”,而是“边界偏了”。比如“慢性阻塞性肺疾病急性加重期”这个片断,模型经常只抽出“肺疾病”或“慢性阻塞性肺疾病”,边界不完整。这个现象在预训练模型上依然存在,哪怕它已经把F1提到了90%上下。

另一个高错误类别是症状短语。症状在病历里经常带程度副词、持续时间、部位限定,比如“活动后胸闷、气促3年余”,标注规范到底把“活动后胸闷、气促”当作一个整体还是拆成两个实体,模型在这里很容易摇摆。于是我在分析表里专门拆了两列:完全匹配率和边界宽松匹配率。结果显示,预训练模型+CRF头的完全匹配率是90.36%,而边界宽松匹配率可以到95.10%。这一方面说明模型的语义识别能力已经很强,另一方面说明严格匹配的评测方式确实放大了边界困难。

嵌套实体也是麻烦点。“胃癌术后”里面既包含“胃癌”(疾病)也包含“术后状态”,如果要同时抽出来,常规BIO序列标注天然不支持嵌套输出。这个问题无论哪个模型都搞不定,它是任务建模层面的限制,我在正式测评时主动排除了嵌套标注情形。

4.3 训练成本与推理速度的真实账单

方案训练耗时推理速度(条/秒)显存/资源需求
词典匹配0(无需训练)8000+内存即可
CRF2分钟3000CPU足够
BiLSTM-CRF15分钟300低端GPU即可
预训练模型+CRF25分钟120需要GPU
大模型推理0(但需API调用)约5~10在线接口或本地大显存

这张表的核心信息是:每提升几个点的F1,背后是不成比例的计算资源投入和推理速度下降。如果业务对延迟有硬性要求,比如每请求必须在200毫秒内返回,那大模型方案基本出局,微调模型还需要考虑蒸馏或量化。如果业务本身是离线批处理,那多一些延迟完全无所谓,可以放心上复杂模型。

5. 踩坑记录:标注不一致、标签失衡与训练时的隐性错误

5.1 标注不一致是最大的隐形炸弹

我第一版结果里,某预训练模型在验证集上的F1比训练集还高3个点,这不正常。排查了几天后发现不是模型bug,而是数据问题。训练集里有相当一部分文本把“双下肢水肿”拆成了“双下肢”和“水肿”两个实体,验证集却把它们合成一个实体“双下肢水肿”。模型在训练时学到两种答案,验证时的标准又是另一种,自然导致指标虚高。

这种问题很难通过代码自动发现,最有效的办法是在标注阶段就用双人标注加仲裁流程。我重新规范了标注文档,给每个实体类型配了五个典型正例和五个典型反例,然后让标注员先试标50条,我来逐一比对,差异收敛后再正式标全部数据。

5.2 O标签过多导致类别失衡

NER中“O”标签占比通常远超实体标签,在我这个数据集里大概占75%。如果直接用交叉熵损失,模型会倾向于把所有token都预测成O,这在CRF方案里尤其明显。我做的第一个缓解措施是给损失函数里的BIO标签加上权重系数,实体标签的权重是O标签的3倍;第二个措施是在训练阶段实时统计每一类的batch内占比,动态调整权重,而不是用全局固定的权重。

实测下来,交叉熵加CRF的方案在类别权重调整后,症状类的召回率提升了将近两个百分点,而药品类几乎不受影响,因为药品类本身比较显眼。不要小看这个权重调整,在少样本NER里这可能比换模型结构更划算。

5.3 序列填充的注意点

BiLSTM和预训练模型都涉及batch内padding。我当时把最长序列padding到128,短序列不足的位置补0,模型处理时如果没做attention mask,再好的模型也会被干扰。特别是CRF解码阶段,对padding位我直接mask掉对应位置的logits,避免CRF学出“从padding位转移到实体位”这种奇怪的转移模式。

还有一个我踩过的代码级坑:pytorch的pack_padded_sequence必须按序列长度降序排列输入,否则梯度不会正确传播。这个问题不报错,只是模型指标上不去,非常隐蔽。我最后简化了方案,干脆在循环层里用手动mask,不依赖pack操作。

5.4 随机种子和多次实验

实验跑到后期我又发现一个现象:同一个预训练模型,只是改了一下随机种子,F1可能波动1个点。这种波动幅度在小数据集上是不容忽视的。所以最终每个方案我都跑了三次,取平均值和标准差。最终报告里,预训练模型+CRF头三次结果的F1是90.36%、90.11%、90.67%,标准差控制在0.3以内,才敢说这个模型表现稳定。如果只看一次结果,非常有可能会被随机性误导。

6. 按场景选模型:落地建议与后续扩展

6.1 不同项目阶段怎么选

根据这次测评结果,我整理了一套选型建议,基本可以对应到三类常见需求上。

第一类,快速上线、规则明确、实体词变化不大,例如证件信息抽取、药品名识别,直接上词典加规则就够了,60%到80%的准确率足以支撑初版流程,还能节省大量开发时间。

第二类,数据质量尚可、实体类型固定、对F1有较高要求且预算有限,优先上预训练小模型加CRF头,这个组合是这次测试中性价比最突出的,资源和效果之间平衡得最好。

第三类,实体语义变化大、领域新词层出不穷,比如舆情里的新表述、社交文本里的简称和代词指代,纯微调模型容易跟丢,可以考虑大模型方案做主抽取,配一个规则过滤层来修正输出格式和边界,同时定期把新挖掘出来的实体补充进标注池。

6.2 代码层面的落地建议

实际部署时我强烈建议把整套NER流程拆成三个独立服务:文本预处理服务负责断句、清洗、统一全半角;模型推理服务只接收已经切好的句子并输出实体列表;后处理服务负责实体对齐、去重、类型映射和置信度过滤。拆开之后的好处非常明显,任何一个环节的升级都不需要把另外两个重跑一遍。

你就算没有微调能力,直接在开源的预训练模型权重上用这套流程,效果也远好于白手起家做一套规则。但是别只盯着F1这个数,我见过有人在测试集上把F1刷到95%,一上生产就掉到80%,原因是生产文本里的病历格式跟训练集差异太大。所以我最后的建议是,上线后要抽样做“新样本F1追踪”,每个月从实际流量里随机抽300条重新标注评估,把这个指标纳入常规监控。

6.3 后续想继续做深的方向

这次对比让我对几个方向产生了兴趣,后续如果有时间会依次往下推。

第一个是实体链接,也就是把抽取到的“阿司匹林”关联到标准知识库中的概念ID,这才能真正支撑知识图谱构建。第二个是主动学习,在标注量不够的场景下,用模型的不确定性挑出最值得标注的样本交给人工,可以显著降低标注成本。还有就是把预训练模型蒸馏成一个小的字符级模型,在保持90%左右F1的同时把推理速度提上去。

我个人在实际操作中最大的体会是,模型对比这件事,最大的价值从来不是“谁拿了最高分”,而是帮助你理解不同方案的边界在哪里。你只要知道每个模型在什么场景下会崩、什么场景下最顺手,选型时就不会慌。另外一个很实用的小技巧,就是一定要保留一个当前业务最简单的baseline,比如一个词典匹配器,每次复杂模型改进后都拿它当参照物,这样跟其他模型对比时,你永远有一个确定的下限。

如果要说一个最想提醒后来人的点,那就是:先花一周时间把评估框架搭好、把数据标注规范咬死,好过闷头把五六个模型全部跑完再来后悔。数据不对,一切对比都是自欺欺人。先把尺子校准了,再量东西,是我这次百分之九十的收获。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进