• 隐马尔可夫模型(HMM)学习笔记一


            学习了李航的《统计学习方法》中隐马尔可夫模型(Hidden Markov Model, HMM),这里把自己对HMM的理解进行总结(大部分是书本原文,O(∩_∩)O哈哈~,主要是想利用python将其实现一遍,这样印象深刻一点儿),并利用python将书本上的例子运行一遍。HMM是可用于标注问题的统计学习模型,描述由隐藏的马尔科夫链随机生成观测序列的过程,属于生成模型。HMM在语音识别、自然语言处理、生物信息、模式识别等领域都有着广泛的应用。

    一、隐马尔可夫模型的定义

           HMM由初始概率分布、状态转移概率分布A以及观测概率分布B三部分确定。HMM模型屏幕快照 2016-06-28 下午9.59.49.png可以用三元符号表示,即:屏幕快照 2016-06-28 下午10.00.24.png。A、B、称为HMM的三要素。

           设Q是所有可能的状态的集合,V是所有可能的观测的集合。屏幕快照 2016-06-28 下午8.44.27.png其中,N是可能的状态数,M是可能的观测数。这里观测状态v是可见的,状态q是不可见的。记I是长度为T的状态序列,O是对应的观测序列,屏幕快照 2016-06-28 下午9.10.53.png

      1、则可定义状态转移概率矩阵B如下:

    屏幕快照 2016-06-28 下午9.29.17.png

    其中屏幕快照 2016-06-28 下午9.32.33.png是在时刻t处于状态 q的条件下在时刻t+1转移到状态 q的概率。

      2、观测概率矩阵B定义如下:

    屏幕快照 2016-06-28 下午9.49.26.png

    其中屏幕快照 2016-06-28 下午9.50.32.png是在时刻t处于状态 q的条件下生成观测 v的概率。

      3、初始状态概率向量如下:

    屏幕快照 2016-06-28 下午9.56.38.png

    其中屏幕快照 2016-06-28 下午9.57.11.png是时刻t=1处于状态qj的概率。

    二、HMM的两个假设

      1、齐次马尔科夫性假设。即隐藏的马尔科夫链在任意时刻 t 的状态只与前一时刻的状态有关,与时刻 t 也无关。

    屏幕快照 2016-07-02 下午7.31.56.png

      2、观测独立性假设。即任意时刻的观测只和该时刻的马尔科夫链的状态有关,与其他观测即状态无关。

    屏幕快照 2016-07-02 下午7.33.50.png

    例题:例子采用书本上的盒子和球模型。

      分析:这里一共四个盒子,则表示所有可能的状态有四种Q={盒子1,盒子2,盒子3,盒子4},N=4。球的颜色只有红白两种,即所有可能的观测为V={红,白},M=2。

      用python中的列表分别表示可能的状态集合Q和观测集合V,用字典存储初始概率分布、状态转移概率分布和观测概率分布。

    # 所有可能的状态集合
    states = ['盒子1', '盒子2', '盒子3', '盒子4']
    # 所有可能的观测集合
    observations = ['红球', '白球']
    
    # 初始状态概率分布pi
    Pi = {'盒子1':0.25, '盒子2':0.25, '盒子3':0.25, '盒子4':0.25}
    # 状态转移概率A
    A = {'盒子1':{'盒子1':0, '盒子2':1, '盒子3':0, '盒子4':0}, 
         '盒子2':{'盒子1':0.4, '盒子2':0, '盒子3':0.6, '盒子4':0}, 
         '盒子3':{'盒子1':0, '盒子2':0.4, '盒子3':0, '盒子4':0.6}, 
         '盒子4':{'盒子1':0, '盒子2':0, '盒子3':0.5, '盒子4':0.5}
    }
    # 观测概率分布B
    B = {'盒子1':{'红球':0.5, '白球':0.5}, 
         '盒子2':{'红球':0.3, '白球':0.7}, 
         '盒子3':{'红球':0.6, '白球':0.4}, 
         '盒子4':{'红球':0.8, '白球':0.2}
    }

      为了后面方便处理,这里引入python的numpy库将Pi、A和B转换为矩阵和向量形式。

    import numpy as np
    # 将初始状态概率转换成向量形式
    Pi = np.array([Pi[x] for x in Pi])
    print('初始状态pi:', Pi)
    
    # 将初始概率分布转换为矩阵形式
    trans_A = np.array([A[x][y] for x in A for y in A[x]])
    
    A = trans_A.reshape((len(A), -1))
    print('状态转移概率分步A:', A)
    
    # 将观测概率分布转换成矩阵形式
    trans_B = np.array([B[x][y] for x in B for y in B[x]])
    B = trans_B.reshape((len(B), -1))
    print('观测概率分布B:', B)

    观测序列的生成过程

      根据隐马尔可夫模型定义,可以将一个长度为T的观测序列屏幕快照 2016-07-02 下午7.40.03.png的生成过程描述如下:

      算法(观测序列的生成)

      输入:隐马尔可夫模型屏幕快照 2016-07-02 下午7.39.49.png,观测序列长度

      输出:观测序列屏幕快照 2016-07-02 下午7.40.03.png

      (1)按照初始状态分布屏幕快照 2016-08-07 下午9.12.18.png产生状态屏幕快照 2016-08-07 下午9.17.01.png

      (2)令t=1

      (3)按照状态屏幕快照 2016-08-07 下午9.17.01.png的观测概率分布屏幕快照 2016-08-07 下午9.17.47.png生成屏幕快照 2016-08-07 下午9.18.02.png

      (4)按照状态屏幕快照 2016-08-07 下午9.17.01.png的状态转移概率分布屏幕快照 2016-08-07 下午9.22.16.png产生状态屏幕快照 2016-08-07 下午9.22.32.png

      令屏幕快照 2016-08-07 下午9.22.46.png;如果屏幕快照 2016-08-07 下午9.23.05.png则转步(3);否则,终止。

      分析:对于按照某个概率分布生成状态,这里采用python中的np.random.multinomial函数按照多项式分布,生成数据,然后再利用np.where得到满足条件的元素对应的下标。定义观测序列生成函数generation_observation。

    # 定义生成观测序列的函数
    def generation_observation(pi, A, B, T):
        def random_res(probs):
            """
            1.np.random.multinomial:
            按照多项式分布,生成数据
            >>> np.random.multinomial(20, [1/6.]*6, size=2)
                    array([[3, 4, 3, 3, 4, 3],
                           [2, 4, 3, 4, 0, 7]])
             For the first run, we threw 3 times 1, 4 times 2, etc.  
             For the second, we threw 2 times 1, 4 times 2, etc.
            2.np.where:
            >>> x = np.arange(9.).reshape(3, 3)
            >>> np.where( x > 5 )
            (array([2, 2, 2]), array([0, 1, 2]))
            """
            return np.where(np.random.multinomial(1,probs) == 1)[0][0]
        
        observation_data = np.zeros(T, dtype=int)  # 初始化生成的观测序列
        observation_states = np.zeros(T, dtype=int)  # 观测序列对应的状态
        observation_states[0] = random_res(pi)  # 根据初始状态分布产生状态1
        observation_data[0] = random_res(B[observation_states[0]])  # 由状态1生成对应的观测值
        for i in range(1, T):
            observation_states[i] = random_res(A[observation_states[i-1]])  #  由前一个状态转变到下一个状态
            observation_data[i] = random_res(B[observation_states[i]])
        return observation_data, observation_states
        
    observation_data, observation_states = generation_observation(Pi, A, B, 10)   
    print(observation_data, observation_states) 
    
    print('观测序列:', [observations[i] for i in observation_data])
    print('状态序列:', [states[i] for i in observation_states])

    三、隐马尔可夫模型的3个基本问題

       1、概率计算问题。给定模型屏幕快照 2016-07-02 下午7.39.49.png和观测序列屏幕快照 2016-07-02 下午7.40.03.png,计算在模型屏幕快照 2016-07-02 下午7.41.04.png下观测序列O出现的概率屏幕快照 2016-07-02 下午7.41.27.png

      2、学习问题。己知观测序列屏幕快照 2016-07-02 下午7.40.03.png,估计模型屏幕快照 2016-07-02 下午7.39.49.png参数,使得在该模型下观测序列概率屏幕快照 2016-07-02 下午7.43.48.png最大。即用极大似然估计的方法估计参数。

      3、预测问题,也称为解码(decoding)问题。已知模型屏幕快照 2016-07-02 下午7.39.49.png和观测序列屏幕快照 2016-07-02 下午7.40.03.png,求对给定观测序列条件概率屏幕快照 2016-07-02 下午7.44.35.png最大的状态序列屏幕快照 2016-07-02 下午7.45.04.png。即给定观测序列,求最有可能的对应的状态序列。

      问题1 概率计算问题

      如果直接采用直接计算法的话,需要列举所有长度为T的状态序列 I,然后求出 I 与观测序列O的联合概率,最后再对所有可能的状态序列求和,得到序列O在HMM模型下生成的概率。这样时间复杂度是O(TNT)阶的,这种算法观测序列长度很长的话计算量太大,所以采用前向、后向算法计算概率。

      (1)前向算法

       首先定义(前向概率)给定隐马尔可夫模型屏幕快照 2016-08-05 上午11.26.12.png,定义到时刻t为止的观测序列为屏幕快照 2016-08-05 上午11.27.00.png且状态为屏幕快照 2016-08-05 上午11.27.16.png的概率为前向概率,记作

    屏幕快照 2016-08-05 上午11.27.49.png

      输入:隐马尔可夫模型屏幕快照 2016-08-05 上午11.26.12.png,观测序列屏幕快照 2016-08-05 上午11.40.07.png;

      输出:观测序列概率屏幕快照 2016-08-05 上午11.28.59.png

      (1)初值

    屏幕快照 2016-08-05 上午11.40.51.png

      (2)递推对屏幕快照 2016-08-05 上午11.42.23.png 

    屏幕快照 2016-08-05 上午11.43.07.png

      (3)终止

    屏幕快照 2016-08-05 上午11.44.45.png

      由于到了时间T,一共有N种状态发转移到最后那个观测,所以最终的结果要将这些概率加起来。而每次递推都是在前一次的基础上做的,所以只需累加O(T)次,所以总体复杂度是O(N2T)。

      分析:前向概率矩阵alpha是N*T维的,可以先用零初始化,之后再按照前向算法一步一步对其值进行替换。定义前向算法方法forward_compute。再利用例题10.2对其进行验证。

    def forward_compute(pi, A, B, o_seq):
    T = len(o_seq)
    N = A.shape[0]
    alpha = np.zeros((N, T)) # 初始化alpha矩阵 N*T维
    # print(alpha)
    # print(B[:, o_seq[0]])
    alpha[:, 0] = pi*B[:, o_seq[0]] # step1 初值
    for t in range(1, T):
    alpha[:, t] = alpha[:, t-1].dot(A) * B[:, o_seq[t]] # step2 递推
    # print(alpha)
    return np.sum(alpha[:, T-1]), alpha # step3 终止 对时刻T的alpha求和
    # 测试 例题10.2
    A = np.array([[0.5, 0.2, 0.3], [0.3, 0.5, 0.2], [0.2, 0.3, 0.5]])
    Pi = np.array([0.2, 0.4, 0.4])
    B = np.array([[0.5, 0.5], [0.4, 0.6], [0.7, 0.3]])
    o_seq = [0, 1, 0]  # 红、白、红
    
    prob, alpha = forward_compute(Pi, A, B, o_seq)
    print('观测概率为:', prob)
    print('alpha:', alpha)

      (2)后向算法

       定义后向概率为给定隐马尔可夫模型屏幕快照 2016-08-05 上午11.26.12.png,定义在时刻t状态为屏幕快照 2016-08-05 上午11.27.16.png的条件下,从t+1到T的部分观测序列为屏幕快照 2016-08-05 下午2.13.27.png的概率为后向概率,记作

    屏幕快照 2016-08-05 下午2.14.07.png

    可以用递推的方法求得后向概率屏幕快照 2016-08-05 下午2.16.37.png及观测序列概率屏幕快照 2016-08-05 下午2.17.04.png

      法(观测序列概率的后向算法)

      输入:隐马尔可夫模型屏幕快照 2016-08-05 上午11.26.12.png,观测序列屏幕快照 2016-08-05 上午11.40.07.png:

      输出:观测序列概率屏幕快照 2016-08-05 上午11.28.59.png

      (1)初值

    屏幕快照 2016-08-05 下午2.19.52.png

    根据定义,从T+1到T的部分观测序列其实不存在,所以硬性规定这个值是1。

      (2)递推。屏幕快照 2016-08-05 下午2.20.45.png

    屏幕快照 2016-08-05 下午2.21.46.png

    aij表示状态i转移到j的概率,bj表示发射Ot+1屏幕快照 2016-08-05 下午2.29.52.png表示j后面的序列对应的后向概率。

      (3)终止

    屏幕快照 2016-08-05 下午2.22.44.png

    最后的求和是因为,在第一个时间点上有N种后向概率都能输出从2到T的观测序列,所以乘上输出O1的概率后求和得到最终结果。

      分析:前向概率矩阵beta也是N*T维的,可以先用零初始化,之后再按照前向算法一步一步对其值进行替换。定义前向算法方法backward_compute。再利用例题10.2对其进行验证。

    def backword_compute(pi, A, B, o_seq):
        T = len(o_seq)
        beta = np.zeros((A.shape[0], T))  # 初始化beta矩阵
        
        beta[:, T-1] = np.ones(A.shape[0])  # step1 初值
        for t in range(T-2, -1, -1):
            beta[:, t] = np.sum(A*B[:, o_seq[t+1]]*beta[:, t+1], axis=1)  # step2 递推
        
        res_prop = np.sum(pi*B[:, o_seq[0]]*beta[:,0], axis=0)  #  求观测序列概率
      # print(beta)
        return res_prop, beta
    # 测试 例题10.2
    A = np.array([[0.5, 0.2, 0.3], [0.3, 0.5, 0.2], [0.2, 0.3, 0.5]])
    Pi = np.array([0.2, 0.4, 0.4])
    B = np.array([[0.5, 0.5], [0.4, 0.6], [0.7, 0.3]])
    o_seq = [0, 1, 0]  # 红、白、红
    prob, beta = backword_compute(Pi, A, B, o_seq)
    print('观测概率:', prob)
    print('beta:', beta)

  • 相关阅读:
    (4.3)基于机器学习(分类)的酒店评论倾向性分析
    (4.2)基于LingPipe的文本基本极性分析【demo】
    (4.1)LingPipe在Eclipse中的运行
    微信获取openid
    微信token
    js跳转整理(简记)
    阅读有感
    normalize.css v2.1.2 翻译
    来,让我们谈一谈 Normalize.css
    jquery ajax事件执行顺序
  • 原文地址:https://www.cnblogs.com/xiaxuexiaoab/p/10233739.html
Copyright © 2020-2023  润新知