淺談分詞算法(2)基於詞典的分詞方法


前言

淺談分詞算法(1)分詞中的基本問題中我們探討了分詞中的基本問題,也提到了基於詞典的分詞方法。基於詞典的分詞方法是一種比較傳統的方式,這類分詞方法有很多,如:正向最大匹配(forward maximum matching method, FMM)、逆向最大匹配(backward maximum matching method,BMM)、雙向掃描法、逐詞遍歷法、N-最短路徑方法以及基於詞的n-gram語法模型的分詞方法等等。對於這類方法,詞典的整理選擇在其中占到了很重要的作用,本文主要介紹下基於n-gram的分詞方法,這類方法在平時的分詞工具中比較常見,而且性能也較好。

目錄

淺談分詞算法(1)分詞中的基本問題
淺談分詞算法(2)基於詞典的分詞方法
淺談分詞算法(3)基於字的分詞方法(HMM)
淺談分詞算法(4)基於字的分詞方法(CRF)
淺談分詞算法(5)基於字的分詞方法(LSTM)

基本原理

貝葉斯公式

提到基於N-Gram的概率分詞方法,首先我們就要說下偉大的貝葉斯理論,說到貝葉斯理論,先說貝葉斯公式:

貝葉斯公式也是概率論當中的基礎,這里我們不再贅述,推薦一篇文章數學之美番外篇:平凡而又神奇的貝葉斯方法,講的很不錯。下面我們主要關注下在分詞當中怎么利用貝葉斯原理。

分詞中的貝葉斯

我們知道通常P(Y)是一個常數,所以在使用貝葉斯公式的時候我們更多用如下的公式:

當貝葉斯理論應用在離散數據集上的時候,可以使用頻率作為概率來進行計算,在分詞算法中,在給定訓練語料庫中,我們以詞為單位進行統計,統計出每個詞出現的頻率,當出現一句待切分的句子時,我們將所有可能的分詞結果統計出來,計算概率最大的作為切分結果。用形式化的語言描述下:
假設訓練數據集為,其中詞典集為D,為長度N的句子中的第i個詞,那么一句話的聯合概率可以表示為:

也就是說句子當中的每個詞的概率都是一個依賴於其前面所有詞的條件概率。說到這里我們就是慣用套路,顯然這東東沒法計算,那怎么辦呢,那就是貝葉斯理論中常用的,做些條件獨立假設唄,這也就是所謂n-gram中n的由來。

  • 1-gram(unigram), 一元模型,句子中的每個詞都是相互獨立的,那么上面的公式可以簡化如下:
  • 2-gram(bigram),二元模型,句子中的每個詞僅僅依賴於其前面的一個詞:
  • 3-gram(trigram),三元模型,句子中的每個詞依賴於其前面兩個詞:

一般來說,我們最多只看到前兩個詞,有研究表明,大於4個以上的模型並不會取得更好的效果(顯然n越大,我們需要找尋n元組的詞出現的頻率就越低,會很直接的導致數據稀疏問題),通常情況下我們使用的是2-gram模型居多。

2-gram分詞舉例

假設待切分語句為:“研究生物學”,我們要怎樣進行切分呢,直觀的講我們可以看出就這么一句簡單的話包含了“研究”、“研究生”、“生物”、“生物學”多個詞語,那么直觀上我們有如下幾種切分方式:

  • 研究/生物學
  • 研究生/物/學
  • 研究/生物/學
  • 研/究/生/物/學

我們將這些切法構建為一幅有向無環圖,結點為詞語,邊為條件概率

(摘自[4])
那么根據最大似然原理,我們分詞的過程轉為了在圖中求解最佳路徑的問題,我們只需要選取任意一種搜索算法,例如在結巴分詞中是利用動態規划找尋最大概率路徑。

1-gram實例

上面說了那么多,還是上code比較有干貨,我們以1-gram為例,來進行一個闡述,這里我們主要參考了結巴分詞。在實現的過程中涉及到的核心問題:建立前綴字典樹、根據句子建立DAG(有向無環圖)、利用動態規划得到最大概率路徑。

建立前綴字典樹

代碼如下:

        with open(dict_path, "rb") as f:
            count = 0
            for line in f:
                try:
                    line = line.strip().decode('utf-8')
                    word, freq = line.split()[:2]
                    freq = int(freq)
                    self.wfreq[word] = freq
                    for idx in range(len(word)):
                        wfrag = word[:idx + 1]
                        if wfrag not in self.wfreq:
                            self.wfreq[wfrag] = 0  # trie: record char in word path
                    self.total += freq
                    count += 1
                except Exception as e:
                    print("%s add error!" % line)
                    print(e)
                    continue

我們利用dict來建立這顆前綴字典樹,遇到一個詞時,會將詞路徑當中所有的子串都記錄在字典樹中。(其實這種方式存放是有大量冗余子串的,不過查詢是會更加方便)

建立DAG

代碼如下:

    def get_DAG(self, sentence):
        DAG = {}
        N = len(sentence)
        for k in range(N):
            tmplist = []
            i = k
            frag = sentence[k]
            while i < N and frag in self.wfreq:
                if self.wfreq[frag]:
                    tmplist.append(i)
                i += 1
                frag = sentence[k:i + 1]
            if not tmplist:
                tmplist.append(k)
            DAG[k] = tmplist
        return DAG

因為在載入詞典的時候已經將word和word的所有前綴加入了詞典,所以一旦frag not in wfreq,即可以斷定frag和以frag為前綴的詞不在詞典里,可以跳出循環。

利用動態規划得到最大概率路徑

值得注意的是,DAG的每個結點,都是帶權的,對於在詞典里面的詞語,其權重為其詞頻,即wfreq[word]。我們要求得route = (w1, w2, w3 ,.., wn),使得Σweight(wi)最大。

動態規划求解法

滿足dp的條件有兩個

  • 重復子問題
  • 最優子結構

我們來分析最大概率路徑問題。

重復子問題
對於結點Wi和其可能存在的多個后繼Wj和Wk,有:

  1. 任意通過Wi到達Wj的路徑的權重為該路徑通過Wi的路徑權重加上Wj的權重{Ri->j} = {Ri + weight(j)} ;
  2. 任意通過Wi到達Wk的路徑的權重為該路徑通過Wi的路徑權重加上Wk的權重{Ri->k} = {Ri + weight(k)} ;

最優子結構
對於整個句子的最優路徑Rmax和一個末端節點Wx,對於其可能存在的多個前驅Wi,Wj,Wk…,設到達Wi,Wj,Wk的最大路徑分別為Rmaxi,Rmaxj,Rmaxk,有:
Rmax = max(Rmaxi,Rmaxj,Rmaxk…) + weight(Wx)
於是問題轉化為:
求Rmaxi, Rmaxj, Rmaxk…
組成了最優子結構,子結構里面的最優解是全局的最優解的一部分。
很容易寫出其狀態轉移方程:
Rmax = max{(Rmaxi,Rmaxj,Rmaxk…) + weight(Wx)}

代碼

代碼如下:

    def get_route(self, DAG, sentence, route):
        N = len(sentence)
        route[N] = (0, 0)
        logtotal = log(self.total)
        for idx in range(N - 1, -1, -1):
            route[idx] = max((log(self.wfreq.get(sentence[idx:x + 1]) or 1) -
                              logtotal + route[x + 1][0], x) for x in DAG[idx])

這里值得注意的是在求頻率時,使用了log函數,將除法變成了減法,防止溢出。

完整代碼

對於句子“我是中國人”,我們可以看到如下圖所示的效果:

我將完整的代碼放在了git上,這里的詞典用的就是結巴分詞中的詞典,其中好多代碼都是從結巴分詞復用過來的,大家需要可以瞅瞅:
https://github.com/xlturing/machine-learning-journey/tree/master/seg_ngram

參考文獻

  1. 數學之美番外篇:平凡而又神奇的貝葉斯方法
  2. 自然語言處理中的N-Gram模型詳解
  3. 概率語言模型的分詞方法
  4. 《統計自然語言處理》宗成慶
  5. 結巴分詞python
  6. jieba分詞學習筆記(三)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM