从“字典查找”到“大模型推理”:命名实体识别(NER)技术的演进之路

在自然语言处理(NLP)的浩瀚星空中,命名实体识别(Named Entity Recognition, 简称 NER)一直是一颗耀眼的基石之星。无论是构建知识图谱、开发智能客服,还是优化搜索引擎和解析医疗病历,NER 都扮演着“数据提取机”的关键角色。

它的核心任务非常直观:从非结构化的文本中,找出具有特定意义的实体,并将其分类到预定义的类别中(如人名、地名、机构名、时间、日期等)。

例如,在句子 “苹果公司CEO蒂姆·库克去年在斯坦福大学发表了演讲” 中,NER 系统需要精准地提取出:

  • 苹果公司(ORG - 机构)
  • 蒂姆·库克(PER - 人名)
  • 去年(TIME - 时间)
  • 斯坦福大学(LOC/ORG - 地点/机构)

回顾 NER 技术的发展史,就像是在阅读一部浓缩的 NLP 演进史。从早期的基于规则的硬编码,到统计机器学习的黄金时代,再到深度学习的端到端学习,直到如今大语言模型(LLM)的降维打击。本文将带你穿越时空,深度剖析 NER 技术的演进之路,并辅以代码实战,揭示背后的技术细节。


一、 混沌初开:基于规则与词典的时代(1990s初)

在计算资源匮乏、数据稀缺的年代,研究人员解决 NER 问题的思路非常直接——“查字典”和“写死规则”。

1. 词典匹配

最简单的 NER 系统本质上是一个基于 Trie 树(字典树)的搜索引擎。我们构建一个包含成千上万人名、地名、机构名的庞大词典,然后让计算机在文本中滑动窗口进行匹配。

  • 缺点:完全无法解决“未登录词”(OOV, Out of Vocabulary)问题。遇到新出现的公司名或网络新词,系统瞬间瘫痪。

2. 基于规则的专家系统

为了解决词典的局限性,计算语言学家们开始制定复杂的上下文规则。例如:

  • 规则:如果某个词以“公司”、“集团”、“局”结尾,且前面是名词,则标记为 ORG。
  • 规则:如果某个词出现在“去”、“到达”之后,且是一个专有名词,则标记为 LOC。

代表系统:著名的 FASTUS 系统和 Proteus 系统。

  • 优点:在特定垂直领域(如金融、法律)准确率极高。
  • 缺点:严重依赖领域专家,规则撰写极其耗时,且规则之间容易发生冲突,可维护性极差。

二、 走向泛化:统计机器学习的黄金岁月(1990s末 - 2010s初)

随着语料库语言学的发展,研究人员开始让计算机自己从数据中寻找规律。NER 进入了统计机器学习时代。

1. 隐马尔可夫模型(HMM)

HMM 假设文本序列是一个由隐藏状态(实体标签)生成的观察序列(文本字符)。它通过计算状态转移概率和发射概率来进行序列标注。

  • 局限:HMM 是一个生成模型,它假设当前时刻的输出只依赖于当前状态,这在语言处理中显得过于简化。

2. 条件随机场(CRF)—— 传统 NER 的王者

为了克服 HMM 的严格独立性假设,CRF(Conditional Random Field) 应运而生。CRF 是一种判别式无向图模型,它不仅考虑了当前词的特征(如词性、前后缀),还通过全局归一化考虑了相邻标签之间的依赖关系(例如,标签 B-ORG 后面不能直接接 I-PER)。

在 CRF 时代,特征工程 是算法工程师的核心竞争力。大家整夜整夜地挖掘如下特征:

  • 词法特征:词本身、大小写、词缀(-tion, -ing)。
  • 拼写特征:是否包含数字、是否包含连字符。
  • 上下文特征:前一个词和后一个词是什么。
  • 词典特征:当前词是否在外部地名词典中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 伪代码示例:传统 CRF 特征提取的典型思路
def extract_features(sentence, i):
word = sentence[i]
features = {
'word.lower()': word.lower(),
'word.isupper()': word.isupper(),
'word.istitle()': word.istitle(),
'word.isdigit()': word.isdigit(),
'suffix_3': word[-3:],
'prefix_2': word[:2],
'prev_word': '' if i == 0 else sentence[i-1].lower(),
'next_word': '' if i == len(sentence)-1 else sentence[i+1].lower(),
# ... 更多人工设计的特征
}
return features

尽管 CRF 效果卓越,但繁重的特征工程限制了其泛化能力。


三、 自动表征:深度学习与端到端的崛起(2010s中 - 2010s末)

Word2Vec 等词向量的出现,彻底改变了 NLP 的游戏规则。NER 终于摆脱了繁琐的人工特征工程,进入了深度学习的黑盒时代。

1. NN/CNN/RNN 的初步尝试

早期的深度学习模型利用词嵌入作为输入,通过 CNN 或 RNN(LSTM/GRU)自动提取文本特征。由于神经网络强大的拟合能力,工程师只需输入原始文本序列,模型就能自动学习到上下文特征。

2. 经典架构:BiLSTM-CRF

2015年左右,BiLSTM-CRF 架构成为了 NER 领域的绝对统治模型,堪称深度学习时代的“ResNet”。

  • BiLSTM(双向长短期记忆网络):负责捕捉双向的上下文语义信息。前向 LSTM 读取“苹果公司”,后向 LSTM 读取“蒂姆·库克”,两者结合,为当前词生成包含丰富全局信息的上下文向量。
  • CRF 层:虽然神经网络提取了高级特征,但它在输出标签序列时可能会犯低级的逻辑错误(如输出 B-PER -> I-ORG)。将传统的 CRF 层接在 BiLSTM 之后,通过学习转移矩阵,可以强制约束输出的合法性。

代码示例:基于 PyTorch 的 BiLSTM-CRF 核心结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import torch
import torch.nn as nn

class BiLSTM_CRF(nn.Module):
def __init__(self, vocab_size, tag_to_ix, embedding_dim, hidden_dim):
super(BiLSTM_CRF, self).__init__()
self.embedding_dim = embedding_dim
self.hidden_dim = hidden_dim
self.vocab_size = vocab_size
self.tag_to_ix = tag_to_ix
self.tagset_size = len(tag_to_ix)

self.word_embeds = nn.Embedding(vocab_size, embedding_dim)
self.lstm = nn.LSTM(embedding_dim, hidden_dim // 2,
num_layers=1, bidirectional=True, batch_first=True)

# 将 BiLSTM 的输出映射到标签空间
self.hidden2tag = nn.Linear(hidden_dim, self.tagset_size)

# CRF 层的转移矩阵,transitions[i,j] 表示从标签 j 转移到标签 i 的分数
self.transitions = nn.Parameter(
torch.randn(self.tagset_size, self.tagset_size)
)

def forward(self, sentence):
# 1. 获取词嵌入
embeds = self.word_embeds(sentence)
# 2. BiLSTM 提取特征
lstm_out, _ = self.lstm(embeds)
# 3. 线性层映射
lstm_feats = self.hidden2tag(lstm_out)
# 4. CRF 解码(寻找最佳标签路径,通常使用 Viterbi 算法)
best_path = self._viterbi_decode(lstm_feats)
return best_path

四、 霸权降临:预训练语言模型(PLM)的降维打击(2018 - 至今)

2018年,Google 提出了 BERT(Bidirectional Encoder Representations from Transformers),NLP 领域迎来了 ImageNet 时刻。NER 技术也随之发生了颠覆性的变化。

1. 从 Word2Vec 到 Contextualized Embeddings

BiLSTM-CRF 时代,词向量是静态的。“苹果”这个词无论是指“水果”还是“公司”,其向量表示都是一样的。
BERT 利用 Transformer 的自注意力机制,能够根据上下文动态生成词向量。在“我吃了一个苹果”和“苹果发布了iPhone”中,BERT 能为“苹果”生成完全不同的向量表示。

2. BERT + 线性层 / BERT + CRF

在 BERT 时代,NER 模型的架构变得异常简单。你甚至不需要 BiLSTM 层,只需要将预训练好的 BERT 模型去掉顶层的 NSP(Next Sentence Prediction)头,加上一个简单的全连接层进行分类即可。由于 BERT 本身已经具备了极强的上下文编码能力,这种极简架构在各大 NER 排行榜(如 CoNLL-2003)上疯狂屠榜。

代码实战:使用 HuggingFace transformers 微调 BERT 进行 NER

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
from transformers import AutoTokenizer, AutoModelForTokenClassification, TrainingArguments, Trainer
from datasets import load_dataset

# 1. 加载预训练模型和分词器 (这里以微软发布的 Bert-base 为例)
model_checkpoint = "bert-base-chinese"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)

# 定义标签映射
label2id = {"O": 0, "B-PER": 1, "I-PER": 2, "B-ORG": 3, "I-ORG": 4, "B-LOC": 5, "I-LOC": 6}
id2label = {v: k for k, v in label2id.items()}

model = AutoModelForTokenClassification.from_pretrained(
model_checkpoint,
num_labels=len(label2id),
id2label=id2label,
label2id=label2id
)

# 2. 定义数据预处理函数 (对齐标签是 NER 的关键)
def tokenize_and_align_labels(examples):
tokenized_inputs = tokenizer(examples["tokens"], truncation=True, is_split_into_words=True)
labels = []
for i, label in enumerate(examples["ner_tags"]):
word_ids = tokenized_inputs.word_ids(batch_index=i)
previous_word_idx = None
label_ids = []
for word_idx in word_ids:
if word_idx is None:
# 特殊符号如 [CLS], [SEP] 标记为 -100 (不参与损失计算)
label_ids.append(-100)
elif word_idx != previous_word_idx:
# 每个词的第一个子词分配真实标签
label_ids.append(label[word_idx])
else:
# 被切分的子词 (如 '苹', '果') 通常分配 -100 或继续当前标签
label_ids.append(-100)
previous_word_idx = word_idx
labels.append(label_ids)
tokenized_inputs["labels"] = labels
return tokenized_inputs

# 3. 训练 (省略数据集加载和 Trainer 的详细配置)
# trainer = Trainer(model=model, args=training_args, train_dataset=tokenized_datasets, ...)
# trainer.train()

五、 新的范式:大语言模型(LLM)时代的 Few-Shot 与 In-Context NER

当我们以为 BERT 已经是 NER 的终局时,ChatGPT、Llama、GPT-4 等大语言模型(LLM)的出现,重新定义了 NER 的玩法。

传统的 NER 被视为一个Token-level的分类任务(序列标注)。而在 LLM 时代,NER 逐渐演变成一个生成式任务(Seq2Seq)。

1. In-Context Learning(上下文学习)与 Few-Shot

现在,你甚至不需要训练任何模型。只需通过精心设计的 Prompt,向大模型展示几个例子,它就能完美理解你的意图并提取实体。

Prompt 示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
请从以下文本中提取命名实体,并以 JSON 格式输出。实体类别包括:人名(PER)、地名(LOC)、机构名(ORG)。

例子 1:
输入:张三去北京出差了。
输出:{"PER": ["张三"], "LOC": ["北京"], "ORG": []}

例子 2:
输入:李彦宏在百度大会上发布了文心一言。
输出:{"PER": ["李彦宏"], "LOC": [], "ORG": ["百度"]}

请处理以下输入:
输入:马斯克计划将 SpaceX 的总部搬迁到火星。
输出:

LLM 会迅速回答:{"PER": ["马斯克"], "LOC": ["火星"], "ORG": ["SpaceX"]}

2. 大模型做 NER 的优缺点

  • 优点:零样本/少样本能力极强;无需标注海量数据进行微调;泛化能力极强,甚至能理解极其复杂的实体定义(如“从文本中提取疾病名称及其对应的治疗药物”)。
  • 缺点
    • 幻觉问题:可能会凭空捏造文本中不存在的实体。
    • 格式不可控:有时输出的 JSON 会不合法,需要额外的正则或程序进行后处理。
    • 推理成本高:相比于在 CPU 上就能跑的 BiLSTM-CRF,动用百亿参数的 LLM 去做基础 NER 无疑是“高射炮打蚊子”,成本极高。

六、 深水区与未来展望:NER 的前沿挑战

尽管通用 NER 已经被解决得很好,但在实际工业落地中,NER 仍面临许多“硬骨头”:

  1. Nested NER(嵌套命名实体识别)
    在医疗、法律等领域,实体往往是嵌套的。例如:“北京大学第三医院”中,“北京大学”是一个 ORG,而“北京大学第三医院”也是一个 ORG。传统的 BIO 序列标注无法解决这种问题,目前学界正在探索使用 Span-level 模型(基于片段的图神经网络)或机器阅读理解(MRC)范式来解决嵌套问题。
  2. Low-resource NER(低资源场景)
    在小语种或极端垂直领域(如文言文、特定军工图纸解析),缺乏标注数据。如何利用主动学习、远程监督或大模型数据合成来提升性能,是一个热门方向。
  3. 多模态 NER
    社交媒体上的图文并茂给纯文本 NER 带来了挑战。结合视觉特征(如从配图中提取特征辅助判断“苹果”是手机还是水果)的多模态 NER 正在崭露头角。

总结

回顾 NER 技术的演进历程,我们可以清晰地看到一条**“规则 -> 统计 -> 深度表征 -> 大模型推理”**的清晰主线。算法模型对人工干预的依赖越来越低,泛化能力越来越强。

时至今日,如果你要构建一个工业级的 NER 系统,通常的选型策略是:

  • 算力有限、追求极速和极高准确率的垂类场景:依然首选 BERT + CRF 架构。
  • 缺乏标注数据、泛化要求高、多类型实体的复杂场景:拥抱 LLM + Prompt Engineering

命名实体识别,这个看似简单的“找词”任务,背后折射出的是人类赋予机器理解世界常识的宏大愿望。随着技术的不断迭代,我们有理由相信,机器提取和理解信息的能力将达到前所未有的高度。


作者注:本文所有代码示例旨在说明模型架构与处理逻辑,在实际工业应用中需根据具体硬件与框架版本进行环境配置与调优。