【火爐煉AI】機器學習044-建立隱馬爾科夫模型
(本文所使用的Python庫和版本號: Python 3.6, Numpy 1.14, scikit-learn 0.19, matplotlib 2.2 )
隱馬爾科夫模型(Hidden Markov Model, HMM)是非常經典的機器學習模型,在語音識別,自然語言處理,模式識別等領域中有著非常廣泛的應用。故而理解和數量掌握HMM是機器學習領域中一項重要的技能。
1. 什麼是隱馬爾科夫模型
隱馬爾科夫模型是統計模型,用來描述一個含有隱含未知引數的馬爾科夫過程,其難點是從可觀察引數確定過程中的隱含引數,然後利用這些引數做進一步的分析。
隱馬爾科夫模型是關於時序的概率模型,描述一個由隱藏的馬爾科夫鏈隨機生成不可觀測的狀態隨機序列,再由各個狀態生成一個可觀測的觀測隨機序列的過程。
1.1 馬爾科夫過程
什麼是馬爾科夫過程?一種無記憶的隨機過程,注意三個時間點:過去,現在,將來,(或者說:昨天,今天,明天)。在已知現在的狀態的情況下,將來會處於哪種狀態只取決於已知的現在狀態,而與過去的狀態無關。即,已知“現在”的狀態情況下,“將來”的狀態和“過去”的狀態是相互獨立的,這種特性成為馬爾科夫性,具有這種性質的隨機過程叫做馬爾科夫過程,也稱為馬爾科夫鏈。
舉個例子:荷花池中一隻青蛙在跳動,從一片荷葉跳到下一片上。假設青蛙跳過的荷葉編號為X0,X1,X2,....Xn,且現在青蛙處於Xn這片荷葉上,那麼青蛙下一刻要跳到哪片荷葉完全是由現在的荷葉Xn來決定的,而與X0,X1....Xn-1這些荷葉無關。這一過程可以認為是馬爾科夫過程。
所以馬爾科夫鏈有很強的時間觀念,在t0時刻,青蛙位於X0荷葉上,t1時刻,青蛙位於X1荷葉上。
1.2 一個鏈條,兩個序列,三個概率
在馬爾科夫模型中,有六個引數非常重要,一個鏈條是指一個馬爾科夫過程,由時間來連線整個鏈條。兩個序列是每個時間點(鏈條上的每個節點)都具有兩個值,由這兩個值組成的鏈條(或序列):
1)狀態序列(State Sequence): 由隱藏的馬爾科夫鏈生成的狀態的序列,當前處於某種狀態,由這些狀態組成的序列。
2)觀測序列(Observation Sequence):很多情況下,狀態並不能完全被我們所看到,我們只能看到某一個狀態所表現出來的結果,這些結果可以被我們觀測到,故而這些觀測結果所組成的序列就是觀測序列,它能部分地代表狀態序列,但是不能完整的展現狀態序列。
三個概率是指馬爾科夫鏈往下推動的動力,代表這鏈條走勢的方向。
1)初始狀態概率:表示最開始時刻處於各個狀態的概率。
2)狀態轉移概率:tn時刻的狀態轉移到下一個tn+1時刻狀態的概率。
3)觀測概率:如果將馬爾科夫鏈看成一個矩陣,每一行代表每一個時刻,從上往下都是按照時間來排列的,每一行就代表某一個時刻,也對應於某一個狀態值,每一列就是這個狀態下的觀測值,觀測概率就是在某狀態下,出現某個觀測值的概率。
還有兩個基本假設:
1)齊次馬爾科夫假設:隱馬爾科夫鏈在任意時刻T的狀態值,只依賴於他前一個時刻的狀態值,而與其他的狀態值和觀測值無關,這也是隱馬爾科夫的特點。
2)觀測獨立性假設:任意時刻的觀測值只依賴於該時刻的狀態,而與其他的觀測值和其他狀態無關。
有一篇博文講的非常好,有助於對隱馬爾科夫模型的理解,該博文是: ofollow,noindex">一文搞懂HMM(隱馬爾可夫模型)
下面我借用這篇博文中的圖片和例子來說明一下上述幾個關鍵引數。
1.3 舉例說明
假如有三個不同骰子,如下圖所示,第一個為平時我們所見的正方體型,有六個面,標號為D6,每擲一次可以得到1-6中的任意一個數字,每個數字的概率為1/6.第二個為四面體,標號為D4,會得到1-4中的任意一個數字,概率為1/4,第三個為八面體,標號D8,會得到1-8中的任意一個數字,概率為1/8.

現在我們從這三個骰子中隨機選擇一個,然後每個骰子擲一次,得到一個數字,一共選10詞,擲10次。那麼我們可能選擇的骰子序列是:D6 D8 D8 D6 D4 D8 D6 D6 D4 D8,得到的數字可能是:1 6 3 5 2 7 3 5 2 4。在每一次時,我們選擇的骰子組成的序列就是狀態序列,如上的(D6 D8 D8 D6 D4 D8 D6 D6 D4 D8),而由該骰子得到的數字就是我們的觀測值,組成的就是觀測序列(如數字1 6 3 5 2 7 3 5 2 4)。
很明顯,觀測序列是我們可以輕而易舉得到的,也是很容易觀察到的,故而有時也稱為可見狀態鏈,而很多時候,我們不知道選用的是哪個骰子,所以骰子這個狀態序列就是一個隱藏的序列,也被稱為隱含狀態鏈。
隱馬爾科夫模型中所說的馬爾科夫鏈其實就是隱含狀態鏈。
上面講到的初始狀態概率就是最開始我們選擇某個骰子的概率,比如此處我們隨機選擇,那麼初始狀態概率就是1/3。
狀態轉移概率就是從一個骰子到下一個骰子的轉換概率,也就是,這一次我們用的是D4骰子,那麼下一次我們應該選擇哪個骰子?此處我們是隨機選擇,故而下一個骰子是D4,D6,D8的概率都是1/3,假如我們修改一下規則,比如D6後面不能選擇D4,且D6後面再選擇D6的概率是0.9,D8的概率是0.1,那麼此時的狀態轉移概率就是(0,0.9,0.1)。由於規則改變,故而狀態轉移概率也發生改變,得到的就是另外一個全新的HMM。需要注意的是,狀態轉移概率只存在於狀態之間的轉移過程中,而不存在觀測值的改變過程中。
觀測概率是指,某一個狀態下得到某個觀測值的概率,這個例子中,加入當前骰子是D6,那麼正常情況下,此處我們得到的觀測值是1-6中的任意一個數字,每個數字的概率為1/6,那麼此時狀態的觀測概率就是每個都是1/6。假如有人出老千,對D6骰子動過手腳,比如使得得到1的概率是1/2,其他數字(2-6)的概率都是1/10,那麼此時的觀測概率也發生改變,也是一個全新的HMM模型。
用圖表示為:注意圖中的隱含狀態鏈就是狀態序列,可見狀態組成的鏈條就是觀測序列,黑色箭頭表示的從一個隱含狀態到下一個銀行狀態的轉換的概率就是狀態轉移概率,紅色箭頭表示的從一個隱含狀態到一個觀測值的輸出概率就是觀測概率。

對於完全隨機的選擇骰子的過程,那麼下一次選擇骰子的概率都是1/3,這種狀態轉移概率可以表示為:

1.4 演算法種類
在真實世界中,很多時候我們並不能知道所有的上述五個引數值,只知道其中的某幾個,那麼這些問題的解決方式也各種各樣,下面主要講解和HMM模型有關的三類演算法,分別解決三種問題。
1)已知狀態數量,狀態轉移概率,觀測序列,求解狀態序列。--解碼問題,用維特比演算法解決。
這類問題就是:假如我知道有哪幾種骰子(狀態數量),每種骰子後面應該怎麼選擇下一個骰子(狀態轉移概率),也知道骰子擲出來的結果(觀測序列),比如擲出來的一系列結果是(數字1 6 3 5 2 7 3 5 2 4),那麼我們怎麼知道每一個步驟都是哪個骰子擲出來的(求解狀態序列),比如數字1是D4,D6,D8哪個骰子的的來的?
這個問題在語音識別領域叫做解碼問題,有兩種解法,第一種解法是求最大似然狀態路徑,也就是,求解一串骰子序列,這串骰子序列產生的觀測結果(即得到1 6 3 5 2 7 3 5 2 4這一串數字)的概率最大。第二種解法是,求每次擲出的骰子分別是某種骰子的概率,比如可以求得第一次擲D4的概率是0.5,D6的概率是0.3,D8的概率是0.2.具體的解法過程請參考其他博文。
2)已知狀態數量,狀態轉移概率,觀測序列,求解得到某個觀測值的概率。 --概率問題,向前向後演算法。
這類問題和上面類似,但是這次我們想知道在某一個時刻得到某個數字的概率。這個問題的求解貌似沒有太多意義,因為得到的只是某個數字的概率,而不是真正的這個數字,但是在賭彩,股價預測等方面,卻又很大的實用價值,如果在賭彩中知道下一刻某個數字出現的概率,或者在股價執行上,知道下一個交易日出現某個價格的概率,那麼我們就能在這個上面賺大把銀子。
3)已知狀態數量,觀測序列,求解狀態轉移概率。--學習問題,B-W演算法。
這是最常見的情況,很多時候我們只知道有幾個骰子,並且擲這些骰子得到了很多數字,那麼怎麼計算每種骰子後面該怎麼選擇下一個骰子了?
關於這些問題的解法,請參考博文: 一文搞懂HMM(隱馬爾可夫模型)
2. 建立HMM模型
隱馬爾科夫模型是一個生成模型,也就意味著一旦掌握了其底層結構,就可以產生資料。
2.1. 檢視資料集序列
本次構建HMM模型的資料都存放在data_hmm.txt中,首先我們載入這個資料集到記憶體中來。這個資料集前兩列是日期,第三列是我們所要分析的序列資料。對這列資料繪圖可以看出其內在邏輯和結構,如下:

2.2. 構建HMM模型
構建HMM模型的程式碼為:
dataset_X=df.iloc[:,2].values.reshape(1,-1).T # 前面兩列是日期,用第2列作資料集 # 需要構建成二維陣列形式,故而需要加上一個軸 print(dataset_X.shape) # 有3312個訓練樣本組成一列 # 建立HMM模型,並訓練 from hmmlearn.hmm import GaussianHMM model = GaussianHMM(n_components=4, covariance_type="diag", n_iter=1000) model.fit(dataset_X) 複製程式碼
由於此處我們只有一列資料,故而需要對這列資料準備一下,將其轉變為二維陣列,此處得到(3312,1)的二維陣列,每一行就是一個觀測值,構成了一個觀測序列。
這個程式碼中用到了hmmlearn模組,這個模組需要自己通過pip install hmmlearn來安裝。這個模組實現了三種HMM模型類,按照觀測狀態是連續狀態還是離散狀態,可以分為兩類,GaussianHMM和GMMHMM是連續觀測狀態的HMM模型,而MultinomialHMM是離散觀測狀態的模型
本專案第一,二列代表時間,且可以看出時間上是連續的,故而使用GaussianHMM模型,這個模型假設觀測狀態符合高斯分佈,而GMMHMM類則假設觀測狀態符合混合高斯分佈。一般我們使用GaussianHMM即可。
GuassianHMM的幾個引數:
1,n_components: 狀態數量,預設為1。
2,covariance_type:有四種: spherical: 在每個狀態下,觀測值的所有特性分量都使用相同的方差值,對應協方差矩陣的非對角為0,對角值相等,即球面特性,這是最簡單的高斯分佈PDF。diag: 在每個狀態下,觀測值使用對角協方差矩陣,該矩陣非對角為0,對角值不相等,是covariance_type的預設引數。full:在每個狀態下,觀測值使用完全協方差矩陣,裡面的元素都為非零。tied: 指所有的狀態使用相同的完全協方差矩陣。這四種PDF型別裡面,spherical, diag和full代表三種不同的高斯分佈概率密度函式,而tied則可以看作是GaussianHMM和GMMHMM的特有實現。其中,full是最強大的,但是需要足夠多的資料來做合理的引數估計;spherical是最簡單的,通常用在資料不足或者硬體平臺效能有限的情況之下;而diag則是這兩者一個折中。在使用的時候,需要根據可觀察態向量不同特性的相關性來選擇合適的型別。
3,n_iter: 最大迴圈次數。
還有一些其他引數,但是隻有上面三個引數最重要,其他可以用預設值即可。
對上面的HMM模型進行訓練之後,可以檢視該模型內部的一些結構和內容,比如:計算每一個隱含狀態的均值和方差:
for i in range(model.n_components): # 打印出每個隱含狀態 mean=model.means_[i][0] variance=np.diag(model.covars_[i])[0] print('Hidden state: {}, Mean={:.3f}, Variance={:.3f}' .format((i+1),mean,variance)) 複製程式碼
-------------------------------------輸---------出--------------------------------
Hidden state: 1, Mean=5.092, Variance=0.677 Hidden state: 2, Mean=2.601, Variance=0.257 Hidden state: 3, Mean=8.099, Variance=0.678 Hidden state: 4, Mean=0.600, Variance=0.254
--------------------------------------------完-------------------------------------
2.3. 檢視HMM模型的預測效果
HMM模型是生成模型,我們訓練這個模型的最終目的是希望它能夠預測未來某個時點的值,對於這個模型,我們預測出1000個數據點,看看模型的效果如何。
# 使用HMM模型生成資料 N=1000 samples,_=model.sample(N) plt.plot(samples[:,0]) 複製程式碼

2.4. 提升HMM模型的效能
上面可以看出,這個模型生成的資料的序列圖和原始的資料序列圖相差甚遠,雖然有點起伏波動貌似一致,但是還難以滿足我們需要,我們需要的是兩者的相似度越大越好,越大表示HMM模型已經找到了資料的變動規律。
# 模型的提升,修改n_components for i in [8,12,16,18,20]: model = GaussianHMM(n_components=i, covariance_type="diag", n_iter=1000) model.fit(dataset_X) samples,_=model.sample(1000) plt.plot(samples[:,0]) plt.title('hidden state N={}'.format(i)) plt.show() 複製程式碼
執行後會得到五副圖,可以看我的原始碼,此處只貼出最後一幅的圖片:

可以看出,隨著隱含狀態越大,得到的預測結果圖和原始序列圖的相似度越大,表明HMM模型越準確。
########################小**********結###############################
1,HMM模型的構建和訓練很簡單,直接使用hmmlearn模組中的GuassianHMM函式即可,其訓練時只需要時序資料即可。
2,比較難的是理解HMM模型,主要理解隱馬爾科夫鏈,兩種序列,三種概率,就能有個大概瞭解。
3,HMM模型中GuassianHMM函式的引數優化空間不大,只是優化隱含狀態數即可,從圖中看出,即使隱含狀態數比較大得到的結果也不是很理想,此時需要用深度學習裡面的RNN或LSTM來得到準確率更高的模型。
#################################################################
注:本部分程式碼已經全部上傳到( 我的github )上,歡迎下載。
參考資料:
1, Python機器學習經典例項,Prateek Joshi著,陶俊傑,陳小莉譯