维特比(Viterbi)算法
算法思想维特比(Viterbi)算法属于一种动态规划算法,目标在于寻找最优路径。我个人用得最多就是与BiLSTM+CRF模型的结合,比如命名实体识别、分词等,计算了每个token的归一化概率矩阵和转移概率矩阵之后,最后根据维特比算法得到整个文本句子的最优概率输出。它的思想是这样:如果最优路径在时刻t经过结点iti_tit,那么这一路径从结点iti_tit到终点iTi_TiT的部分路径,对..
算法思想
维特比(Viterbi)算法属于一种动态规划算法,目标在于寻找最优路径。我个人用得最多就是与BiLSTM+CRF模型的结合,比如命名实体识别、分词等,计算了每个token的归一化概率矩阵和转移概率矩阵之后,最后根据维特比算法得到整个文本句子的最优概率输出。
它的思想是这样:如果最优路径在时刻t经过结点 i t i_t it,那么这一路径从结点 i t i_t it到终点 i T i_T iT的部分路径,对于从 i t i_t it到 i T i_T iT的所有可能的部分路径来说,必须是最优的。
依据这个原理,我们只需从时刻t=1开始,递推的计算在时刻t状态为 s i s_i si的各条部分路径的最大分数,然后按着这条最大路径往下走,走到时刻t = T的最大分数即为最优路径的分数。
实际例子
举个具体的例子来说明Viterbi完成的编码过程:
score:预测概率矩阵。比如第一行代表的含义:时刻t=1(第一个token)状态 s = s 1 s=s_1 s=s1(属于第一种标签)的概率为0.8,状态 s + s 2 s+s2 s+s2(属于第二种标签)的概率为0.1…
transition:转移矩阵。比如第一行:从状态 s 1 s_1 s1(第一种标签)向 s 1 s_1 s1(第一种标签)转移的概率为0.5,向 s 2 s_2 s2(第二种标签)转移的概率为0.2…
如果你应用在命名实体识别或者分词模型中,那这就是它的预测输出结果。
在寻找最优路径的时候,需要有一个用于存储累计得分的变量
-
因为时刻t=1没有发生状态转移,所以此时的累计得分为{0.8, 0.1, 0.1}。第一个数字0.8代表时刻t=1选择状态1的累计分数,其他同理。
-
来到时刻t=2,上一个时刻即t=1,状态1-3都可以转移到状态1,那么就要寻找转移到状态1的最优选择,比如状态1转移状态1的分数为:0.8(时刻t=1选择状态1的分数) + 0.5(状态1转移到状态1的转移分数) + 0.1(时刻t=2即当前选择状态1的分数)= 1.4。
状态2和3的计算方法同理,然后得到更新后的累计得分为(1.4, 1.5, 1.5)
-
时刻t=3也是一样的。最后的累计分数为{2.1, 2.6, 2.2}。分别对应的路径就是上图中加粗的部分,那全局最优的路径当然就是累计分数最高的2.6对应的路径了** ( s 1 − − − > s 2 − − − > s 2 ) (s_1--->s_2--->s_2) (s1−−−>s2−−−>s2)**。
-
至此,Viterbi算法执行完毕。
代码实现
import numpy as np
def viterbi_decode(score, transition_params):
"""Decode the highest scoring sequence of tags outside of TensorFlow.
This should only be used at test time.
Args:
score: A [seq_len, num_tags] matrix of unary potentials.
transition_params: A [num_tags, num_tags] matrix of binary potentials.
Returns:
viterbi: A [seq_len] list of integers containing the highest scoring tag
indices.
viterbi_score: A float containing the score for the Viterbi sequence.
"""
# 用于存储累计分数的数组
trellis = np.zeros_like(score)
# 用于存储最优路径索引的数组
backpointers = np.zeros_like(score, dtype=np.int32)
# 第一个时刻的累计分数
trellis[0] = score[0]
for t in range(1, score.shape[0]):
# 各个状态截止到上个时刻的累计分数 + 转移分数
v = np.expand_dims(trellis[t - 1], 1) + transition_params
# max(各个状态截止到上个时刻的累计分数 + 转移分数)+ 选择当前状态的分数
trellis[t] = score[t] + np.max(v, 0)
# 记录累计分数最大的索引
backpointers[t] = np.argmax(v, 0)
# 最优路径的结果
viterbi = [np.argmax(trellis[-1])]
# 反向遍历每个时刻,得到最优路径
for bp in reversed(backpointers[1:]):
viterbi.append(bp[viterbi[-1]])
viterbi.reverse()
viterbi_score = np.max(trellis[-1])
return viterbi, viterbi_score
欢迎关注同名公众号:“我就算饿死也不做程序员”。
交个朋友,一起交流,一起学习,一起进步。
更多推荐
所有评论(0)