1. 程式人生 > >隱馬爾科夫模型(Hidden Markov Model,HMM)

隱馬爾科夫模型(Hidden Markov Model,HMM)

【轉自:https://blog.csdn.net/mingzai624/article/details/52399235

介紹

定義

wiki上有定義:

隱馬爾可夫模型(Hidden Markov Model,HMM)是統計模型,它用來描述一個含有隱含未知引數的馬爾可夫過程。其難點是從可觀察的引數中確定該過程的隱含引數。然後利用這些引數來作進一步的分析,例如模式識別。

馬可夫模型的概率

這裡寫圖片描述
這裡用x表示狀態, y表示觀察值

假設觀察到的結果為 Y:

Y=y(0),y(1),…,y(L-1)

隱藏條件為X:

X=x(0),x(1),…,x(L-1)

長度為 L,則馬可夫模型的概率可以表達為:

這裡寫圖片描述

詳解

我們拋開教材上拗口的紅球白球與盒子模型吧,來看一個簡單的擲骰子的例子。

示例說明

假設我手裡有三個不同的骰子。第一個骰子是我們平常見的骰子(稱這個骰子為D6),6個面,每個面(1,2,3,4,5,6)出現的概率是1/6。第二個骰子是個四面體(稱這個骰子為D4),每個面(1,2,3,4)出現的概率是1/4。第三個骰子有八個面(稱這個骰子為D8),每個面(1,2,3,4,5,6,7,8)出現的概率是1/8。 
這裡寫圖片描述

假設我們開始擲骰子,我們先從三個骰子裡挑一個,挑到每一個骰子的概率都是1/3。然後我們擲骰子,得到一個數字,1,2,3,4,5,6,7,8中的一個。不停的重複上述過程,我們會得到一串數字,每個數字都是1,2,3,4,5,6,7,8中的一個。

例如我們可能得到這麼一串數字(擲骰子10次):1 6 3 5 2 7 3 5 2 4

這串數字叫做可見狀態鏈(對應上面公式的觀察到的結果為 Y)。但是在隱馬爾可夫模型中,我們不僅僅有這麼一串可見狀態鏈,還有一串隱含狀態鏈(對應上面公式的隱藏的為 X)。

在這個例子裡,這串隱含狀態鏈就是你用的骰子的序列。比如,隱含狀態鏈有可能是:D6 D8 D8 D6 D4 D8 D6 D6 D4 D8

一般來說,

HMM中說到的馬爾可夫鏈其實是指隱含狀態鏈,隱含狀態(骰子)之間存在轉換概率(transition probability)。

在我們這個例子裡,D6的下一個狀態是D4,D6,D8的概率都是1/3。D4的下一個狀態是D4,D6,D8的轉換概率也都一樣是1/3。這樣設定是為了最開始容易說清楚,但是我們其實是可以隨意設定轉換概率的。比如,我們可以這樣定義,D6後面不能接D4,D6後面是D6的概率是0.9,是D8的概率是0.1,這樣就是一個新的HMM,一般情況權重設定也確實是不一樣的。

同樣的,

儘管可見狀態之間沒有轉換概率,但是隱含狀態和可見狀態之間有一個概率叫做輸出概率(emission probability)。

就我們的例子來說,六面骰(D6)產生1的輸出概率是1/6。產生2,3,4,5,6的概率也都是1/6。我們同樣可以對輸出概率進行其他定義。比如,我有一個被賭場動過手腳的六面骰子,擲出來是1的概率更大,是1/2,擲出來是2,3,4,5,6的概率是1/10。

這裡寫圖片描述

而三個骰子之間也是可以相互轉換的,其轉換關係示意圖如下所示。

這裡寫圖片描述

其實對於HMM來說,如果提前知道所有隱含狀態之間的轉換概率和所有隱含狀態到所有可見狀態之間的輸出概率,做模擬是相當容易的。

但是應用HMM模型時候呢,往往是缺失了一部分資訊的,有時候你知道骰子有幾種,每種骰子是什麼,但是不知道擲出來的骰子序列;有時候你只是看到了很多次擲骰子的結果,剩下的什麼都不知道。

如果應用演算法去估計這些缺失的資訊,就成了一個很重要的問題。這應該如何做呢?下面就來說明。

描述問題

這裡就要順帶著說明與HMM模型相關的演算法了,演算法分為三類,分別對應著解決三種問題:

  1. 評估問題。 
    知道骰子有幾種(隱含狀態數量),每種骰子是什麼(轉換概率),根據擲骰子擲出的結果(可見狀態鏈),我想知道擲出這個結果的概率

    這個問題看似意義不大,因為你擲出來的結果很多時候都對應了一個比較大的概率,問這個問題的目的呢,其實是檢測觀察到的結果和已知的模型是否吻合。如果很多次結果都對應了比較小的概率,那麼就說明我們已知的模型很有可能是錯的,有人偷偷把我們的骰子給換了。通常利用前向演算法,分別計算每個產生給定觀測序列的概率,然後從中選出最優的HMM模型。

  2. 解碼問題。 
    知道骰子有幾種(隱含狀態數量),每種骰子是什麼(轉換概率),根據擲骰子擲出的結果(可見狀態鏈),我想知道每次擲出來的都是哪種骰子(隱含狀態鏈)

  3. 學習問題。 
    知道骰子有幾種(隱含狀態數量),不知道每種骰子是什麼(轉換概率),觀測到很多次擲骰子的結果(可見狀態鏈),我想反推出每種骰子是什麼(轉換概率)

    這個問題很重要,因為這是最常見的情況。很多時候我們只有可見結果,不知道HMM模型裡的引數,我們需要從可見結果估計出這些引數,這是建模的一個必要步驟,通常使用Baum-Welch演算法解決。

解決方案

1.計算結果概率

知道骰子有幾種,每種骰子是什麼,每次擲的都是什麼骰子,根據擲骰子擲出的結果,求產生這個結果的概率。

這裡寫圖片描述

解法無非就是概率相乘:

這裡寫圖片描述 
這裡寫圖片描述

2.計算隱含狀態概率

計算不可見的隱含狀態概率,破解骰子序列,這裡有兩種解法。

第一種解法,解最大似然路徑問題

舉個栗子:

我知道我有三個骰子,六面骰,四面骰,八面骰。我也知道我擲了十次的結果(1 6 3 5 2 7 3 5 2 4),我不知道每次用了那種骰子,我想知道最有可能的骰子序列。

其實最簡單而暴力的方法就是窮舉所有可能的骰子序列,然後依照第零個問題的解法把每個序列對應的概率算出來。然後我們從裡面把對應最大概率的序列挑出來就行了。如果馬爾可夫鏈不長,當然可行。如果長的話,窮舉的數量太大,就很難完成了。

第二種解法,維特比演算法(Viterbi algorithm)

維特比(Viterbi)演算法實際是用動態規劃解隱馬爾可夫模型預測問題,即用動態規劃(dynamic programming)求概率最大路徑(最優路徑)。這時一條路徑對應著一個狀態序列。

請不太理解動態規劃演算法的同學檢視我之前的動態規劃演算法博文,現在我們來看看如何利用Vertibi演算法計算骰子出現的概率。

還是舉個栗子:

首先,如果我們只擲一次骰子:

這裡寫圖片描述

看到結果為1。對應的最大概率骰子序列就是D4,因為D4產生1的概率是1/4,高於1/6和1/8。 
把這個情況拓展,我們擲兩次骰子:

這裡寫圖片描述

結果為1,6。這時問題變得複雜起來,我們要計算三個值,分別是第二個骰子是D6,D4,D8的最大概率。顯然,要取到最大概率,第一個骰子必須為D4。這時,第二個骰子取到D6的最大概率是:

這裡寫圖片描述 
這裡寫圖片描述

同樣的,我們可以計算第二個骰子是D4或D8時的最大概率。我們發現,第二個骰子取到D6的概率最大。而使這個概率最大時,第一個骰子為D4。所以最大概率骰子序列就是D4 D6。 
繼續拓展,我們擲三次骰子:

這裡寫圖片描述

同樣,我們計算第三個骰子分別是D6,D4,D8的最大概率。我們再次發現,要取到最大概率,第二個骰子必須為D6。這時,第三個骰子取到D4的最大概率是

這裡寫圖片描述 
這裡寫圖片描述

同上,我們可以計算第三個骰子是D6或D8時的最大概率。我們發現,第三個骰子取到D4的概率最大。而使這個概率最大時,第二個骰子為D6,第一個骰子為D4。所以最大概率骰子序列就是D4 D6 D4。

小結 
寫到這裡,大家應該看出點規律了。既然擲骰子一二三次可以算,擲多少次都可以以此類推。我們發現,我們要求最大概率骰子序列時要做這麼幾件事情。 
首先,不管序列多長,要從序列長度為1算起,算序列長度為1時取到每個骰子的最大概率。 
然後,逐漸增加長度,每增加一次長度,重新算一遍在這個長度下最後一個位置取到每個骰子的最大概率。因為上一個長度下的取到每個骰子的最大概率都算過了,重新計算的話其實不難。當我們算到最後一位時,就知道最後一位是哪個骰子的概率最大了。 
最後,我們要把對應這個最大概率的序列從後往前推出來。

推薦 
吳軍博士在《數學之美》第二版中用中國鐵路作比很形象的描述了維特比演算法的動態規劃過程,大家有興趣可以去了解一下。

其他示例

還有一個來自wiki的經典的老王治病的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

PS:示例數值是對概率值取對數之後的結果(可以讓概率相乘的計算變成對數相加),其中-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,詳見我的github程式碼

到此,已經介紹完HMM模型的五元引數,假設現在手頭上已經有這些引數的具體概率值,並且已經載入進來,(也就是該模型的字典),那麼我們只剩下Viterbi這個演算法函式,這個模型就算可以開始使用了。 
貼一個jieba分詞(java版)的Viterbi演算法程式碼:

public void viterbi(String sentence, List<String> tokens) {
        Vector<Map<Character, Double>> v = new Vector<Map<Character, Double>>();
        Map<Character, Node> path = new HashMap<Character, Node>();

        v.add(new HashMap<Character, Double>());
        for (char state : states) {
            Double emP = emit.get(state).get(sentence.charAt(0));
            if (null == emP)
                emP = MIN_FLOAT;
            v.get(0).put(state, start.get(state) + emP);
            path.put(state, new Node(state, null));
        }

        for (int i = 1; i < sentence.length(); ++i) {
            Map<Character, Double> vv = new HashMap<Character, Double>();
            v.add(vv);
            Map<Character, Node> newPath = new HashMap<Character, Node>();
            for (char y : states) {
                Double emp = emit.get(y).get(sentence.charAt(i));
                if (emp == null)
                    emp = MIN_FLOAT;
                Pair<Character> candidate = null;
                for (char y0 : prevStatus.get(y)) {
                    Double tranp = trans.get(y0).get(y);
                    if (null == tranp)
                        tranp = MIN_FLOAT;
                    tranp += (emp + v.get(i - 1).get(y0));
                    if (null == candidate)
                        candidate = new Pair<Character>(y0, tranp);
                    else if (candidate.freq <= tranp) {
                        candidate.freq = tranp;
                        candidate.key = y0;
                    }
                }
                vv.put(y, candidate.freq);
                newPath.put(y, new Node(y, path.get(candidate.key)));
            }
            path = newPath;
        }
        double probE = v.get(sentence.length() - 1).get('E');
        double probS = v.get(sentence.length() - 1).get('S');
        Vector<Character> posList = new Vector<Character>(sentence.length());
        Node win;
        if (probE < probS)
            win = path.get('S');
        else
            win = path.get('E');

        while (win != null) {
            posList.add(win.value);
            win = win.parent;
        }
        Collections.reverse(posList);

        int begin = 0, next = 0;
        for (int i = 0; i < sentence.length(); ++i) {
            char pos = posList.get(i);
            if (pos == 'B')
                begin = i;
            else if (pos == 'E') {
                tokens.add(sentence.substring(begin, i + 1));
                next = i + 1;
            }
            else if (pos == 'S') {
                tokens.add(sentence.substring(i, i + 1));
                next = i + 1;
            }
        }
        if (next < sentence.length())
            tokens.add(sentence.substring(next));
    }

對演算法有疑問的可以參考這段動畫,將程式碼單步一遍,什麼都明白了:

這裡寫圖片描述

Hanlp的作者hankcs也寫過wiki中的診所和天氣預測的例子,歡迎Java使用者查閱,連結在這裡。還有維特比演算法在ansj分詞的應用示例

我的github也有jieba版的Viterbi演算法:

https://github.com/shibing624/jieba-analysis/blob/master/src/main/java/com/huaban/analysis/jieba/viterbi/FinalSeg.java

參考文獻

學位論文:

基於字的分詞方法的研究與實現 遊治勇 電子科技大學 碩士學位論文 2015年 
基於粒子群演算法和支援向量機的中文文字分類研究 劉偉麗 河南工業大學 碩士學位論文 2010年 
基於ActiveLearning的中文分詞領域自適應方法的研究 許華婷 北京交通大學 碩士學位論文 2012年

網路文獻:

HMM wiki 
Viterbi wiki 
Matlab HMM tool 
我愛自然語言處理HMM 
HMM演算過程參考 
中文分詞應用 
hankcs使用層疊HMM角色標註 
HMM模型