词性标注与命名实体识别:让机器读懂语言的“语法”与“语义”
好,咱们接着聊。上一章我们讲了分词,把句子切成了一个个词。但光切词还不够,机器还是不知道这些词在句子里扮演什么角色。比如“苹果”这个词,它可以是水果,也可以是公司名。怎么区分?这就轮到词性标注和命名实体识别上场了。
我个人习惯把这两项技术看作搜索引擎的“语法课”和“语义课”。词性标注教机器认识词的词性——名词、动词、形容词;命名实体识别则更进一步,教机器识别出具体的实体——人名、地名、组织名。说白了,一个管“词性”,一个管“实体”。
词性标注原理:给每个词贴个“标签”
词性标注,英文叫 Part-of-Speech Tagging,简称 POS Tagging。它的任务很简单:给句子里的每个词,分配一个词性标签。
举个例子:
输入:我 爱 自然 语言 处理
输出:我/代词 爱/动词 自然/形容词 语言/名词 处理/名词
你可能会问:“这有什么难的?不就是查字典吗?”嗯,还真不是。中文里一词多性的情况太常见了。比如“锁”这个词:
- “我锁了门” —— 这里的“锁”是动词
- “这把锁坏了” —— 这里的“锁”是名词
同一个词,在不同语境下词性完全不同。机器怎么判断?
常用的方法有两种:
- 基于规则的方法:人工编写词性搭配规则。比如“形容词后面通常跟名词”,“动词前面通常是名词或代词”。这种方法在早期很流行,但维护成本高,而且覆盖不全。
- 基于统计的方法:用大量标注好的语料训练模型。最经典的就是隐马尔可夫模型(HMM)和条件随机场(CRF)。
我在项目中遇到过这样一个坑:用 HMM 做词性标注时,模型对生僻词的处理特别差。因为 HMM 假设当前词性只依赖前一个词性,这个“马尔可夫假设”太强了,遇到没见过的新词,模型基本就懵了。
命名实体识别(NER):从词性到实体
词性标注之后,我们知道了每个词的词性。但搜索引擎真正关心的,是那些“有意义的实体”。比如:
- “张三” —— 人名
- “北京” —— 地名
- “阿里巴巴” —— 组织名
- “2024年1月1日” —— 时间
命名实体识别(Named Entity Recognition,NER)就是干这个的。它从文本中识别出这些预定义的实体类别。
为什么 NER 对搜索引擎这么重要?我举个例子:
用户搜索“苹果发布会”。如果搜索引擎只做分词,得到的是“苹果/名词 发布会/名词”。但通过 NER,搜索引擎能识别出“苹果”是一个组织名(Apple Inc.),而不是水果。这样搜索结果就能精准地返回苹果公司的发布会信息,而不是水果摊的促销活动。
你想想看,如果没有 NER,搜索引擎得多尴尬。
CRF 与深度学习 NER 方法
好,接下来我们聊聊具体怎么实现 NER。目前主流的方法有两类:CRF 和深度学习。
条件随机场(CRF)
CRF,全称 Conditional Random Field。它是一种概率图模型,专门用来做序列标注任务。
CRF 的核心思想是:给整个标签序列打分。它不只考虑每个位置的标签,还考虑标签之间的转移关系。比如,在 NER 中,“B-Person”(人名开始)后面通常跟着“I-Person”(人名内部),而不会直接跳到“B-Location”(地点开始)。CRF 能学到这种转移约束。
我曾经用 CRF 做过一个中文 NER 系统,效果还不错。但有个问题:CRF 需要人工设计特征。比如词本身、词性、前后词、词的长度等等。特征工程做得好,效果就好;做得不好,效果就拉胯。
# CRF 特征示例(简化版)
def word2features(sent, i):
word = sent[i]
features = {
'word': word,
'is_first': i == 0,
'is_last': i == len(sent) - 1,
'prefix': word[:2],
'suffix': word[-2:],
'prev_word': '' if i == 0 else sent[i-1],
'next_word': '' if i == len(sent)-1 else sent[i+1],
}
return features
这段代码展示了 CRF 中常用的特征。你看,每个特征都是人工定义的。这活儿干起来挺累的,而且不同领域(比如医疗、法律)的特征还不一样。
深度学习 NER 方法
深度学习的出现,彻底改变了 NER 的玩法。最经典的架构就是 BiLSTM-CRF。
这个架构分三层:
- Embedding 层:把每个词映射成向量。可以用预训练的词向量(如 Word2Vec、GloVe),也可以用 BERT 这样的预训练模型。
- BiLSTM 层:双向 LSTM 捕捉上下文信息。它从左到右、从右到左各跑一遍,然后把两个方向的隐状态拼接起来。这样每个位置的表示都包含了完整的上下文。
- CRF 层:还是用 CRF 来做标签解码。深度学习负责提取特征,CRF 负责约束标签序列的合法性。
说白了,深度学习负责“理解语义”,CRF 负责“保证语法”。两者结合,效果杠杠的。
# BiLSTM-CRF 伪代码
class BiLSTM_CRF(nn.Module):
def __init__(self, vocab_size, tag_size, embedding_dim, hidden_dim):
self.embedding = nn.Embedding(vocab_size, embedding_dim)
self.lstm = nn.LSTM(embedding_dim, hidden_dim // 2, bidirectional=True)
self.fc = nn.Linear(hidden_dim, tag_size)
self.crf = CRF(tag_size)
def forward(self, x):
emb = self.embedding(x)
lstm_out, _ = self.lstm(emb)
logits = self.fc(lstm_out)
return self.crf.decode(logits)
这段代码看着简单,但实际训练时有很多细节。比如学习率怎么调?LSTM 的层数设多少?要不要用 dropout?这些都得根据具体数据来试。
我记得有一次做中文 NER,用 BiLSTM-CRF 训练了 50 个 epoch,F1 值卡在 85% 上不去。后来我把词向量换成了 BERT 的预训练向量,F1 直接飙到了 92%。嗯,预训练模型的力量,不服不行。
当前主流:BERT + CRF
现在做 NER,大家基本都直接用 BERT 了。BERT 本身就是一个强大的上下文编码器,它输出的每个 token 的表示已经包含了丰富的语义信息。在 BERT 后面接一个 CRF 层,就是目前最主流的方案。
为什么还要加 CRF?因为 BERT 虽然强,但它对标签之间的转移关系没有显式建模。比如“B-Org”后面不能直接跟“I-Per”,这种约束 CRF 能天然处理。所以 BERT + CRF 是黄金搭档。
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| CRF | 可解释性强,小数据效果好 | 需要人工特征,泛化能力弱 | 数据量小(<1万条) |
| BiLSTM-CRF | 自动提取特征,效果稳定 | 训练慢,对长文本不友好 | 数据量中等(1万-10万条) |
| BERT-CRF | 效果最好,泛化能力强 | 计算资源消耗大,推理慢 | 数据量大(>10万条),对精度要求高 |
这张表是我在实际项目中总结出来的。选哪种方法,取决于你的数据量和资源。别一上来就上 BERT,如果你的数据只有几千条,CRF 可能更合适。
好了,这一章的内容就到这里。词性标注和 NER 是搜索引擎理解语言的基石。没有它们,搜索引擎就像个文盲,只知道词,不知道意。下一章我们会聊到句法分析,看看机器是怎么理解句子结构的。