隐马尔科夫模型原理、实现及应用

294 阅读4分钟

隐马尔科夫模型原理、实现及应用

一、隐马尔科夫模型原理

1.隐马尔科夫模型的定义(一个定义)

隐马尔科夫模型(Hidden Markov Model,HMM)是关于时序的概率模型,描述一个由隐藏的马尔科夫链随机生成的不可观测的状态随机序列,再由各个状态生成一个观测而产生观测随机序列的过程。

隐藏的马尔科夫链随机生成的状态的序列称为状态序列(state sequence),每个状态产生一个观测,由此产生的观测的随机序列称为观测序列(observation sequence)。

隐马尔科夫模型可以表示为λ=(π,A,B)\lambda=(\pi,A,B)

其中π\pi表示初始状态概率向量,AA表示状态转移矩阵,BB表示观测概率矩阵。

π=[πi]\pi=[\pi_i]
A=[aij]N×NA=[a_{ij}]_{N\times N}
B=[bij]N×MB=[b_{ij}]_{N\times M}

其中N表示HMM模型状态集合元素的数量,M表示HMM模型观测状态集合元素的数量。aija_{ij}表示从状态ii到状态jj的转移概率,bijb_{ij}表示从状态ii到观测jj的观测概率。

2.隐马尔科夫模型的假设(两个假设)
(1)齐次马尔科夫性假设

隐藏的马尔科夫链随机生成的状态序列在任意时刻tt 只依赖于前一时刻t1t-1的状态,与其他时刻的状态及观测无关。

P(itit1,ot1,it2,ot2,...,i1,o1)=P(itit1)P(i_t|i_{t-1},o_{t-1},i_{t-2},o_{t-2},...,i_1,o_1)=P(i_t|i_{t-1})
(2)观测独立性假设

任意时刻的观测只依赖于当前时刻的状态,而与其他时刻的状态及观测无关。

P(otit,it1,ot1,...,i1,o1)=P(otit)P(o_t|i_t,i_{t-1},o_{t-1},...,i_1,o_1)=P(o_t|i_t)
3.隐马尔科夫模型能解决的问题(三个问题)
(1)evaluate问题

给定模型λ\lambda,如何计算其产生观测序列O=(o1,o2,...,ot)O=(o_1,o_2,...,o_t)的概率P(Oλ)P(O|\lambda)

a.前向算法

定义

αi(t)=P(o1,o2,...,ot,it=qiλ)\alpha_i(t)=P(o_1,o_2,...,o_t,i_t=q_i|\lambda)

αi(1)=πi×bi(o1)\alpha_i(1)=\pi_i\times b_i(o_1)
αj(t+1)=P(o1,o2,...,ot,ot+1,it+1=qjλ)=i=1NP(o1,o2,...ot,ot+1,it=qi,it+1=qjλ)=i=1NP(ot+1o1,o2,...,ot,it=qi,it+1=qj)×P(o1,o2,...,ot,it=qi,it+1=qjλ)=i=1NP(ot+1it+1=qj)×P(it+1=qjo1,o2,...,ot,it=qi)×P(o1,o2,...,ot,it=qiλ)=i=1Nbj(ot+1)×aij×αi(t)P(Oλ)=i=1Nαi(T)\alpha_j(t+1)=P(o_1,o_2,...,o_t,o_{t+1},i_{t+1}=q_j|\lambda)\\ =\sum_{i=1}^{N}P(o_1,o_2,...o_t,o_{t+1},i_t=q_i,i_{t+1}=q_j|\lambda)\\ =\sum_{i=1}^{N}P(o_{t+1}|o_1,o_2,...,o_t,i_t=q_i,i_{t+1}=q_j)\times P(o_1,o_2,...,o_t,i_t=q_i,i_{t+1}=q_j|\lambda)\\ =\sum_{i=1}^NP(o_{t+1}|i_{t+1}=q_j)\times P(i_{t+1}=q_j|o_1,o_2,...,o_t,i_t=q_i)\times P(o_1,o_2,...,o_t,i_t=q_i|\lambda)\\ =\sum_{i=1}^Nb_j(o_{t+1})\times a_{ij}\times \alpha_i(t)P(O|\lambda)=\sum_{i=1}^N\alpha_i(T)
P(Oλ)=i=1Nαi(T)P(O|\lambda)=\sum_{i=1}^N\alpha_i(T)
b.后向算法

定义

βi(t)=P(ot+1,ot+2,...,oTit=qi,λ)\beta_i(t)=P(o_{t+1},o_{t+2},...,o_T|i_t=q_i,\lambda)

βi(T)=1\beta_i(T)=1
βi(t)=P(ot+1,ot+2,...,oTit=qi,λ)=j=1NP(ot+1,ot+2,...,oT,it+1=qjit=qi,λ)=j=1NP(ot+2,ot+3,...,oTot+1,it+1=qj,it=qi)×P(ot+1,it+1=qjit=qi,λ)=j=1NP(ot+2,ot+3,...,oTit+1=qj)×P(ot+1it+1=qj)×P(it+1=qjit=qi,λ)=j=1Nβj(t+1)×bj(ot+1)×aij\beta_i(t)=P(o_{t+1},o_{t+2},...,o_T|i_t=q_i,\lambda)\\ =\sum_{j=1}^NP(o_{t+1},o_{t+2},...,o_T,i_{t+1}=q_j|i_t=q_i,\lambda)\\ =\sum_{j=1}^NP(o_{t+2},o_{t+3},...,o_T|o_{t+1},i_{t+1}=q_j,i_t=q_i)\times P(o_{t+1},i_{t+1}=q_j|i_t=q_i,\lambda)\\ =\sum_{j=1}^NP(o_{t+2},o_{t+3},...,o_T|i_{t+1}=q_j)\times P(o_{t+1}|i_{t+1}=q_j)\times P(i_{t+1}=q_j|i_t=q_i,\lambda)\\ =\sum_{j=1}^N\beta_j(t+1)\times b_j(o_{t+1})\times a_{ij}
P(Oλ)=i=1Nπi×bi(o1)×βi(1)P(O|\lambda)=\sum_{i=1}^N\pi_i\times b_i(o_1)\times \beta_i(1)
(2)decoding问题

给定模型λ\lambda,找到能产生观测序列O=(o1,o2,,ot)O=(o_1,o_2,…,o_t)的最有可能的隐藏状态序列I=(i1,i2,...it)I=(i_1,i_2,...i_t)

Imax=argmaxIP(IO,λ)I_{max}=argmax_IP(I|O,\lambda)

直观来看,可以直接采用遍历算法来计算ImaxI_{max}

argmaxIP(IO)=argmaxIP(OI)P(I)=argmaxIP(o1i1)P(o2i2)...P(itit1it2...i1)=argmaxIi=1TP(otit)P(itit1)argmax_IP(I|O)=argmax_IP(O|I)P(I)\\ =argmax_IP(o_1|i_1)P(o_2|i_2)...P(i_ti_{t-1}i_{t-2}...i_1)\\ =argmax_I\prod_{i=1}^TP(o_t|i_t)P(i_t|i_{t-1})

但这种方法时间复杂度过高,为O(NT)O(N^T),其中N为隐藏状态个数,T为序列长度

a.viterbi算法

viterbi算法本质上是一种动态规划算法,因此,首先需要建立递推关系

定义

δt(i)=maxi1,i2,...itP(o1,o2,...,ot,i1,i2,...,it1,it=qi)\delta_t(i)=max_{i_1,i_2,...i_t}P(o_1,o_2,...,o_t,i_1,i_2,...,i_{t-1},i_t=q_i)\\

δ1(i)=P(o1,i1=qi)δt+1(j)=maxi=1Nδt(i)aijbj(ot+1)\delta_1(i)=P(o_1,i_1=q_i)\\ \delta_{t+1}(j)=max_{i=1}^N\delta_t(i)a_{ij}b_{j}(o_{t+1})

递推到最后一步,并记录下每一步产生最大值的ii,再反向查找每一步的中间结果,最终求得隐藏状态序列。

(3)learning问题

learning问题有两种情况,一种情况是有完整的数据集,包括隐藏状态序列集观测序列,这种情况直接直接采用统计方法,即可得到πAB\pi,A,B三个参数。另外一种是没有完整的数据集,比如对于分词任务来说,没有已经标注好的数据,即缺乏隐藏状态序列,只有原始的文本,即观测序列,这种情况下需要采用EM算法进行参数估计(这块目前还没有深入学习)

二、隐马尔科夫模型实现

package ergo.personal.algorithm.hmm;

import java.util.Arrays;

public class HMM {
    public static String[] Q=new String[]{"S1","S2"};
    public static int Q_NUM=2;
    public static String[] O=new String[]{"O1","O2","O3"};
    public static int O_NUM=3;
    public static String[] R=new String[]{"O2","O2","O1"};
    public static int R_NUM=3;
    public static double[][] A=new double[][]{{0.1,0.9},{0.8,0.2}};
    public static double[][] B=new double[][]{{0.3,0.3,0.4},{0.2,0.2,0.6}};
    public static double[] PI=new double[]{0.6,0.4};

    //PI初始概率分布,A状态转移矩阵,B观测矩阵B=mxn,m表示隐藏状态个数,n表示观测状态个数,Q表示隐藏状态,O表示观测状态,R表示实际观测串
    public static double evaluate_forward(double[] PI, double[][] A, double[][] B, int Q_NUM, String[] Q, int O_NUM, String[] O, int R_NUM,String[] R){
        double[][] alpha=new double[Q_NUM][R_NUM];
        for(int t=0;t<R_NUM;t++){
            for(int i=0;i<Q_NUM;i++){
                if(0==t){
                    alpha[i][t]=PI[i]*B[i][Arrays.asList(O).indexOf(R[t])];
                }
                else{
                    double tmpVal=0;
                    for(int m=0;m<Q_NUM;m++){
                        tmpVal+=alpha[m][t-1]*A[m][i];
                    }
                    alpha[i][t]=B[i][Arrays.asList(O).indexOf(R[t])]*tmpVal;
                }
            }
        }
        double result=0;
        for(int i=0;i<Q_NUM;i++){
            result+=alpha[i][R_NUM-1];
        }
        return result;
    }
    public static double evaluate_backward(double[] PI, double[][] A, double[][] B, int Q_NUM, String[] Q, int O_NUM, String[] O ,int R_NUM, String[] R){
        double[][] beta=new double[Q_NUM][R_NUM];
        for(int t=R_NUM-1;t>=0;t--){
            for(int i=0;i<Q_NUM;i++){
                if(R_NUM-1==t){
                    beta[i][t]=1;
                }
                else{
                    double tmpVal=0;
                    for(int m=0;m<Q_NUM;m++){
                        tmpVal+=beta[m][t+1]*A[i][m]*B[m][Arrays.asList(O).indexOf(R[t+1])];
                    }
                    beta[i][t]=tmpVal;
                }
            }
        }
        double result=0;
        for(int i=0;i<Q_NUM;i++){
            result+=beta[i][0]*PI[i]*B[i][Arrays.asList(O).indexOf(R[0])];
        }
        return result;
    }
    public static String[] viterbi(double[] PI, double[][] A, double[][] B, int Q_NUM, String[] Q, int O_NUM, String[] O, int R_NUM, String[] R){
        double[][] delta=new double[Q_NUM][R_NUM];
        int[][] idx=new int[Q_NUM][R_NUM];
        String[] maxQ=new String[R_NUM];
        for(int t=0;t<R_NUM;t++){
            for(int i=0;i<Q_NUM;i++){
                if(0==t){
                    delta[i][0]=PI[i]*B[i][Arrays.asList(O).indexOf(R[0])];
                    idx[i][0]=0;
                }else{
                    int maxQIndex=0;
                    double maxQValue=-1;
                    for(int m=0;m<Q_NUM;m++){
                        double tmpValue=delta[m][t-1]*A[m][i]*B[i][Arrays.asList(O).indexOf(R[t])];
                        if(tmpValue>maxQIndex){
                            maxQValue=tmpValue;
                            maxQIndex=m;
                        }
                    }
                    delta[i][t]=maxQValue;
                    idx[i][t]=maxQIndex;
                }
            }
        }
        double maxQValue=-1;
        int maxQIndex=0;
        for(int i=0;i<Q_NUM;i++){
            if(delta[i][R_NUM-1]>maxQValue){
                maxQValue=delta[i][R_NUM-1];
                maxQIndex=i;
            }
        }
        maxQ[R_NUM-1]=Q[maxQIndex];
        for(int i=R_NUM-2;i>=0;i--){
            maxQIndex=idx[maxQIndex][i+1];
            maxQ[i]=Q[maxQIndex];
        }
        return maxQ;
    }

    public static void main(String[] args){
        System.out.print("观察序列:");
        Arrays.asList(R).stream().forEach(System.out::print);
        System.out.println();
        System.out.println(evaluate_forward(PI,A,B,Q_NUM,Q,O_NUM,O,R_NUM,R));
        System.out.println(evaluate_backward(PI,A,B,Q_NUM,Q,O_NUM,O,R_NUM,R));
        String[] S=viterbi(PI,A,B,Q_NUM,Q,O_NUM,O,R_NUM,R);
        for(String s:S){
            System.out.println(s);
        }
    }
}

三、隐马尔科夫模型应用

1.中文分词

隐马尔科夫模型可以用于中文分词任务,其中观测序列为文本,隐藏状态为SBME标签。

例如:

O=王二毕业于中国科学技术大学

I=BEBMEBMMMMMME

本质上是马尔科夫模型的decoding问题,即给定观察序列O,求解最有可能产生观察序列O的隐藏状态序列I。

2.命名实体识别

原理同中文分词一致。