結巴分詞3--基於漢字成詞能力的HMM模型識別未登陸詞

做者:zhbzz2007 出處:http://www.cnblogs.com/zhbzz2007 歡迎轉載,也請保留這段聲明。謝謝!html

1 算法簡介

結巴分詞2--基於前綴詞典及動態規劃實現分詞 博文中,博主已經介紹了基於前綴詞典和動態規劃方法實現分詞,可是若是沒有前綴詞典或者有些詞不在前綴詞典中,jieba分詞同樣能夠分詞,那麼jieba分詞是如何對未登陸詞進行分詞呢?這就是本文將要講解的,基於漢字成詞能力的HMM模型識別未登陸詞。python

利用HMM模型進行分詞,主要是將分詞問題視爲一個序列標註(sequence labeling)問題,其中,句子爲觀測序列,分詞結果爲狀態序列。首先經過語料訓練出HMM相關的模型,而後利用Viterbi算法進行求解,最終獲得最優的狀態序列,而後再根據狀態序列,輸出分詞結果。git

2 實例

2.1 序列標註

序列標註,就是將輸入句子和分詞結果看成兩個序列,句子爲觀測序列,分詞結果爲狀態序列,當完成狀態序列的標註,也就獲得了分詞結果。github

以「去北京大學玩」爲例,咱們知道「去北京大學玩」的分詞結果是「去 / 北京大學 / 玩」。對於分詞狀態,因爲jieba分詞中使用的是4-tag,所以咱們以4-tag進行計算。4-tag,也就是每一個字處在詞語中的4種可能狀態,B、M、E、S,分別表示Begin(這個字處於詞的開始位置)、Middle(這個字處於詞的中間位置)、End(這個字處於詞的結束位置)、Single(這個字是單字成詞)。具體以下圖所示,「去」和「玩」都是單字成詞,所以狀態就是S,「北京大學」是多字組合成的詞,所以「北」、「京」、「大」、「學」分別位於「北京大學」中的B、M、M、E。算法

2.2 HMM模型

關於HMM模型的介紹,網絡上有不少的資源,好比 52nlp整理的 HMM相關文章索引 。博主在此就再也不具體介紹HMM模型的原理,可是會對分詞涉及的基礎知識進行講解。網絡

HMM模型做的兩個基本假設:app

  • 1.齊次馬爾科夫性假設,即假設隱藏的馬爾科夫鏈在任意時刻t的狀態只依賴於其前一時刻的狀態,與其它時刻的狀態及觀測無關,也與時刻t無關;函數

    P(states[t] | states[t-1],observed[t-1],...,states[1],observed[1]) = P(states[t] | states[t-1]) t = 1,2,...,T源碼分析

  • 2.觀測獨立性假設,即假設任意時刻的觀測只依賴於該時刻的馬爾科夫鏈的狀態,與其它觀測和狀態無關,學習

    P(observed[t] | states[T],observed[T],...,states[1],observed[1]) = P(observed[t] | states[t]) t = 1,2,...,T

HMM模型有三個基本問題:

  • 1.機率計算問題,給定模型 \(\lambda =(A,B,\pi)\) 和觀測序列 \(O=(o_{1},o_{2},...,o_{T})\) ,怎樣計算在模型 \(\lambda\) 下觀測序列O出現的機率 \(P(O|\lambda)\) ,也就是Forward-backward算法;
  • 2.學習問題,已知觀測序列 \(O=(o_{1},o_{2},...,o_{T})\) ,估計模型 \(\lambda =(A,B,\pi)\) ,使得在該模型下觀測序列的機率 \(P(O|\lambda)\) 儘量的大,即用極大似然估計的方法估計參數;
  • 3.預測問題,也稱爲解碼問題,已知模型 \(\lambda =(A,B,\pi)\) 和觀測序列 \(O=(o_{1},o_{2},...,o_{T})\) ,求對給定觀測序列條件機率 \(P(S|O)\) 最大的狀態序列 \(I=(s_{1},s_{2},...,s_{T})\) ,即給定觀測序列。求最有可能的對應的狀態序列;

其中,jieba分詞主要中主要涉及第三個問題,也即預測問題。

HMM模型中的五元組表示:

  • 1.觀測序列;
  • 2.隱藏狀態序列;
  • 3.狀態初始機率;
  • 4.狀態轉移機率;
  • 5.狀態發射機率;

這裏仍然以「去北京大學玩」爲例,那麼「去北京大學玩」就是觀測序列。

而「去北京大學玩」對應的「SBMMES」則是隱藏狀態序列,咱們將會注意到B後面只能接(M或者E),不可能接(B或者S);而M後面也只能接(M或者E),不可能接(B或者S)。

狀態初始機率表示,每一個詞初始狀態的機率;jieba分詞訓練出的狀態初始機率模型以下所示。其中的機率值都是取對數以後的結果(可讓機率相乘轉變爲機率相加),其中-3.14e+100表明負無窮,對應的機率值就是0。這個機率表說明一個詞中的第一個字屬於{B、M、E、S}這四種狀態的機率,以下能夠看出,E和M的機率都是0,這也和實際相符合:開頭的第一個字只多是每一個詞的首字(B),或者單字成詞(S)。這部分對應jieba/finaseg/prob_start.py,具體能夠進入源碼查看。

P={'B': -0.26268660809250016,
 'E': -3.14e+100,
 'M': -3.14e+100,
 'S': -1.4652633398537678}

狀態轉移機率是馬爾科夫鏈中很重要的一個知識點,一階的馬爾科夫鏈最大的特色就是當前時刻T = i的狀態states(i),只和T = i時刻以前的n個狀態有關,即{states(i-1),states(i-2),...,states(i-n)}。
再看jieba中的狀態轉移機率,其實就是一個嵌套的詞典,數值是機率值求對數後的值,以下所示,

P={'B': {'E': -0.510825623765990, 'M': -0.916290731874155},
 'E': {'B': -0.5897149736854513, 'S': -0.8085250474669937},
 'M': {'E': -0.33344856811948514, 'M': -1.2603623820268226},
 'S': {'B': -0.7211965654669841, 'S': -0.6658631448798212}}

P['B']['E']表明的含義就是從狀態B轉移到狀態E的機率,由P['B']['E'] = -0.5897149736854513,表示當前狀態是B,下一個狀態是E的機率對數是-0.5897149736854513,對應的機率值是0.6,相應的,當前狀態是B,下一個狀態是M的機率是0.4,說明當咱們處於一個詞的開頭時,下一個字是結尾的機率要遠高於下一個字是中間字的機率,符合咱們的直覺,由於二個字的詞比多個字的詞更常見。這部分對應jieba/finaseg/prob_trans.py,具體能夠查看源碼。

狀態發射機率,根據HMM模型中觀測獨立性假設,發射機率,即觀測值只取決於當前狀態值,也就以下所示,

P(observed[i],states[j]) = P(states[j]) * P(observed[i] | states[j])

其中,P(observed[i] | states[j])就是從狀態發射機率中得到的。

P={'B': {'一': -3.6544978750449433,
       '丁': -8.125041941842026,
       '七': -7.817392401429855,
...
'S': {':': -15.828865681131282,
  '一': -4.92368982120877,
  '丁': -9.024528361347633,
...

P['B']['一']表明的含義就是狀態處於'B',而觀測的字是‘一’的機率對數值爲P['B']['一'] = -3.6544978750449433。這部分對應jieba/finaseg/prob_emit.py,具體能夠查看源碼。

2.3 Viterbi算法

Viterbi算法其實是用動態規劃求解HMM模型預測問題,即用動態規劃求機率路徑最大(最優路徑)。這時候,一條路徑對應着一個狀態序列。

根據動態規劃原理,最優路徑具備這樣的特性:若是最優路徑在時刻t經過結點 \(i_{t}^{*}\) ,那麼這一路徑從結點 \(i_{t}^{*}\) 到終點 \(i_{T}^{*}\) 的部分路徑,對於從 \(i_{t}^{*}\)\(i_{T}^{*}\) 的全部可能的部分路徑來講,必須是最優的。由於假如不是這樣,那麼從 \(i_{t}^{*}\)\(i_{T}^{*}\) 就有另外一條更好的部分路徑存在,若是把它和從 \(i_{t}^{*}\) 到達 \(i_{T}^{*}\) 的部分路徑鏈接起來,就會造成一條比原來的路徑更優的路徑,這是矛盾的。依據這個原理,咱們只須要從時刻t=1開始,遞推地計算在時刻t狀態i的各條部分路徑的最大機率,直至獲得時刻t=T狀態爲i的各條路徑的最大機率。時刻t=T的最大機率就是最優路徑的機率 \(P^{*}\) ,最優路徑的終結點 \(i_{T}^{*}\) 也同時獲得。以後,爲了找出最優路徑的各個結點,從終結點 \(i_{T}^{*}\) 開始,由後向前逐步求得結點 \(i_{T-1}^{*},...,i_{1}^{*}\) ,最終獲得最優路徑 \(I^{*}=(i_{1}^{*},i_{2}^{*},...,i_{T}^{*})\)

2.4 輸出分詞結果

由Viterbi算法獲得狀態序列,也就能夠根據狀態序列獲得分詞結果。其中狀態以B開頭,離它最近的以E結尾的一個子狀態序列或者單獨爲S的子狀態序列,就是一個分詞。以」去北京大學玩「的隱藏狀態序列」SBMMES「爲例,則分詞爲」S / BMME / S「,對應觀測序列,也就是」去 / 北京大學 / 玩」。

3 源碼分析

jieba分詞中HMM模型識別未登陸詞的源碼目錄在jieba/finalseg/下,

__init__.py 實現了HMM模型識別未登陸詞;

prob_start.py 存儲了已經訓練好的HMM模型的狀態初始機率表;

prob_trans.py 存儲了已經訓練好的HMM模型的狀態轉移機率表;

prob_emit.py 存儲了已經訓練好的HMM模型的狀態發射機率表;

3.1 HMM模型參數訓練

HMM模型的參數是如何訓練出來,此處能夠參考jieba中Issue 模型的數據是如何生成的? #7,以下是jieba的開發者的解釋:

來源主要有兩個,一個是網上能下載到的1998人民日報的切分語料還有一個msr的切分語料。另外一個是我本身收集的一些txt小說,用ictclas把他們切分(可能有必定偏差),而後用python腳本統計詞頻。

要統計的主要有三個機率表:1)位置轉換機率,即B(開頭),M(中間),E(結尾),S(獨立成詞)四種狀態的轉移機率;2)位置到單字的發射機率,好比P("和"|M)表示一個詞的中間出現」和"這個字的機率;3) 詞語以某種狀態開頭的機率,其實只有兩種,要麼是B,要麼是S。

3.2 基於HMM模型的分詞流程

jieba分詞會首先調用函數cut(sentence),cut函數會先將輸入句子進行解碼,而後調用__cut函數進行處理。__cut函數就是jieba分詞中實現HMM模型分詞的主函數。__cut函數會首先調用viterbi算法,求出輸入句子的隱藏狀態,而後基於隱藏狀態進行分詞。

def __cut(sentence):
    global emit_P
    # 經過viterbi算法求出隱藏狀態序列
    prob, pos_list = viterbi(sentence, 'BMES', start_P, trans_P, emit_P)
    begin, nexti = 0, 0
    # print pos_list, sentence
    # 基於隱藏狀態序列進行分詞
    for i, char in enumerate(sentence):
        pos = pos_list[i]
        # 字所處的位置是開始位置
        if pos == 'B':
            begin = i
        # 字所處的位置是結束位置
        elif pos == 'E':
            # 這個子序列就是一個分詞
            yield sentence[begin:i + 1]
            nexti = i + 1
        # 單獨成字
        elif pos == 'S':
            yield char
            nexti = i + 1
    # 剩餘的直接做爲一個分詞,返回
    if nexti < len(sentence):
        yield sentence[nexti:]

3.3 Viterbi算法

首先先定義兩個變量, \(\delta,\psi\),定義在時刻t狀態i的全部單個路徑 \((i_{1},i_{2},...,i_{t})\) 中機率最大值爲

\(\delta_{t}(i) = max_{i_{1},i_{2},..,i_{n}}P(i_{t} = i,i_{t-1},...,i_{1},o_{t},...,o_{1}|\lambda), i = 1,2,...,N\)

由此可得變量 \(\delta\) 的遞推公式爲,

\(\delta_{t+1}(i) = max_{i_{1},i_{2},..,i_{n}}P(i_{t+1} = i,i_{t},...,i_{1},o_{t+1},...,o_{1}|\lambda)\)

\(=max_{1\le j \le N}[\delta_{t}(j)*a_{ji}]*b_{i}(o_{t+1}),i = 1,2,...,N,t = 1,2,...,N-1\)

定義在時刻t狀態i的全部單個路徑 \((i_{1},i_{2},...,i_{t-1},i)\) 中機率最大的路徑的第t-1個結點爲,

\(\psi_{t}(i) = argmax_{1 \le j \le N}[\delta_{t-1}(j)*a_{ji}]\)

Viterbi算法的大體流程:

輸入:模型 \(\lambda =(A,B,\pi)\) 和觀測序列 \(O=(o_{1},o_{2},...,o_{T})\)

輸出:最優路徑 \(I^{*}=(i_{1}^{*},i_{2}^{*},...,i_{T}^{*})\)

(1)初始化

\(\delta_{1}(i) = \pi_{i} * b_{i}(o_{1}),i = 1,2,...,N\)

\(\psi_{1}(i) = 0,i = 1,2,...,N\)

(2)遞推

\(\delta_{t}(i) = max_{1\le j \le N}[\delta_{t-1}(j)*a_{ji}]*b_{i}(o_{t}),i = 1,2,...,N\)

\(\psi_{t}(i) = argmax_{1 \le j \le N}[\delta_{t-1}(j)*a_{ji}],,i = 1,2,...,N\)

(3)終止

\(P^{*} = max_{1\le j \le N}\delta_{T}(i)\)

\(i_{T}^{*} = argmax_{1 \le i \le N}[\delta_{T}(i)]\)

(4)最優路徑回溯,對於t=T-1,T-2,...,1,

\(i_{t}^{*} = \psi_{t+1}(i)\)

最終求得最優路徑 \(I^{*}=(i_{1}^{*},i_{2}^{*},...,i_{T}^{*})\)

jieba分詞實現Viterbi算法是在viterbi(obs, states, start_p, trans_p, emit_p)函數中實現。viterbi函數會先計算各個初始狀態的對數機率值,而後遞推計算,每時刻某狀態的對數機率值取決於上一時刻的對數機率值、上一時刻的狀態到這一時刻的狀態的轉移機率、這一時刻狀態轉移到當前的字的發射機率三部分組成。

def viterbi(obs, states, start_p, trans_p, emit_p):
    V = [{}]  # tabular
    path = {}
    # 時刻t = 0,初始狀態
    for y in states:  # init
        V[0][y] = start_p[y] + emit_p[y].get(obs[0], MIN_FLOAT)
        path[y] = [y]
    # 時刻t = 1,...,len(obs) - 1
    for t in xrange(1, len(obs)):
        V.append({})
        newpath = {}
        # 當前時刻所處的各類可能的狀態
        for y in states:
            # 獲取發射機率對數
            em_p = emit_p[y].get(obs[t], MIN_FLOAT)
            # 分別獲取上一時刻的狀態的機率對數,該狀態到本時刻的狀態的轉移機率對數,本時刻的狀態的發射機率對數
            # 其中,PrevStatus[y]是當前時刻的狀態所對應上一時刻可能的狀態
            (prob, state) = max(
                [(V[t - 1][y0] + trans_p[y0].get(y, MIN_FLOAT) + em_p, y0) for y0 in PrevStatus[y]])
            V[t][y] = prob
            # 將上一時刻最優的狀態 + 這一時刻的狀態
            newpath[y] = path[state] + [y]
        path = newpath

    # 最後一個時刻
    (prob, state) = max((V[len(obs) - 1][y], y) for y in 'ES')

    # 返回最大機率對數和最優路徑
    return (prob, path[state])

相關優化:

  • 1.將每一時刻最優機率路徑記錄下來,避免了第4步的最優路徑回溯;

  • 2.提早創建一個當前時刻的狀態到上一時刻的狀態的映射表,記錄每一個狀態在前一時刻的可能狀態,下降計算量;以下所示,當前時刻的狀態是B,那麼前一時刻的狀態只多是(E或者S),而不多是(B或者M);

    PrevStatus = {
    'B': 'ES',
    'M': 'MB',
    'S': 'SE',
    'E': 'BM'
    }

4 Reference

李航,統計學習方法.

jieba 分詞源代碼研讀(3)

jieba中文分詞源碼分析(四)

相關文章
相關標籤/搜索