共计 2453 个字符,预计需要花费 7 分钟才能阅读完成。
为什么需要 BERT 来做 NER?
刚接触命名实体识别 (Named Entity Recognition, NER) 时,我用过传统的 BiLSTM+CRF 方法。虽然效果还行,但遇到这些头疼问题:

- 领域迁移成本高:在医疗领域训练好的模型,放到金融领域就大幅下降
- 小样本表现差:标注数据少于 1 万条时,识别 F1 值经常不到 70%
- 长实体识别困难:比如 ” 北京大学第三医院 ” 这种复合机构名,传统模型容易拆错
直到尝试了 BERT 预训练模型,这些问题有了质的改善。BERT 通过海量无监督预训练(Pre-training),已经学会了丰富的语言知识。我们在特定领域做微调(Fine-tuning)时,就像站在巨人的肩膀上——用少量标注数据就能达到过去 10 倍数据量的效果。
模型选型实战对比
在 HuggingFace 模型库里有几十种预训练模型,我测试了三种主流架构在 CoNLL-2003 英文数据集的表现(Tesla V100 16GB 环境):
| 模型类型 | F1 值 | 显存占用 | 推理速度(句 / 秒) |
|---|---|---|---|
| BERT-base | 92.3 | 3.2GB | 340 |
| RoBERTa-large | 92.7 | 7.1GB | 210 |
| ALBERT-xxlarge | 92.5 | 4.8GB | 190 |
对于新手来说,建议从 BERT-base 开始:
- 资源消耗适中,普通 Colab 就能跑
- 社区支持最好,遇到问题容易找到解决方案
- 在多数场景下已经能达到业务要求
手把手搭建流程
环境准备
# 安装核心库(建议新建虚拟环境)pip install transformers torch datasets seqeval
数据标注规范
NER 常用 BIO 标注法(Begin, Inside, Outside),例如:
华 /B-PER 为 /I-PER 是 /O 深 /B-ORG 圳 /I-ORG 的 /O 知 /B-ORG 名 /I-ORG 企 /I-ORG 业 /I-ORG
实际项目中推荐使用 BILOU 方案(Begin, Inside, Last, Outside, Unit),对实体边界识别更精准。
核心训练代码
from transformers import BertTokenizer, BertForTokenClassification
# 加载预训练模型
model = BertForTokenClassification.from_pretrained(
"bert-base-chinese",
num_labels=len(label_list) # 实体类别数
)
tokenizer = BertTokenizer.from_pretrained("bert-base-chinese")
# 数据预处理示例
def encode_tags(tags, encodings):
# 将标签对齐 tokenizer 的分词结果
encoded_labels = []
for i, label in enumerate(tags):
word_ids = encodings.word_ids(batch_index=i)
previous_word_idx = None
label_ids = []
for word_idx in word_ids:
if word_idx != previous_word_idx:
label_ids.append(label[word_idx])
else:
label_ids.append(-100) # 特殊掩码值
previous_word_idx = word_idx
encoded_labels.append(label_ids)
return encoded_labels
五大优化技巧
1. 混合精度训练
from torch.cuda.amp import autocast, GradScaler
scaler = GradScaler()
with autocast():
outputs = model(**inputs)
loss = outputs.loss
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()
这样能减少 30%-50% 的显存占用,训练速度提升 20% 以上。
2. 梯度累积
当显存不足时,可以通过多次小批量累计梯度:
total_loss = 0
for step, batch in enumerate(train_loader):
loss = model(**batch).loss
loss = loss / 4 # 假设累积 4 步
total_loss += loss.item()
loss.backward()
if (step+1) % 4 == 0:
optimizer.step()
optimizer.zero_grad()
3. 标签不平衡处理
医疗 NER 中常见实体占比不足 1% 的情况,推荐两种方案:
-
Focal Loss:降低易分类样本的权重
from torch.nn import CrossEntropyLoss loss_fct = CrossEntropyLoss(weight=class_weights, reduction='none') pt = torch.exp(-loss) loss = (1-pt)**2 * loss # γ=2 -
动态采样:根据实体出现频率调整采样权重
中文 NER 特殊问题
- 分词影响:BERT 本身是字级别模型,但某些实体需要词信息
-
解决方案:在输入层拼接词向量(如 LAC 分词结果)
-
嵌套实体:如 ” 北京大学生 ” 既是机构名又是人群
-
可采用层次化标注或多头预测
-
领域迁移:法律文书中的实体定义可能与通用领域不同
- 建议:先用领域文本继续预训练(Domain-Adaptive Pretraining)
延伸思考方向
-
如何融入外部知识图谱增强实体识别?比如在医疗 NER 中链接到医学百科
-
少样本场景下,能否通过 prompt tuning 方式提升效果?
-
对于实时性要求高的场景,如何平衡模型大小和推理速度?
经过三个月的项目实践,我们团队的 NER 准确率从最初的 78% 提升到了 92%。最关键的心得是:不要一开始就追求复杂模型,先把数据质量和标注规范做好,再用 BERT 基础版本来迭代优化。遇到显存问题时,梯度累积 + 混合精度这两招能解决 80% 的情况。希望这篇实战总结能帮你少走弯路!
