维特比算法原理
- 维特比算法是一种动态规划算法,它能用来解决HMM和CRF中的预测问题,并能找到全局最优解。维特比算法就是在每一时刻, 计算当前时刻落在每种隐状态的最大概率, 并记录这个最大概率是从前一时刻哪一个隐状态转移过来的, 最后再从结尾回溯最大概率, 也就是最有可能的最优路径。
- 在求解维特比算法时候,要生成两个矩阵,一个用来存放当前时刻落在每种隐状态的最大概率,另一个用来记录这个最大概率是从前一时刻哪一个隐状态转移过来的。
维特比算法矩阵求解过程,以HMM为例
HMM有2个观测值,3个隐状态,所以转移矩阵为[33]矩阵,发射矩阵为[32]矩阵。
import numpy as np
A = np.array([ #A为转移矩阵
[.5, .2, .3],
[.3, .5, .2],
[.2, .3, .5]
])
B = b = np.array([ #B为发射矩阵
[.5,.5],
[.4,.6],
[.7,.3]
])
pi = np.array([ #C为初始矩阵
[.2],
[.4],
[.4]
])
T1_prev = np.array([0.1, 0.16, 0.28])#初始矩阵乘以状态为0列的发射概率
T1_prev = np.expand_dims(T1_prev, axis=-1)#扩展成一列,利用np广播机制与转移矩阵相乘。
# 因为第1时刻的观测为v_1, 所以取B矩阵的第1列, 即所有隐状态生成观测v_1的概率
p_Obs_State = B[:, 1]
p_Obs_State = np.expand_dims(p_Obs_State, axis=0)
#矩阵相乘得到[3*3]矩阵,因为一共3个隐状态,从一个时刻转移到下一时刻共有9种方法。
T1_prev * p_Obs_State * A
# 在行的维度求max,得到每个时刻上状态最大值概率
np.max(T1_prev * p_Obs_State * A, axis=0)
# 看看所得的max概率的路径是从哪里来的, 在上一步从哪个隐状态转移过来的
np.argmax(T1_prev * p_Obs_State * A, axis=0)
Tensorlow1.13.0中维特比算法实现源码
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
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
源码解析
- viterbi_decode函数需要score, transition_param两个参数矩阵,其中score可以理解为HMM中的发射矩阵,transition_param为转移矩阵,以下图两个矩阵为例。(O,B_ORG,E_ORG)为隐藏状态,也就是我们要通过Viterbi解码预测的值。"我爱中国"4个字为观测变量。
-
首先,通过代码trellis = np.zeros_like(score), backpointers = np.zeros_like(score, dtype=np.int32)创建两个个矩阵,矩阵trellis记录最大概率值(得分值),backpointers记录最大概率(得分值)从上一时刻从个状态转移过来的。trellis[0] = score[0]相当于初始化过程,将score第一行得分值赋给trellis。因为有4个观测值,所以要计算3次转移的得分值,用 for t in range(1, score.shape[0]):完成3次迭代。
-
利用np.expand_dims(trellis[t - 1], 1)把trellis得分值扩展成一列,利用numpy的广播机制与转移矩阵相加。得到的V矩阵如下图所示:
- 其中每一列都表示从上一个状态转移到当前状态的概率值(得分值)。比如第一列表示分别从(O,B_ORG,E_ORG)三个状态转移到O状态的概率值(得分值)。
- np.max(v, 0)是对每一列求最大值,记录当前状态得分的最大值。np.argmax(v, 0)记录当前状态的最大值是上一个时刻哪个状态转移过来的。
- trellis[t] = score[t] + np.max(v, 0),用每个状态得分最大值加上Score中的得分值,最终得到最大得分值。维特比算法中要计算把三个值相加:上一时刻每个状态的最大值+ 转移矩阵+发射矩阵(Score)中的得分值。得分值其实就是概率,因为经过Log变化,所以概率相乘变成了得分值相加。
- backpointers[t] = np.argmax(v, 0)记录了每个隐状态得分最大值是从上一时刻哪个隐状态过来的。
- trellis和backpointers最终结果如下图所示:
-
viterbi = [np.argmax(trellis[-1])]取得最后一行最大值的索引。然后根据backpointers矩阵递推回溯直到第一时刻。
-
如上图 backpointers矩阵所示,最后一行最大索引2,来源于上一时刻索引1,上一时刻索引1来源于上上时刻索引0,以此类推,返回viterbi的值为[[0, 0, 1, 2]]。所以,观测值"我爱中国",对应的隐状态为[O,O,B_ORG,E_ORG]。