先介绍一下使用的资源,分词使用的语料来自于SIGHAN Bakeoff 2005的 icwb2-data.rar,《中文分词入门之资源》里有很详细的介绍:

/icwb2-data.rar/training/msr_training.utf8  用以训练HMM,其中包含已分词汇约2000000个

/icwb2-data.rar/testing/pku_test.utf8           测试集

/icwb2-data.rar/scripts/score                           一个perl脚本,测试分词效果

         我们使用经典的字符标注模型,首先需要确定标注集,在前面的介绍中,我们使用的是{B,E}的二元集合,研究表明基于四类标签的字符标注模型明显优于两类标签,原因是两类标签过于简单而损失了部分信息。四类标签的集合是 {B,E,M,S},其含义如下:

B:一个词的开始

E:一个词的结束

M:一个词的中间

S:单字成词

举例:你S现B在E应B该E去S幼B儿M园E了S

用四类标签为msr_training.utf8做好标记后,就可以开始用统计的方法构建一个HMM。我们打算构建一个2-gram(bigram)语言模型,也即一个1阶HMM,每个字符的标签分类只受前一个字符分类的影响。现在,我们需要求得HMM的状态转移矩阵 A 以及混合矩阵 B。其中:

                                     Aij = P(Cj|Ci)  =  P(Ci,Cj) / P(Ci) = Count(Ci,Cj) / Count(Ci)

                                     Bij = P(Oj|Ci)  =  P(Oj,Ci) / P(Ci) = Count(Oj,Ci) / Count(Ci)

公式中C = {B,E,M,S},O = {字符集合},Count代表频率。在计算Bij时,由于数据的稀疏性,很多字符未出现在训练集中,这导致概率为0的结果出现在B中,为了修补这个问题,我们采用加1的数据平滑技术,即:

                                     Bij = P(Oj|Ci)  =  (Count(Oj,Ci) + 1)/ Count(Ci)

         这并不是一种最好的处理技术,因为这有可能低估或高估真实概率,更加科学的方法是使用复杂一点的Good—Turing技术,这项技术的的原始版本是图灵当年和他的助手Good在破解德国密码机时发明的。求得的矩阵A如下:

        B       M                    E              S

B  0.0 | 0.19922840916814916 | 0.8007715908318509 | 0.0

M  0.0 | 0.47583202978061256 | 0.5241679702193874 | 0.0

E  0.6309567616935934 | 0.0 | 0.0 | 0.36904323830640656

S  0.6343402140354506 | 0.0 | 0.0 | 0.36565844303914763

矩阵中出现的概率为0的元素表明B-B, B-S, M-B, M-S, E-M, E-E, S-M, S-E这8种组合是不可能出现的。这是合乎逻辑的。求得的矩阵B,部分如下:

   o1               o2            o3                o4

B 7.8127868094E-7 1.460991186336E-4 0.007293548529516 6.047041844505E-4……

M ……

E …….

S ……

我们设定初始向量Pi = {0.5, 0.0, 0.0, 0.5}(M和E不可能出现在句子的首位)。至此,HMM模型构建完毕。其实用统计方法构建HMM并不复杂,麻烦的是写程序,特别是需要将分类标签和字符集合进行编码,这是个极其繁琐的过程。

我用的JAVA写程序,因此使用的Jahmm这个开源项目来进行相关的计算,对于一个HMM模型,现在我们可以写入一个观察序列,用Viterbi算法获得一个隐藏序列(分词结果)。具体做法是(举例为虚构):

1 . 对测试集按标点符号,空格,换行符拆分为一条一条的只包含字符的单句:

……

中共中央总书记

国家主席

……

2. 将句子转化为对应的编码,也即观察序列:

……

101 121 101 47 1010 32 1992

332 3241 893 2111

……

3. 输入观察序列,使用Viterbi算法输出隐藏序列(0:B,1:M,2:E,3:S)

……

0112333

0202

……

4. 输出最终分词结果:

……

中共中央/总/书/记

国家/主席

……

现在我们在对测试集pku_test.utf8分词,并用perl脚本进行评测,结果如下=== SUMMARY:
=== TOTAL INSERTIONS:    5627
=== TOTAL DELETIONS:    10639
=== TOTAL SUBSTITUTIONS:    18194
=== TOTAL NCHANGE:    34460
=== TOTAL TRUE WORD COUNT:    104372
=== TOTAL TEST WORD COUNT:    99360:

=== TOTAL TRUE WORDS RECALL:  0.724   正确切分出的词的数目/应切分出的词的总数

=== TOTAL TEST WORDS PRECISION: 0.760  正确切分出的词的数目/切分出的词的总数

=== F MEASURE:      0.742   F1 = 2 * Precision*Recall / (Precision + Recall)

=== OOV Recall Rate:    0.250      未登录词召回率

精度为:76%,召回为:72.4%。一个不太让人满意的结果,我们来看看他都干了些什么:

                                                        改判/被/告人/死/刑立/即/执行

这是一个较典型的例子,可见分词结果中出现了“告人”,“刑立”等字典中不存在的词,这些是由于HMM分词的“自由度”太大造成的,当然这种较大的“自由度”也有好处,比如:

                                                        检察院/鲍绍坤/检察/长

         看!HMM成功的识别出一个人名,这是基于词典的分词方法做不到的。实际上,在ICTCLAS系统中(2004)就是用的HMM来识别人名。下一节将会介绍一个基于词典的混合HMM分词器。

作者 itenyh

《Itenyh版-用HMM做中文分词四:A Pure-HMM 分词器》有15条评论
  1. 能不能提供一个能运行的实例程序(包括训练语料),对新手来说非常急需。

    [回复]

  2. 真是巧了,我最近也写一个HMM的分词和标注程序,我用Python写的,还想要写好了,也贴到这里来呢。
    我用了多一些的表记,对数字和英文缩写都做了特殊的 处理,平滑也采用 +1 方法。
    程序仍在调试,这两天有结果,再分享!

    [回复]

  3. 我原先按照博主的假设:Pi = {0.5, 0.0, 0.0, 0.5}去对msr_test.utf8进行分词效果很差。
    后来我发现Pi应该根据训练语料库中B、M、E、S出现的概率得出来,并且实现验证这样做的效果得到大大提升。

    [回复]

    itenyh 回复:

    提升的具体结果是怎样的? 你使用的Pi是多少?

    [回复]

  4. 我自己用java实现了训练状态转移矩阵,但是结果跟你的稍微有点不同。http://sbp810050504.blog.51cto.com/2799422/1251640
    求解,先行谢过了!

    [回复]

  5. 您好,按照您的方法。
    将训练集转化为 SSBMEBE这样的集合
    因为 Aij = P(Cj|Ci) = P(Ci,Cj) / P(Ci) = Count(Ci,Cj) / Count(Ci)
    在计算的时候,i的取值范围为[1,N-1]
    而在count(Ci)的时候,i的取值范围是[1,N]
    这会导致Aij的总和不为1,是这样理解的吗?

    [回复]

    itenyh 回复:

    Exactly!

    确切的说,如果训练集最后一个出现的字符为i的话,那么sig(j)Aij的确不会为1。

    所以,作为最后一个字符的计数如你所说也应当只计算到N-1。

    [回复]

  6. 文中

    2. 将句子转化为对应的编码,也即观察序列:

    101 121 101 47 1010 32 1992

    332 3241 893 2111

    这套编码是根据什么来的?

    [回复]

  7. 我想用hmm做个英文短语识别的,有没有什么好的英文短语的语料库可以推荐下

    [回复]

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注