IK分詞器在是一款 基於詞典和規則 的中文分詞器。本文講解的IK分詞器是獨立於elasticsearch、Lucene、solr,可以直接用在java代碼中的部分。關於如何開發es分詞插件,后續會有文章介紹。
IK分詞器的源碼:Google Code,直接下載請點擊這里。
一、兩種分詞模式
IK提供兩種分詞模式:智能模式和細粒度模式(智能:對應es的IK插件的ik_smart,細粒度:對應es的IK插件的ik_max_word)。
先看兩種分詞模式的demo和效果
|
輸出如下:
非智能分詞結果:ik analyzer 是 一個 一 個 結合 詞典 分詞 和文 文法 分詞 的 中文 分詞 開源 工具包 工具 包 它 使用 用了 全新 的 正向 迭代 最 細粒度 細粒 粒度 切分 切 分 算法
----------------------------分割線------------------------------
智能分詞結果:ik analyzer 是 一個 結合 詞典 分詞 和 文法 分詞 的 中文 分詞 開源 工具包 它 使 用了 全新 的 正向 迭代 最 細粒度 切分 算法
可以看到:細粒度分詞,包含每一種切分可能,而智能模式,只包含各種切分路徑中最可能的一種。
二、源碼概覽
根據文章開頭提供的鏈接下載源碼在idea中打開后,目錄結構如下
我們只需要關注cfg,core,dic三個包。
把lucene,query,sample,solr四個包下的代碼注釋掉,這幾個包的代碼是封裝IKSegmenter,適配其他類庫分詞器的接口(lucene,solr),我們在此無需關注。
cfg包括IK的配置接口以及默認配置類,
core包括了IK的分詞器接口ISegmenter,分詞器核心類IKSegmenter,語義單元類Lexeme,上下文AnalyzeContext,以及子分詞器LetterSegementer(英文字符子分詞器),CN_QuantifierSegmenter(中文量詞子分詞器),CJKSegmenter(中日韓字符分詞器),
dic包括了詞典類Dictionary,詞典樹分段類DictSegmenter,用來記錄詞典匹配命中記錄的類Hit,以及主詞典main2012.dic和中文量詞詞典quantifier.dic
三、詞典
目前,IK分詞器自帶主詞典擁有27萬左右的漢語單詞量。此外,對於分詞組件應用場景所涉及的領域不同,需要各類專業詞庫的支持,為此IK提供了對擴展詞典的支持。同時,IK還提供了對用戶自定義的停止詞(過濾詞)的擴展支持。
1.詞典的初始化
在分詞器IKSegmenter首次實例化時,默認會根據DefaultConfig找到主詞典和中文量詞詞典路徑,同時DefaultConfig會根據classpath下配置文件IKAnalyzer.cfg.xml,找到擴展詞典和停止詞典路徑,用戶可以在該配置文件中配置自己的擴展詞典和停止詞典。
找到個詞典路徑后,初始化Dictionary.java,Dictionary是單例的。在Dictionary的構造函數中加載詞典。Dictionary是IK的詞典管理類,真正的詞典數據是存放在DictSegment中,該類實現了一種樹結構,如下圖。
舉個例子,要對字符串“A股市場”進行分詞,首先拿到字符串的第一個字符'A',在上面的tree中可以匹配到A節點,然后拿到字符串第二個字符'股',首先從前一個節點A往下找,我們找到了股節點,股是一個終點節點。所以,“A股“是一個詞。
Dictionary加載主詞典,以,將主詞典保存到它的_MainDict字段中,加載完主詞典后,立即加載擴展詞典,擴展詞典同樣保存在_MainDict中。
|
fillSegment方法是DictSegment加載單個詞的核心方法,charArray是詞的字符數組,先是從存儲節點搜索詞的第一個字符,如果不存在則創建一個節點用於存儲第一個字符,后面遞歸存儲,直到最后一個字符。
|
停止詞和數量詞同樣的加載方法。參考Dictionary中loadStopWordDict()和loadQuantifierDict()方法。
tips,熱詞更新:
當詞典初始化完畢后,可以調用Dictionary的addWords(Collection<String> words)方法往主詞典_MainDict添加熱詞。
|
四、基於詞典的切分
上面提到,主詞典加載在Dictionary的_MainDict字段(DictSegment類型)中,
創建IKSegmenter時,需要傳進來一個Reader實例,IK分詞時,采用流式處理方式。
在IKSegmenter的next()方法中,首先調用AnalyzeContext.fillBuffer(this.input)從Reader讀取8K數據到到segmentBuff的char數組中,然后調用子分詞器CJKSegmenter(中日韓文分詞器),CN_QuantifierSegmenter(中文數量詞分詞器),LetterSegmenter(英文分詞器)的analyze方法依次從頭處理segmentBuff中的每一個字符。
LetterSegmenter.analyze():英文分詞器邏輯很簡單,從segmentBuff中遇到第一個英文字符往后,直到碰到第一個非英文字符,這中間的所有字符則切分為一個英文單詞。
CN_QuantifierSegmenter.analyze():中文量詞分詞器處理邏輯也很簡單,在segmentBuff中遇到每一個中文數量詞,然后檢查該數量詞后一個字符是否未中文量詞(根據是否包含在中文量詞詞典中為判斷依據),如是,則分成一個詞,如否,則不是一個詞。
|
CJKSegmenter.analyze則比較復雜一些,拿到第一個字符,調用Dictionary.matchInMainDict()方法,實際就是調用_MainDict.match()方法,在主詞典的match方法中去匹配,首先判斷該字能否單獨成詞(即判斷_MainDict中該詞所在第一個層的節點狀態是否為1),如果能則加入上下文中保存起來。然后再判斷該詞是否可能為其他詞的前綴(即判斷_MainDict中該詞所在第一層節點是否還有子節點),如果是則保存在分詞器的臨時字段tmpHits中。
再往后拿到segmentBuff中第二個字符,首先判斷該詞是否存在上一輪保存在temHits中的字符所在節點的子節點中,如果存在則判斷這兩個字符能否組成完整的詞(同樣,依據字符節點的狀態是否為1來判斷),如果成詞,保存到上下文中,並且繼續判斷是否可能為其他詞的前綴(還是判斷該字符節點是否還有子節點),如果有,繼續保存到tmpHits中,如果沒有,則拋棄。然后再講該字符重復與第一個字符一樣的操作即可。
|
當子分詞器處理完segmentBuff中所有字符后,字符的所有成詞情況都已保存到上下文的orgLexemes字段中。
調用分詞歧義裁決器IKArbitrator,如果分詞器使用細粒度模式(useSmart=false),則裁決器不做不做歧義處理,將上下文orgLexemes字段中所有成詞情況全部保存到上下文pathMap中。
然后調用context.outputToResult()方法根據pathMap中的成詞情況,將最終分詞結果保存到上下文的result字段中。
至此,segmentBuff中所有字符的分詞結果全部保存在result中了,通過IKSegmenter.next()方法一個一個返回給調用者。
當next方法返回result所有分詞后,分詞器再從Reader中讀取下一個8K數據到segmentBuff中,重復上述所有步驟,直至Reader全部讀取完畢。
五、基於規則的歧義判斷
分詞裁決器IKArbitrator只有在Smart模式才會生效。
judge是IKArbitrator處理分詞歧義的方法。
裁決器從上下文orgLexemes讀取所有的成詞,判斷有交叉(有交叉即表示分詞有歧義)的成詞,然后,遍歷每一種不交叉的情況,用LexemePath對象表示,然后保存到自定義有序鏈表TreeSet中,最后first()取出鏈表第一個元素,即為最佳分詞結果。
|
然后我們看一下LexemePath對象的比較規則,即為IK的歧義判斷規則,LexemePath實現了Comparable接口。
從compareTo方法可以得出,IK歧義判斷規則如下,優先級從上到下一致降低:
1.分詞文本長度越長越好
2.分詞個數越少越好
3.分詞路徑跨度越大越好
4.分詞位置越靠后的優先
5.詞長越平均越好
6.詞元位置權重越大越好(這個我也沒明白,先這樣,后面有需要再弄明白具體細節)
|
六、總結
總的來說,IK分詞是一個基於詞典的分詞器,只有包含在詞典的詞才能被正確切分,IK解決分詞歧義只是根據幾條可能是最佳的分詞實踐規則,並沒有用到任何概率模型,也不具有新詞發現的功能。
原文地址:https://blog.csdn.net/jiandabang/article/details/83539783