ansj中文分詞分詞過程淺析

nsj詞典加載及簡要分詞過程 粗略看了點ansj源代碼,記錄備忘。   詞典等配置文件加載類 (以調用 NlpAnalysis 分詞類爲例): 1,   MyStaticValue 初始化 NlpAnalysis 中 static 的靜態變量 spli
ansj詞典加載及簡要分詞過程

粗略看了點ansj源代碼,記錄備忘。

 

詞典等配置文件加載類(以調用NlpAnalysis分詞類爲例):

1,  MyStaticValue

初始化NlpAnalysis中static的靜態變量splitword時調用了MyStaticValue類,該類中幾乎所有變化、方法均是靜態的。包括以ResourceBundle.getBundle("library")獲取library.properties配置文件,讀取用戶詞典路徑、歧義詞典路徑、是否用戶辭典不加載相同的詞isSkipUserDefine、isRealName。並讀取resources目錄下的company、person、newword、nature(詞性表、詞性關聯表)等文件夾中的數據,及resources目錄下bigramdict.dic(bi-gram模型)、英文詞典englishLibrary.dic、數字詞典numberLibrary.dic,以及加載crf模型。

2DATDictionary

在MyStaticValue讀取數據中,如加載bigramdict.dic時,會根據詞調用DATDictionary.getItem獲取AnsjItem,而在DATDictionary類中包含靜態變量DAT,加載core.dic這個核心詞典,構造雙數組trie樹

3UserDefineLibrary

NlpAnalysis的父類Analysis中定義成員ambiguityForest,初始化爲UserDefineLibrary.ambiguityForest。UserDefineLibrary類中均爲靜態方法。該類加載配置文件中userLibrary項對應路徑的詞典,包括用戶詞典,及歧義詞典。均加載爲Forest。

 

 

 

各詞典內容及加載:

core.dic爲構造雙數組trie樹,森林。

DATDictionary.loadDAT()中加載,返回DoubleArrayTire。(有限狀態的自動機。每個節點代表自動機的一個狀態,根據變量的不同,進行狀態轉移,當到達結束狀態或者無法轉移的時候,完成查詢DoubleArrayTire.getItem

理解雙數組:1是base-index,2是check-index。base用於確定狀態的轉移,check用於檢驗轉移的正確性

11萬。第一行是樹大小。

列:index(詞id),name(詞),base,check,status,{詞性=詞頻,詞性=詞頻….}

Index是dat數組的下標,對於字,是字符的ascii

name不一定是一個詞,也可能是詞的前綴

base默認爲65536(2的16次方)。詞的index爲前綴詞的base+末字。如index(泰晤士報)=base(泰晤士)+‘報’。65536表示爲葉子節點

check是詞由哪個詞轉換過來的,即前綴。如公因數、公因式的check爲118193,而118193爲公因的id。而公因的check爲20844,爲公的id。單字爲-1

status是當前單詞的狀態。status>1時用index、詞性詞頻構成詞。詞的默認詞性爲詞頻最大的詞性。IN_SYSTEM中只保存status<4的詞,status<2的詞name被賦爲null。status爲各個值的意義,見Analysis.analysis方法及以下詞典文件:1爲詞性詞頻爲null的字、詞,不能單獨存在,應繼續;4爲圓半角英文字母及';5爲數字、小數點、百分號;2、3爲詞,其中2表示是個詞但是還可以繼續,3表示停止已經是個詞了。

core詞典參考附件

 

人名標註先後加載person/person.dicperson/asian_name_freq.data

人名加載在DATDictionary.loadDAT()方法中僅次於讀取何鑫詞典生成雙數組trie樹執行。PersonAttrLibrary調用MyStaticValue加載。兩者加載在同一個map中,key爲詞,value爲PersonNatureAttr。兩個pna不同。前者的pna調用addFreq設置begin、end、split、allFreq,後者的pna調用setlocFreq設置詞在某一長度的詞中某一位置的詞頻。加載完後若詞的長度爲1且不在dat中,將其添加到dat中

person.dic詞語的上下文。格式列:詞,index,freq。index取值爲11(人名的下文),12(兩個中國人名之間的成分),44(可拆分的姓名)

asian_name_freq.data(對象反序列化)字體位頻率表。初始文件加載結果爲一個map,key爲詞,value爲大小爲3的數組,各元素分別爲大小爲2、3、4的數組。分別表示在2字人名、3字人名、4字人名中第1-2,1-3,1-4個位置出現的概率。參考http://ansjsun.iteye.com/blog/1678483

 

 

詞性及詞性標註

nature/nature.map四列,index,詞性index,詞性,詞性allfreq。其中所列的詞性比下表中提供的詞性少,僅有其中的一類和部分二類詞性(也並非子集)。

詞性表參考附件

 

nature/nature.table是詞性關聯表。行數(50行)等同於nature.map中的行數,並且與nature.map相對應,即每行表示的詞性同nature.map中的詞性。每行中有50個列,即構成50*50的矩陣,每個(i,j)位置的數值表示從前一個詞的詞性i變化到下一個詞的詞性j的發生頻次。用在詞性標註工具類NatureRecognition

詞性標註:NatureRecognition.recognition()。以傳入的分詞結果構造NatureRecognition對象,其中成員natureTermTable爲二維數組,每行表示當前詞的詞性數組NatureTerm[]。

 

bigramdict.dic

詞關聯表。Bi-Gram。詞典中爲詞與詞之間的關聯數據,@前爲from,後爲to及詞頻。

Bi-Gram,是二元的N-Gram(漢語語言模型,又稱爲一階馬爾科夫鏈。該模型基於這樣一種假設,第n個詞的出現只與前面N-1個詞相關,而與其它任何詞都不相關)。計算出每種分詞後句子出現的概率,並找出其中概率最大的,即爲最好的分詞方法。

 

用戶自定義詞典:library.properties中配置,MyStaticValue加載配置文件,由UserDefineLibrary根據詞典路徑加載詞典。用戶詞典可以爲目錄,其中的詞典必須後綴名爲dic,如果設置了MyStaticValue.isSkipUserDefine,且在覈心詞典中存在該詞,則跳過;若當前行只有列數不爲3,以默認詞性userDefine/詞頻1000構造。Library.insertWord(forest, value)加載用戶自定義詞典:添加過程:初始branch指向forest。每個字生成一個branch,除最後一個字生成的branch的status爲3,參數爲resultParams外,其餘的branch的status爲1,參數爲null。在當前字branch的子節點branches中二分查找下一個字(其中branches是字典序排列的),若查找失敗,添加到合適位置上,否則更新當前branch的status(1繼續,2是個詞語但是還可以繼續,3確定nature)

 

歧義詞典ambiguity.dic

加載過程中不負責解析歧義,只區分原詞temp和剩下所有的詞resultParams(數組),逐字添加temp到forest中。歧義詞典只有一個文件。添加temp的過程爲同添加用戶自定義詞典的過程,調用Library.insertWord。歧義詞典中的詞性不是很重要,但是需要。詞性也可是null,若是null,分詞後該詞無詞性標註。歧義詞典中也可以只有一個詞,定義一定要分的詞,如“江湖上         江湖上     n”,這種情況下“江湖上”是比分的。要慎用。

 

 

 

crf模型:crf/crf.model

由MyStaticValue調用。該model文件是由crf++生成的明文模型調用GZIPOutputStream壓縮過後的文件。Model.writeModel執行。Model.loadModel調用GZIPInputStream執行解壓

CRF++模型文件model

 

模型格式參考:http://www.hankcs.com/nlp/the-crf-model-format-description.html(CRF模型格式)

 

CRFModel.parseFile讀取模型文件。依次讀取文件頭【其中maxid爲特徵數,即特徵權值的行數】、標籤【statusMap存儲輸出標籤,即狀態。tagNum爲標籤數,若爲BEMS則爲4】、模板【同crf++訓練是的template文件中的內容,去掉空行。Template.parse解析模板,返回一個Template t,其ft變量爲二維數組,其大小爲模板的行數,t.ft[index] = ints,其中index爲每行模板的編號,ints爲數組,其值爲對應的行號(代碼中爲空格分隔,且與列位置無關)。如“U05:%x[-2,0]/%x[-1,0]/%x[0,0]”,t.ft[5] = ints[-2,-1,0],t.left、t.right分別爲ints中數值的最小值與最大值】、特徵函數【每行是一個TempFeature。如“107540 U05:一/方/面”,id爲107540/tagNum,featureId爲5,name爲詞,即“一方面”。其中id爲16開始,而0-15爲BEMS轉移到BEMS的轉移函數,id也不是連續的,而是隔了tagNum個】、特徵函數權值【依id順序對應每個特徵函數的權值,前16行爲轉移函數權值,用二維數組status表示。隨後返回map,myGrad,key爲詞,value爲Feature。連續的4個權值對應一個特徵函數BEMS狀態下的權值,例如第17-20行權值對應id爲16的特徵函數。每讀一行權重,更新Feature的value、w,其中value爲4個權重累加,w爲二維數組,大小同t.ft的大小,w[fIndex][sta],fIndex對應TempFeature中的featureId,sta爲0-tagNum-1,相同的fIndex和sta權重累加,即若存在相同的特徵函數和輸出狀態,權重累加】。

SplitWord(Model model)根據statusMap標籤構造SplitWord對象

 

 

分詞過程Analysis.analysisStr

<!--[if !supportLists]-->1,  <!--[endif]-->構造最短路徑圖Graph

<!--[if !supportLists]-->2,  <!--[endif]-->判斷是否啓用歧義詞典。若是,找出句子中是否包含歧義詞。若不存在,對整個句子調用Analysis.analysis;若存在,優先歧義詞:以歧義詞分隔原句子,根據歧義分詞數組中的詞及詞性逐個添加到graph中,並對非歧義詞的部分分別調用Analysis.analysis。Analysis.analysis的過程爲按字從DAT中找,通過GetWordsImpl.allWords()查詢字在DAT中的base、check等獲得狀態返回單字或詞,調用graph.addTerm添加節點到graph的terms數組中,同時標註是否爲數字,英文

 

以下例子:“讓戰士們過一個歡樂祥和的新春佳節”,添加完後terms爲如圖1所示

      而以下例子:“讓戰士們過一個闔家歡樂的新春佳節”,添加完後terms爲如圖2所示

 


 

原因是闔、闔家在core中的status均爲1,認爲不是詞應繼續;而歡、祥、戰等的status爲2,認爲是詞,只是可以繼續

<!--[if !supportLists]-->1,  <!--[endif]-->調用getResult(graph)獲取分詞結果。各粒度的分詞結果區別就在於該方法,analysisStr都沒有重寫,都是調用父類的。各重寫的getResult(graph)方法中均定義Merger類,包含merger()和getResult()方法,代爲獲取分詞結果。Merger中首先調用graph.walkPath()遍歷打分。 (官方說明:N最短路徑的粗切分,根據隱馬爾科夫模型和viterbi算法,達到最優路徑的規劃)

graph.walkPath()計算過程:從根節點開始,依次獲取terms中各個節點,對其各個前置節點,分別計算分值,取分值最小的設置爲其from節點。其中該分值表示爲從from節點到當前節點的可能性,計算該分值在MathUtil.compuScore中,(hmm/viterbi算法:轉移概率+表現概率),爲from節點的score+當前value,而當前value由from所屬詞性的頻率及bigramdic中設定的from到當前詞的關聯數值等決定。隨後調用optimalRoot()根據路徑從後往前修改terms數組,將不在路徑上的term設爲null,非null的依次就是該句的分詞結果

BaseAnalysis的分詞過程極爲以上的過程

    ToAnalysis多支持了用戶詞典、數字、人名的識別。在Merger.merger()中增加了NumRecognition.recognition()、AsianPersonRecognition().recognition()、userDefineRecognition等

其中userDefineRecognition是在基礎分詞步驟3的基礎上,遍歷所生成的terms數組中的詞,根據詞是否結束,即狀態1-3,識別是不是在用戶自定義詞典中。若自定義詞生效,即需要更新terms數組。具體步驟:

1UserDefineRecognition().recognition()執行後找出了句子中在用戶自定義詞典中的詞,在對應詞的位置生成新的節點term,爲原term的next節點

2graph.rmLittlePath()匹配最長的term,執行後若無交叉,以最長匹配更新terms中的詞,否則暫不修改

3graph.walkPathByScore()mergerByScore對節點遍歷打分。打分類似於walkPath(),區別在於walkPath()計算分值時使用了viterbi算法,而該方法僅考慮了詞頻。當前詞的分值爲本詞的負詞頻與from詞的分值之和。由此分值往後傳遞。詞頻高的詞優於詞頻低的詞就在這體現。執行後的結果是根據最優路徑修改各詞的from節點和分值

以下例子:"上海電力怎爸爸去哪兒麼辦",原分詞結果爲[上海/ns,電力/n, 怎/r, 爸爸/n, 去/v, 哪兒/r, 麼/y,辦/v],若添加用戶詞“爸爸去哪兒”,該詞生效,其中term“爸爸去哪兒”爲term“爸爸”的next節點,分詞結果爲“上海/電力/怎/爸爸去哪兒/麼/辦”;若用戶詞爲“怎爸”,不能生效;甚至用戶詞爲“爸爸去哪”,也不能生效,雖然能識別出“爸爸”和“去”,但是“哪兒”不在用戶詞典所創建的樹中。若用戶詞包含“爸爸去哪兒”和“去哪兒了呢”,且前者詞頻高於後者,前者被分出來;否則後者被分出來,詞頻相同時根據從後往前原理,也是後者優先。

IndexAnalysisToAnalysis類Merger的merger方法相同,區別在於Merger的getResult方法,後者僅移除terms數組中爲null的term,而前者針對長度大於等於3的詞,還會調用GetWordsImpl.allWords()進行一次分詞,將其中長度超過1的詞也添加到terms數組中

NlpAnalysisToAnalysis的區別在於它在標準分詞的基礎上會進行詞性調整NatureRecognition.recognition(),並引入了crf模型來分詞,以及增加了新詞發現LearnTool等功能

其中詞性標註NatureRecognition.recognition(),標準分詞結果中的詞性是取得core詞典中該詞freq最高的詞性,而該方法會對所有的詞性比較,計算各個詞性到後一個詞詞性的可能性,該可能性與nature.table中定義的詞性相關性及詞性本身的頻率有關,計算見MathUtil.compuNatureFreq。將計算結果最大的設爲後一個詞性的from詞性。

LearnTool.learn方法中只有對亞洲和外國人名的識別,沒有其他功能。

 

 

其他輔助類及輔助方法:

GetWordsImpl.getStatement 0.代表這個字不在詞典中 1.繼續 2.是個詞但是還可以繼續 3.停止已經是個詞了。

 

WordAlert是字符串規範的轉換類

MathUtil是計算的類,包括計算兩個詞性之間的分數(NatureLibrary.getTwoNatureFreq,根據NATURETABLE),兩個詞之間的分數(NgramLibrary.getTwoWordFreq,NgramLibrary中加載)等

 

Analysis.setRealName,可能是分詞過程中將部分詞進行了標準化,比如繁體轉簡體,%等(見DATDictionary.IN_SYSTEM),該方式是返回原句的分詞。但是測試“%”始終返回的是原詞

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章