HMM概念介紹
HMM是關于時序的概率模型,描述一個隱藏的馬爾科夫鏈隨機生成不可觀測的狀態隨機序列,再由各個狀態生成一個可觀測的隨機序列的過程。
下面用一個例子來解釋:
我在東京的女朋友每天根據天氣{下雨,天晴}決定當天的活動{散步,購物,清理房間}中的一種,她不告訴我每天東京的天氣狀況,我只能從他的朋友圈看到他今天的活動。
這個例子其中{下雨,天晴}便是隱含狀態鏈;{散步,購物,清理房間}是可見狀態鏈;隱含狀態(天氣)之間的相互轉換概率叫做狀態轉移概率;盡管可見狀態(活動)之間沒有轉換概率,但是隱含狀態和可見狀態之間有一個概率(即不同的天氣狀況下進行不同的活動的概率)叫做發射概率;還有個初始概率即最開始是晴天還是雨天的概率。
任何一個HMM都可以通過下列五元組來描述:
:param obs:觀測序列
:param states:隱狀態
:param start_p:初始概率(隱狀態)
:param trans_p:轉移概率(隱狀態)
:param emit_p: 發射概率 (隱狀態表現為顯狀態的概率)
HMM的三個問題
HMM有三個基本問題:
預測問題(解碼問題)
知道天氣有幾種狀況(隱含狀態數量),天氣之間的轉換概率(轉移概率),根據每天的活動情況(可見狀態鏈),我想知道每天的天氣狀況(隱含狀態鏈)。概率計算問題
知道天氣有幾種狀況(隱含狀態數量),天氣之間的轉換概率(轉移概率),根據每天的活動情況(可見狀態鏈),我想知道今天做了這種活動的概率。學習問題
知道天氣有幾種狀況(隱含狀態數量),不知道天氣之間的轉換概率(轉移概率),根據每天的活動情況(可見狀態鏈),我想反推出天氣之間的轉換概率(轉移概率)。
問題解決
本文重點介紹第一個問題的解決算法維特比算法。
維特比算法
假設我在朋友圈看到我女朋友最近三天的活動分別是{散步、購物、清理房間},那我該如何估算這三天的天氣狀況。
預測問題可以用維特比算法來解決。維特比算法實際是用動態規劃求解隱馬爾科夫模型預測問題,即用動態規劃求概率最大路徑,即每天最可能的天氣狀況。
很明顯,第一天天晴還是下雨可以算出來:
定義
V[時間][今天天氣] = 概率
,注意今天天氣指的是,前幾天的天氣都確定下來了(概率最大)今天天氣是X的概率,這里的概率就是一個累乘的概率了。因為第一天我的朋友去散步了,所以第一天下雨的概率
V[第一天][下雨] = 初始概率[下雨] * 發射概率[下雨][散步] = 0.6 * 0.1 = 0.06
,同理可得V[第一天][天晴] = 0.24
。從直覺上來看,因為第一天朋友出門了,她一般喜歡在天晴的時候散步,所以第一天天晴的概率比較大,數字與直覺統一了。從第二天開始,對于每種天氣Y,都有
前一天天氣是X的概率 * X轉移到Y的概率 * Y天氣下朋友進行這天這種活動的概率
。因為前一天天氣X有兩種可能,所以Y的概率有兩個,選取其中較大一個作為V[第二天][天氣Y]的概率
,同時將今天的天氣加入到結果序列中。比較
V[最后一天][下雨]
和[最后一天][天晴]
的概率,找出較大的哪一個對應的序列,就是最終結果。
Python實現
# 打印路徑概率表
def print_dptable(V):
print(" ", end=' ')
for i in range(len(V)):
print("%7d" % i, end=' ')
print('\n')
for y in V[0].keys():
print("%.5s: " % y, end=' ')
for t in range(len(V)):
print("%.7s" % ("%f" % V[t][y]), end=' ')
print('\n')
def viterbi(obs, states, start_p, trans_p, emit_p):
"""
:param obs:觀測序列
:param states:隱狀態
:param start_p:初始概率(隱狀態)
:param trans_p:轉移概率(隱狀態)
:param emit_p: 發射概率(隱狀態表現為顯狀態的概率)
:return:
"""
# 路徑概率表 V[時間][隱狀態] = 概率
V = [{}]
# 一個中間變量,代表當前狀態是哪個隱狀態
path = {}
# 初始化初始狀態 (t == 0)
for y in states:
V[0][y] = start_p[y] * emit_p[y][obs[0]]
path[y] = [y]
# 對 t > 0 跑一遍維特比算法
for t in range(1, len(obs)):
V.append({})
newpath = {}
for y in states:
# 概率 隱狀態 = 前狀態是y0的概率 * y0轉移到y的概率 * y表現為當前狀態的概率
(prob, state) = max([(V[t - 1][y0] * trans_p[y0][y] * emit_p[y][obs[t]], y0) for y0 in states])
# 記錄最大概率
V[t][y] = prob
# 記錄路徑
newpath[y] = path[state] + [y]
# 不需要保留舊路徑
path = newpath
print_dptable(V)
(prob, state) = max([(V[len(obs) - 1][y], y) for y in states])
return (prob, path[state])
def main():
states = ('Rainy', 'Sunny')
observations = ('walk', 'shop', 'clean')
start_probability = {'Rainy': 0.6, 'Sunny': 0.4}
transition_probability = {
'Rainy' : {'Rainy': 0.7, 'Sunny': 0.3},
'Sunny' : {'Rainy': 0.4, 'Sunny': 0.6},
}
emission_probability = {
'Rainy' : {'walk': 0.1, 'shop': 0.4, 'clean': 0.5},
'Sunny' : {'walk': 0.6, 'shop': 0.3, 'clean': 0.1},
}
result = viterbi(observations,
states,
start_probability,
transition_probability,
emission_probability)
return result
result = main()
print(result)
結果
0 1 2
Rainy: 0.06000 0.03840 0.01344
Sunny: 0.24000 0.04320 0.00259
(0.01344, ['Sunny', 'Rainy', 'Rainy'])
HMM和維特比算法在NLP上的應用
具體到分詞系統,可以將天氣當成“標簽”,活動當成“字或詞”。那么,幾個NLP的問題就可以轉化為:
詞性標注
給定一個詞的序列(也就是句子),找出最可能的詞性序列(標簽是詞性)。如ansj分詞和ICTCLAS分詞等。分詞
給定一個字的序列,找出最可能的標簽序列(斷句符號:[詞尾]或[非詞尾]構成的序列)。結巴分詞目前就是利用BMES標簽來分詞的,B(開頭),M(中間),E(結尾),S(獨立成詞)命名實體識別
給定一個詞的序列,找出最可能的標簽序列(內外符號:[內]表示詞屬于命名實體,[外]表示不屬于)。如ICTCLAS實現的人名識別、翻譯人名識別、地名識別都是用同一個Tagger實現的。