最近碰到一个分词匹配需求——给定一个关键词表,做为自定义分词词典,用户query文本分词后,是否有词落入这个自定义词典中?现有的大多数Java系的分词方案基本都支持添加自定义词典,可是却不支持HDFS路径的。所以,我须要寻找一种简单高效的分词方案,稍做包装便可支持HDFS。MMSeg分词算法正是完美地契合了这种需求。java
MMSeg是蔡志浩(Chih-Hao Tsai)提出的基于字符串匹配(亦称基于词典)的中文分词算法。基于词典的分词方案没法解决歧义问题,好比,“武汉市长江大桥”是应分词“武汉/市长/江大桥”仍是“武汉市/长江/大桥”。基于此,有人提出了正向最大匹配策略,可是可能会出现分词错误的状况,好比:若词典中有“武汉市长”,则原句被分词成“武汉市长/江大桥”。单纯的最大匹配仍是没法完美地解决歧义,于是MMSeg在正向最大匹配的基础上设计了四个启发式规则。isnowfy大神的《浅谈中文分词》对于各类主流的分词算法作了精辟的论述。git
MMSeg的字符串匹配算法分为两种:github
在complex分词算法中,MMSeg将切分的相邻三个词做为词块(chunk),应用以下四个消歧义规则:算法
这篇文章《mmseg分词算法及实现》对于这四个规则作了更为细致的介绍,本文无再赘言了。apache
MMSeg的Java实现有mmseg4j,本地路径添加自定义词典分词:并发
String txt = "在一块儿并发生了中文分词."; // user-defined dictionary parent-path Dictionary dic = Dictionary.getInstance("src\\resources\\dict"); Seg seg = new ComplexSeg(dic); MMSeg mmSeg = new MMSeg(new StringReader(txt), seg); Word word = null; while ((word = mmSeg.next()) != null) { System.out.print(word + "|"); }
mmseg4j("com.chenlb.mmseg4j" % "mmseg4j-core" % "1.10.0"
)没有wiki,经过分析源码才知道getInstance方法的路径参数应是父目录,而且词典文件的命名应符合规范:chars.dic(单字词表)、wordsXXX.dic(词长>1词表)。mmseg4j所加载的分词词典为类Dictionary数据成员Map<Character, CharNode> dict
,其中Character
为词的首字,CharNode
是一棵trie树,存储拥有共同前缀(首字)的词。loadWord
方法为加载自定义词表。dom
基于上面的代码分析,封装添加HDFS路径词典的Scala代码以下:oop
import com.chenlb.mmseg4j.CharNode import org.apache.hadoop.conf.Configuration import org.apache.hadoop.fs.{FSDataInputStream, FileSystem, Path} import scala.io.Source /** * @author rain */ object MMSegUtil { // str[1:-1], the last len(str)-1 characters def tail(str: String): Array[Char] = { str.toCharArray.takeRight(str.length - 1) } // load user-define word dictionary def loadWord(path: String, dic: java.util.Map[Character, CharNode]) = { val fs = FileSystem.get(new Configuration) val in: FSDataInputStream = fs.open(new Path(path)) Source.fromInputStream(in).getLines() .filter(_.length > 1) .foreach { line => val cn: CharNode = dic.get(line.charAt(0)) cn match { case null => dic.put(line.charAt(0), cn) case _ => cn.addWordTail(tail(line)) } } } }
便可在Spark程序中调用分词:ui
val dictionary = Dictionary.getInstance() MMSegUtil.loadWord(dicPath, dictionary.getDict) val seg = new ComplexSeg(dictionary)
值得指出,ComplexSeg类有List remove操做,于是不适于作成广播变量,否则则报ConcurrentModificationException。推荐的作法,配合RDD的mapPartitions在for yield外层new ComplexSeg;至关于每一个Partition都有一个ComplexSeg。.net
mmseg4j存在分词不许确的状况,好比,『培养并发挥热忱的特性』被分词成『培养/并发/挥/热忱/的/特性』。这是由于mmseg4j的chars词典中,挥 20429
的词频高于并 2789
(针对于MMSeg的规则4)。基于词典的分词方案的准确性,严重依赖于词典;必需要有好的词典,才会有好的分词结果。