jieba分詞學習筆記(三)


DAG(有向無環圖)

有向無環圖,directed acyclic graphs,簡稱DAG,是一種圖的數據結構,其實很naive,就是沒有環的有向圖_(:з」∠)_python

DAG在分詞中的應用很廣,不管是最大機率路徑,仍是後面套NN的作法,DAG都普遍存在於分詞中。數據結構

由於DAG自己也是有向圖,因此用鄰接矩陣來表示是可行的,可是jieba採用了python的dict,更方便地表示DAG,其表示方法爲:app

{prior1:[next1,next2...,nextN],prior2:[next1',next2'...nextN']...}

以句子 "國慶節我在研究結巴分詞"爲例,其生成的DAG的dict表示爲:函數

{0: [0, 1, 2], 1: [1], 2: [2], 3: [3], 4: [4], 5: [5, 6], 6: [6], 7: [7, 8], 8: [8], 9: [9, 10], 10: [10]}

其中,

國[0] 慶[1] 節[2] 我[3] 在[4] 研[5] 究[6] 結[7] 巴[8] 分[9] 詞[10]

get_DAG()函數代碼以下:code

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

frag即fragment,能夠看到代碼循環切片句子,FREQ便是詞典的{word:frequency}的dictget

由於在載入詞典的時候已經將word和word的全部前綴加入了詞典,因此一旦frag not in FREQ,便可以判定frag和以frag爲前綴的詞不在詞典裏,能夠跳出循環。it

由此獲得了DAG,下一步就是使用dp動態規劃對最大機率路徑進行求解。io

最大機率路徑

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

動態規劃求解法

知足dp的條件有兩個cli

  • 重複子問題

  • 最優子結構

咱們來分析最大機率路徑問題。

重複子問題

對於結點Wi和其可能存在的多個後繼Wj和Wk,有:

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

即對於擁有公共前驅Wi的節點Wj和Wk,須要重複計算到達Wi的路徑。

最優子結構

對於整個句子的最優路徑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)}

代碼

上面理解了,代碼很簡單,注意一點total的值在加載詞典的時候求出來的,爲詞頻之和,而後有一些諸如求對數的trick,代碼是典型的dp求解代碼。

def calc(self, sentence, DAG, route):
        N = len(sentence)
        route[N] = (0, 0)
        logtotal = log(self.total)
        for idx in xrange(N - 1, -1, -1):
            route[idx] = max((log(self.FREQ.get(sentence[idx:x + 1]) or 1) -
                              logtotal + route[x + 1][0], x) for x in DAG[idx])
相關文章
相關標籤/搜索