在HMM等场景只中中会使用到维特比算法,那么维特比算法具体是怎么实现的呢?
例子:老师布置的作业和老师当天的心情有关。
老师今天的心情是good,那么第二天的心情可能是good、neutral、bad中的一种。如果老师心情很好,很有可能会布置一个简单的作业,也可能会布置很难的作业,但是简单作业的概率更高;同样的,当老师的心情是bad的时候,更有可能布置一个很难的作业。作业的难度有A、B、C三种情况。
这里,把心情叫做状态序列,把作业叫做观测序列。
比如从周一到周五:
状态序列 | good | neutral | good | bad | bad |
观测序列 | A | A | B | C | C |
周一老师的心情是good,布置的作业是难度A,第二天,老师的心情变成了neutral,同样布置的作业是难度A,周三老师的心情变成了good,布置的作业难度是B……
介绍一下隐马尔可夫模型中的两个假设:
(1)当前的状态只和前一个状态有关,即周二的状态只和周一的状态有关。
(2)观测到的数据之和当前的状态有关,即周三布置的作业难度只和周三的心情有关。
初始状态矩阵
周二的状态可以从周一的状态通过乘上概率得到,但是周一的状态要怎么得到呢?
这时候就需要事前给出初始状态。
[0.33,0.33,0.33]
意思是第一天(初始时),心情是good的概率是0.33,心情是neutral的概率是0.33。
状态转移矩阵
状态转移函数是good,neutral,bad三种心情之间的转变概率。纵坐标是当前心情,横坐标是转变到的心情。
(bad,good)=0说明如果今天的心情是bad,那么第二天是不会变成good的,有0.2的概率变成neutral,有0.58的概率还是bad。
good | neutral | bad | |
good | 0.2 | 0.3 | 0.5 |
neutral | 0.2 | 0.2 | 0.6 |
bad | 0.0 | 0.2 | 0.8 |
观测矩阵
观测矩阵是当前状态下布置各种作业的概率
A | B | C | |
good | 0.7 | 0.2 | 0.1 |
neutral | 0.3 | 0.4 | 0.3 |
bad | 0.0 | 0.1 | 0.9 |
对于neutral这一行,如果老师的心情是neutral,他布置难度为A的作业的概率是0.3,难度为B是0.4,难度为C是0.9。
计算方式
接下来就说维特比算法要怎么用来计算。
现在已经知道了初始状态矩阵+状态转移矩阵+观测矩阵。
也知道了老师一周五天布置的作业是A C B A C,求解出老师一周最有可能的心情是什么样的?
第一天:当心情是good的时候,布置作业A的概率:0.33*0.7=0.23.(0.33是心情为good的概率,0.7是心情为good的时候布置作业A的概率)。
当心情是neutral的时候,布置作业A的概率:0.33*0.3=0.1
当心情是bad的时候,布置作业A的概率:0.33*0=0.0
关键是如何求解周二的:
求解周二是在周一的基础上进行的。
以(good,C)为例子,意思是周二心情是good的时候,布置作业C的最大概率(注意,这里以及之后求的都是最大概率)。
从周一来到周二并且布置作业C有三条路,从(good,A)到(good,C),从(neutral,A)到(good,C),从(bad,A)到(good,C)。
概率是:
0.23*0.2*0.1=0.0046;(0.23是(good,A)的概率,0.2是心情从good变成good的概率,0.1是心情为good时布置作业C的概率)
0.1*0.2*0.1=0.002;(0.23是(neutral,A)的概率,0.2是心情从neutral变成good的概率,0.1是心情为good时布置作业C的概率)
0=0;
在这里求出的三个数值,最大的是0.0046,所以(good,C)就是0.0046,这个是从(good,A)这条路来的,花上一个箭头,从(good,C)指向(good,A)。
同理,求出(neutral,C)和(bad,C),他们取得最大值的时候,都是从(good,A)这条路过来的。这样就完成了周二的表格。
接着完成周三、四、五的表格,求解周四时,是在周三的基础上进行的,周四要填的3个数据,每一个数据都有三条路可以达到,选择概率最大值。
最后得到的表格如下,数值可能不对,但是形式是这样的:
回溯
最后进行回溯:
最后一列中概率最大值是0.0005216,然后它是从0.0011592这条路得到的,0.0011592是从0.00828得到的,按照红色箭头的顺序进行回溯,便可以得到:
(bad,C)-->(good,A)-->(neutral,B)-->(bad,C)-->(good,A).
对应的时间是周五到周一:
返回来,从周一到周五:
(good,A)-->(bad,C)-->(neutral,B)-->(good,A)-->(bad,C).
即作业是ACBAC时,最有可能的情绪是:good,bad,neutral,good,bad。
代码
# viterbi algorithm---Teacher emotions and homework
import numpy as np
# Initial probability
pai = np.array([0.333333, 0.333333, 0.333333])
# transition probability
A = np.array([[0.2, 0.3, 0.5], [0.2, 0.2, 0.6], [0.0, 0.2, 0.8]])
# Observation probability
B = np.array([[0.7, 0.2, 0.1], [0.3, 0.4, 0.3], [0.0, 0.1, 0.9]])
# condition number
statesN = 3
# Status dictionary
stateDict = {0: "good", 1: "neutral", 2: "bad"}
# Observed number
observedN = 3
# Observation dictionary
observedDict = {0: 'A', 1: 'B', 2: 'C'}
# observation sequence
observedSequence = np.array([0, 2, 1, 0, 2])
# time span
T = 5
# Initializes the dynamic programming table
V = np.zeros((T, statesN))
# A backtracking table that stores the optimal path
backpointer = np.zeros((T, statesN), dtype=int)
# Initialize the first column
V[0, :] = pai * B[:, observedSequence[0]]
# Recursive computation
for t in range(1, T):
for s in range(statesN):
trans_prob = V[t - 1, :] * A[:, s]
max_trans_prob = np.max(trans_prob)
V[t, s] = max_trans_prob * B[s, observedSequence[t]]
backpointer[t, s] = np.argmax(trans_prob)
# Backtrack to find the most likely sequence of states
best_path = np.zeros(T, dtype=int)
best_path[T - 1] = np.argmax(V[T - 1, :])
for t in range(T - 2, -1, -1):
best_path[t] = backpointer[t + 1, best_path[t + 1]]
# Print result
print("\nprobability matrix:")
for t in range(T):
print(f" {V[t, :]}")
print("observation sequence:", ''.join([observedDict[i] for i in observedSequence]))
print("The most likely sequence of states:", ' -> '.join([stateDict[i] for i in best_path]))
运行结果: