隐马尔科夫模型原理、实现及应用
一、隐马尔科夫模型原理
1.隐马尔科夫模型的定义(一个定义)
隐马尔科夫模型(Hidden Markov Model,HMM)是关于时序的概率模型,描述一个由隐藏的马尔科夫链随机生成的不可观测的状态随机序列,再由各个状态生成一个观测而产生观测随机序列的过程。
隐藏的马尔科夫链随机生成的状态的序列称为状态序列(state sequence),每个状态产生一个观测,由此产生的观测的随机序列称为观测序列(observation sequence)。
隐马尔科夫模型可以表示为
其中表示初始状态概率向量,表示状态转移矩阵,表示观测概率矩阵。
其中N表示HMM模型状态集合元素的数量,M表示HMM模型观测状态集合元素的数量。表示从状态到状态的转移概率,表示从状态到观测的观测概率。
2.隐马尔科夫模型的假设(两个假设)
(1)齐次马尔科夫性假设
隐藏的马尔科夫链随机生成的状态序列在任意时刻 只依赖于前一时刻的状态,与其他时刻的状态及观测无关。
(2)观测独立性假设
任意时刻的观测只依赖于当前时刻的状态,而与其他时刻的状态及观测无关。
3.隐马尔科夫模型能解决的问题(三个问题)
(1)evaluate问题
给定模型,如何计算其产生观测序列的概率
a.前向算法
定义
则
b.后向算法
定义
则
(2)decoding问题
给定模型,找到能产生观测序列的最有可能的隐藏状态序列
直观来看,可以直接采用遍历算法来计算
但这种方法时间复杂度过高,为,其中N为隐藏状态个数,T为序列长度
a.viterbi算法
viterbi算法本质上是一种动态规划算法,因此,首先需要建立递推关系
定义
则
递推到最后一步,并记录下每一步产生最大值的,再反向查找每一步的中间结果,最终求得隐藏状态序列。
(3)learning问题
learning问题有两种情况,一种情况是有完整的数据集,包括隐藏状态序列集观测序列,这种情况直接直接采用统计方法,即可得到三个参数。另外一种是没有完整的数据集,比如对于分词任务来说,没有已经标注好的数据,即缺乏隐藏状态序列,只有原始的文本,即观测序列,这种情况下需要采用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.命名实体识别
原理同中文分词一致。