日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 >

HanLP二元核心词典详细解析

發(fā)布時間:2025/6/15 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 HanLP二元核心词典详细解析 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

2019獨(dú)角獸企業(yè)重金招聘Python工程師標(biāo)準(zhǔn)>>>

本文分析:HanLP版本1.5.3中二元核心詞典的存儲與查找。當(dāng)詞典文件沒有被緩存時,會從文本文件CoreNatureDictionary.ngram.txt中解析出來存儲到TreeMap中,然后構(gòu)造start和pair數(shù)組,并基于這兩個數(shù)組實(shí)現(xiàn)詞共現(xiàn)頻率的二分查找。當(dāng)已經(jīng)有緩存bin文件時,那直接讀取構(gòu)建start和pair數(shù)組,速度超快。

源碼實(shí)現(xiàn)

二元核心詞典的加載

二元核心詞典在文件:CoreNatureDictionary.ngram.txt,約有46.3 MB。程序啟動時先嘗試加載CoreNatureDictionary.ngram.txt.table.bin 緩存文件,大約22.9 MB。這個緩存文件是序列化保存起來的。

?ObjectInputStream in = new ObjectInputStream(IOUtil.newInputStream(path));

?start = (int[]) in.readObject();

?pair = (int[]) in.readObject();

當(dāng)緩存文件不存在時,拋出異常:警告: 嘗試載入緩存文件E:/idea/hanlp/HanLP/data/dictionary/CoreNatureDictionary.ngram.txt.table.bin發(fā)生異常[java.io.FileNotFoundException: 然后解析CoreNatureDictionary.ngram.txt

?

br = new BufferedReader(new InputStreamReader(IOUtil.newInputStream(path), "UTF-8"));

while ((line = br.readLine()) != null){

????String[] params = line.split("\\s");

????String[] twoWord = params[0].split("@", 2);

????...

}

然后,使用一個TreeMap<Integer, TreeMap<Integer, Integer>> map來保存解析的每一行二元核心詞典條目。

?

TreeMap<Integer, TreeMap<Integer, Integer>> map = new TreeMap<Integer, TreeMap<Integer, Integer>>();

?

int idA = CoreDictionary.trie.exactMatchSearch(a);//二元接續(xù)的 @ 前的內(nèi)容

int idB = CoreDictionary.trie.exactMatchSearch(b);//@ 后的內(nèi)容

?

TreeMap<Integer, Integer> biMap = map.get(idA);

if (biMap == null){

????biMap = new TreeMap<Integer, Integer>();

????map.put(idA, biMap);//

}

biMap.put(idB, freq);

比如二元接續(xù):“一 一@中”,@ 前的內(nèi)容是:“一 一”,@后的內(nèi)容是 “中”。由于同一個前綴可以有多個后續(xù),比如:

?

一一@中 1

一一@為 6

一一@交談 1

所有以 '一 一' 開頭的 @ 后的后綴 以及對應(yīng)的頻率 都保存到 相應(yīng)的biMap中:biMap.put(idB, freq);。注意:biMap和map是不同的,map保存整個二元核心詞典,而biMap保存某個詞對應(yīng)的所有后綴(這個詞 @ 后的所有條目)

?

map中保存二元核心詞典示意圖如下:

二元核心詞典主要由CoreBiGramTableDictionary.java 實(shí)現(xiàn)。這個類中有兩個整型數(shù)組 支撐 二元核心詞典的快速二分查找。

?

????/**

?????* 描述了詞在pair中的范圍,具體說來<br>

?????* 給定一個詞idA,從pair[start[idA]]開始的start[idA + 1] - start[idA]描述了一些接續(xù)的頻次

?????*/

????static int start[];//支持快速地二分查找

????/**

?????* pair[偶數(shù)n]表示key,pair[n+1]表示frequency

?????*/

????static int pair[];

start 數(shù)組

首先初始化一個與一元核心詞典Trie樹 size 一樣大小 的start 數(shù)組:

?

int maxWordId = CoreDictionary.trie.size();

...

start = new int[maxWordId + 1];

然后,遍歷一元核心詞典中的詞,尋找這些詞是 是否有二階共現(xiàn)(或者說:這些詞是否存在 二元接續(xù))

?

for (int i = 0; i < maxWordId; ++i){

????TreeMap<Integer, Integer> bMap = map.get(i);

????if (bMap != null){

????????for (Map.Entry<Integer, Integer> entry : bMap.entrySet()){

????????????//省略其他代碼

????????????++offset;//統(tǒng)計(jì)以 這個詞 為前綴的所有二階共現(xiàn)的個數(shù)

????????}

????}//end if

????start[i + 1] = offset;

}// end outer for loop

if (bMap != null)表示 第 i 個詞(i從下標(biāo)0開始)在二元詞典中有二階共現(xiàn),于是 統(tǒng)計(jì)以 這個詞 為前綴的所有二階共現(xiàn)的個數(shù),將之保存到 start 數(shù)組中。下面來具體舉例,start數(shù)組中前37個詞的值如下:

其中start[32]=0,start[33]=0,相應(yīng)的 一元核心詞典中的詞為 ( )。即,一個左括號、一個右括號。而這個 左括號 和 右括號 在二元核心詞典中是不存在詞共現(xiàn)的(接續(xù))。也就是說在二元核心詞典中 沒有 (@xxx這樣的條目,也沒有 )@xxx 這個條目(xxx 表示任意以 ( 或者 ) 為前綴 的后綴接續(xù))。因此,這也是start[32] 和 start[33]=0 都等于0的原因。

?

部分詞的一元核心詞典如下:

?

再來看 start[34]=22,start[35]=23。在一元核心詞典中,第34個詞是"一 一",而在二元核心詞典中 '一 一'的詞共現(xiàn)共有22個,如下:

?

?

在一元核心詞典中,第35個詞是 "一 一列舉",如上圖所示,"一 一列舉" 在二元核心中只有一個詞共現(xiàn):“一 一列舉@芒果臺”。因此,start[35]=22+1=23。從這里也可以看出:

給定一個詞idA,從pair[start[idA]]開始的start[idA + 1] - start[idA]描述了一些接續(xù)的頻次

比如,idA=35,對應(yīng)詞“一 一列舉”,它的接續(xù)頻次為1,即:23-22=1

這樣做的好處是什么呢?自問自答一下:^~^,就是大大減少了二分查找的范圍。

pair 數(shù)組

pair數(shù)組的長度是二元核心詞典行數(shù)的兩倍

?

int total = 0;

while ((line = br.readLine()) != null){

????//省略其他代碼

????total += 2;

}

pair數(shù)組 偶數(shù) 下標(biāo) 存儲 保存的是 一元核心詞典中的詞 的下標(biāo),而對應(yīng)的偶數(shù)加1 處的下標(biāo) 存儲 這個詞的共現(xiàn)頻率。即: pair[偶數(shù)n]表示key,pair[n+1]表示frequency

?

????????????pair = new int[total]; ?// total是接續(xù)的個數(shù)*2

?

????????????for (int i = 0; i < maxWordId; ++i)

????????????{

????????????????TreeMap<Integer, Integer> bMap = map.get(i);//i==0?

????????????????if (bMap != null)//某個詞在一元核心詞典中, 但是并沒有出現(xiàn)在二元核心詞典中(這個詞沒有二元核心詞共現(xiàn))

????????????????{

????????????????????for (Map.Entry<Integer, Integer> entry : bMap.entrySet())

????????????????????{

????????????????????????int index = offset << 1;

????????????????????????pair[index] = entry.getKey();//詞 在一元核心詞典中的id

????????????????????????pair[index + 1] = entry.getValue();//頻率

????????????????????}

????????????????}

????????????}

舉例來說:對于 '一 一@中',pair數(shù)組是如何保存這對詞的詞共現(xiàn)頻率的呢?

'一 一'在 map 中第0號位置處,它是一元核心詞典中的第34個詞。?共有22個共現(xiàn)詞。如下:

其中,第一個共現(xiàn)詞是 '一 一 @中',就是'一 一'與 '中' 共同出現(xiàn),出現(xiàn)的頻率為1。而 ''中'' 在一元核心詞典中的 4124行,如下圖所示:

?

因此,'一 一@中'的pair數(shù)組存儲如下:

?

0=4123 (‘中’在一元核心詞典中的位置(從下標(biāo)0開始計(jì)算))

?

1=1 ('一 一@中'的詞共現(xiàn)頻率)

?

2=5106 ('為' 在一元核心詞典中的位置) 【為 p 65723】

?

3=6 ('一 一@為'的詞共現(xiàn)頻率)

?

由此可知,對于二元核心詞典共現(xiàn)詞而言,共同前綴的后續(xù)詞 在 pair數(shù)組中是順序存儲的,比如說:前綴'一 一'的所有后綴:中、為、交談……按順序依次在 pair 數(shù)組中存儲。而這也是能夠?qū)?pair 數(shù)組進(jìn)行二分查找的基礎(chǔ)。

?

一 一@中 1

一 一@為 6

一 一@交談 1

一 一@介紹 1

一 一@作 1

一 一@分析

?

.......//省略其他

?

二分查找

現(xiàn)在來看看 二分查找是干什么用的?為什么減少了二分查找的范圍。為了獲取某 兩個詞(idA 和 idB) 的詞共現(xiàn)頻率,需要進(jìn)行二分查找:

?

public static int getBiFrequency(int idA, int idB){

????//省略其他代碼

????int index = binarySearch(pair, start[idA], start[idA + 1] - start[idA], idB);

????return pair[index + 1];

}

根據(jù)前面介紹,start[idA + 1] - start[idA]就是以 idA 為前綴的 所有詞的 詞共現(xiàn)頻率。比如,以 '一 一' 為前綴的詞一共有22個,假設(shè)我要查找 '一 一@向' 的詞共現(xiàn)頻率是多少?在核心二元詞典文件CoreNatureDictionary.ngram.txt中,我們知道 '一 一@向' 的詞共現(xiàn)頻率為2,但是:如何用程序快速地實(shí)現(xiàn)查找呢?

?

二元核心詞典的總個數(shù)還是很多的,比如在HanLP1.5.3大約有290萬個二元核心詞條,如果每查詢一次 idA@idB 的詞共現(xiàn)頻率就要從290萬個詞條里面查詢,顯然效率很低。若先定位出 所有以 idA 為前綴的共現(xiàn)詞:idA@xx1,idA@xx2,idA@xx3……,然后再從從這些 以idA為前綴的共現(xiàn)詞中進(jìn)行二分查找,來查找 idA@idB,這樣查找的效率就快了許多。

?

而start 數(shù)組保存了一元詞典中每個詞 在二元詞典中的詞共現(xiàn)情況: start[idA] 代表 idA在 pair 數(shù)組中共現(xiàn)詞的起始位置,而start[idA + 1] - start[idA]代表 以idA 為前綴的共現(xiàn)詞一共有多少個,這樣二分查找的范圍就只在 start[idA] 和 start[idA] + (start[idA + 1] - start[idA]) - 1之間了。

?

????private static int binarySearch(int[] a, int fromIndex, int length, int key)

????{

????????int low = fromIndex;

????????int high = fromIndex + length - 1;

????????//省略其他代碼

說到這里,再多說一點(diǎn):二元核心詞典的二分查找 是為了獲取 idA@idB 的詞共現(xiàn)頻率,而這個詞共現(xiàn)頻率的用處之一就是最短路徑分詞算法(維特比分詞),用來計(jì)算最短路徑的權(quán)重。關(guān)于最短路徑分詞,可參考這篇解析:

?

//只列出關(guān)鍵代碼

List<Vertex> vertexList = viterbi(wordNetAll);//求解詞網(wǎng)的最短路徑

to.updateFrom(node);//更新權(quán)重

double weight = from.weight + MathTools.calculateWeight(from, this);//計(jì)算兩個頂點(diǎn)(idA->idB)的權(quán)重

int nTwoWordsFreq = CoreBiGramTableDictionary.getBiFrequency(from.wordID, to.wordID);//查核心二元詞典

int index = binarySearch(pair, start[idA], start[idA + 1] - start[idA], idB);//二分查找 idA@idB共現(xiàn)頻率

總結(jié)

有時候由于特定項(xiàng)目需要,需要修改核心詞典。比如添加一個新的二元詞共現(xiàn)詞條 到 二元核心詞典中去,這時就需要注意:添加的新詞條需要存在于一元核心詞典中,否則添加無效。另外,添加到CoreNatureDictionary.ngram.txt里面的二元共現(xiàn)詞的位置不太重要,因?yàn)橄嗤那熬Y 共現(xiàn)詞 都會保存到 同一個TreeMap中,但是最好也是連續(xù)放在一起,這樣二元核心詞典就不會太混亂。

?

文章來源 hapjin的博客

轉(zhuǎn)載于:https://my.oschina.net/u/3793864/blog/2966681

總結(jié)

以上是生活随笔為你收集整理的HanLP二元核心词典详细解析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。