借鉴:https://github.com/Continue7777/HMM/
依旧基于三个问题进行实现
1.评估
(1)描述
给定观测序列O(o1,o2,…,oT)和模型u = (π,A,B),求出P(O | u),即给定模型下观测序列的概率是多少?
(2)实际算法
不再提穷举这件事了,无法应对长序列。
定义前向变量算子αt(i)=P(O1,O2,…,Ot,Xt = Si | u),前向变量表示t时刻,si状态结束是之前路径上的总概率。可知,对αT(i)求和便能得到评估结果。而此时时间复杂度因为有了动态规划,乘法次数在T*N^2的级别。
算法描述:
(3)Python前向算法代码
#计算公式中的alpha二维数组 def _forward(self,observationsSeq):
T = len(observationsSeq)
N = len(self.pi)
alpha = np.zeros((T,N),dtype=float)
alpha[0,:] = self.pi * self.B[:,observationsSeq[0]] #numpy可以简化循环
for t in range(1,T):
for n in range(0,N):
alpha[t,n] = np.dot(alpha[t-1,:],self.A[:,n]) * self.B[n,observationsSeq[t]] #使用内积简化代码
return alpha
2.预测
(1)描述
维比特算法:
维特比算法是一个特殊但应用最广的动态规划算法,利用动态规划,可以解决任何一个图中的最短路径问题。而维特比算法是针对一个特殊的图——篱笆网络的有向图(Lattice )的最短路径问题而提出的。 它之所以重要,是因为凡是使用隐含马尔可夫模型描述的问题都可以用它来解码,包括今天的数字通信、语音识别、机器翻译、拼音转汉字、分词等。——《数学之美》
(2)维比特算法
θ1(j) = πj
状态1时刻概率都是初始概率
θt(j) = max( θt-1(i) * aij * bj ot) i∈[1,N]
状态t时刻为t-1时刻θ变量*转移概率的最大值
φt(j) = argmax( θt-1(i) * aij ) i∈[1,N]
状态t时刻回溯上一个最大概率路径
(3)Python维比特算法代码
#维特比算法进行预测,即解码,返回最大路径与该路径概率
def viterbi(self,observationsSeq):
T = len(observationsSeq)
N = len(self.pi)
prePath = np.zeros((T,N),dtype=int)
dpMatrix = np.zeros((T,N),dtype=float)
dpMatrix[0,:] = self.pi * self.B[:,observationsSeq[0]]
for t in range(1,T):
for n in range(N):
probs = dpMatrix[t-1,:] * self.A[:,n] * self.B[n,observationsSeq[t]]
prePath[t,n] = np.argmax(probs)
dpMatrix[t,n] = np.max(probs)
maxProb = np.max(dpMatrix[T-1,:])
maxIndex = np.argmax(dpMatrix[T-1,:])
path = [maxIndex]
for t in reversed(range(1,T)):
path.append(prePath[t,path[-1]])
path.reverse()
return maxProb,path
3.学习
(1)描述
给定一个观察序列,求出模型的参数(π,A,B),使用算法baum-welch算法。
(2)baum-welch算法
#计算公式中的beita二维数组
def _backward(self,observationsSeq):
T = len(observationsSeq)
N = len(self.pi)
beta = np.zeros((T,N),dtype=float)
beta[T-1,:] = 1
for t in reversed(range(T-1)):
for n in range(N):
beta[t,n] = np.sum(self.A[n,:] * self.B[:,observationsSeq[t+1]] * beta[t+1,:])
return beta
#前后向算法学习参数
def baumWelch(self,observationsSeq,criterion=0.001):
T = len(observationsSeq)
N = len(self.pi)
while True:
# alpha_t(i) = P(O_1 O_2 ... O_t, q_t = S_i | hmm)
# Initialize alpha
alpha = self._forward(observationsSeq)
# beta_t(i) = P(O_t+1 O_t+2 ... O_T | q_t = S_i , hmm)
# Initialize beta
beta = self._backward(observationsSeq)
#根据公式求解XIt(i,j) = P(qt=Si,qt+1=Sj | O,λ)
xi = np.zeros((T-1,N,N),dtype=float)
for t in range(T-1):
denominator = np.sum( np.dot(alpha[t,:],self.A) * self.B[:,observationsSeq[t+1]] * beta[t+1,:])
for i in range(N):
molecular = alpha[t,i] * self.A[i,:] * self.B[:,observationsSeq[t+1]] * beta[t+1,:]
xi[t,i,:] = molecular / denominator
#根据xi就可以求出gamma,注意最后缺了一项要单独补上来
gamma = np.sum(xi,axis=2)
prod = (alpha[T-1,:] * beta[T-1,:])
gamma = np.vstack((gamma, prod /np.sum(prod)))
newpi = gamma[0,:]
newA = np.sum(xi,axis=0) / np.sum(gamma[:-1,:],axis=0).reshape(-1,1)
newB = np.zeros(self.B.shape,dtype=float)
for k in range(self.B.shape[1]):
mask = observationsSeq == k
newB[:,k] = np.sum(gamma[mask,:],axis=0) / np.sum(gamma,axis=0)
if np.max(abs(self.pi - newpi)) < criterion and \ np.max(abs(self.A - newA)) < criterion and \ np.max(abs(self.B - newB)) < criterion:
break
self.A,self.B,self.pi = newA,newB,newpi