资讯动态

BERT+LLM混合架构:突破NER长尾实体抽取瓶颈的工程实践

发布时间:2026/9/17 0:03:47 来源:尧图企业网站定制
做NER做到一定阶段很多人都会撞上一堵墙基于BERT的序列标注模型在测试集上刷到95%以后就再也上不去了改loss、调CRF、换预训练模型折腾一圈可能只涨0.2个点。而另一边LLM把大段文本丢进去让它把实体都拎出来确实能识别出很多老模型从未见过的命名方式但输出格式不稳定、实体边界经常飘线上时延和成本也让人不敢全量替换。于是我开始琢磨一个问题与其在两个方案之间二选一不如让它们各干各擅长的活。这篇文章就是把BERT和LLM组合成一套混合架构后我在落地过程中踩过的坑、想明白的道理、整理出来的可复用方案一次性写清楚。这套思路适合谁如果你手头有一套已经上线的BERT系NER服务想引入LLM提升长尾实体的召回但又不能接受把线上流量全部切给大模型或者你正在做垂直领域的实体抽取标注数据不够、标注成本又高想在有限资源下把效果再往上顶一顶那这篇内容应该对你有用。1. 为什么做混合架构BERT单打独斗的瓶颈与LLM的机会1.1 NER性能卡在哪不只是标注数据不够很多人觉得NER效果上不去就是标注数据太少其实这只是表象。真正卡住BERT系模型的核心问题有三个。第一个是标签体系覆盖不足。数据标注阶段标注人员天然只会按照给定的实体类型去标那些不在标注规范里的叫法、简称、新词即使模型见过也不会识别成实体因为训练目标里根本没有这种答案。比如在医疗文本里医生写“急性心梗”而不是“急性心肌梗死”如果训练数据里没有这种简写标注模型就会把它拆成“急性”“心梗”两个普通词什么也抽不出来。第二个是实体边界不稳定。BERT做序列标注本质上是给每个token打一个BIO标签模型对“什么时候开始、什么时候结束”非常敏感。遇到嵌套实体比如“北京大学人民医院”里既包含“北京大学”又包含“人民医院”序列标注模型很难同时输出两层边界通常只会识别出最外层或最内层另一层丢失。第三个是长尾分布问题。真实业务文本里的实体频率是幂律分布的头部实体占了大半样本尾部实体每种可能只有几十个甚至几个样本。模型为了降低整体loss会把大量参数精力花在头部实体上尾部实体学不到足够特征上线后换个说法就认不出来。这三个问题叠加起来就造成了大家经常看到的现象测试集上好看线上badcase一抓一大把而且badcase集中在不规范的表达上。1.2 BERT系模型的优点与天花板BERT系模型在NER任务里依然是绝对的主力原因很实在推理快、行为稳定、方便上线。一个标准的BERT-BiLSTM-CRF或者BERT-Softmax实体抽取模型在GPU上对一条几十字的文本做推断延迟在几毫秒到十几毫秒级别吞吐量完全可以支撑全天候的线上调用。更重要的是它的输出形式很规整——每个token一个标签解码出来就是确定的实体集合不会出现格式错乱的问题。但它的天花板也很清晰。模型学到的知识全部来自训练语料训练语料里出现频率低或者没出现过的实体写法它只能靠泛化硬猜猜不中就是召回率掉点。想提升效果最直接的办法就是加数据但标注成本高、周期长尤其垂直领域还需要专业人员标注一个人的产能有限几千条数据可能就要标上一两个月。我常打一个比方BERT系模型像一个经验丰富但只会按照军规操作的老员工你给他教过的场景他做得又快又好一旦遇到“军规”之外的新情况他就容易卡壳。1.3 LLM的突破与不可靠LLM在NER上的优势正好打在BERT的短板上。第一是零样本和少样本能力你只要在prompt里写清楚“抽取机构名称、时间、金额”再配上两三个示例它就能处理从来没有见过的文本泛化能力比微调过的BERT强很多。第二是上下文理解对于“2023年他离开了担任十年的公司”这种句子LLM能结合语义判断“他”是一个需要抽取的人名指代信息而BERT序列标注模型只能死板地看字面。但LLM直接做NER的问题也很突出。首先是输出不稳定同一个prompt同一个输入多次调用结果可能不同实体边界偶尔会多一个字少一个字“北京人民医院”有时候输出成“北京人民医”这种错误在结构化后非常难查。其次是有幻觉模型会生成原文里根本不存在的实体尤其当你要求它“仔细抽取”时它反而会脑补出一些合理但错误的实体。再次是成本和延迟一个大模型的单次调用成本可能是BERT推理的几十倍如果每条线上请求都交给LLM账单和响应时间都会失控。所以LLM适合做“查漏补缺”的角色而不是全权接管。这也是我决定做混合架构的出发点。1.4 混合架构的设计出发点这套混合架构的核心思路只有一句话用便宜的模型处理大部分确定性高的实体抽取把模型拿不准的部分交给LLM兜底再用规则做最后的校验和修正。我把这个思路称为“调度分级”。先让BERT按原有流程抽取一遍计算每个实体的置信度置信度高的实体直接进入结果列表置信度低的实体或者没有实体但语义上明显是被遗漏的区域再拼成一个片段交给LLM重新抽取最后把两路结果合并、去重、做冲突消解形成一个最终的实体集合。这样做的好处非常明显线上大部分流量的成本还是BERT水平只有一小部分低置信度流量被路由到LLM成本可控同时LLM补充了BERT的盲区长尾实体的召回率有实打实的提升而且两套模型彼此独立故障时可以随时回退到纯BERT模式不至于全链路瘫痪。2. 整体方案选型调度分级是核心而不是简单拼接2.1 整体流程设计整套架构的流转过程我按执行顺序拆成五步文本进入后先做清洗和分句保持原有NER服务的前置逻辑不变。BERT实体抽取模块对每一句做序列标注输出带概率分数的实体候选。置信度评估模块根据概率分数、实体长度、类型先验等维度把实体候选分为“高置信”和“低置信”两档。低置信区域的上下文片段被拼装成prompt交给LLM重新抽取。结果合并模块把BERT高置信实体和LLM抽取结果合并通过边界对齐和冲突消解生成最终结果。整个流程看起来不复杂但每一步都有可以深挖的细节。尤其是第二步到第三步之间的衔接如果处理不好低置信片段切得不准LLM拿到的是残缺上下文效果会大打折扣。2.2 实体分级策略我习惯把业务里的实体类型分成三档对应不同的调度策略。稳定型实体比如日期、时间、金额、电话号码、证件号这类实体格式固定、模式清晰BERT通常已经做得很好基本不需要LLM介入只要在规则层做一轮格式校验就行。常见型实体比如人名、地名、机构名、职位名这类实体在训练数据里有一定覆盖但写法多变BERT经常会漏掉一些不常见的别名或者新出现的写法可以把这部分实体的低置信结果交给LLM复核。复杂型实体比如嵌套实体、跨句实体、同一实体的不同指代、需要结合上下文判断的实体这类是BERT的明显弱项建议对包含这类实体类型的句子做整体LLM补充抽取而不只是抽低置信片段。实体分级不是拍脑袋需要观察训练数据和线上badcase的分布。我建议拿出一周的真实线上样本人工过一遍漏识别和错识别的badcase按实体类型统计你很快就能看到哪些实体类型是BERT的稳定区哪些是重灾区。2.3 为什么选择“低置信度触发LLM”而不是端到端融合有些方案会把BERT和LLM做成一个端到端的大模型让LLM直接吸收BERT的编码层结果或者反过来用BERT蒸馏LLM的知识。这类方案理论效果可能更好但工程成本非常高而且调试难度大不适合绝大多数业务团队。我选择“低置信度触发LLM”是出于三个现实考量。第一是改动面小原有BERT服务完全不动只在外面包一层调度逻辑风险低可以灰度发布。第二是成本可控LLM调用量等于低置信片段的比例一般占到总流量的10%到20%而不是100%。第三是模型可以独立迭代LLM侧如果发现更好的prompt或者换了更强的模型只影响调度后的处理不影响BERT主体上线的灵活性高。如果你的团队有充足的人力和算力端到端融合当然可以探索但对于大部分项目先跑通这种轻量级的混合架构拿到实际收益比一步到位去搞大融合要靠谱得多。2.4 适用边界不是所有项目都适合混合这套架构也不是银弹。如果你的业务文本非常规范比如营业执照地址、身份证信息、UGC内容里固定的标签字段那直接用规则或者BERT就够了引入LLM纯属增加成本。如果业务对实体抽取的实时性要求极高比如必须在几十毫秒内返回结果LLM的延迟很难满足这时候混合架构就不合适应该优先优化单模型效果。另外如果业务场景对幻觉零容忍抽出来的实体不允许有任何“原文里不存在”的内容那LLM参与抽取就会带来校验压力需要额外做严格的原文对齐校验这部分成本也要提前算进去。所以我一直认为技术选型不是越先进越好而是越匹配业务越好。混合架构适合的是“有一定容忍度、但需要持续提升效果”的文本抽取场景。3. 核心实现从BERT到LLM的完整链路搭建3.1 BERT实体抽取模块的实现要点先看BERT这边的实现。如果你的线上服务已经有一个效果稳定的序列标注模型这部分基本不用重写。我按常见的中文NER结构说明一些关键点。模型结构推荐用BERTCRF如果不方便引入CRF纯BERT-Softmax也可以但实体边界稳定性会差一些。我在项目中用的是BERTBiLSTMCRF虽然BiLSTM层的增益在大型预训练模型面前越来越小但在实体的连续性约束上还是有帮助的。训练时label建议用BIOUL格式比BIO多了一个“Usingle”和一个“Llast”单字实体和实体结尾更明确解码时不容易出错。比如“北京”这个词如果采用BIO格式就是“B-ORG I-ORG”而采用BIOUL格式就是“B-ORG L-ORG”遇到“京”这种单个字成实体的情况BIOUL能直接用U标记不会造成歧义。解码端如果用了CRF路径得分可以直接用来作为实体置信度的基础。下面是一段简化后的解码和置信度计算思路import torch def decode_with_crf(logits, tag_ids, attention_mask): # logits: B, seq_len, num_tags # 使用transformers的CRF层 paths, scores crf.decode(logits, maskattention_mask.byte()) return paths, scores def compute_entity_score(paths, scores, label_map, tokenizer): 把实体级的标签序列转成dict, 同时记录每个实体的平均路径得分 entity_list [] seq_score scores[0].item() # 遍历token级标签, 按BIOUL规则切出实体 for token_idx, tag_id in enumerate(paths[0].tolist()): tag label_map[tag_id] # ... 解析实体边界, 累加平均得分 ... return entity_list实际项目中我还会在每个token的softmax概率均值上乘一个长度惩罚系数来得到实体级别的置信度。长度惩罚系数的作用是防止短实体概率虚高比如“京”这种单字实体即使概率很高也应该比长实体更谨慎。3.2 置信度计算与阈值设定置信度计算是这套架构的关键一步直接决定了哪些实体进入LLM复核流程。我综合了三个信号第一个信号是实体内部token的平均softmax概率。这个好理解实体内部的每个token都有概率分布平均概率越高越可信。第二个信号是CRF路径得分与最优路径得分之间的差距如果存在多个得分接近的路径说明模型对边界不确定置信度要打折。第三个信号是实体类型的历史准确率有些实体类型本身就是重灾区比如嵌套型机构名即使模型给出较高概率也值得让LLM再确认一遍。具体到阈值怎么定我建议不要拍脑袋而是拿一批验证集样本跑一遍统计不同阈值下实体被LLM复核的比例和准确率提升幅度选择性价比最高的点。比如在我们一个垂直文本抽取项目里计算出来的最优阈值是0.72BERT输出置信度高于0.72的实体准确率已经到97%以上不需要复核低于0.72的实体送到LLM后整体准确率能提升5个百分点以上而触发LLM的流量只占总请求的12%。这个数字在不同业务里肯定不同但方法论是通用的阈值定得高LLM触发多、成本高、提升可能明显阈值定得低省成本但漏掉的badcase也多了。我通常会用网格搜索跑一遍0.6到0.9之间的阈值选一个准确率提升量和成本增量的平衡点。3.3 LLM补充抽取的Prompt与结构化输出LLM侧的实现重点在prompt设计和输出约束。先给一个我实际用过的prompt模板你是一名严谨的实体抽取助手。请从用户文本中抽取所有满足以下类型的实体 - 疾病名称 - 药物名称 - 检查项目名称 要求 1. 实体必须原样出现在文本中不允许改写、不允许归纳。 2. 不要输出文本中不存在的实体。 3. 对于嵌套实体把每一层都分别列出。 4. 只输出JSON格式{entities: [{type: 疾病名称, text: 急性心梗}]} 5. 如果没有实体输出{entities: []} 用户文本{snippet}这个prompt里有几个细节很关键。一是明确“实体必须原样出现在文本中”这是对抗幻觉最有效的一句。二是输出固定为JSON方便程序解析建议配合当前大模型普遍支持的JSON output mode使用可以进一步降低格式错乱的概率。三是给出few-shot示例如果有标注样本可以在prompt里塞两三个典型示例能明显提升边界抽取的稳定性。LLM侧的输入片段我会做一个截取策略把低置信实体所在的句子作为核心再取前后各一句作为上下文补充。因为有些实体需要上下文才能判断类型只给一个孤立句子可能信息不够。片段长度我一般控制在200到300字以内既能保证LLM有足够的上下文又不会因为输入太长导致成本飙升。3.4 结果合并与冲突消解两路结果合并的时候最常遇到三件事重复、冲突、边界不一致。重复很好处理把相同类型、相同文本的实体做一次去重就行。冲突是指同一个实体BERT认为是机构名LLM认为是人名这时候我采用“高置信优先”的策略如果BERT的置信度高于阈值以BERT结果为准如果BERT置信度低、这条实体本身就是因为低置信才触发LLM的以LLM结果为准。边界不一致是指两个模型识别出的实体文本有包含或者交叉关系比如BERT抽的是“人民医院”LLM抽的是“北京人民医院”这时我会保留更完整、覆盖面更大的那个实体。建议做一份合并规则表写清楚各种冲突场景的处理方式方便后续维护排查。4. 工程落地性能收益、成本控制与问题排查4.1 实测效果准确率、时延与成本我以一个垂直领域的文本抽取项目为例简单复盘一下混合架构上线前后的对比。这个项目主要是从合同类文本中抽取机构名、金额、日期等关键要素历史积累的badcase集中在机构名的简称和嵌套表达上。改进前纯BERT模型在测试集上的实体级F1约为88%。改进后混合架构F1提升到92%以上。其中提升最明显的是机构名和嵌套实体这两个类型召回率分别提升了6到8个百分点。准确率方面由于LLM的幻觉实体在合并阶段被规则层拦截掉了不少整体准确率没有明显下降还有小幅提升。时延方面纯BERT阶段单次请求平均耗时在15毫秒左右。加了混合架构之后因为绝大多数请求不需要调用LLM平均耗时为40毫秒左右。这个增幅在多数业务场景里是可以接受的但对于要求极低延迟的场景还是需要谨慎评估。成本方面由于LLM只在低置信触发时启用整体成本增加幅度在20%以内相比全量切换LLM的方案要划算得多。模型配置上我用的是参数量中等偏小的LLM模型针对结构化抽取任务足够用没必要一上来就上最大规模的商用模型。4.2 延迟与成本控制不要为整篇文档调用LLM很多人第一次做混合架构时容易犯一个错误把低置信片段直接扩展成整个文档丢给LLM。这样确实省事但成本直接翻倍而且长文本输入还会让模型输出变得不稳定。我的做法是把低置信片段截断到句子级别最多带上下一句作为上下文。你可以把BERT的序列标注结果按句子边界重新组织只抽取包含低置信实体的句子。如果一条句子里没有低置信实体就完全不用过LLM。另一个控制成本的技巧是缓存。同一句文本如果在短时间内被多次请求LLM结果是可以缓存下来的特别是合同、文书这类重复性较高的业务场景缓存命中率能到20%以上。我用Redis做了一层简单的缓存key是文本片段的hash值value是LLM返回的实体JSON缓存时间设成24小时效果不错。4.3 高频踩坑记录与排查速查表把这些经常碰到的问题整理成了一张速查表方便大家排查的时候对照使用。问题现象可能原因处理思路LLM输出JSON格式经常崩prompt约束不够、模型未开JSON mode开启结构化输出模式增加一个格式校验修复环节实体被LLM“润色”改写prompt没有强调原文抽取显式加一句“实体必须原样出现在文本中禁止改写”BERT和LLM结果严重冲突两套标签体系不一致在合并层做同义归一统一实体归一化规则低置信触发率居高不下置信度阈值设得太高用验证集重新统计阈值触发率控制在10%-20%LLM调用成本和时延超预期整篇文档进LLM了严格按句子级片段截断只传上下文相关部分4.4 线上质量监控与回归机制混合架构上线后监控比之前的单模型复杂了不少。原来只需要盯着BERT的准确率和召回率现在要额外看低置信触发率、LLM调用成功率、LLM单独结果的准确率、合并后实体集合的质量。我的做法是建立一个独立的评测流水线每天自动从线上日志里随机抽取一定比例的样本跑一遍全流程推理然后和人工标注的标准答案做对比。如果发现某个实体类型的准确率或召回率出现下滑就要逐层排查是BERT侧退化还是LLM侧prompt被改动还是合并规则出问题。这个回流链路也方便后续利用LLM标注数据重点训练BERT让两个模型协同进化。另外额外强调一个安全原则不要把LLM的输出当成最终结果直接入库一定要做原文对齐校验。我会写一个校验函数把LLM输出的每个实体都在原始文本里做一次字符串匹配匹配不上的直接丢到“待人工复核”队列而不是硬着头皮入库。5. 复盘与扩展这套架构能走多远5.1 什么情况下收益最大经过这段实践的复盘我认为混合架构在三个条件下收益最明显。一是领域垂直且术语重复度高。比如医疗、法律、金融这类领域实体类型相对固定但写法千奇百怪BERT漏掉的往往是那些“同义不同形”的实体而这些恰好是LLM因为看到大量通用语料而能够泛化的。二是标注数据不足但业务等不起。如果给你足够的时间和标注预算理论上用纯BERT微调也能达到很好的效果但在业务节奏快的环境下用LLM做补充召回是性价比最高的过渡方案。三是线上已有成熟的BERT底座。混合架构对已有系统很友好不需要推翻重来只需要在外面包一层调度层和合并层就能快速看到效果。如果一个项目不具备这些条件比如数据已经极其规范、或者对延迟极其敏感那我会建议把精力花在单模型优化上而不是上混合架构。5.2 后续可做的三个扩展方向这套架构后续至少有三个方向可以继续挖。第一个方向是LLM反哺训练数据。LLM在低置信片段上产生了大量标注结果经过人工抽检和修正后可以作为新的训练数据回流到BERT的迭代中。这样做之后BERT会逐渐学会处理那些曾经需要LLM兜底的case长期看低置信触发率会下降成本会越来越低。第二个方向是从实体抽取升级到关系抽取和事件抽取。混合架构的调度逻辑并不只适用于NER关系抽取里同样存在“BERT做候选、LLM做判断”的搭配方式。把实体和关系做在同一个输出schema里可以顺路搭建一套更完整的结构化信息抽取管线。第三个方向是加入主动学习。如果业务允许一个“人工复核队列”可以把低置信样本、LLM输出不稳定的样本都送进去让标注人员只处理那些最需要判断力的问题而不是从零开始标注原始语料。我实测下来主动学习能把同样的标注预算带来的F1提升翻倍。最后再说一点我个人的实操体会。这套架构上线初期的收益曲线并不是一条直线刚上线时因为合并层规则没写好整体效果甚至比纯BERT还差。后来我把合并规则一条条拆出来单独做A/B测试才慢慢把效果调正。混合架构真正难的地方其实不在训练模型而在于把两个模型的输出拼成一个统一、稳定、可解释的结果——这句话是整个过程里我踩了最多坑才得到的教训。如果你也正在做NER优化或者准备引入LLM补充抽取能力不妨先从一个小范围的低置信触发实验开始把链路跑通、把监控建好再逐步扩大范围。这套思路不一定适用所有场景但它至少给了你一种“不用推翻现有系统”的改进路径。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价