IK分词器原理
IKAnalyzer是一個開源的,基于Java語言開發的輕量級的中文分詞語言包,它是以Lucene為應用主體,結合詞典分詞和文法分析算法的中文詞組組件。從3.0版本開始,IK發展為面向java的公用分詞組件,獨立Lucene項目,同時提供了對Lucene的默認優化實現。
以下這篇博客是我在通讀IK分詞代碼后對IK分詞的理解,如果有什么地方出現紕漏請大家指正。
回到最初
如果讓我自己在看IK分詞之前自己寫一個分詞器,那我會怎么做? 如果我會想到的是:比如對"周末有歐冠決賽"做分詞處理,人為的分詞是:“周末|有|歐冠|決賽”,那為什么會出現這樣的分詞效果,首先必須有一個詞典,詞典中可以標記出“周末”、“歐冠”、“決賽”這些詞,因為分詞器并不具備人工智能的能力,所以它所做的就是把文本和詞典中的詞進行比較,按照詞典中已有的詞進行匹配,匹配完成之后即達到了分詞的效果。
現在雖然找不到第一個寫分詞的人是如何設計分詞的算法的,但是我想是差不多的。
K分詞其實也是這么來做的。將一段文字進行IK分詞處理一般經過:詞典加載、預處理、分詞器分詞、歧義處理、善后結尾 五個部分。
詞典加載:
主程序需要加載詞典。IK分詞的詞典主要包括 main2012.dic(主詞典)、quantifier.dic(量詞)、stopword.dic(停用詞)、ext.dic(擴展詞,可選)四個字典。字典的結構使用字典樹進行存儲。關于字典樹的具體介紹可以參考:?http://www.cnblogs.com/rush/archive/2012/12/30/2839996.html
預處理:
預處理是在加載文檔的時候做的,其實預處理部分只是做兩件事情:
- 識別出每個字符的字符類型
- 字符轉化:全角轉半角,大寫轉小寫
字符類型是在分詞的時候需要,不同的分詞器會根據不同的字符類型做特別處理。而字符轉化則是要求文本字符與詞典中的字符想匹配,比如對于12288、32這兩個字符,分別是漢字和英文的空格,如果一篇文本中包含了漢字包含了這兩個字符,那么分詞器在分詞的時候需要判斷兩個都是空格。同理在英文大小寫上也存在類似的問題。預處理主要在org.wltea.analyzer.core.CharacterUtil.java類中實現。
package org.wltea.analyzer.core;import java.lang.Character.UnicodeBlock;class CharacterUtil {public static final int CHAR_USELESS = 0;public static final int CHAR_ARABIC = 1;public static final int CHAR_ENGLISH = 2;public static final int CHAR_CHINESE = 4;public static final int CHAR_OTHER_CJK = 8;CharacterUtil() {}static int identifyCharType(char input) {if(input >= 48 && input <= 57) {//數字0-9return 1;} else if(input >= 97 && input <= 122 || input >= 65 && input <= 90) {//a-z,A-Zreturn 2;} else {UnicodeBlock ub = UnicodeBlock.of(input);return ub != UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS && ub != UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS && ub != UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A?(ub != UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS && ub != UnicodeBlock.HANGUL_SYLLABLES && ub != UnicodeBlock.HANGUL_JAMO && ub != UnicodeBlock.HANGUL_COMPATIBILITY_JAMO && ub != UnicodeBlock.HIRAGANA && ub != UnicodeBlock.KATAKANA && ub != UnicodeBlock.KATAKANA_PHONETIC_EXTENSIONS?0:8):4;}}static char regularize(char input, boolean lowercase) {if(input == 12288) {//全角空格input = 32;//半角空格} else if(input > '\uff00' && input < '⦅') {input -= 'ﻠ';} else if(input >= 65 && input <= 90 && lowercase) {input = (char)(input + 32);}return input;} }分詞器分詞:
IK分詞包括三個分詞器:LetterSegmenter(字符分詞器),CN_QuantifierSegmenter(中文數量詞分詞器),CJKSegmenter(中日韓文分詞器)。分詞器分詞有兩種模式,即:smart模式和非smart模式,非smart模式可以認為是最小力度的分詞,smart模式則反之。
具體的實例:張三說的確實在理smart模式的下分詞結果為: 張三 | 說的 | 確實 | 在理而非smart模式下的分詞結果為:張三 | 三 | 說的 | 的確 | 的 | 確實 | 實在 | 在理IK分詞使用了”正向迭代最細粒度切分算法“,簡單說來就是: Segmenter會逐字識別詞元,設輸入”中華人民共和國“并且”中“單個字也是字典里的一個詞,那么過程是這樣的:”中“是詞元也是前綴(因為有各種中開頭的詞),加入詞元”中“;繼續下一個詞”華“,由于中是前綴,那么可以識別出”中華“,同時”中華“也是前綴因此加入”中華“詞元,并把其作為前綴繼續;接下來繼續發現“華人”是詞元,“中華人”是前綴,以此類推……
關于IK分詞的三個分詞器,基本上邏輯是一樣的,如果有興趣了解的話,重點看一下CJKSegmenter就好了。
歧義處理:
非smart模式所做的就是將能夠分出來的詞全部輸出;smart模式下,IK分詞器則會根據內在方法輸出一個認為最合理的分詞結果,這就涉及到了歧義判斷。
以“張三說的確實在理”為依據,分詞的結果為:
張三 | 三 | 說的 | 的確 | 的 | 確實 | 實在 | 在理在判斷歧義的時候首先要看詞與詞之間是否用沖突,“張三”與“三”相沖突,“說的”,“的確”,“的”,“確實”,“實在”,“在理”相沖突,所以分成兩部分進行歧義判斷。我們以后一種為例:“說的”,“的確”,“的”,“確實”,“實在”, 按照默認的生成的一組結果為“說的|確實|在理”,與這個結果相沖突的詞是“的確”、“的”、“實在”。回滾生成的結果,將這三個詞倒敘逐一放入到生成的結果中,生成可選分詞方案是:
"實在"放入后,分詞方案為:"說的|實在|"; "的"放入后,生成的分詞方案為:"的|確實|在理"; "的確"放入后,生成的分詞方案為:"的確|實在";這樣加上原有的一共四種可選的分詞方案,從中選取最優的方案。選取的原則順序如下:
1、比較有效文本長度 2、比較詞元個數,越少越好 3、路徑跨度越大越好 4、最后一個詞元的位置越靠后約好(根據統計學結論,逆向切分概率高于正向切分,因此位置越靠后的優先) 5、詞長越平均越好 6、詞元位置權重比較總體說來就是: 主要使用的就是貪心算法獲取局部最優解,然后繼續處理來獲取最終的結果。
善后結尾:
- 處理遺漏中文字符,遍歷輸入文本,把詞之間每個中文字符也作為詞輸出,雖然詞典中沒有這樣的詞。
- 處理數量詞,如果中文量詞剛好出現在中文數詞的后面,或者中文量詞剛好出現在阿拉伯數字的后面,則把數詞和量詞合并。比如"九寸""十二畝"。
其它
Ik的排序方法特別粗略,如果比較發現path1的詞個數,比path2的個數少,就直接判定path1更優。其實這樣的規則,并未完整的參考各個分出來的詞的實際情況,我們可能想加入每個詞經統計出現的頻率等信息,做更全面的打分,這樣IK原有的比較方法就是不可行的。
總結
- 上一篇: jQuery复选框多选问题
- 下一篇: springboot指定首页(静态资源导