HMM,它用来描述一个含有隐含未知参数的马尔可夫过程。其难点是从可观察的参数中确定该过程的隐含参数(后面要讨论到的Viterbi算法)。然后利用这些参数来作进一步的分析,例如模式识别。在中文信息处理方面,它主要用于词性标注,计算机并不知道一句话中某个词的具体词性,需要通过相应的模型和算法来使计算机能够识别出一句话中具体某个词的词性,那么模型就是某个HMM,算法就是在此模型上的Viterbi算法。
我的工作是以北大1998年1月份的语料为基础,求出此HMM,然后在这个HMM的基础之上设计Viterbi算法,实现将一句已经分好词的句子进行词性自动标注。下面就将我的学习成果和大家一起分享,有不对的地方请大家指正
HMM中包含的三个值分别是:初始分布、转移矩阵、发射矩阵。
初始分布就是一个隐藏状态的初始概率分布。就词性标注来说,初始分布表示的是一句话中第一个词是什么词性的概率分布。
转移矩阵:如果有N个隐藏了的状态(词性),那么转移矩阵就是一个N*N的矩阵,它的元素表示的是一种状态转化到另一种状态的概率。
发射矩阵:有时也称为混淆矩阵,它的行表示的是状态(词性),它的列所表示的是(词语),它的元素表示在给定一个词性(n)的情况下,它是(中国)的概率。
维特比算法提供了一种有效的计算方法来分析隐马尔科夫模型的观察序列,并捕获最可能的隐藏状态序列。它利用递归减少计算量,并使用整个序列的上下文来做判断,从而对包含“噪音”的序列也能进行良好的分析。
在使用时,维特比算法对于网格中的每一个单元(cell)都计算一个局部概率,同时包括一个反向指针用来指示最可能的到达该单元的路径。当完成整个计算过程后,首先在终止时刻找到最可能的状态,然后通过反向指针回溯到t=1时刻,这样回溯路径上的状态序列就是最可能的隐藏状态序列了。
下面是我实现的Viterbi算法的核心代码
typedef struct viterBiNode
{
double probity;
int backpointer;
}ViterBiNode;//算法需要用到的结构体
for(int j=0;j<num;j++)//列或者是词
{
for(int i=0;i<N;i++)//行
{ if(j==0)//第一个词
vbNode[i][j].probity = mynode[i].probity*MyEmitArray[i][wordLocation];
else//其他的词
{
double mymax=0.0f;
int myloc=-1;
for(int t=0;t<N;t++)//找出前一时刻局部概率和转移概率乘积最大的一个
{
double temp=0.0f;
temp=vbNode[t][j-1].probity*ChangeResultArray[t][i];//局部概率和转移概率乘积
if(mymax<temp)
{
mymax=temp;//将乘下来的值最大的赋给mymax
myloc=t;//记录反向指针
}
}
vbNode[i][j].backpointer=myloc;//求得反向指针
wordLocation=WordSearch(MyWordArray,tempstr,myWordnumber);
vbNode[i][j].probity=mymax*MyEmitArray[i][wordLocation];
//局部概率和转移概率和观察概率的乘积
if(j>=num-1)//最后一个词语时可以算出结果
{
if(pMax<vbNode[i][j].probity)
{
pMax=vbNode[i][j].probity;
pPos=i;
}
}
}
}