python实现HMM

一份完全按照李航<<统计学习方法>>介绍的HMM代码。

#coding=utf8
'''
Created on 2017-8-5
里面的代码许多地方可以精简,但为了百分百还原公式,就没有精简了。
@author: adzhua
'''

import numpy as np

class HMM(object):
    def __init__(self, A, B, pi):
        '''
        A: 状态转移概率矩阵
        B: 输出观察概率矩阵
        pi: 初始化状态向量
        '''
        self.A = np.array(A)
        self.B = np.array(B)
        self.pi = np.array(pi)
        self.N = self.A.shape[0]    # 总共状态个数
        self.M = self.B.shape[1]    # 总共观察值个数   
      
    
    # 输出HMM的参数信息
    def printHMM(self):
        print ("==================================================")
        print ("HMM content: N =",self.N,",M =",self.M)
        for i in range(self.N):
            if i==0:
                print ("hmm.A ",self.A[i,:]," hmm.B ",self.B[i,:])
            else:
                print ("      ",self.A[i,:],"       ",self.B[i,:])
        print ("hmm.pi",self.pi)
        print ("==================================================")
                    
    
    # 前向算法  
    def forwar(self, T, O, alpha, prob):
        '''
        T: 观察序列的长度
        O: 观察序列
        alpha: 运算中用到的临时数组
        prob: 返回值所要求的概率
        '''    
        
        # 初始化
        for i in range(self.N):
            alpha[0, i] = self.pi[i] * self.B[i, O[0]]

        # 递归
        for t in range(T-1):
            for j in range(self.N):
                sum = 0.0
                for i in range(self.N):
                    sum += alpha[t, i] * self.A[i, j]
                alpha[t+1, j] = sum * self.B[j, O[t+1]]        
        
        # 终止
        sum = 0.0
        for i in range(self.N):
            sum += alpha[T-1, i]
        
        prob[0] *= sum   

    
    # 带修正的前向算法
    def forwardWithScale(self, T, O, alpha, scale, prob):
        scale[0] = 0.0
        
        # 初始化
        for i in range(self.N):
            alpha[0, i] = self.pi[i] * self.B[i, O[0]]
            scale[0] += alpha[0, i]
            
        for i in range(self.N):
            alpha[0, i] /= scale[0]
        
        # 递归
        for t in range(T-1):
            scale[t+1] = 0.0
            for j in range(self.N):
                sum = 0.0
                for i in range(self.N):
                    sum += alpha[t, i] * self.A[i, j]
                
                alpha[t+1, j] = sum * self.B[j, O[t+1]]
                scale[t+1] += alpha[t+1, j]
            
            for j in range(self.N):
                alpha[t+1, j] /= scale[t+1]
         
        # 终止
        for t in range(T):
            prob[0] += np.log(scale[t])       
            
            
    def back(self, T, O, beta, prob):  
        '''
        T: 观察序列的长度    len(O)
        O: 观察序列
        beta: 计算时用到的临时数组
        prob: 返回值;所要求的概率
        ''' 
        
        # 初始化               
        for i in range(self.N):
            beta[T-1, i] = 1.0
        
        # 递归
        for t in range(T-2, -1, -1): # 从T-2开始递减;即T-2, T-3, T-4, ..., 0
            for i in range(self.N):
                sum = 0.0
                for j in range(self.N):
                    sum += self.A[i, j] * self.B[j, O[t+1]] * beta[t+1, j]
                
                beta[t, i] = sum
       
        # 终止
        sum = 0.0
        for i in range(self.N):
            sum +=  self.pi[i]*self.B[i,O[0]]*beta[0,i]
        
        prob[0] = sum    
        
        
    # 带修正的后向算法
    def backwardWithScale(self, T, O, beta, scale):
        '''
        T: 观察序列的长度 len(O)
        O: 观察序列
        beta: 计算时用到的临时数组
        '''
        # 初始化
        for i in range(self.N):
            beta[T-1, i] = 1.0
        
        # 递归               
        for t in range(T-2, -1, -1):
            for i in range(self.N):
                sum = 0.0
                for j in range(self.N):
                    sum += self.A[i, j] * self.B[j, O[t+1]] * beta[t+1, j]
                
                beta[t, i] = sum / scale[t+1]       
                
    
    # viterbi算法            
    def viterbi(self, O):
        '''
        O: 观察序列
        '''
        T = len(O)
        # 初始化
        delta = np.zeros((T, self.N), np.float)
        phi = np.zeros((T, self.N), np.float)
        I = np.zeros(T)
        
        for i in range(self.N):
            delta[0, i] = self.pi[i] * self.B[i, O[0]]
            phi[0, i] = 0.0
        
        # 递归
        for t in range(1, T):
            for i in range(self.N):
                delta[t, i] = self.B[i, O[t]] * np.array([delta[t-1, j] * self.A[j, i] for j in range(self.N)] ).max()
                phi = np.array([delta[t-1, j] * self.A[j, i] for j in range(self.N)]).argmax()
            
        # 终止
        prob = delta[T-1, :].max()
        I[T-1] = delta[T-1, :].argmax()
        
        for t in range(T-2, -1, -1):
            I[t] = phi[I[t+1]]
            
        
        return prob, I
    
    
    # 计算gamma(计算A所需的分母;详情见李航的统计学习) : 时刻t时马尔可夫链处于状态Si的概率
    def computeGamma(self, T, alpha, beta, gamma):
        ''''''
        for t in range(T):
            for i in range(self.N):
                sum = 0.0
                for j in range(self.N):
                    sum += alpha[t, j] * beta[t, j]
                
                gamma[t, i] = (alpha[t, i] * beta[t, i]) / sum   
    
    # 计算sai(i,j)(计算A所需的分子) 为给定训练序列O和模型lambda时
    def computeXi(self, T, O, alpha, beta, Xi):
        
        for t in range(T-1):
            sum = 0.0
            for i in range(self.N):
                for j in range(self.N):
                    Xi[t, i, j] = alpha[t, i] * self.A[i, j] * self.B[j, O[t+1]] * beta[t+1, j]
                    sum += Xi[t, i, j]
            
            for i in range(self.N):
                for j in range(self.N):
                    Xi[t, i, j] /= sum
   
    
    #  输入 L个观察序列O,初始模型:HMM={A,B,pi,N,M}
    def BaumWelch(self, L, T, O, alpha, beta, gamma):                                    
        DELTA = 0.01 ; round = 0 ; flag = 1 ; probf = [0.0]
        delta = 0.0; probprev = 0.0 ; ratio = 0.0 ; deltaprev = 10e-70
        
        xi = np.zeros((T, self.N, self.N)) # 计算A的分子
        pi = np.zeros((T), np.float)    # 状态初始化概率
        
        denominatorA = np.zeros((self.N), np.float) # 辅助计算A的分母的变量
        denominatorB = np.zeros((self.N), np.float)
        numeratorA = np.zeros((self.N, self.N), np.float)   # 辅助计算A的分子的变量
        numeratorB = np.zeros((self.N, self.M), np.float)   # 针对输出观察概率矩阵
        scale = np.zeros((T), np.float)
        
        while True:
            probf[0] =0
            
            # E_step
            for l in range(L):
                self.forwardWithScale(T, O[l], alpha, scale, probf)
                self.backwardWithScale(T, O[l], beta, scale)
                self.computeGamma(T, alpha, beta, gamma)    # (t, i)
                self.computeXi(T, O[l], alpha, beta, xi)    #(t, i, j)
                
                for i in range(self.N):
                    pi[i] += gamma[0, i]
                    for t in range(T-1):
                        denominatorA[i] += gamma[t, i]
                        denominatorB[i] += gamma[t, i]
                    denominatorB[i] += gamma[T-1, i]
                
                    for j in range(self.N):
                        for t in range(T-1):
                            numeratorA[i, j] += xi[t, i, j]
                        
                    for k in range(self.M): # M为观察状态取值个数
                        for t in range(T):
                            if O[l][t] == k:
                                numeratorB[i, k] += gamma[t, i]    
                                
            
            # M_step。 计算pi, A, B
            for i in range(self.N): # 这个for循环也可以放到for l in range(L)里面
                self.pi[i] = 0.001 / self.N + 0.999 * pi[i] / L
                
                for j in range(self.N):
                    self.A[i, j] = 0.001 / self.N + 0.999 * numeratorA[i, j] / denominatorA[i]                    
                    numeratorA[i, j] = 0.0
                
                for k in range(self.M):
                    self.B[i, k] = 0.001 / self.N + 0.999 * numeratorB[i, k] / denominatorB[i]
                    numeratorB[i, k] = 0.0   
                
                #重置
                pi[i] = denominatorA[i] = denominatorB[i] = 0.0
                
            if flag == 1:
                flag = 0
                probprev = probf[0]
                ratio = 1
                continue
            
            delta = probf[0] -  probprev 
            ratio = delta / deltaprev   
            probprev = probf[0]
            deltaprev = delta
            round += 1
            
            if ratio <= DELTA :
                print('num iteration: ', round)   
                break
        

if __name__ == '__main__':
    print ("python my HMM")
    
    # 初始的状态概率矩阵pi;状态转移矩阵A;输出观察概率矩阵B; 观察序列
    pi = [0.5,0.5]
    A = [[0.8125,0.1875],[0.2,0.8]]
    B = [[0.875,0.125],[0.25,0.75]]
    O = [
         [1,0,0,1,1,0,0,0,0],
         [1,1,0,1,0,0,1,1,0],
         [0,0,1,1,0,0,1,1,1]
        ]
    L = len(O)
    T = len(O[0])   #  T等于最长序列的长度就好了
    
    hmm = HMM(A, B, pi)
    alpha = np.zeros((T,hmm.N),np.float)
    beta = np.zeros((T,hmm.N),np.float)
    gamma = np.zeros((T,hmm.N),np.float)
    
    # 训练
    hmm.BaumWelch(L,T,O,alpha,beta,gamma)
    
    # 输出HMM参数信息
    hmm.printHMM()    
        
                    
            
    
    
    
        
        
        


  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值