1. 程式人生 > >【轉】中文分詞之HMM模型詳解

【轉】中文分詞之HMM模型詳解

實現 含義 jieba 順序 清晰 bsp 中國 matrix 統計

關於HMM模型的介紹,網上的資料已經爛大街,但是大部分都是在背書背公式,本文在此針對HMM模型在中文分詞中的應用,講講實現原理。

盡可能的撇開公式,撇開推導。結合實際開源代碼作為例子,爭取做到雅俗共賞,童叟無欺

沒有公式,就沒有傷害。

模型介紹

第一次聽說HMM模型是從李開復的博文論文中聽說的:

李開復1988年的博士論文發表了第一個基於隱馬爾科夫模型(HMM)的語音識別系統Sphinx,被《商業周刊》評為1988年美國最重要的科技發明。

出處請見KaifuLeeHMM

乍一聽似乎很玄妙,但是其實很簡單。下面是相關參數介紹,也是第一眼覺得很抽象,但是慢慢看下去隨著具體含義的解釋就漸漸清晰。

HMM(Hidden Markov Model): 隱式馬爾科夫模型。

HMM模型可以應用在很多領域,所以它的模型參數描述一般都比較抽象,以下篇幅針對HMM的模型參數介紹直接使用它在中文分詞中的實際含義來講:

HMM的典型介紹就是這個模型是一個五元組:

  • StatusSet: 狀態值集合
  • ObservedSet: 觀察值集合
  • TransProbMatrix: 轉移概率矩陣
  • EmitProbMatrix: 發射概率矩陣
  • InitStatus: 初始狀態分布

HMM模型可以用來解決三種問題:

  1. 參數(StatusSet, TransProbMatrix, EmitRobMatrix, InitStatus)已知的情況下,求解觀察值序列
    。(Forward-backward算法)
  2. 參數(ObservedSet, TransProbMatrix, EmitRobMatrix, InitStatus)已知的情況下,求解狀態值序列。(viterbi算法)
  3. 參數(ObservedSet)已知的情況下,求解(TransProbMatrix, EmitRobMatrix, InitStatus)。(Baum-Welch算法)

其中,第三種問題最玄乎也最不常用,第二種問題最常用,【中文分詞】,【語音識別】, 【新詞發現】, 【詞性標註】 都有它的一席之地。所以本文主要介紹第二種問題,即【viterbi算法求解狀態值序列】的方法。

五元組參數在中文分詞中的具體含義

接下來我們講實的,不講虛的,針對中文分詞應用,直接給五元組參數賦予具體含義:

StatusSet & ObservedSet

狀態值集合為(B, M, E, S): {B:begin, M:middle, E:end, S:single}。分別代表每個狀態代表的是該字在詞語中的位置,B代表該字是詞語中的起始字,M代表是詞語中的中間字,E代表是詞語中的結束字,S則代表是單字成詞。

觀察值集合為就是所有漢字(東南西北你我他…),甚至包括標點符號所組成的集合。

狀態值也就是我們要求的值,在HMM模型中文分詞中,我們的輸入是一個句子(也就是觀察值序列),輸出是這個句子中每個字的狀態值。 比如:

小明碩士畢業於中國科學院計算所

輸出的狀態序列為

BEBEBMEBEBMEBES

根據這個狀態序列我們可以進行切詞:

BE/BE/BME/BE/BME/BE/S

所以切詞結果如下:

小明/碩士/畢業於/中國/科學院/計算/所

同時我們可以註意到:

B後面只可能接(M or E),不可能接(B or S)。而M後面也只可能接(M or E),不可能接(B, S)

沒錯,就是這麽簡單,現在輸入輸出都明確了,下文講講輸入和輸出之間的具體過程,裏面究竟發生了什麽不可告人的秘密,請看下文:

上文只介紹了五元組中的兩元【StatusSet, ObservedSet】,下文介紹剩下的三元【InitStatus, TransProbMatrix, EmitProbMatrix】。

這五元的關系是通過一個叫Viterbi的算法串接起來, ObservedSet序列值是Viterbi的輸入, 而StatusSet序列值是Viterbi的輸出, 輸入和輸出之間Viterbi算法還需要借助三個模型參數, 分別是InitStatus, TransProbMatrix, EmitProbMatrix, 接下來一一講解:

InitStatus

初始狀態概率分布是最好理解的,可以示例如下:

#B
-0.26268660809250016
#E
-3.14e+100
#M
-3.14e+100
#S
-1.4652633398537678

示例數值是對概率值取對數之後的結果(可以讓概率相乘的計算變成對數相加),其中-3.14e+100作為負無窮,也就是對應的概率值是0。下同。

也就是句子的第一個字屬於{B,E,M,S}這四種狀態的概率,如上可以看出,E和M的概率都是0,這和實際相符合,開頭的第一個字只可能是詞語的首字(B),或者是單字成詞(S)。

TransProbMatrix

轉移概率是馬爾科夫鏈很重要的一個知識點,大學裏面學過概率論的人都知道,馬爾科夫鏈最大的特點就是當前T=i時刻的狀態Status(i),只和T=i時刻之前的n個狀態有關。也就是:

{Status(i-1), Status(i-2), Status(i-3), ... Status(i - n)}

更進一步的說,HMM模型有三個基本假設(具體哪三個請看文末備註)作為模型的前提,其中有個【有限歷史性假設】,也就是馬爾科夫鏈的n=1。即Status(i)只和Status(i-1)相關,這個假設能大大簡化問題。

回過頭看TransProbMatrix,其實就是一個4x4(4就是狀態值集合的大小)的二維矩陣,示例如下:

矩陣的橫坐標和縱坐標順序是BEMS x BEMS。(數值是概率求對數後的值,別忘了。)

-3.14e+100 -0.510825623765990 -0.916290731874155 -3.14e+100
-0.5897149736854513 -3.14e+100 -3.14e+100 -0.8085250474669937
-3.14e+100 -0.33344856811948514 -1.2603623820268226 -3.14e+100
-0.7211965654669841 -3.14e+100 -3.14e+100 -0.6658631448798212

比如TransProbMatrix[0][0]代表的含義就是從狀態B轉移到狀態B的概率,由

TransProbMatrix[0][0] = -3.14e+100

可知,這個轉移概率是0,這符合常理。由狀態各自的含義可知,狀態B的下一個狀態只可能是ME,不可能是BS,所以不可能的轉移對應的概率都是0,也就是對數值負無窮,在此記為-3.14e+100

由上TransProbMatrix矩陣可知,對於各個狀態可能轉移的下一狀態,且轉移概率對應如下:

#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

EmitProbMatrix

這裏的發射概率(EmitProb)其實也是一個條件概率而已,根據HMM模型三個基本假設(哪三個請看文末備註)裏的【觀察值獨立性假設】,觀察值只取決於當前狀態值,也就是:

P(Observed[i], Status[j]) = P(Status[j]) * P(Observed[i]|Status[j])

其中P(Observed[i]|Status[j])這個值就是從EmitProbMatrix中獲取。

EmitProbMatrix示例如下:

#B
耀:-10.460283,涉:-8.766406,談:-8.039065,伊:-7.682602,洞:-8.668696,...
#E
耀:-9.266706,涉:-9.096474,談:-8.435707,伊:-10.223786,洞:-8.366213,...
#M
耀:-8.47651,涉:-10.560093,談:-8.345223,伊:-8.021847,洞:-9.547990,....
#S
蘄:-10.005820,涉:-10.523076,唎:-15.269250,禑:-17.215160,洞:-8.369527...

雖然EmitProbMatrix也稱為矩陣,這個矩陣太稀疏了,實際工程中一般是將上面四行發射轉移概率存儲為4個Map,詳見代碼HMMSegment。

到此,已經介紹完HMM模型的五元參數,假設現在手頭上已經有這些參數的具體概率值,並且已經加載進來,(也就是有該模型的字典了,詳見HMMDict裏面的hmm_model.utf8),那麽我們只剩下Viterbi這個算法函數,這個模型就算可以開始使用了。所以接下來講講Viterbi算法。

HMM中文分詞之Viterbi算法

輸入樣例:

小明碩士畢業於中國科學院計算所

Viterbi算法計算過程如下:

定義變量

二維數組 weight[4][15],4是狀態數(0:B,1:E,2:M,3:S),15是輸入句子的字數。比如 weight[0][2] 代表 狀態B的條件下,出現‘碩‘這個字的可能性。

二維數組 path[4][15],4是狀態數(0:B,1:E,2:M,3:S),15是輸入句子的字數。比如 path[0][2] 代表 weight[0][2]取到最大時,前一個字的狀態,比如 path[0][2] = 1, 則代表 weight[0][2]取到最大時,前一個字(也就是)的狀態是E。記錄前一個字的狀態是為了使用viterbi算法計算完整個 weight[4][15] 之後,能對輸入句子從右向左地回溯回來,找出對應的狀態序列。

使用InitStatus對weight二維數組進行初始化

已知InitStatus如下:

#B
-0.26268660809250016
#E
-3.14e+100
#M
-3.14e+100
#S
-1.4652633398537678

且由EmitProbMatrix可以得出

Status(B) -> Observed(小)  :  -5.79545
Status(E) -> Observed(小)  :  -7.36797
Status(M) -> Observed(小)  :  -5.09518
Status(S) -> Observed(小)  :  -6.2475

所以可以初始化 weight[i][0] 的值如下:

weight[0][0] = -0.26268660809250016 + -5.79545 = -6.05814
weight[1][0] = -3.14e+100 + -7.36797 = -3.14e+100
weight[2][0] = -3.14e+100 + -5.09518 = -3.14e+100
weight[3][0] = -1.4652633398537678 + -6.2475 = -7.71276

註意上式計算的時候是相加而不是相乘,因為之前取過對數的原因。

遍歷句子計算整個weight二維數組

//遍歷句子,下標i從1開始是因為剛才初始化的時候已經對0初始化結束了
for(size_t i = 1; i < 15; i++)
{
    // 遍歷可能的狀態
    for(size_t j = 0; j < 4; j++) 
    {
        weight[j][i] = MIN_DOUBLE;
        path[j][i] = -1;
        //遍歷前一個字可能的狀態
        for(size_t k = 0; k < 4; k++)
        {
            double tmp = weight[k][i-1] + _transProb[k][j] + _emitProb[j][sentence[i]];
            if(tmp > weight[j][i]) // 找出最大的weight[j][i]值
            {
                weight[j][i] = tmp;
                path[j][i] = k;
            }
        }
    }
}

如此遍歷下來,weight[4][15]path[4][15] 就都計算完畢。

確定邊界條件和路徑回溯

邊界條件如下:

對於每個句子,最後一個字的狀態只可能是 E 或者 S,不可能是 M 或者 B。

所以在本文的例子中我們只需要比較 weight[1(E)][14]weight[3(S)][14] 的大小即可。

在本例中:

weight[1][14] = -102.492;
weight[3][14] = -101.632;

所以 S > E,也就是對於路徑回溯的起點是 path[3][14]

回溯的路徑是:

SEBEMBEBEMBEBEB

倒序一下就是:

BE/BE/BME/BE/BME/BE/S

所以切詞結果就是:

小明/碩士/畢業於/中國/科學院/計算/所

到此,一個HMM模型中文分詞算法過程就闡述完畢了。

也就是給定我們一個模型,我們對模型進行載入完畢之後,只要運行一遍Viterbi算法,就可以找出每個字對應的狀態,根據狀態也就可以對句子進行分詞。

模型的訓練問題

以上講的前提是基於模型來進行切詞,也就是假設我們手頭上的HMM模型已經是被訓練好了的(也就是InitStatus, TransProbMatrix, EmitProbMatrix這三個模型的關鍵參數都是已知的),沒有涉及到這三個參數是如何得到的。 這三個參數其實也是基於已分詞完畢的語料進行統計計算,計算出相應的頻率和條件概率就可以算出這三個參數。具體在此就不講了。

備註

HMM模型的三個基本假設如下:

  • 有限歷史性假設:
P(Status[i]|Status[i-1],Status[i-2],... Status[1]) = P(Status[i]|Status[i-1])
  • 齊次性假設(狀態和當前時刻無關):
P(Status[i]|Status[i-1]) = P(Status[j]|Status[j-1])
  • 觀察值獨立性假設(觀察值只取決於當前狀態值):
P(Observed[i]|Status[i],Status[i-1],...,Status[1]) = P(Observed[i]|Status[i])

源碼參考

HMMSegment

【轉】中文分詞之HMM模型詳解