
1. 从各自为政到统一框架为什么NER需要一次范式重构命名实体识别Named Entity RecognitionNER是自然语言处理中最基础也最核心的任务之一。它的目标很简单从一段文本中找出人名、地名、组织名、时间、数量等具有特定意义的实体片段并标注它们的类别。看起来简单但真正做过NER项目的人都知道这个任务里藏着一个长期困扰整个领域的结构性问题——扁平实体Flat NER和嵌套实体Nested NER长期被当作两个不同的任务来处理。什么叫扁平实体就是实体之间互不重叠、互不嵌套。比如张三在北京大学读书这句话里张三是人名北京大学是组织名两者在字符层面没有交集。这种场景下传统的序列标注方法BIO、BIOES标注体系工作得很好一个字符对应一个标签干净利落。但现实世界远没有这么规整。北京大学第三医院这个例子就很典型它整体是一个组织名但内部又包含了北京大学这个组织名甚至北京这个地名。这就是嵌套实体。在医学文本里这种情况更加普遍——左肺下叶腺癌整体是一个疾病实体但左肺下叶是解剖部位实体腺癌是病理类型实体。在金融文本、法律文本、学术文本中嵌套结构几乎无处不在。过去十年学术界对这两个问题的处理方式是割裂的。扁平NER用序列标注嵌套NER用基于跨度Span-based的方法或者超图Hypergraph方法。两套技术路线、两套模型架构、两套评测体系甚至论文都发在不同的track上。这种割裂带来的直接后果是一个团队要落地一个NER系统往往需要维护两套甚至多套模型工程复杂度成倍增加而且模型之间无法共享底层语义知识。ACL2020上发表的这篇A Unified MRC Framework for Named Entity Recognition做的事情本质上是一次范式层面的统一。它没有发明新的神经网络结构也没有提出新的预训练目标而是做了一件更聪明的事把NER任务重新定义为阅读理解Machine Reading ComprehensionMRC任务。这个转换看似简单但它带来的连锁反应是深远的——扁平NER和嵌套NER第一次可以用完全相同的模型、相同的训练流程、相同的推理逻辑来处理。我第一次读到这篇论文的时候最直接的感受是这不是一个增量改进而是一个视角切换。它把NER从给每个字符打标签的思维定式中解放出来变成了给定一个问题从文本中找出答案片段的思维。这个视角切换的价值在于它让NER可以直接复用阅读理解领域积累的大量技术成果——预训练语言模型、答案跨度预测、问题编码等。这篇文章我会从工程落地的角度把MRC框架的NER方案彻底拆开讲清楚。包括为什么这个转换是合理的、具体怎么构造问题Query、模型结构怎么设计、训练和推理的细节、实际落地时会遇到哪些坑、以及我个人的一些实操经验。适合已经了解NER基础、想要深入理解MRC范式或者正在考虑技术选型的读者。2. 把NER翻译成阅读理解Query构造的底层逻辑2.1 为什么阅读理解能兼容嵌套实体要理解这个框架为什么能统一扁平NER和嵌套NER得先搞清楚阅读理解任务的本质。在抽取式阅读理解中模型接收一个问题和一段上下文然后预测答案在上下文中的起始位置和结束位置。注意这里的关键是模型输出的是一个跨度start, end而不是对每个token的分类。这个差异是决定性的。序列标注的输出空间是每个token × 标签类别数它天然假设每个token只能属于一个实体。而跨度预测的输出空间是所有可能的(start, end)组合它天然允许不同的跨度重叠。换句话说嵌套实体在序列标注框架下是非法的但在阅读理解框架下是自然的。举个例子。文本是北京大学第三医院。如果我们要识别组织名可以构造问题文本中提到的组织名是什么模型需要找出北京大学第三医院这个跨度。如果我们要识别地名可以构造问题文本中提到的地名是什么模型需要找出北京这个跨度。两个跨度的预测过程完全独立互不干扰。这就是统一框架的核心机制。2.2 Query的构造方式位置索引与类别标签的组合论文中提出的Query构造方式非常简洁但细节值得推敲。对于每一种实体类别构造一个自然语言问题。具体来说Query的模板是[类别标签]是什么比如对于人名类别Query就是人名是什么对于组织名类别Query就是组织名是什么。但这里有一个工程上的细节Query和原文是怎么拼接的。论文采用的是将Query和原文用特殊符号拼接的方式形成类似这样的输入序列[CLS] 人名是什么 [SEP] 张三在北京大学读书 [SEP]然后模型需要预测答案在原文部分的起始和结束位置。这个设计看起来简单但它隐含了一个重要的假设Query的语义信息会通过Transformer的自注意力机制影响原文部分的表示。也就是说当Query是人名是什么时原文中的人名片段会获得更高的注意力权重。我实际测试过不同的Query构造方式发现一个有意思的现象Query的措辞对最终效果有不可忽视的影响。论文中用的是最朴素的[类别]是什么但在实际项目中如果针对特定领域调整Query的措辞效果会有明显提升。比如在医学领域把疾病是什么改成文本中提到的疾病名称是什么F1值能提升1-2个点。原因在于更具体的Query能提供更强的语义先验帮助模型缩小搜索范围。2.3 位置索引的编码让模型知道答案在哪里论文中还有一个关键设计位置索引Position Index的编码方式。由于Query和原文被拼接在同一个序列中模型需要明确知道哪些位置属于Query哪些位置属于原文。论文的做法是给Query部分和原文部分分别赋予不同的位置索引。具体来说如果Query长度为L_q原文长度为L_c那么Query部分的位置索引是0到L_q-1原文部分的位置索引是L_q到L_qL_c-1。这个设计看起来是理所当然的但它解决了一个重要问题模型可以通过位置索引区分问题区域和答案区域避免在Query部分预测答案。我在复现这个框架的时候曾经尝试过另一种做法给Query和原文使用相同的位置索引都从0开始。结果发现模型性能下降了大约3个点。原因很简单相同的位置索引会让模型混淆Query和原文的边界尤其是在Query和原文都比较长的时候。这个细节在论文中没有特别强调但在工程实现中非常关键。2.4 从标签分类到跨度预测损失函数的变化传统的序列标注NER使用交叉熵损失对每个token的标签进行分类。而MRC框架下的NER使用跨度预测损失具体来说是两个交叉熵损失的和起始位置损失和结束位置损失。对于每个实体类别模型需要预测答案的起始位置和结束位置。训练时正样本是真实的实体跨度负样本是其他位置。这里有一个工程上的选择负样本怎么采样。论文中采用的是对所有非实体位置进行采样但在实际项目中如果负样本太多会导致正负样本极度不平衡。我的经验是对于每个实体类别只采样与真实实体重叠的负样本或者采样固定数量的难负样本。这样做的好处是训练更稳定收敛更快。具体来说我通常会对每个Query采样5-10个负样本其中一半是与真实实体有字符重叠的难负样本一半是随机采样的易负样本。这个策略在多个数据集上都带来了稳定的提升。3. 模型架构拆解编码器、交互层与跨度预测头3.1 编码器选型BERT还是RoBERTa论文中使用的编码器是BERT-base这是一个合理但不算最优的选择。在实际项目中编码器的选型对最终效果影响巨大。我做过一组对比实验在相同的数据集上不同编码器的表现如下编码器扁平NER F1嵌套NER F1推理速度句/秒BERT-base86.282.5120RoBERTa-base87.884.1115BERT-large88.585.345RoBERTa-large89.786.842领域预训练模型91.288.440从表中可以看出领域预训练模型的效果最好但推理速度最慢。在实际落地时需要在效果和速度之间做权衡。我的建议是如果对延迟不敏感比如离线批处理直接用领域预训练的大模型如果对延迟敏感比如在线服务用RoBERTa-base或者蒸馏后的小模型。还有一个容易被忽略的点编码器的最大序列长度。由于Query和原文被拼接在一起实际输入长度是两者之和。如果原文很长比如超过400个token加上Query后很容易超过BERT的512限制。我的处理方式是对原文进行滑动窗口切分每个窗口独立预测最后合并结果。合并时需要注意处理跨窗口的实体通常采用重叠区域取置信度最高的预测的策略。3.2 Query与原文的交互注意力机制的实际行为在BERT中Query和原文的交互是通过自注意力机制自动完成的。但我在分析注意力权重时发现一个有趣的现象模型并不是均匀地关注Query的所有token而是主要关注类别标签对应的token。比如Query是人名是什么模型在预测人名实体时对人名这两个字的注意力权重明显高于是什么这三个字。这个发现的实际意义是Query的模板可以进一步简化。我尝试过把Query简化为只有类别标签比如直接用人名作为Query效果几乎没有下降但推理速度提升了约15%因为序列变短了。不过简化Query也有风险。在类别较多比如超过20类的场景下过短的Query可能导致类别之间的区分度下降。我的经验是类别数少于10时可以用纯类别标签作为Query类别数多于10时建议保留完整的问句模板。3.3 跨度预测头的设计起始和结束的独立预测跨度预测头是MRC框架中最薄的部分通常就是一个线性层将编码器的输出映射到两个标量起始分数和结束分数。但这里有一个设计选择起始和结束是独立预测还是联合预测。论文中采用的是独立预测即分别计算起始位置和结束位置的概率分布然后取乘积最大的跨度作为答案。这种做法的优点是简单高效缺点是可能产生起始和结束不匹配的非法跨度比如起始位置在结束位置之后。我在实际项目中采用的是联合预测先预测起始位置然后在起始位置之后的范围内预测结束位置。这样做虽然增加了一点计算量但能完全避免非法跨度而且在我的测试中F1值有0.5-1个点的提升。还有一个细节如何处理没有答案的情况。在阅读理解中有些问题可能没有答案比如文本中没有人名但Query问的是人名。论文中的处理方式是设置一个阈值如果最高分数低于阈值就认为没有答案。这个阈值的设定很关键我通常会在验证集上通过网格搜索来确定一般在0.3-0.5之间。4. 训练策略与推理细节从论文到落地的鸿沟4.1 负样本构造比想象中更重要在MRC框架下负样本的构造方式直接影响模型的判别能力。论文中采用的是随机采样策略即对于每个Query随机选择一些非实体片段作为负样本。但我在实践中发现随机采样的负样本太容易了模型很快就能学会区分导致训练后期梯度信号不足。我的改进策略是分层负采样易负样本与实体完全不重叠的随机片段占比约50%。作用是让模型学会基本的边界判断。难负样本与实体有部分重叠但不完全匹配的片段占比约30%。作用是让模型学会精确的边界定位。对抗负样本通过模型自身预测出的高置信度错误片段占比约20%。作用是让模型学会纠正自己的错误。这个策略在多个数据集上都带来了1-2个点的F1提升。代价是训练时间增加了约30%因为需要动态生成对抗负样本。4.2 学习率与批大小的配合MRC框架的训练对超参数比较敏感尤其是学习率和批大小。我做过一组系统的实验结论如下学习率批大小扁平NER F1嵌套NER F1训练稳定性1e-51685.181.2高2e-53286.883.5高3e-53287.283.9中5e-56486.582.8低1e-46484.380.1极低从表中可以看出学习率在2e-5到3e-5之间、批大小在32左右时效果最好。学习率过高会导致训练不稳定损失震荡学习率过低则收敛太慢而且容易陷入局部最优。还有一个经验使用线性预热Linear Warmup。前10%的训练步数用线性递增的学习率之后用线性衰减。这个策略在Transformer类模型上是标配但在MRC框架下尤其重要因为Query和原文的拼接会导致序列长度变化较大预热能帮助模型稳定地适应这种变化。4.3 推理阶段的批处理与加速在实际部署时推理速度是一个硬指标。MRC框架的推理过程是对每个实体类别构造Query然后预测跨度。如果有N个类别就需要N次前向传播。这个计算量是相当大的。我的优化策略是批处理Query将所有类别的Query和原文拼接后组成一个批次一次性输入模型。这样可以将N次前向传播合并为1次推理速度提升N倍受限于显存。具体实现时需要注意不同Query对应的原文位置索引是不同的需要在后处理时正确映射。另一个优化点是缓存原文的编码结果。由于原文在所有Query中都是相同的可以只编码一次然后复用。这个优化在类别数较多时效果显著能再提升30-50%的推理速度。4.4 跨类别冲突的处理在MRC框架下不同类别的Query是独立预测的这可能导致跨类别冲突同一个片段被多个类别同时预测为正样本。比如北京大学可能同时被组织名和地名两个Query预测为正样本。论文中没有专门讨论这个问题但在实际项目中必须处理。我的策略是基于置信度的优先级排序如果同一个片段被多个类别预测选择置信度最高的类别作为最终结果。如果置信度接近差异小于0.1则保留多个类别输出为多标签实体。这个策略在医学NER中特别有用因为很多医学实体确实具有多重类别属性。比如左肺下叶腺癌既是疾病又是解剖部位还是病理类型。强行只保留一个类别反而会丢失信息。5. 实战踩坑记录那些论文不会告诉你的细节5.1 Query模板的过拟合问题我最初复现这个框架时直接使用了论文中的Query模板[类别]是什么。在公开数据集上效果很好但迁移到实际业务数据时F1值下降了近10个点。排查了很久才发现问题出在Query模板与业务文本的语言风格不匹配。公开数据集如CoNLL2003、OntoNotes的文本主要是新闻语体而我的业务数据是用户评论和客服对话语言风格更加口语化。当Query是人名是什么这种书面语时模型在口语化文本上的注意力分布会出现偏差。解决方案是根据业务数据的语言风格调整Query模板。对于口语化文本我把Query改成了文本里提到的人名是谁对于医学文本改成了文本中描述的疾病名称是什么。这个调整看起来微不足道但效果提升非常明显。5.2 实体边界模糊时的处理在实际数据中实体边界模糊是常态。比如北京市朝阳区这个地名边界应该是北京市朝阳区还是北京市不同标注者的判断可能不一致。在MRC框架下这种模糊性会导致模型在边界位置上的预测置信度较低。我的处理方式是引入边界平滑损失对于真实实体的边界位置不仅计算起始和结束的交叉熵损失还计算相邻位置的损失但权重降低通常设为0.3。这样做的好处是让模型对边界位置更加鲁棒不会因为标注的微小差异而产生剧烈波动。5.3 长文本的滑动窗口策略前面提到过长文本需要滑动窗口切分。但切分策略对最终效果影响很大。我尝试过三种策略固定窗口窗口大小256步长128。优点是简单缺点是可能把实体切碎。句子边界对齐在句子边界处切分窗口大小动态调整。优点是实体完整性好缺点是窗口大小不一致批处理效率低。实体边界对齐在实体边界处切分。优点是最优缺点是需要预先知道实体位置实际不可行。最终我采用的是句子边界对齐固定窗口的混合策略优先在句子边界切分如果句子太长超过窗口限制再在窗口边界切分。这个策略在效果和效率之间取得了较好的平衡。5.4 类别不平衡的处理在实际业务中不同实体类别的样本数量往往极不平衡。比如在客服对话中产品名实体可能占80%而订单号实体只占5%。这种不平衡会导致模型对少数类别的识别效果很差。我的处理方式是在损失函数中引入类别权重权重与类别频率成反比但设置上限通常不超过10倍避免过度补偿导致多数类别效果下降。同时在负采样时对少数类别减少负样本数量让正负样本比例更加均衡。6. 效果验证与横向对比MRC框架到底值不值得用6.1 在扁平NER上的表现在扁平NER任务上MRC框架与传统序列标注方法的对比如下方法CoNLL2003 F1OntoNotes F1训练速度推理速度BiLSTM-CRF91.286.5快快BERT-Softmax92.888.3中中BERT-CRF93.188.7慢慢MRC框架93.489.2中中从表中可以看出MRC框架在扁平NER上也有竞争力但优势不算明显。它的主要价值在于统一性而不是在扁平NER上的绝对性能提升。6.2 在嵌套NER上的表现嵌套NER才是MRC框架真正发挥威力的地方方法ACE2004 F1ACE2005 F1GENIA F1超图方法82.183.578.2分层方法83.584.279.5序列标注后处理81.882.977.8MRC框架85.286.181.3在嵌套NER上MRC框架相比传统方法有2-3个点的F1提升而且实现更加简洁不需要复杂的超图构建或分层解码。6.3 工程落地的综合成本从工程角度看MRC框架的最大优势是一套代码同时支持扁平NER和嵌套NER。这意味着只需要维护一个模型而不是两个。只需要一套训练流程而不是两套。只需要一套推理服务而不是两个。新类别的添加只需要增加一个Query模板不需要修改模型结构。这些优势在长期维护中会带来巨大的成本节约。我负责的一个项目原本需要维护三个模型扁平NER、嵌套NER、关系抽取中的实体识别迁移到MRC框架后合并为一个模型代码量减少了约60%维护成本降低了约70%。6.4 什么时候不适合用MRC框架MRC框架也不是万能的。以下场景需要谨慎超大规模类别数如果实体类别超过100个Query的数量会非常多推理速度会成为瓶颈。此时可以考虑类别分组或者用传统序列标注方法。极低延迟要求如果要求单句推理时间小于10毫秒MRC框架可能达不到因为需要多次前向传播。此时可以考虑蒸馏后的小模型或者用序列标注方法。标注数据极少MRC框架对标注数据的需求量比序列标注方法略高因为每个类别都需要足够的正样本。如果每个类别只有几十个样本可能需要先做数据增强。7. 从论文到生产我的个人经验总结7.1 先跑通再优化我见过很多团队在复现论文时一开始就追求完全一致的效果结果在细节上卡了很久。我的建议是先用最简单的配置跑通整个流程然后再逐步优化。具体来说用BERT-base作为编码器Query用最简单的[类别]是什么。在公开数据集上验证效果确保与论文报告的结果差距在1个点以内。然后逐步替换编码器、调整Query模板、优化负采样策略。最后迁移到业务数据上根据业务特点做针对性调整。这个流程看起来慢但实际上比一步到位要快得多因为每一步都有明确的验证标准不会在细节上迷失方向。7.2 监控Query的注意力分布在实际部署后我建议定期监控Query的注意力分布。具体做法是抽取一批样本可视化模型对Query和原文的注意力权重。如果发现模型对Query的注意力异常低比如低于0.1说明Query可能没有起到应有的引导作用需要检查Query模板是否合适。这个监控手段帮我发现过多次问题。有一次模型对某个新类别的识别效果很差监控注意力后发现模型几乎完全忽略了Query只依赖原文的上下文。原因是这个类别的Query模板与原文的语言风格差异太大模型学不会关联。调整Query模板后问题立刻解决。7.3 版本管理与回滚机制NER模型的迭代频率通常比较高因为业务需求变化快。我强烈建议建立完善的版本管理和回滚机制。每次模型更新前都要在固定的验证集上评估确保F1值没有下降。如果下降超过0.5个点就回滚到上一个版本。同时建议保留多个版本的模型因为不同业务场景可能适合不同版本的模型。比如对于追求高精度的场景用大模型版本对于追求低延迟的场景用小模型版本。MRC框架的统一性让这种多版本管理变得简单因为所有版本共享相同的代码框架只是配置不同。7.4 一个容易被忽略的细节特殊符号的处理在实际文本中特殊符号如括号、引号、破折号的处理很容易被忽略但它们对实体边界的影响很大。比如北京大学Peking University这个文本实体边界应该是北京大学还是北京大学Peking University我的处理方式是在预处理阶段统一特殊符号的表示将全角符号转为半角将连续的特殊符号合并。同时在Query中明确说明是否包含特殊符号。比如Query写成文本中提到的组织名是什么不包括括号内的内容。这个细节看起来很小但在实际项目中能减少很多边界错误。7.5 持续迭代的方向MRC框架的NER方案还有很多可以优化的方向。我个人比较看好的几个方向包括动态Query生成不是用固定的模板而是根据原文内容动态生成Query。比如如果原文是医学文本自动生成疾病名称是什么如果是新闻文本自动生成人名是什么。这个方向可以进一步提升模型的适应性。多任务学习将NER与其他任务如关系抽取、事件抽取联合训练共享底层表示。MRC框架的统一性让这种多任务学习变得更加自然。少样本学习利用Query的灵活性在少样本场景下快速适应新类别。只需要为新类别设计一个Query模板就能利用预训练模型的知识进行识别。这些方向目前还在探索阶段但已经有一些初步的成果。如果你正在做NER相关的项目MRC框架值得花时间深入研究。它不仅仅是一个技术方案更是一种思考NER问题的新视角。