按照网上资料,中文分词算法可分为三大类:基于字典、词库匹配的分词方法;基于词频度统计的分词方法和基于知识理解的分词方法。基于词库的方法,有几个问题要解决,一是词库和数据结构,二是字符串在词库的匹配方式,三是多种满足匹配的选择。正向最大匹配是基于词库的分词方法,基本思想是按照文字的正方向,与词库中的词作比对,如果多个词匹配,则取最长的词。有正向,就有逆向,就是反方向读取语句中的字去比对,据统计,准确率比正向的高。正向最大匹配就拿mmseg4j的代码学习下。找到的mmseg4j版本是1.9.1。
首先,类MMSeg使用PushbackReader将字符一个一个按顺序读取,判断字符的类型,将字符保存到一个StringBuilder中,直到遇到不符规则的字符,将该字符推回到输入流的前端。如果输入中有中文字符(其他的日文之类的这里就不管了),则对中文字符子串进行分词处理。里面有3种分词方式,先看下SimpleSeg。
while(read && (data=readNext()) != -1) {
<span style="white-space:pre"> </span>read = false; //遇到同类字符zhiwei
int type = Character.getType(data);
String wordType = Word.TYPE_WORD;
switch(type) {
<span style="white-space:pre"> </span>……<span style="white-space:pre"> </span>//判断字符类型,分别做不同的处理。里面还会读取字符,直到遇到不符规则的字符
<span style="white-space:pre"> </span>}
<span style="white-space:pre"> </span>}
SimpleSeg就是寻找在词库中最长的匹配。
public Chunk seg(Sentence sen) {
Chunk chunk = new Chunk();
char[] chs = sen.getText();
for(int k=0; k<3&&!sen.isFinish(); k++) {
int offset = sen.getOffset();
int maxLen = 0;
//有了 key tree 的支持可以从头开始 max match
maxLen = dic.maxMatch(chs, offset);
chunk.words[k] = new Word(chs, sen.getStartOffset(), offset, maxLen+1);
offset += maxLen + 1;
sen.setOffset(offset);
}
return chunk;
}
在遇到叶子节点或匹配不下去时,就是最长的匹配。
public ArrayList<Integer> maxMatch(ArrayList<Integer> tailLens, char[] sen, int offset) {
TreeNode node = head;
for(int i=offset; i<sen.length; i++) {
node = node.subNode(sen[i]);
if(node != null) {
if(node.isAlsoLeaf()) {
tailLens.add(i-offset+1);
}
} else {
break;
}
}
return tailLens;
}
ComplexSeg与SimpleSeg相比,每次匹配时,有两种切分的情况,第一种是从词库找到最大匹配的词,第二种将剩下的待切分的字符串的第一个字作为单字词。两种切出一个新词后,将剩余字符串继续切分,直到产生了三个词后终止循环,根据规则选择最合适的分词。如果3次切分每次都能在词库找到长度大于1的词,就产生6种切分方式。分词规则可以看看这里 MMSEG的歧义规则。