📑 本页目录(点开跳转)
命名实体识别(NER)
⏱ 35 分钟 | ⭐ 结构最复杂的 NLP 题型
🎯 一句话
NER 比文本分类难,难在它是「带结构的预测」——标签之间有约束(B 后面不能直接跟另一个 B),实体有边界,边界差一个字就全错。所以这一章的重点是解码器的选择和边界对齐的工程细节,后者才是真正吃掉大部分调试时间的地方。
一、NER任务概述
1.1 任务定义
| 维度 | 说明 |
|---|---|
| 核心目标 | 从非结构化文本中识别出具有特定意义的实体,并标注其类别和边界 |
| 输入 | 原始文本序列 |
| 输出 | 实体文本 + 实体类别 + 起始/结束位置 |
| 评价指标 | Macro-F1(严格匹配:实体文本+位置+类别完全一致才算正确) |
1.2 竞赛特殊挑战
| 挑战 | 说明 |
|---|---|
| 位置敏感性 | 实体位置偏移一个字符即判为错误,即使实体文本正确 |
| 标注噪声 | 医疗等领域标注主观性强,存在大量漏标和错标 |
| 类别不均衡 | 部分实体类别样本极少 |
| 嵌套实体 | 一个实体可能包含在另一个实体内 |
| 脱敏数据 | 部分竞赛将原始文本脱敏为数字ID |
二、标注方法
2.1 BIO标注体系
| 标签 | 含义 | 示例 |
|---|---|---|
| B-X | 实体X的开始Token | "北" → B-LOC |
| I-X | 实体X的内部Token | "京" → I-LOC |
| O | 非实体Token | "是" → O |
2.2 BIOES标注体系
| 标签 | 含义 | 示例 |
|---|---|---|
| B-X | 实体X的开始Token | "北" → B-LOC |
| I-X | 实体X的内部Token | - |
| E-X | 实体X的结束Token | "京" → E-LOC |
| S-X | 单Token实体X | "中" → S-ORG |
| O | 非实体Token | "是" → O |
2.3 标注方法选择
| 维度 | BIO | BIOES |
|---|---|---|
| 标签数量 | 2N+1 | 4N+1 |
| 边界信息 | 隐式 | 显式(E标签标记结束) |
| 单字实体 | 需B+I两个标签 | S标签直接标记 |
| 竞赛推荐 | 通用 | 边界敏感任务推荐 |
三、编码器架构
3.1 主流编码器
| 编码器 | 说明 | 适用场景 |
|---|---|---|
| BERT | 双向Transformer编码器 | 基线首选 |
| RoBERTa | 动态Mask + 更大语料 | 强于BERT |
| DeBERTa | 注意力解耦 | 天花板最高 |
| MacBERT | MLM as Correction | 中文优化 |
| BiLSTM | 双向LSTM | 轻量级方案 |
3.2 编码器选择建议
| 维度 | 说明 |
|---|---|
| 起步 | BERT Base快速验证管线 |
| 进阶 | RoBERTa-wwm-ext / MacBERT |
| 冲刺 | DeBERTa V3 Large/XLarge |
| 显存受限 | BiLSTM + CRF轻量方案 |
四、解码器对比
4.1 CRF(条件随机场)
| 维度 | 说明 |
|---|---|
| 核心原理 | 在给定观测序列(Token表征)的条件下,求解全局最优的标签序列 |
| 转移矩阵 | 学习标签间的转移概率(如B-PER→I-PER概率高,B-PER→I-ORG概率低) |
| 全局最优 | 通过Viterbi算法保证输出标签序列的全局一致性 |
| 约束效果 | 自动避免非法标签转移(如O→I-X),无需人工规则 |
| 训练开销 | 需要计算配分函数,训练速度比Linear慢2-3倍 |
| 推理开销 | Viterbi解码增加少量计算 |
4.2 Linear(线性分类头)
| 维度 | 说明 |
|---|---|
| 核心原理 | 对每个Token独立分类,不考虑标签间依赖 |
| 实现方式 | 将每个Token的隐层向量接一个全连接层 |
| 优势 | 训练和推理速度快 |
| 劣势 | 可能产生非法标签转移(如O→I-X),需后处理修正 |
| 竞赛表现 | 配合后处理规则,效果可接近CRF |
4.3 Span-based(基于片段的解码)
| 维度 | 说明 |
|---|---|
| 核心原理 | 枚举所有可能的文本片段(Span),对每个Span分类 |
| 优势 | 天然支持嵌套实体,避免序列标注的边界问题 |
| 劣势 | 计算复杂度O(n^2),片段数量多 |
| 适用场景 | 嵌套实体、长实体 |
4.4 解码器选择建议
| 解码器 | 速度 | 效果 | 嵌套支持 | 推荐场景 |
|---|---|---|---|---|
| CRF | 中 | 高 | 不支持 | 通用首选 |
| Linear | 快 | 中 | 不支持 | 快速迭代 |
| Span-based | 慢 | 高 | 支持 | 嵌套实体 |
四-B、高级解码器
GlobalPointer
| 维度 | 说明 |
|---|---|
| 核心思想 | 将NER视为span分类问题,用相对位置编码高效枚举所有可能span |
| 实现方式 | 对每个Token对(i,j)计算其为实体span的得分,用旋转位置编码(RoPE)引入相对位置 |
| 优势 | 天然支持嵌套实体,计算效率高于传统Span-based(O(n)而非O(n^2)) |
| 损失函数 | 多标签交叉熵,每个实体类型独立计算 |
| 竞赛表现 | 中文NER竞赛中表现优异,是Span-based的强力替代 |
MRC-NER(机器阅读理解式NER)
| 维度 | 说明 |
|---|---|
| 核心思想 | 将NER转化为阅读理解任务:对每种实体类型构造问题,预测答案span |
| 问题构造 | 如"找到文本中所有的疾病名称",模型预测start和end位置 |
| 优势 | 统一不同NER范式(扁平/嵌套/不连续),少样本下效果好 |
| 劣势 | 每种实体类型需单独推理,N类实体需推理N次 |
| 竞赛应用 | 嵌套实体、不连续实体、少样本NER |
Biaffine解码器
| 维度 | 说明 |
|---|---|
| 核心思想 | 用双仿射变换对每对Token计算关系得分,判断是否属于同一实体 |
| 公式 | score(i,j) = x_i * U * x_j^T + W * [x_i; x_j] + b |
| 优势 | 同时建模Token对关系和实体边界,支持嵌套实体 |
| 适用场景 | 依存解析、语义角色标注、嵌套NER |
解码器选择决策
| 场景 | 推荐解码器 | 理由 |
|---|---|---|
| 扁平实体、通用 | CRF | 稳定可靠,竞赛首选 |
| 嵌套实体 | GlobalPointer / Span-based | 天然支持嵌套 |
| 少样本NER | MRC-NER | 问题构造引入先验知识 |
| 追求速度 | Linear + 后处理 | 训练推理都快 |
| 追求极致 | GlobalPointer / Biaffine | 表达能力最强 |
五、FLAT:中文NER的利器
| 维度 | 说明 |
|---|---|
| 核心问题 | 中文NER面临"词边界模糊"问题,字级别标注丢失词信息,词级别标注受分词错误影响 |
| FLAT方案 | Flat-Lattice Transformer,将词信息以"附加注意力"的方式注入字符表征 |
| 实现方式 | 将词典中匹配到的词作为额外Token,通过特殊的位置编码与字符建立关联 |
| 位置编码 | 使用相对位置编码,每个词有head和tail两个位置 |
| 核心优势 | 不改变原始字符序列结构,仅通过注意力机制融合词信息 |
| 效果 | 在中文NER基准上显著优于纯字符模型 |
六、Offset Mapping修正
| 维度 | 说明 |
|---|---|
| 问题背景 | 分词器(Tokenizer)将原始文本切分为Token后,需要将Token级别的预测结果映射回原始字符位置 |
| Offset Mapping | 分词器返回的偏移映射表,记录每个Token在原文中的起止位置 |
| 常见问题 | 特殊Token([CLS]、[SEP])的Offset为(0,0),需过滤 |
| 子词对齐 | 一个词可能被切分为多个子词Token,需合并子词的预测结果 |
| 空格处理 | 部分分词器会将空格单独切分或合并,需特殊处理 |
| 修正代码 | 遍历Offset Mapping,跳过(0,0),合并同一词的子词预测 |
七、分词器空格切分修复
| 维度 | 说明 |
|---|---|
| 问题背景 | 部分分词器(如RoBERTa的BPE分词器)会将连续空格切分为独立Token |
| 导致后果 | 预测的实体边界可能包含多余空格,导致提交分数为零 |
| 解决方案 | 在提取预测区间时,动态检查并剥离首尾空格 |
| 头部处理 | 如果区间开头是空格,起始位置右移 |
| 尾部处理 | 如果区间结尾是空格,结束位置左移 |
八、Bad Case分析
| 维度 | 说明 |
|---|---|
| 核心方法 | 逐条分析验证集上预测错误的样本,归纳错误模式 |
| 常见错误模式 | (1) 边界偏移(多一个字或少一个字);(2) 类别混淆;(3) 漏标;(4) 误报 |
| 分析维度 | 错误类型统计、错误实体长度分布、错误类别分布 |
| 改进闭环 | Bad Case → 发现问题 → 修改模型/规则 → 重新验证 |
| 后处理规则来源 | 大部分后处理规则来源于Bad Case分析 |
九、NER模型融合策略
9.1 融合挑战
| 维度 | 说明 |
|---|---|
| 分词器差异 | 不同模型使用不同分词器,Token无法直接对齐 |
| 解决方案 | 通过Offset Mapping将Token级预测投射回字符级,在字符级融合 |
9.2 字符级融合流程
1. 各模型独立预测 → 得到Token级概率
2. 通过Offset Mapping → 映射到字符级概率
3. 字符级概率加权平均 → 得到融合概率
4. 从融合概率中解码实体 → 输出最终结果
9.3 融合权重优化
| 维度 | 说明 |
|---|---|
| 等权重 | 所有模型权重相同,简单有效 |
| 按F1加权 | 验证集F1高的模型权重更大 |
| 网格搜索 | 搜索最优权重组合 |
| Stacking | 用元模型学习最优组合方式 |
十、NER竞赛实战要点
10.1 提分路线图
1. 基线 → BERT Base + CRF + BIO标注
2. 编码器升级 → RoBERTa / DeBERTa
3. 解码器优化 → CRF调参 / Span-based
4. 数据增强 → 实体替换 + 同义词替换
5. 训练优化 → 对抗训练 + SWA
6. 伪标签 → K折伪标签降噪
7. 后处理 → 边界纠偏 + 空格修复
8. 模型融合 → 多模型字符级融合
10.2 常见踩坑点
| 坑点 | 原因 | 解决方案 |
|---|---|---|
| 提交分数为0 | 位置计算错误或格式问题 | 检查Offset Mapping和提交格式 |
| 线下远高于线上 | 验证集分布与测试集不一致 | 对抗验证 + 分层K折 |
| 实体边界偏移 | 分词器空格处理不当 | 动态剥离首尾空格 |
| CRF训练极慢 | 序列过长 | 截断或使用Linear+后处理 |
| 伪标签过拟合 | 在全量训练集上生成 | 仅在未见过的Fold上生成 |
🔗 这一章连到哪里
| 去哪 | 为什么 |
|---|---|
| 大模型导论 03 Tokenizer 与上下文 | Offset Mapping 修正、空格切分踩坑的根源都是 subword 分词 |
| 大模型导论 02 Transformer 原理 | NER 编码器就是 Transformer Encoder,token 级输出从哪来 |
| ML基础 13 RNN 与序列建模 | BiLSTM 编码器和序列标注任务的原始形态 |
✅ 检查点
- NER 为什么比文本分类难?难在哪里?
- CRF 解决了什么问题?它和「每个位置独立 softmax」的区别是什么?
- GlobalPointer / Span 类解码器相比 CRF 的优势是什么?
- Offset Mapping 为什么是 NER 工程里最容易出错的一环?
- NER 的模型融合为什么不能像分类那样直接平均概率?
👀 答案
- 因为它是带结构的预测:①标签之间有约束(BIO 里 I 不能直接跟在 O 后面)②边界必须精确——实体多一个字少一个字都算全错,评估通常是严格匹配 ③嵌套实体(一个实体里包含另一个)传统 BIO 表示不了。
- CRF 显式建模了标签之间的转移概率,解码时用维特比算法找全局最优的标签序列。而每个位置独立 softmax 是逐位置贪心——它可能输出 O→I 这种非法序列。CRF 保证了输出序列本身是合法的。
- ①天然支持嵌套实体(直接预测所有可能的 span 是不是实体)②解码更快(不需要维特比)③直接优化 span 级目标,和评估口径一致(评估本来就是按实体算的,不是按 token)。代价是候选 span 是 O(n²) 的,长文本上开销大。
- 因为分词器切出来的 token 和原始字符位置不是一一对应的:子词切分、空格处理、特殊字符归一化都会造成偏移。一旦映射错一位,预测出来的实体边界就整体平移,分数断崖式下跌,而模型本身完全没问题——这是最难查的一类 bug。必须写一个用原文反查的断言来验证。
- 因为 NER 的输出是结构化的实体集合,不是一个概率向量。常见做法:投票法(同一实体被 N 个模型中的 K 个以上预测出来才保留)、字符级概率平均(把各模型的 token 级概率对齐到字符级再平均,避免不同分词器的错位)、或先各自解码再做 span 级融合。
🛑 可以停在这里
先记住这几件事
- NER 不只判断类别,还要保证实体边界与标签结构正确。
- BIO、CRF 与 span 方法表达的结构不同,嵌套实体要选择合适表示。
- 用原文反查 offset 和抽取结果,先排除对齐错误再调模型。