維特比算法(Viterbi)
維特比算法
維特比算法shiyizhong 動態規划算法用於最可能產生觀測時間序列的-維特比路徑-隱含狀態序列,特別是在馬爾可夫信息源上下文和隱馬爾科夫模型中。術語“維特比路徑”和“維特比算法”也被用於尋找觀察結果最有可能解釋的相關dongtai 規划算法。例如在統計句法分析中動態規划可以被用於發現最有可能的上下文無關的派生的字符串,有時被稱為“維特比分析”。
利用動態規划尋找最短路徑
動態規划是運籌學的一個分支,是求解決策過程最優化的數學方法,通常情況下應用於最優化的問題,這類問題一般有很多可行的解,每個解有一個值,而我們希望從中找到最優的答案。
在計算機科學領域,應用動態規划的思想解決的最基本的一個問題就是:尋找有向無環圖(籬笆網絡)當中兩個點之間的最短路徑(實際應用於地圖導航、語音識別、分詞、機器翻譯等等)
下面舉一個比較簡單的例子做說明:求S到E的最短路徑,如下圖(各點之間距離不相同):
我們知道,要找到S到E之間最短路徑,最容易想到的方法就是窮舉法。也就是把所有可能的路徑都例舉出來。從S走向A層共有4種走法,從A層走向B層又有4種走法,從B層走向C層又有4種走法,然后C層走向E點只有一種選擇。所以最終我們窮舉出了4*4*4=64種可能。顯然,這種方法必定可行,但在實際的應用當中,對於數量及其龐大的節點數和邊數的圖,其計算復雜度也將會變得非常大,而計算效率也會隨之降低。
因此,這里選擇適用一種基於動態規划的方式來尋找最佳路徑。
所謂動態規划。其核心就是“動態”的概念,把大的問題細分為多個小的問題,基於每一步的結果再去尋找下一步的策略,通過每一步走過之后的局部最優去尋找全局最優,這樣解釋比較抽象,下面直接用回剛剛的例子說明。如下圖:
首先,我們假設S到E之間存在一條最短路徑(紅色),且這條路徑經過C2點,那么我們便一定能夠確定從S到C2的64條(4*4*4=64)子路經當中,該子路經一定最短。(證明:反證法。如果S到C2之間存在一條更短的子路經,那么便可以用它來替代原先的路徑,而原來的路徑顯然就不是最短了,這與原假設自相矛盾)。
同理,我們也可以得出從S到B2點為兩點間最短子路經的結論。這時候,真相便慢慢浮出水面:既然如此,我們計算從S點出發到點C2的最短路徑,是不是只要考慮從S出發到B層所有節點的最短路徑就可以了?答案是肯定的!因為,從S到E的“全局最短”路徑必定經過這些“局部最短”子路經。沒錯!這就是上面提及到的通過局部最優的最優去尋找全局最優,問題的規模被不斷縮小!
接下來,要揭曉答案了!繼續看下圖:
回顧之前的分析:我們計算從S到C2點的最短路徑時候只需要考慮從S出發到B層所有節點的最短路徑,B層也是。對B2來說,一共有4條路線可達,分別是A1->B2,A2->B2,A3->B2, A4->B2。我們需要做的就是A2->B2這條路線保留,而其他3條刪掉(因為根據以上的分析,他們不可能構成全程的最短路線)。Ok,來到這里,我們會發現一個和小“漏洞”,這段S->A2->B2->C2->E的路線只是我一廂情願的假設,最短路徑下不一定是經過以上這些點。所以,我們要把每層的每個節點都考慮進來。
以下是具體做法:
step1:從點S出發。對於第一層的4個節點,算出他們的距離d(S,A1),d(S,A2),d(S,A3),d(S,A4),因為只有一步,所以這些距離都是S到它們各自的最短距離
step2:對於B層的所有節點(B1,B2,B3,B4),要計算出S到他們的最短距離。我們知道,對於特定的節點B2,從S到它的路徑可以經過A層的任何一個節點(A1,A2,A3,A4)。對應的路徑長就是d(S,B2)=d(S,Ai)+d(Ai,B2)(其中i=1,2,3,4)。由於A層有4個節點(即i有4個取值),我們要一一計算,然后找到最小值。這樣,對於B層的每個節點,都需要進行4次運算,而B層有4個節點,所以共有4*4=16次運算。
step3:這一步是該算法的核心。我們從step2計算得出的階段結果只保留4個最短路徑值(每個節點保留一個)。那么,若從B層走向C層來說,該步驟的級數已經不再是4*4,而是變成4!也就是說,從B層到C層的最短路徑只需要基於B層得出的4個結果來計算。這種方法一直持續到最后一個狀態,每一步計算的復雜度為相鄰兩層的計算復雜度為4*4乘積的正比!再通俗點說,連接着兩兩相鄰層的計算符合變成了“+”號,取代了原先的“*”號。用這種方法,只需要進行4*4*2=32次計算!
其實上述的算法就是著名的維特比算法,事實上非常簡單!
若假設整個網格的寬度為D,網格長度為N,那么弱適用窮舉法整個最短路徑的算法復雜度為O(D^N),而適用這種算法的計算復雜度為O(ND^2).試想一下,弱D與N都非常大,適用維特比算法的效率將會提高幾個數量級!
嘗試用高中知識去理解一下Veterbi算法
1、 題目背景
從前有個村兒,村里的人身體情況只有兩種可能:健康或者發燒
假設這個村兒的人沒有體溫計或者百度這種神奇的東西,它唯一判斷身體情況的途徑是到村頭小診所詢問
月兒通過詢問村民的感覺,判斷他的病情,再假設村民只會回答正常、頭暈或冷
有一天村里的澳巴盧就去月兒那詢問了
第一天他告訴月兒他感覺正常
第二天他告訴月兒他有點冷
第三天他告訴月兒感覺有點頭暈
那么問題來了,月兒如何根據阿魯的描述情況,推斷出這三天中阿魯的一個身體狀態呢?
為此月兒上百度搜google,一番狂搜,發現維特比算法正好能解決這個問題,月兒樂了
2、 已知情況
隱含的身體狀況={健康,發燒}
可觀察的感覺狀態={正常、冷、頭暈}
月兒預判的阿魯的身體狀態的概率分布={健康:0.6,發燒:0.4}
月兒認為的阿魯的身體健康的轉換概率分布={
健康->健康:0.7
健康->發燒:0.3
發燒->健康:0.4
發燒->發燒:0.6
}
月兒認為的在相應的健康狀況條件下,阿魯的感覺的概率分布={
健康,正常:0.5,冷:0.4,頭暈:0.1;
發燒,正常:0.1,冷:0.3,頭暈:0.6
}
阿魯連續三天的身體感覺依次是:正常、冷、頭暈。
3、 已知上述,求:阿魯這三天的身體健康狀態變換的過程是怎么樣的?
4、 過程:
根據Viterbi理論,后一天的狀態會依賴前一天的狀態和當前的可能觀察的狀態。那么只要根據第一天的正常狀態依次推算找出到達第三天頭暈狀態的最大概率,就可以知道這三天的身體變換情況。
1.初始情況:
P(健康) = 0.6,P(發燒)=0.4。
2.求第一天的身體情況:
計算在阿驢感覺正常的情況下最可能的身體狀態。
P(今天健康) = P(正常|健康)*P(健康|初始情況) = 0.5 * 0.6 = 0.3
P(今天發燒) = P(正常|發燒)*P(發燒|初始情況) = 0.1 * 0.4 = 0.04
那么就可以認為第一天最可能的身體狀態是:健康。
3.求第二天的身體狀況:
計算在阿驢感覺冷的情況下最可能的身體狀態。
那么第二天有四種情況,由於第一天的發燒或者健康轉換到第二天的發燒或者健康。
P(前一天發燒,今天發燒) = P(前一天發燒)*P(發燒->發燒)*P(冷|發燒) = 0.04 * 0.6 * 0.3 = 0.0072
P(前一天發燒,今天健康) = P(前一天發燒)*P(發燒->健康)*P(冷|健康) = 0.04 * 0.4 * 0.4 = 0.0064
P(前一天健康,今天健康) = P(前一天健康)*P(健康->健康)*P(冷|健康) = 0.3 * 0.7 * 0.4 = 0.084
P(前一天健康,今天發燒) = P(前一天健康)*P(健康->發燒)*P(冷|發燒) = 0.3 * 0.3 *.03 = 0.027
那么可以認為,第二天最可能的狀態是:健康。
4.求第三天的身體狀態:
計算在阿驢感覺頭暈的情況下最可能的身體狀態。
P(前一天發燒,今天發燒) = P(前一天發燒)*P(發燒->發燒)*P(頭暈|發燒) = 0.027 * 0.6 * 0.6 = 0.00972
P(前一天發燒,今天健康) = P(前一天發燒)*P(發燒->健康)*P(頭暈|健康) = 0.027 * 0.4 * 0.1 = 0.00108
P(前一天健康,今天健康) = P(前一天健康)*P(健康->健康)*P(頭暈|健康) = 0.084 * 0.7 * 0.1 = 0.00588
P(前一天健康,今天發燒) = P(前一天健康)*P(健康->發燒)*P(頭暈|發燒) = 0.084 * 0.3 *0.6 = 0.01512
那么可以認為:第三天最可能的狀態是發燒。
5.結論
根據如上計算。這樣月兒斷定,阿驢這三天身體變化的序列是:健康->健康->發燒。
這個算法大概就是通過已知的可以觀察到的序列,和一些已知的狀態轉換之間的概率情況,通過綜合狀態之間的轉移概率和前一個狀態的情況計算出概率最大的狀態轉換路徑,從而推斷出隱含狀態的序列的情況。
1 import numpy as np 2 def viterbi(trainsition_probability,emission_probability,pi,obs_seq): 3 #轉換為矩陣進行運算 4 trainsition_probability=np.array(trainsition_probability) 5 emission_probability=np.array(emission_probability) 6 pi=np.array(pi) 7 obs_seq = [0, 2, 3] 8 # 最后返回一個Row*Col的矩陣結果 9 Row = np.array(trainsition_probability).shape[0] 10 Col = len(obs_seq) 11 #定義要返回的矩陣 12 F=np.zeros((Row,Col)) 13 #初始狀態 14 F[:,0]=pi*np.transpose(emission_probability[:,obs_seq[0]]) 15 for t in range(1,Col): 16 list_max=[] 17 for n in range(Row): 18 list_x=list(np.array(F[:,t-1])*np.transpose(trainsition_probability[:,n])) 19 #獲取最大概率 20 list_p=[] 21 for i in list_x: 22 list_p.append(i*10000) 23 list_max.append(max(list_p)/10000) 24 F[:,t]=np.array(list_max)*np.transpose(emission_probability[:,obs_seq[t]]) 25 return F 26 27 if __name__=='__main__': 28 #隱藏狀態 29 invisible=['Sunny','Cloud','Rainy'] 30 #初始狀態 31 pi=[0.63,0.17,0.20] 32 #轉移矩陣 33 trainsion_probility=[[0.5,0.375,0.125],[0.25,0.125,0.625],[0.25,0.375,0.375]] 34 #發射矩陣 35 emission_probility=[[0.6,0.2,0.15,0.05],[0.25,0.25,0.25,0.25],[0.05,0.10,0.35,0.5]] 36 #最后顯示狀態 37 obs_seq=[0,2,3] 38 #最后返回一個Row*Col的矩陣結果 39 F=viterbi(trainsion_probility,emission_probility,pi,obs_seq) 40 print(F)
結果:
[[ 0.378 0.02835 0.00070875]
[ 0.0425 0.0354375 0.00265781]
[ 0.01 0.0165375 0.01107422]]
每列代表Dry,Damp,Soggy的概率,每行代表Sunny,Cloud,Rainy,所以可以看出最大概率的天氣為{Sunny,Cloud,Rainy}