中文分词的方法非常多,基于词库是最基本的,但是当前各大互联网公司基本上不会仅仅依赖于词库的分词,一般以机器学习的分词为主,词库分词的方式为辅。在很久以前,我提过利用隐马尔科夫模型进行中文分词,条件随机场其实是隐马尔科夫模型的一次升级版本,网上有很多关于条件随机场模型的分词,但是基本上很难看懂,也许是论文的缘故,那些作者习惯了一上来就是一堆复杂的公式,我也看了一些,获取有些作者自己都没搞懂,就弄了一篇论文。本文从实践的角度,提供一种基于条件随机场模型的中文分词解决方案。
第一步:准备语料库。
首先,你必须得准备一个已经分好词的语料库,用于机器学习,如下图所示:
如果你没有,可以在这里下载。
第二步:初步语料库特征学习
和隐马尔科夫模型一样,条件随机场也是基于学习字的状态来进行状态分析的,对于一个字来说,它有4个状态,分别是:词头(Begin)、词中(Middle)、词尾(End)、单字成词(Single),简称B,M,E,S。
因此需要把第一步的语料库,分析出每一个字的状态,例如:“收益”需要改进为:“收|B 益|E”, 通过将语料库的每一个分好的词,添加其状态信息,如下图所示:
当然,如果你比较懒,也没有关系,我这里提供好已经分析完毕的数据,可以在这里下载。
第三步:特征学习
有了第二步的初步学习,第三步就相对容易多了,特征学习是整个过程中非常重要的部分,在进行特征学习之前,必须弄清楚一个问题,要学习哪些特征?如下:
1. 这一个字一共在语料库中出现的次数是多少?例如“我”字一共出现了256次。
2. 这一个字,出现为词头(B)、词中(M)、词尾(E)、单字成词(S)的概率是多少?即为在256出现“我”字的时候,“我”作为词头的概率是多少,词中的概率是多少,依次类推。
3. 这一个字,当为词头(B)的时候,它转移到下一个词的状态概率是多少?每一个字都有属于自己的状态,但是这个字的后面一个字,也有属于自己的状态,那么当前字的状态,到下一个字的状态(或许是B、M、E、S之一)的概率是多少。例“我”字,当“我”的状态为B的时候,后面跟的字中,状态为B的为0个,状态为M的为10个,状态为E的为20个,状态为S的0个。依次统计“我”的状态为M、E、S的时候,下一字的状态。此过程俗称:状态转移概率计算。此项会形成一个4X4的矩阵。
截至前面三个特征学习,也许你会感觉与隐马尔科夫模型的方式没有太大区别,但是从理论上研究,条件随机场的准确率一定是高于隐马尔科夫模型的。是因为条件随机场会学习上下文关系,也就是多计算了,当一个字出现的时候,它的前一个字是什么,后一个字是什么,这个概率是多少,也就是我们第四个特征学习。
4. 这一个字出现的时候,后面字出现的是什么,概率为多少?例如当状态为B的“我”,下一个字是“们”的概率为67.9%,例如状态为S的“我”,上一个字是“的”的概率为21%,下一个字是“爱”的概率为17.8%等等,记录每一个字在四种状态下上下文关系,是非常重要的一步。这一步中,我们仅仅记录了上一个字和下一个字的上下文关系,条件允许的情况下,我们可以记录上两个字和下两个字的上下文关系。
利用代码,就是如下示例:
/** * Feature for each word. * * @author liufanping * */ private static class Feature { /** * The State transition matrix */ private Double[][] transfer; /** * The observation sequence transition matrix */ private Double[] status; /** * The predecessor State */ private TreeMap<Integer, Double> preStatus; /** * The next status */ private TreeMap<Integer, Double> nextStatus; /** * Total count. */ private int cnt; ... ...
}
上面代码中需要注意两个事项:
1. 每一个字都会有这样一个特征类,可以利用一个哈希表,key存字,value存其特征。
2. 记录前后上下文的preStatus ,key是上一个字和当前字状态的组合,nextStatus,key是下一个字和当前字状态的组合,例如当前字是“我”,当前字的状态是B,下一个字可能是“们”,在nextStatus中,key就是“们_B”的哈希值,value均是出现这种情况的概率。
我相信从语料库中学习这四个特征,应该不是一件很难的事情,自己根据描述完善代码即可。
第四步:开始分词
既然特征已经训练好了,怎么去给一个句子分词呢?例如用户输入“希腊的经济结构较特殊”,怎么就可以进行分词了呢?其实很简单,下面就是数学计算了。
1. 将“希腊的经济结构较特殊”变成字符数组。即“希”、“腊”、“的”、“经”、“济”、“结”、“构”、“较”、“特”、“殊”。
2. 取出每一个字对应特征(第三步产生的内容)。
现在每一个字的特征以及取出来了,后面怎么办呢?我们先分析下,我们其实就是在给每一个字确定它是B、M、E、S四个状态中的哪一种,因此可以绘制一个矩阵。
到这里,既然是矩阵,而且是求矩阵里面的一个路径,那么很容易想到维特比算法。到这里,我们只需要计算出“希”字在B、M、E、S的状态值,后面就游刃而解。
我们设定矩阵中的值为S, S[字][当前状态]=MAX(P[上一个字任何状态][当前状态]*S[上一个字][任何一个状态])+W[前(后)一个字_当前状态][当前字]+R[当前状态概率] (备注:R是特征二,P是特征三,W前是特征四的上文部分,W后是特征四的下文部分)
例如“腊”字,在为状态B上的值为:S[腊][B]=MAX(P[B][B]*S[希][B],P[M][B]*S[希][M],P[E][B]*S[希][E],P[S][B]*S[希][S])+W前[希_B][腊]+W后[的_B][腊]+R[B],同理可以计算S[腊][M],S[腊][E],S[腊][S]。
依次类推,可以计算出后面其它字的情况。由于“希”字出现在首字,不存在其它状态转移到其状态的概率,因此,S[希][B]=W前[_B][希]+W后[腊_B][希]+R[B],依次计算出“希”字其它值。
上面过程,希望反复用研究下,研究透后,你会发现很简单,通过计算会得到如下矩阵:
看到这里,你似乎已经看到分词的结果了,在矩阵中所有值的后面,都有一个小括号,里面存放的是路径,也就是这个值是通过上一个的哪一个值过来的(因为有计算max的过程,就会有路径选择),记录下来之后,以便于路径回溯。
当得到这个矩阵之后,我们只需要将“殊”中的最大值取出,即为1.1867对应状态是End,来自上一字的状态0,也就是“特”字的Begin,再次查看“特”字来自于上一个字“较”的Single。因此,判别状态如下
“希/B 腊/E 的/S 经/B 济/E 结/B 构/E 较/S 特/B 殊/E”
翻译成分词结果就是“希腊 的 经济 结构 较 特殊”。
完成的实现分词过程,如果你对此项有点模糊,建议看下维特比算法。
写到最后,我也要感谢您能够看到最后,这是比较简单的一种利用条件随机场进行分词的方式,复杂的,可以在这个基础上进行优化升级。
诚挚的谢意!