原文地址 https://blog.csdn.net/herosofearth/article/details/52227842



紧接上篇博客,上文已经介绍了前向-后向算法。前向-后向算法是通过递推地计算前向-后向概率来高效地进行隐马尔可夫模型的概率计算。该算法的时间复杂度是O(N2T)
O(N2T)。

五、学习算法
在很多实际的情况下,HMM 不能被直接地判断,这就变成了一个学习问题。因为对于给定的可观察状态序列OO来说,没有任何一种方法可以精确地找到一组最优的
HMM 参数λλ使P(O|λ)P(O|λ) 最大,于是人们寻求使其局部最优的解决办法,而Baum-Welch算法就成了HMM学习问题的一个近似的解决方法。

Baum-Welch算法首先对于HMM的参数进行一个初始的估计,但这个很可能是一个错误的猜测,然后通过对于给定的数据评估这些参数的的有效性(比如交叉验证)并减少它们所引起的错误来更新HMM参数,使得和给定的训练数据的误差变小。
实际上,Baum-Welch算法是EM算法的一个特例。EM算法是解决无监督学习问题的一把“利器”。下面我们就使用这把“利器”解决HMM的参数学习问题。

1.首先我们需要定义两个辅助变量,这两个变量可以用前文介绍过的前向概率和后向概率进行定义。
(1)给定模型λλ和观测序列OO,在时刻tt处于qiqi的概率,记为
γt(i)=P(it=qi|O,λ)γt(i)=P(it=qi|O,λ)
可以通过前向-后向概率计算,事实上γt(i)=P(it=qi|O,λ)=P(it=qi,O|λ)P(O|λ)
γt(i)=P(it=qi|O,λ)=P(it=qi,O|λ)P(O|λ)
根据前向概率和后向概率的定义可知:αt(i)βt(i)=P(it=qi,O|λ)αt(i)βt(i)=P(it=qi,O|λ),于是得到:γt(i)=αt(
i)βt(i)P(O|λ)=αt(i)βt(i)∑Nj=1αt(j)βt(j)
γt(i)=αt(i)βt(i)P(O|λ)=αt(i)βt(i)∑j=1Nαt(j)βt(j)
(2)给定模型λλ和观测序列OO,在时刻tt处于状态qiqi且在时刻t+1t+1处于状态qjqj的概率,记为ξt(i,j)=P(it=qi,it+1=qj|
O,λ)ξt(i,j)=P(it=qi,it+1=qj|O,λ)
通过前向-后向概率计算得:ξt(i,j)=αt(i)aijbj(ot+1)βt+1(j)∑Ni=1∑Nj=1αt(i)aijbj(ot+1)βt+1(j)
ξt(i,j)=αt(i)aijbj(ot+1)βt+1(j)∑i=1N∑j=1Nαt(i)aijbj(ot+1)βt+1(j)
该变量在网格中所代表的关系如下:

(3)将γt(i)γt(i)和ξt(i,j)ξt(i,j)对各个时刻tt求和,可以得到Baum-Welch算法中需要的期望值:
a.在观测OO下状态ii出现的期望值:∑Tt=1γt(i)∑t=1Tγt(i)
b.在观测OO下由状态ii转移的期望值:∑T−1t=1γt(i)∑t=1T−1γt(i)
c.在观测OO下由状态ii转移到状态jj的期望值:∑T−1t=1ξt(i,j)∑t=1T−1ξt(i,j)


2.算法
Baum-Welch算法的推导过程实质上就是EM算法的过程,其中还会利用拉格朗日函数法求解极大化问题。具体的证明过程可查阅相关论文。下面直接给出算法:
输入:观测序列O=(o1,o2,⋯,oT)O=(o1,o2,⋯,oT)
输出:隐马尔科夫模型参数。
(1)初始化
对n=0n=0,选取a(0)ij,bj(k)(0),π(0)iaij(0),bj(k)(0),πi(0),得到初始模型λ(0)=(A(0),B(0),π(0
))λ(0)=(A(0),B(0),π(0))
(2)递推。对n=1,2,⋯n=1,2,⋯

a(n+1)ij=∑T−1t=1ξt(i,j)∑T−1t=1γt(i)aij(n+1)=∑t=1T−1ξt(i,j)∑t=1T−1γt(i)
bj(k)(n+1)=∑Tt=1,ot=vkγt(j)∑Tt=1γt(j)bj(k)(n+1)=∑t=1,ot=vkTγt(j)∑t=1Tγt(j)
π(n+1)i=γ1(i)πi(n+1)=γ1(i)
这里的递推公式用到了1.(3)部分提及的期望值。
(3)结束。得到模型参数λ(n+1)=(A(n+1),B(n+1),π(n+1))λ(n+1)=(A(n+1),B(n+1),π(n+1))


3.代码实现
(1)依旧是相同的结构体(这里使用类并不是真的面向对象编程,当然封装完整的算法可以使用OOP范式)定义。
class HMM: def __init__(self, pi, A, B): self.pi = pi self.A = A self.B = B
self.M = B.shape[1] self.N = A.shape[0]
* 1
* 2
* 3
* 4
* 5
* 6
* 7
(2)主算法函数
def baum_welch(hmm, obs): T = len(obs) M = hmm.M N = hmm.N alpha =
zeros((N,T)) beta = zeros((N,T)) scale = zeros(T) gamma = zeros((N,T)) xi =
zeros((N,N,T-1)) # 计算初始化参数 logprobprev, alpha, scale = forward_with_scale(hmm,
obs)# 计算前向概率 beta = backward_with_scale(hmm, obs, scale) # 计算后向概率 gamma =
compute_gamma(hmm, alpha, beta)# 计算单个状态概率 xi = compute_xi(hmm, obs, alpha,
beta)# 计算两个状态概率 logprobinit = logprobprev # 开始迭代 while True: # E-step hmm.pi =
0.001 + 0.999*gamma[:,0] # 这里使用平滑化技巧,以免参数过于接近0 for i in range(N): denominator =
sum(gamma[i,0:T-1]) for j in range(N): numerator = sum(xi[i,j,0:T-1])
hmm.A[i,j] = numerator / denominator hmm.A =0.001 + 0.999*hmm.A for j in range(0
,N): denominator = sum(gamma[j,:])for k in range(0,M): numerator = 0.0 for t in
range(0,T): if observation.index(observed[t]) == k: numerator += gamma[j,t]
hmm.B[j,k] = numerator / denominator hmm.B =0.001 + 0.999*hmm.B # M-step
logprobcur, alpha, scale = forward_with_scale(hmm, obs) beta =
backward_with_scale(hmm, obs, scale) gamma = compute_gamma(hmm, alpha, beta) xi
= compute_xi(hmm, obs, alpha, beta) delta = logprobcur - logprobprev
logprobprev = logprobcurif delta <= DELTA: # 迭代停止条件 break logprobfinal =
logprobcurreturn hmm.A,hmm.B,hmm.pi
* 1
* 2
* 3
* 4
* 5
* 6
* 7
* 8
* 9
* 10
* 11
* 12
* 13
* 14
* 15
* 16
* 17
* 18
* 19
* 20
* 21
* 22
* 23
* 24
* 25
* 26
* 27
* 28
* 29
* 30
* 31
* 32
* 33
* 34
* 35
* 36
* 37
* 38
* 39
* 40
* 41
* 42
* 43
* 44
* 45
* 46
* 47
* 48
* 49
* 50
* 51
* 52
* 53
(3)相关支持函数

主函数调用了均值平滑化的前向-后向概率计算算法:
def forward_with_scale(hmm, obs): """see scaling chapter in "A tutorial on
hidden Markov models and selected applications in speech recognition." """ T
= len(obs) N = hmm.N alpha = zeros((N,T)) scale = zeros(T) alpha[:,0] =
hmm.pi[:] * hmm.B[:,observation.index(obs[0])] scale[0] = sum(alpha[:,0])
alpha[:,0] /= scale[0] for t in range(1,T): for n in range(0,N): alpha[n,t] =
sum(alpha[:,t-1] * hmm.A[:,n]) * hmm.B[n,observation.index(obs[t])] scale[t] =
sum(alpha[:,t]) alpha[:,t] /= scale[t] logprob = sum(log(scale[:]))return
logprob, alpha, scaledef backward_with_scale(hmm, obs, scale): T = len(obs) N =
hmm.N beta = zeros((N,T)) beta[:,T-1] = 1 / scale[T-1] for t in reversed(range(0
,T-1)): for n in range(0,N): beta[n,t] = sum(hmm.B[:,observation.index(obs[t+1
])] * hmm.A[n,:] * beta[:,t+1]) beta[n,t] /= scale[t] return beta
* 1
* 2
* 3
* 4
* 5
* 6
* 7
* 8
* 9
* 10
* 11
* 12
* 13
* 14
* 15
* 16
* 17
* 18
* 19
* 20
* 21
* 22
* 23
* 24
* 25
* 26
* 27
* 28
* 29
* 30
* 31
* 32
计算γt(i)γt(i)和ξt(i,j)ξt(i,j)的函数如下:
def compute_gamma(hmm, alpha, beta): gamma = zeros(alpha.shape) gamma =
alpha[:,:] * beta[:,:] gamma = gamma / sum(gamma,0) return gamma def compute_xi
(hmm, obs, alpha, beta): T = len(obs) N = hmm.N xi = zeros((N, N, T-1)) for t in
range(0,T-1): for i in range(0,N): for j in range(0,N): xi[i,j,t] = alpha[i,t]
* hmm.A[i,j] * hmm.B[j,observation.index(obs[t+1])] * beta[j,t+1] xi[:,:,t] /=
sum(sum(xi[:,:,t],1),0) return xi
* 1
* 2
* 3
* 4
* 5
* 6
* 7
* 8
* 9
* 10
* 11
* 12
* 13
* 14
* 15
* 16
* 17
(4)测试程序
这里随机初始化参数,我们依然使用上篇博文的测试用例:
if __name__ == "__main__": A = random.random([3,3]) B = random.random([3,2]) pi
=random.random(3) hmm = HMM(pi, A, B) observed = ['red', 'write', 'red'] A,B,pi
= baum_welch(hmm, observed) print('state transition probability matrix A:\n',
A) print('observation probability matrix B:\n', B) print('initial state
probability vector pi:\n', pi)
* 1
* 2
* 3
* 4
* 5
* 6
* 7
* 8
* 9
* 10
* 11
* 12
* 13
测试结果如下:

由于此算法是无监督学习算法,在实际项目使用中应该不断使用交叉验证方法使得模型验证误差最小。由于这里缺乏充足数据,所以就不演示此过程了。

六、预测算法

这个问题比较有趣。在很多情况下,我们对隐藏状态更有兴趣,因为其中包含了一些不能被直接观察到的有价值的信息。比如说在海藻和天气的例子中,一个隐居的人只能看到海藻的状态,但是他想知道天气的状态。这时候我们就可以使用Viterbi算法来根据可观察序列预测最优可能的隐藏状态的序列,当然前提是已知一个HMM模型。
Viterbi算法实际上是用动态规划求解预测问题,即求解概率最大路径(最优路径),实际上任何一个HMM模型都可以用图(graph)表示。
根据动态规划原理,我们需要提取一个最优子结构:如果最优路径在时刻tt通过结点i∗tit∗,那么这一路径从结点i∗tit∗到终点i∗TiT∗
的部分子路径必须是都是最优子路径。可以用“剪切-粘贴法”(实则是反证法)证明:假如其中一条子路径不是最优路径(把它“剪切”掉),那么从i∗tit∗到i∗TiT∗
必然有另一条更好的子路径存在,如果把它和从i∗1i1∗到i∗tit∗
的部分路径连接起来(粘贴),就会整体上得到一条比原来的路径更优的路径,与原来的最优假设矛盾。依据动态规划原理,我们只需从时刻t=1t=1开始,递推地计算在时刻t
t状态为ii的各条子路径的最大概率,直至得到时刻t=Tt=T状态为ii的各条路径的最大概率。时刻为t=Tt=T的最大概率即为最优路径的概率P∗P∗
,同时也得到最优路径的终结点i∗TiT∗。之后只需回溯部分最优结点,便可得到最优路径。
1.首先定义两个变量:δδ和ΨΨ。
(1)定义在时刻tt状态为ii的所有单条路径(i1,i2,⋯,it)(i1,i2,⋯,it)中的概率最大值为
δt(i)=maxi1,i2,⋯,it−1P(it+1=i,it,⋯,i1,ot+1,⋯,o1|λ),i=1,2,⋯,N
δt(i)=maxi1,i2,⋯,it−1P(it+1=i,it,⋯,i1,ot+1,⋯,o1|λ),i=1,2,⋯,N
由定义可得变量δδ的递推公式:δt+1(t)=max1≤j≤N[δt(j)aji]bi(ot+1),i=1,2,⋯,N;t=1,2,⋯,T−1
δt+1(t)=max1≤j≤N[δt(j)aji]bi(ot+1),i=1,2,⋯,N;t=1,2,⋯,T−1
(2)定义在时刻tt状态为ii的所有单个路径(i1,i2,⋯,it−1,i)(i1,i2,⋯,it−1,i)中概率最大的路径的第t−1t−1个结点为:Ψt(
i)=argmax1≤j≤N[δt−1aji],i=1,2,⋯,NΨt(i)=arg⁡max1≤j≤N[δt−1aji],i=1,2,⋯,N即用后向指针ΨΨ
来记录导致某个状态最大局部概率的前一个状态,在算法中用于回溯最优路径。
下面给出算法步骤:
输入:模型参数λ=(A,B,π)λ=(A,B,π)和观测序列O=(o1,o2,⋯,oT)O=(o1,o2,⋯,oT);
输出:最优路径I∗=(i∗1,i∗2,⋯,i∗T)I∗=(i1∗,i2∗,⋯,iT∗)
(1)初始化δ1(t)=πibi(o1),i=1,2,⋯,Nδ1(t)=πibi(o1),i=1,2,⋯,N
Ψ1(i)=0,i=1,2,⋯,NΨ1(i)=0,i=1,2,⋯,N
(2)递推。对t=2,3,⋯,Tt=2,3,⋯,T δt(i)=max1≤j≤N[δt−1(j)aji]bi(ot),i=1,2,⋯,N
δt(i)=max1≤j≤N[δt−1(j)aji]bi(ot),i=1,2,⋯,NΨt(i)=argmax1≤j≤N[δt−1aji],i=1,2,⋯,N
Ψt(i)=arg⁡max1≤j≤N[δt−1aji],i=1,2,⋯,N
(3)终止P∗=max1≤i≤NδT(i)P∗=max1≤i≤NδT(i)i∗T=argmax1≤i≤N[δT(i)]
iT∗=arg⁡max1≤i≤N[δT(i)]
(4)最优路径回溯。对t=T−1,T−2,⋯,1t=T−1,T−2,⋯,1
i∗t=Ψt+1(i∗t+1)it∗=Ψt+1(it+1∗)
最后得到最优路径即最优隐藏状态序列I∗=(i∗1,i∗2,⋯,i∗T)I∗=(i1∗,i2∗,⋯,iT∗)


2.代码实现
from numpy import * observation = ['red','write'] class HMM: def __init__
(self, pi, A, B): self.pi = pi self.A = A self.B = B self.M = B.shape[1] self.N
= A.shape[0] def viterbi(hmm, obs): T = len(obs) N = hmm.N psi = zeros((N,T)) #
reverse pointer delta = zeros((N,T)) q = zeros(T) temp = zeros(N) delta[:,0] =
hmm.pi[:] * hmm.B[:,observation.index(obs[0])] # 初始化 # 动态规划过程 for t in range(1
,T):for n in range(0,N): temp = delta[:,t-1] * hmm.A[:,n] max_ind =
argmax(temp) psi[n,t] = max_ind delta[n,t] = hmm.B[n,observation.index(obs[t])]
* temp[max_ind]# 最优路径回溯 max_ind = argmax(delta[:,T-1]) q[T-1] = max_ind prob =
delta[:,T-1][max_ind] for t in reversed(range(0,T-1)): q[t] = psi[int(q[t+1]),t+
1] return prob, q, delta
* 1
* 2
* 3
* 4
* 5
* 6
* 7
* 8
* 9
* 10
* 11
* 12
* 13
* 14
* 15
* 16
* 17
* 18
* 19
* 20
* 21
* 22
* 23
* 24
* 25
* 26
* 27
* 28
* 29
* 30
* 31
* 32
* 33
* 34
* 35
测试程序如下:
if __name__ == "__main__": A = array([[0.5,0.2,0.3], [0.3,0.5,0.2],
[0.2,0.3,0.5]]) B = array([[0.5,0.5], [0.4,0.6], [0.7,0.3]]) pi = array([0.2,
0.4,0.4]) hmm = HMM(pi, A, B) observed = ['red', 'write', 'red'] prob, q, delta
= viterbi(hmm, observed)print('The maximum probability of every single path
is:\n', delta) print('The probability of optimal path is %f.' % prob) print(
'The optimal sequence of state is:\n', q)
* 1
* 2
* 3
* 4
* 5
* 6
* 7
* 8
* 9
* 10
* 11
* 12
* 13
* 14
* 15
* 16
* 17
测试结果如下:

结果可用概率图表示如下:

可见最优隐藏状态序列应为I∗=(i∗1,i∗2,i∗3)=(3,3,3)I∗=(i1∗,i2∗,i3∗)=(3,3,3)(程序用索引表示为(2,2,2)
(2,2,2),索引是从00开始的)。

七、参考论文
1.Lawrence R. Rabiner, A Tutorial on Hidden Markov Models and Selected
Applications in Speech Recognition. Proceedings of the IEEE, 77 (2), p.
257–286, February 1989(下载链接:http://download.csdn.net/detail/herosofearth/9605209
<http://download.csdn.net/detail/herosofearth/9605209>)

友情链接
KaDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:[email protected]
QQ群:637538335
关注微信