隱馬爾可夫模型(HMM)和Viterbi算法

1. 隱馬爾可夫模型(HMM)

在說隱馬爾可夫模型前還有一個概念叫做“馬爾科夫鏈”,既是在給定當前知識或信息的情況下,觀察對象過去的歷史狀態對於預測將來是無關的。也可以說在觀察一個系統變化的時候,他的下一個狀態如何的概率只需要觀察和統計當前的狀態即可正確得出。隱馬爾可夫鏈和貝葉斯網絡的模型思維有些接近,區別在於隱馬爾可夫的模型更爲簡化。而且隱馬爾可夫鏈是一個雙重的隨機過程,不僅狀態轉移之間是一個隨機事件,狀態和輸出之間也是一個隨機過程。

如下圖:
這裏寫圖片描述

在一個完整的觀察過程中有一些狀態的轉換,如X1X1這些輸出值來進行模型建立和計算狀態轉移的概率。

知乎上一個很好的例子:
https://www.zhihu.com/question/20962240/answer/33438846

對於HMM來說,如果前提知道所有的隱含狀態之間的轉換概率和所有隱含狀態到所有可見狀態之間的輸出概率,進行模擬是很容易的。但是往往會缺失一部分信息,如何應用算法去估計缺失的信息,就成爲了一個重要的問題。

和HMM模型相關的算法分爲3類,分別解決3種問題。

  • 知道隱含狀態數量、轉換概率、根據可見狀態鏈的結果想知道隱含狀態鏈。這個問題在語音識別領域叫做解碼問題,求解這個問題有兩種方法。第一種是求最大最大似然狀態路徑(一串骰子序列,使其產生的觀測結果的概率最大)。第二種解法是求每次擲出的骰子分別是某種骰子的概率。
  • 知道隱含狀態數量、轉換概率、根據可見狀態鏈的結果想知道產生結果的概率。這個問題的實際意義在於檢測觀察到的模型是否和已知的模型吻合。如果很多次結果都是對應了比較小的概率,那麼就說明已知的模型很可能是錯的。
  • 知道隱含狀態數量、不知道轉換概率、根據可見狀態鏈的結果想反推出轉換概率。這個問題是最重要的,因爲是最爲常見的。很多時候只有可見結果,不知道HMM模型中的參數,需要從可見結果估計這些參數。

2. 維特比算法

當馬爾科夫鏈很長時,根據古典概型的分佈特點來計算概率時窮舉的數量太大,就很難得到結果。這時就要用到維特比算法。維特比算法就是爲了找出可能性最大的隱藏序列,即剛剛提到的第一個問題。這種算法是一種鏈的可能性問題,現在應用最廣泛的是CDMA和打字提示功能。

維特比算法的整體思路是尋找上一段信息和它跟隨的下一段信息的轉換概率問題。

2.1 打字輸入提示應用

這裏寫圖片描述

這是常用的搜狗輸入法的示例。在本例中只是考慮最簡單的因素,下面是模擬輸入法的猜測方法給出一個算法示例,先給出各級轉換矩陣。如下:

這裏寫圖片描述

其實這些概率矩陣都是由統計產生,而且每個雙字詞、三字詞等的輸入矩陣都由這種統計產生。在輸入雙字詞漢子拼音時會根據轉移概率表進行計算。多個詞相連就是多個轉移矩陣的概率相乘計算,從而得到概率最大的輸入可能項。

2.2 代碼:

# coding=utf-8
import numpy as np

jin = ['近', '斤', '今', '金', '盡']
jin_per = [0.3, 0.2, 0.1, 0.06, 0.03] # 單字詞概率矩陣

jintian = ['天', '填', '田', '甜', '添']
jintian_per = [
    [0.001, 0.001, 0.001, 0.001, 0.001],  # 轉移矩陣
    [0.001, 0.001, 0.001, 0.001, 0.001],
    [0.990, 0.001, 0.001, 0.001, 0.001],
    [0.002, 0.001, 0.850, 0.001, 0.001],
    [0.001, 0.001, 0.001, 0.001, 0.001]]

wo = ['我', '窩', '喔', '握', '臥']
wo_per = [0.400, 0.150, 0.090, 0.050, 0.030]

women = ['們', '門', '悶', '燜', '捫']
women_per = [
    [0.970, 0.001, 0.003, 0.001, 0.001],
    [0.001, 0.001, 0.001, 0.001, 0.001],
    [0.001, 0.001, 0.001, 0.001, 0.001],
    [0.001, 0.001, 0.001, 0.001, 0.001],
    [0.001, 0.001, 0.001, 0.001, 0.001]]

qing = ['請', '晴', '清', '輕', '情']
qing_per = [0.2, 0.12, 0.09, 0.05, 0.02]

qinghua = ['話', '畫', '化', '花', '華']
qinghua_per = [
    [0.001, 0.001, 0.001, 0.001, 0.001],
    [0.001, 0.001, 0.001, 0.001, 0.001],
    [0.001, 0.001, 0.001, 0.001, 0.950],
    [0.001, 0.001, 0.850, 0.001, 0.001],
    [0.003, 0.001, 0.001, 0.002, 0.001]]

da = ['大', '打', '達', '搭', '噠']
da_per = [0.300, 0.130, 0.120, 0.110, 0.090]

daxue = ['學', '雪', '血', '薛', '穴']
daxue_per = [
    [0.800, 0.140, 0.023, 0.004, 0.001],
    [0.001, 0.001, 0.001, 0.001, 0.001],
    [0.001, 0.001, 0.001, 0.001, 0.001],
    [0.001, 0.001, 0.001, 0.001, 0.001],
    [0.001, 0.001, 0.001, 0.001, 0.001]]

N = 5


# 一個字的預測
def found_from_oneword(oneword_per):
    index = []
    values = []
    a = np.array(oneword_per)
    print 'a:',a
    print 'argsort(a):',np.argsort(a)[::-1][:N]
    for v in np.argsort(a)[::-1][:N]: # 遍歷降序前5的概率索引,
        index.append(v)
        values.append(oneword_per[v])
    return index, values

# 一個詞的預測
def found_from_twoword(oneword_per, twoword_per):
    last = 0
    for i in range(len(oneword_per)):
        #print oneword_per[i],'\n', twoword_per[i]
        current = np.multiply(oneword_per[i], twoword_per[i]) # 字概率乘以轉移概率
        if i == 0:
            last = current
        else:
            last = np.concatenate((last, current), axis=0) # 字符串拼接
    #print 'last:',last  # 單個字概率矩陣乘以轉移概率矩陣
    index = []
    values = []
    #print np.argsort(last)[::-1][:N]
    for v in np.argsort(last)[::-1][:N]: # 概率從大到低的排列(前5個)
        index.append([v / 5, v % 5]) # 由數組的索引值得到其在矩陣中的座標值
        values.append(last[v])
    print 'index:',index,'\n','values:',values 
    return index, values

# 字詞的預測
def predict(word):
    if word == 'jin':
        for i in found_from_oneword(jin_per)[0]: # 遍歷概率索引值
            print jin[i] # 輸出排序後的索引對應的值
        print '.......................................'
    elif word == 'jintian':
        for i in found_from_twoword(jin_per, jintian_per)[0]: # 傳入的是概率矩陣和轉移矩陣
            print 'i:',i
            # jin[i[0]]:詞組在矩陣中的第一個字的下標;jintian[i[1]]:在矩陣中的列表示後一個字的下標
            print jin[i[0]] + jintian[i[1]] 
        print '.......................................'
    elif word == 'wo':
        for i in found_from_oneword(wo_per)[0]:
            print wo[i]
        print '.......................................'
    elif word == 'women':
        for i in found_from_twoword(wo_per, women_per)[0]:
            print wo[i[0]] + women[i[1]]
        print '.......................................'
    elif word == 'jintianwo':
        index1, values1 = found_from_oneword(wo_per)
        print 'index1, values1:', index1, '\n',values1 # 先得到'wo'的排序
        index2, values2 = found_from_twoword(jin_per, jintian_per) # 再得到'jintian'排序
        print 'index2, values2:', index2, '\n',values2
        last = np.multiply(values1, values1) # 得到'jintian'和'wo'的最大概率乘積
        for i in np.argsort(last)[::-1][:N]:
            print jin[index2[i][0]], jintian[index2[i][1]], wo[i]
        print '.......................................'
    elif word == 'jintianwomen':
        index1, values1 = found_from_twoword(jin_per, jintian_per) # 得到'jintian'的排序
        index2, values2 = found_from_twoword(wo_per, women_per) # 得到'women'的排序
        last = np.multiply(values1, values1) # 得到'jintian'和'women'相乘的排序
        for i in np.argsort(last)[::-1][:N]:
            print jin[index1[i][0]], jintian[index1[i][1]], wo[index2[i][0]], women[index2[i][1]]
        print '.......................................'
    else:
        pass


if __name__ == '__main__':
    predict('jin')
    # 近
    # 斤
    # 今
    # 金
    # 盡
    predict('jintian')
    # 今天
    # 金田
    # 近天
    # 近填
    # 近田
    predict('wo')
    # 近
    # 斤
    # 今
    # 金
    # 盡
    predict('women')
    # 我們
    # 我悶
    # 我門
    # 我燜
    # 我捫
    predict('jintianwo')
    # 今 天 我
    # 金 田 窩
    # 近 天 喔
    # 近 填 握
    # 近 田 臥
    predict('jintianwomen')
    # 今 天 我 們
    # 金 田 我 悶
    # 近 田 我 捫
    # 近 填 我 燜
    # 近 天 我 門
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159

運行結果:

a: [ 0.3   0.2   0.1   0.06  0.03]
argsort(a): [0 1 2 3 4]
近
斤
今
金
盡
.......................................
index: [[2, 0], [3, 2], [0, 0], [0, 1], [0, 2]] 
values: [0.099000000000000005, 0.050999999999999997, 0.00029999999999999997, 0.00029999999999999997, 0.00029999999999999997]
i: [2, 0]
今天
i: [3, 2]
金田
i: [0, 0]
近天
i: [0, 1]
近填
i: [0, 2]
近田
.......................................
a: [ 0.4   0.15  0.09  0.05  0.03]
argsort(a): [0 1 2 3 4]
我
窩
喔
握
臥
.......................................
index: [[0, 0], [0, 2], [0, 1], [0, 3], [0, 4]] 
values: [0.38800000000000001, 0.0012000000000000001, 0.00040000000000000002, 0.00040000000000000002, 0.00040000000000000002]
我們
我悶
我門
我燜
我捫
.......................................
a: [ 0.4   0.15  0.09  0.05  0.03]
argsort(a): [0 1 2 3 4]
index1, values1: [0, 1, 2, 3, 4] 
[0.4, 0.15, 0.09, 0.05, 0.03]
index: [[2, 0], [3, 2], [0, 0], [0, 1], [0, 2]] 
values: [0.099000000000000005, 0.050999999999999997, 0.00029999999999999997, 0.00029999999999999997, 0.00029999999999999997]
index2, values2: [[2, 0], [3, 2], [0, 0], [0, 1], [0, 2]] 
[0.099000000000000005, 0.050999999999999997, 0.00029999999999999997, 0.00029999999999999997, 0.00029999999999999997]
今 天 我
金 田 窩
近 天 喔
近 填 握
近 田 臥
.......................................
index: [[2, 0], [3, 2], [0, 0], [0, 1], [0, 2]] 
values: [0.099000000000000005, 0.050999999999999997, 0.00029999999999999997, 0.00029999999999999997, 0.00029999999999999997]
index: [[0, 0], [0, 2], [0, 1], [0, 3], [0, 4]] 
values: [0.38800000000000001, 0.0012000000000000001, 0.00040000000000000002, 0.00040000000000000002, 0.00040000000000000002]
今 天 我 們
金 田 我 悶
近 田 我 捫
近 填 我 燜
近 天 我 門
.......................................
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61

從運行結果可以看出其實就是通過統計得到的概率矩陣和轉移矩陣的乘積之後的排序計算。

3. 筆記

3.1 for v in np.argsort(a)[::-1][:N]的用法:

In [7]: a=array([1, 2, 3])

In [8]: a
Out[8]: array([1, 2, 3])

In [9]: a[::-1]
Out[9]: array([3, 2, 1])

In [10]: a[::-1][:3]
Out[10]: array([3, 2, 1])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

3.2 last = np.concatenate((last, current), axis=0)的使用:

>>> import numpy as np
>>> a = np.array([[1, 2], [3, 4]])
>>> a
array([[1, 2],
       [3, 4]])
>>> b = np.array([[5, 6]])
>>> np.concatenate((a, b), axis=0)
array([[1, 2],
       [3, 4],
       [5, 6]])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

參考:《白話大數據與機器學習》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章