机器学习_6.隐马算法的代码实现

借鉴: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

猜你喜欢

转载自blog.csdn.net/qq_37865996/article/details/84144085