Viterbi 算法
隐马尔可夫模型
隐马尔可夫模型(Hidden Markov Model, HMM)是含有隐状态与观测状态的马尔可夫过程。
在基于隐马尔可夫模型的中文分词(或词性标注)任务中,已知的字符(或词语)是观测状态,该字符(或词语)的状态(或词性)为隐状态。
比如结巴分词在没有词典可供参考时就使用了隐马尔可夫模型, 将中文词语中的字符标注为 B、E、M、S 四个隐状态之一,分别表示开始字符、结束字符、中间字符以及独立成词的字符。例如:“熊猫”可标注为 BE, 表示“熊”是一个词语的开始字符, “猫”是该词语的结束位置, “宽窄巷子”可标注为 BMME, 表示开始、中间、中间和结束。
digraph {
label="隐马尔可夫模型示例";
labelloc=t;
rankdir=LR;
M1[label="M"]; M2[label="M"];
B -> M1 -> M2 -> E;
{ rank=same; c1[style=filled, label="宽"]; B -> c1; }
{ rank=same; c2[style=filled, label="窄"]; M1 -> c2; }
{ rank=same; c3[style=filled, label="巷"]; M2 -> c3; }
{ rank=same; c4[style=filled, label="子"]; E -> c4; }
}
隐马尔可夫模型由三组概率决定:
- 初始分布:该过程最初的状态分布;
- 转移概率:隐状态之间可以相互转换,从一个隐状态转移到另一个隐状态的概率;
- 输出概率:在某个隐状态时,观测到特定观测状态的概率。
结巴分词中隐状态的初始分布为:
B | S |
---|---|
0.7689828525554734 | 0.2310171474445266 |
隐状态之间的转移概率为:
B | E | M | S | |
---|---|---|---|---|
B | 0 | 0.8518218565181658 | 0.14817814348183422 | 0 |
E | 0.5544853051164425 | 0 | 0 | 0.44551469488355755 |
M | 0 | 0.7164487459986911 | 0.2835512540013088 | 0 |
S | 0.48617017333894563 | 0 | 0 | 0.5138298266610544 |
由以上概率可以计算双字词的概率为 $$P\{BE\} = P\{B\} * P\{E|B\} = 0.6550364010944383$$
于是中文文本中词语按长度分类的占比(%)如下:
单字词 | 双字词 | 多字词 |
---|---|---|
23.1 | 65.5 | 11.4 |
也就是说大多数中文词语是双字的,仅一成词语包含了 3 个或 3 个以上字符,这符合我们对于中文词语的印象。
但用于分词时,这样的模型会造成多字词难于被识别,通常会被分得更小的情况。例如将“戈尔巴乔夫”分为“戈尔巴”和“乔夫”两个词。
Viterbi 算法
Viterbi 算法基于最大似然估计的思想,使用动态规划找出一串文本 s 的标注方案(共 N 种标注可选),使在该组标注的情况下,输出该文本的概率最大。
Viterbi 变量 $\delta(m, i) = P^{\ast}\{C_m=c_m | T_m=t_i\}$ 为 s 的第 m 个字符 $C_m$ 在该处标注 $T_m=t_i$ 时的最大似然,显然它是这个动态规划问题的最优子结构。
因此有最优 Bellman 方程 $$\delta(m, i) = \begin{cases} P\{t_i\} \cdot P\{c_m|t_i\} &\text{if } m = 1\\ \max\limits_{1 \le j \le N} \{ \delta(m - 1, j) \cdot P\{t_i|t_j\} \} &\text{if } m > 1 \end{cases}$$
其中 $P\{t_i\}$ 为标注的初始分布,$P\{c_m|t_i\}$ 为标注 $t_i$ 时输出字符 $c_m$ 的输出概率,$P\{t_i|t_j\}$ 为当前标注 $t_j$ 时下一个标注为 $t_i$ 的转移概率。
在迭代计算最优 Bellman 方程时,记录相应的最优方案,就可以得到最优的标注方案。
在前面的例子里,待标注文本 s = "宽窄巷子",标注集合为 {B, E, M, S}。
digraph {
label="Viterbi 算法示例";
labelloc=t;
rankdir=LR;
node [label="t1:B"];
t1B; t2B; t3B; t4B[style=invis];
node [label="t2:E"];
t1E[style=invis]; t2E; t3E; t4E;
node [label="t3:M"];
t1M[style=invis]; t2M; t3M; t4M[style=invis];
node [label="t4:S"];
t1S[xlabel="P{S}"]; t2S; t3S; t4S;
edge [style=invis];
{ rank=same; c1[style=filled, label="c1:宽"]; t1B -> t1E -> t1M -> t1S; t1S -> c1 [style=none, label="P{宽|S}"]; }
{ rank=same; c2[style=filled, label="c2:窄"]; t2B -> t2E -> t2M -> t2S -> c2; }
{ rank=same; c3[style=filled, label="c3:巷"]; t3B -> t3E -> t3M -> t3S -> c3; }
{ rank=same; c4[style=filled, label="c4:子"]; t4B -> t4E -> t4M -> t4S -> c4; }
t1B -> t2B -> t3B -> t4B;
edge [style=solid];
t1B -> t2E;
t1S -> t2B;
t1S -> t2S[label="P{S|S}"];
t2B -> { t3E; t3M; }
t2E -> { t3B; t3S; }
t2M -> t3E;
t2S -> { t3B; t3S; }
t3B -> t4E;
{ t3E; t3S; } -> t4S;
t1B -> t2M -> t3M -> t4E [color=red];
t4E -> c4 [color=red, fontcolor=red, label="δ(4, 2)"];
}
归约为 Dijkstra 算法
Dijkstra 算法是经典的单源最短路径算法,用于求解图中一个节点到其它所以节点的最短路径。
Viterbi 算法与 Dijkstra 算法有相同的思想,都是以起始点向外层层扩展直到终点,在这过程中不断更新最优的路径。
可以将 Viterbi 算法涉及的几组概率转换为自信息 $$I(x) = -\log P(x)$$ 并作为权重赋给标注之间的转移边以及标注与字符之间的输出边。
这些自信息权重之和就等于对应概率之积,具有最大似然的路径就具有最小的权重。
同样以前面 Viterbi 算法的示例“宽窄巷子”为例:
strict digraph {
label="Dijkstra 算法示例";
labelloc=t;
rankdir=LR;
s[style=none, label="^"];
e[style=none, label="$"];
node [label="B"]; t1B; t2B; t3B; t4B[style=invis];
node [label="E"]; t1E[style=invis]; t2E; t3E; t4E;
node [label="M"]; t1M[style=invis]; t2M; t3M; t4M[style=invis];
node [label="S"]; t1S; t2S; t3S; t4S;
c1B [label="宽|B"]; c1E [style=invis]; c1M [style=invis]; c1S [label="宽|S"];
c2B [label="窄|B"]; c2E [label="窄|E"]; c2M [label="窄|M"]; c2S [label="窄|S"];
c3B [label="巷|B"]; c3E [label="巷|E"]; c3M [label="巷|M"]; c3S [label="巷|S"];
c4B [style=invis]; c4E [label="子|E"]; c4M [style=invis]; c4S [label="子|S"];
s -> t1B -> c1B -> t2M -> c2M -> t3M -> c3M -> t4E -> c4E [color=red];
c4E -> e [color=red, fontcolor=red, label="δ(4, 2)"];
s -> t1S [label="-log P{S}"];
c1B -> t2E;
t1S -> c1S [label="-log P{宽|S}"];
c1S -> t2B;
c1S -> t2S[label="-log P{S|S}"];
t2B -> c2B -> { t3E; t3M; }
t2E -> c2E -> { t3B; t3S; }
c2M -> t3E;
t2S -> c2S -> { t3B; t3S; }
t3B -> c3B -> t4E;
t3E -> c3E;
t3S -> c3S;
{ c3E; c3S; } -> t4S -> c4S;
c4S -> e;
edge [style=invis];
{ rank=same; t1B -> t1E -> t1M -> t1S; }
{ rank=same; c1B -> c1E -> c1M -> c1S; }
{ rank=same; t2B -> t2E -> t2M -> t2S; }
{ rank=same; c2B -> c2E -> c2M -> c2S; }
{ rank=same; t3B -> t3E -> t3M -> t3S; }
{ rank=same; c3B -> c3E -> c3M -> c3S; }
{ rank=same; t4B -> t4E -> t4M -> t4S; }
{ rank=same; c4B -> c4E -> c4M -> c4S; }
t1B -> c1B -> t2B -> c2B -> t3B -> c3B -> t4B -> c4B;
}
因此,在词性标注问题上(中文分词是词性标注的一个应用)Viterbi 算法可以归约到 Dijkstra 算法,工程中如果已有 Dijkstra 算法的实现,将相应概率转换为自信息后调用即可,不需要再额外实现 Viterbi 算法。
Page Not Found
Try to search through the entire repo.