约 30 分钟 · 需要条件概率与第1章的分词

读完你能: 区分 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)把词性标签当作看不见的隐藏状态,把词元当作看得见的观察。记标签序列为 t1,…,tTt_1,\dots,t_T,观察序列为 w1,…,wTw_1,\dots,w_T,则

P(t1:T,w1:T)=πt1bt1(w1)∏k=2Tatk−1,tkbtk(wk).P(t_{1:T},w_{1:T})=\pi_{t_1}b_{t_1}(w_1)\prod_{k=2}^{T}a_{t_{k-1},t_k}b_{t_k}(w_k).
  • πj\pi_j:第一个标签是 jj 的概率;
  • aija_{ij}:标签 ii 后面紧跟标签 jj 的概率(转移概率);
  • bj(w)b_j(w):标签 jj 生成词元 ww 的概率(发射概率)。

这个公式依赖两个简化假设:每个标签只取决于前一个标签;每个词只取决于它自己位置上的标签。

解码时我们想要的是给定词序列后最可能的标签序列,即最大化 P(t1:T∣w1:T)P(t_{1:T}\mid w_{1:T})。由于

P(t1:T∣w1:T)=P(t1:T,w1:T)P(w1:T),P(t_{1:T}\mid w_{1:T})=\frac{P(t_{1:T},w_{1:T})}{P(w_{1:T})},

而分母对所有候选路径都一样,所以直接比较联合概率就够了。

从已标注的句子估计这些量时,先数清三种次数:

πj=以标签 j 开头的句子数句子总数,aij=count⁡(i→j)∑kcount⁡(i→k),bj(w)=count⁡(w 被标为 j)count⁡(标签 j).\pi_j=\frac{\text{以标签 }j\text{ 开头的句子数}}{\text{句子总数}},\qquad a_{ij}=\frac{\operatorname{count}(i\to j)}{\sum_k\operatorname{count}(i\to k)},\qquad b_j(w)=\frac{\operatorname{count}(w\text{ 被标为 }j)}{\operatorname{count}(\text{标签 }j)}.

转移概率的分母是从该标签出发的转移总数,发射概率的分母是该标签对应的词元总数,两者不能交换。未见词或未见转移会带来零概率,训练时需约定未知词类别或平滑方法,并保持每行概率和为 1。

如果模型还定义了专门的结束状态,算到最后一个词时要再乘一次“转移到结束状态”的概率。下面的小例子不设结束状态。

4. 手算一个两词例子

只有两个标签:N(名词)和 V(动词),观察序列是 time flies。

起始概率 N V
π\pi 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 0.6×0.5×0.3×0.1=0.0090.6\times0.5\times0.3\times0.1=0.009
N → V 0.6×0.5×0.7×0.6=0.1260.6\times0.5\times0.7\times0.6=0.126
V → N 0.4×0.1×0.8×0.1=0.00320.4\times0.1\times0.8\times0.1=0.0032
V → V 0.4×0.1×0.2×0.6=0.00480.4\times0.1\times0.2\times0.6=0.0048

最优路径是 N → V。要注意,给每个词各自挑最可能的标签,拼起来不一定是整体最优的路径,因为标签之间的转移概率也要算进去。

5. Viterbi:每个终点只留最好的那条路

KK 个标签、TT 个词,穷举要看 KTK^T 条路径,很快就算不动了。Viterbi 用动态规划解决这个问题:

δ1(j)=πjbj(w1),δk(j)=bj(wk)max⁡i[δk−1(i)aij],\delta_1(j)=\pi_jb_j(w_1),\qquad \delta_k(j)=b_j(w_k)\max_i[\delta_{k-1}(i)a_{ij}],
ψk(j)=arg⁡max⁡i[δk−1(i)aij].\psi_k(j)=\arg\max_i[\delta_{k-1}(i)a_{ij}].

δk(j)\delta_k(j) 是“前 kk 个词、且第 kk 个词标为 jj”的所有路径中最大的概率,ψk(j)\psi_k(j) 记下这条最优路径的上一个标签。之所以能只留最好的一条,是因为一旦确定了当前标签,后面怎么走只和这个标签有关,和更早的路径无关;所以到达同一标签的几条前缀里,较差的那些以后也不可能反超。

位置 以 N 结尾的最优值 以 V 结尾的最优值
time 0.6×0.5=0.30.6\times0.5=0.3 0.4×0.1=0.040.4\times0.1=0.04
flies max⁡(0.3×0.3, 0.04×0.8)×0.1=0.009\max(0.3\times0.3,\ 0.04\times0.8)\times0.1=0.009 max⁡(0.3×0.7, 0.04×0.2)×0.6=0.126\max(0.3\times0.7,\ 0.04\times0.2)\times0.6=0.126

最后一列里 V 最大(0.126),它的前驱 ψ2(V)=N\psi_2(\mathrm V)=\mathrm N,回溯得到 N → V,与上一节穷举的结果一致。

时间复杂度是 O(TK2)O(TK^2):每个位置、每个标签都要在 KK 个前驱里取最大。要恢复路径,必须保存回溯指针。序列很长时,许多小概率连乘会下溢成 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 的总概率是多少?它等于最优路径的概率吗?

把四条路径的联合概率相加:0.009+0.126+0.0032+0.0048=0.1430.009+0.126+0.0032+0.0048=0.143。最优路径的概率是 0.1260.126,两者不同。在已观察到这两个词的条件下,最优路径的后验概率为 0.126/0.143≈0.8810.126/0.143\approx0.881。

2. 实体边界对了但类型错了,严格的实体级评估算对吗?

不算。严格匹配要求起点、终点和类型三者全部一致。