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

前言

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

目錄

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

基本原理

貝葉斯公式

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

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

分詞中的貝葉斯

咱們知道一般P(Y)是一個常數,因此在使用貝葉斯公式的時候咱們更多用以下的公式:

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

也就是說句子當中的每一個詞的機率都是一個依賴於其前面全部詞的條件機率。說到這裏咱們就是慣用套路,顯然這東東無法計算,那怎麼辦呢,那就是貝葉斯理論中經常使用的,作些條件獨立假設唄,這也就是所謂n-gram中n的由來。算法

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

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

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分詞學習筆記(三)
相關文章
相關標籤/搜索