第2章 语言分析与信息抽取:NER、词性、依存与 Viterbi
读完你能: 区分 NER、词性标注与依存分析;写出 HMM 的路径概率;手算 Viterbi 表并回溯出路径;说明为什么抽取结果必须保留否定、时间和指代信息。
1. 三种分析回答三个问题
以句子 Maya joined Acme in Paris. 为例:
| 分析 | 回答什么问题 | 一种合理的结果 |
|---|---|---|
| 命名实体识别 NER | 哪段文字是实体,属于哪一类 | Maya:人;Acme:机构;Paris:地点 |
| 词性标注 POS | 每个词在句中属于哪类词 | Maya:专有名词;joined:动词 |
| 依存分析 | 哪个词依附于哪个词,是什么关系 | Maya 是 joined 的主语,Acme 是它的宾语 |
表里是人工给出的示意结果。换一个模型、分词规则或训练领域,实际输出的标签可能不同。
NER 其实包含两件事:找边界和定类型。如果把 New York 拆成了两个实体,即使两段都标成“地点”,边界也已经错了。
2. BIO 标签与实体边界
BIO 标注以词元为单位描述实体:B-ORG 表示一个机构名的第一个词元,I-ORG 表示它后续的词元,O 表示不属于任何实体。
| 词元 | New | York | Labs | hired | Maya |
|---|---|---|---|---|---|
| 标签 | B-ORG | I-ORG | I-ORG | O | B-PER |
严格的实体级评估要求起点、终点和类型都对。所以逐词准确率高,完整实体的 F1 不一定高——一个三词实体错了一个词元,逐词只错三分之一,实体级却算整个错。另外,普通 BIO 序列很难表示嵌套实体或不连续实体,这类情况需要别的表示方式。
3. 词性为什么不能逐词查字典
flies 在 time flies 里多半是动词,在 fruit flies 里则是名词。同一个词的词性要看它的邻居和整句结构。
隐马尔可夫模型(HMM)把词性标签当作看不见的隐藏状态,把词元当作看得见的观察。记标签序列为 ,观察序列为 ,则
- :第一个标签是 的概率;
- :标签 后面紧跟标签 的概率(转移概率);
- :标签 生成词元 的概率(发射概率)。
这个公式依赖两个简化假设:每个标签只取决于前一个标签;每个词只取决于它自己位置上的标签。
解码时我们想要的是给定词序列后最可能的标签序列,即最大化 。由于
而分母对所有候选路径都一样,所以直接比较联合概率就够了。
从已标注的句子估计这些量时,先数清三种次数:
转移概率的分母是从该标签出发的转移总数,发射概率的分母是该标签对应的词元总数,两者不能交换。未见词或未见转移会带来零概率,训练时需约定未知词类别或平滑方法,并保持每行概率和为 1。
如果模型还定义了专门的结束状态,算到最后一个词时要再乘一次“转移到结束状态”的概率。下面的小例子不设结束状态。
4. 手算一个两词例子
只有两个标签:N(名词)和 V(动词),观察序列是 time flies。
| 起始概率 | N | V |
|---|---|---|
| 0.6 | 0.4 |
| 转移 | 到 N | 到 V |
|---|---|---|
| 从 N | 0.3 | 0.7 |
| 从 V | 0.8 | 0.2 |
| 发射 | time | flies | 其他词合计 |
|---|---|---|---|
| N | 0.5 | 0.1 | 0.4 |
| V | 0.1 | 0.6 | 0.3 |
两个词、两个标签,一共只有四条路径,可以逐条算出来:
| 路径 | 联合概率 |
|---|---|
| N → N | |
| N → V | |
| V → N | |
| V → V |
最优路径是 N → V。要注意,给每个词各自挑最可能的标签,拼起来不一定是整体最优的路径,因为标签之间的转移概率也要算进去。
5. Viterbi:每个终点只留最好的那条路
个标签、 个词,穷举要看 条路径,很快就算不动了。Viterbi 用动态规划解决这个问题:
是“前 个词、且第 个词标为 ”的所有路径中最大的概率, 记下这条最优路径的上一个标签。之所以能只留最好的一条,是因为一旦确定了当前标签,后面怎么走只和这个标签有关,和更早的路径无关;所以到达同一标签的几条前缀里,较差的那些以后也不可能反超。
| 位置 | 以 N 结尾的最优值 | 以 V 结尾的最优值 |
|---|---|---|
| time | ||
| flies |
最后一列里 V 最大(0.126),它的前驱 ,回溯得到 N → V,与上一节穷举的结果一致。
时间复杂度是 :每个位置、每个标签都要在 个前驱里取最大。要恢复路径,必须保存回溯指针。序列很长时,许多小概率连乘会下溢成 0,实践中改用对数概率,把连乘变成连加。
max 和 sum 解的是两个问题
Viterbi 对前驱取 max,得到最可能的那一条路径;前向算法对前驱求和,得到整个观察序列的概率。把 max 换成 sum,算出来的就不再是同一个东西了。
6. 依存分析:把“谁对谁做了什么”连起来
依存树用有向边把中心词和依附于它的词连起来。句子的主要谓词通常是根,主语、宾语等通过带标签的边挂到它下面;具体的关系标签名在不同标注体系里会有差别。
从 Maya joined Acme 抽出三元组 (Maya, joined, Acme),比单纯列出两个实体有用得多。但碰到 Maya did not join Acme,如果还是抽出同样的三元组,意思就完全反了。除了否定,时间、条件、被动语态和代词指的是谁,也都会改变一条事实的含义。
所以实用的抽取结果至少要带上否定标记:
{"subject":"Maya","relation":"joined","object":"Acme","negated":false}
实体链接是另一项任务:它把文中出现的名字对应到知识库里的具体对象,解决重名问题。NER 只负责找出名字、判断类型,并不做消歧。
7. 看工具输出时看什么
在 spaCy 里,Doc.ents 给出实体跨度;每个词元的 pos_、tag_、dep_、head 分别是粗词性、细粒度标签、依存关系和它所依附的中心词。下面的代码需要先安装 spaCy 和对应的语言模型。模型的输出只能参考,不能当成标准答案。
import spacy
nlp = spacy.load("en_core_web_sm")
doc = nlp("Maya joined Acme in Paris.")
for entity in doc.ents:
print(entity.text, entity.label_)
for token in doc:
print(token.text, token.pos_, token.dep_, token.head.text)
批量处理文本时用 nlp.pipe(texts),比逐条调用 nlp() 快得多。各属性的含义见 spaCy 语言特征文档。
8. 小结
NER 找实体的跨度和类型,词性标注给每个词定词类,依存分析连出词与词之间的语法关系。HMM 用转移概率和发射概率描述标签序列;Viterbi 每一步为每个标签只保留最优前缀,最后靠回溯指针还原整条路径——不能简单地取每一列的最大值拼起来。
自测
1. 上例中观察序列 time flies 的总概率是多少?它等于最优路径的概率吗?
把四条路径的联合概率相加:。最优路径的概率是 ,两者不同。在已观察到这两个词的条件下,最优路径的后验概率为 。
2. 实体边界对了但类型错了,严格的实体级评估算对吗?
不算。严格匹配要求起点、终点和类型三者全部一致。
- 感谢你赐予我前进的力量

