基於HMM的中文分詞
模型介紹
第一次聽說HMM模型是從李開復的博文論文中聽說的:
李開復1988年的博士論文發表了第一個基於隱馬爾科夫模型(HMM)的語音識別系統Sphinx,被《商業週刊》評為1988年美國最重要的科技發明。
出處請見KaifuLeeHMM
乍一聽似乎很玄妙,但是其實很簡單。下面是相關引數介紹,也是第一眼覺得很抽象,但是慢慢看下去隨著具體含義的解釋就漸漸清晰。
HMM(Hidden Markov Model): 隱式馬爾科夫模型。
HMM模型可以應用在很多領域,所以它的模型引數描述一般都比較抽象,以下篇幅針對HMM的模型引數介紹直接使用它在中文分詞中的實際含義來講:
HMM的典型介紹就是這個模型是一個五元組:
StatusSet: 狀態值集合
ObservedSet: 觀察值集合
TransProbMatrix: 轉移概率矩陣
EmitProbMatrix: 發射概率矩陣
InitStatus: 初始狀態分佈
HMM模型可以用來解決三種問題:
引數(StatusSet,TransProbMatrix,EmitRobMatrix,InitStatus)已知的情況下,求解觀察值序列。(Forward-backward演算法)
引數(ObservedSet,TransProbMatrix,EmitRobMatrix,InitStatus)已知的情況下,求解狀態值序列。(viterbi演算法)
引數(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])