最近碰到一個分詞匹配需求——給定一個關鍵詞表,做爲自定義分詞詞典,用戶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)。基於詞典的分詞方案的準確性,嚴重依賴於詞典;必需要有好的詞典,纔會有好的分詞結果。