維特比演算法

維特比演算法（Viterbi algorithm）是一種動態規劃演算法。它用於尋找最有可能產生觀測事件序列的維特比路徑 —— 隱含狀態序列，特別是在馬爾可夫資訊源上下文和隱馬爾可夫模型中。

術語 “維特比路徑” 和 “維特比演算法” 也被用於尋找觀察結果最有可能解釋相關的動態規劃演算法。例如在統計句法分析中動態規劃演算法可以被用於發現最可能的上下文無關的派生（解析）的字串，有時被稱為 “維特比分析”。

維特比演算法由安德魯・維特比於 1967 年提出，用於在數字通訊鏈路中解卷積以消除噪音。 此演算法被廣泛應用於 CDMA 和 GSM 數字蜂窩網路、撥號調變解調器、衛星、深空通訊和 802.11 無線網路中解卷積碼。現今也被常常用於語音識別、關鍵字識別、計算語言學和生物資訊學中。例如在語音（語音識別）中，聲音訊號做為觀察到的事件序列，而文字字串，被看作是隱含的產生聲音訊號的原因，因此可對聲音訊號應用維特比演算法尋找最有可能的文字字串。

演算法

假設給定隱式馬爾可夫模型（HMM）狀態空間 ，共有 k 個狀態，初始狀態 的機率為 ，從狀態 到狀態 的轉移機率為 。 令觀察到的輸出為 。 產生觀察結果的最有可能的狀態序列 由遞推關係給出：

此處 是前 個最終狀態為 的觀測結果最有可能對應的狀態序列的機率。 透過儲存向後指標記住在第二個等式中用到的狀態 可以獲得維特比路徑。宣告一個函式 ，它返回若 時計算 用到的 值 或若 時的。這樣:

這裡我們使用了的標準定義

演算法複雜度為

例子

想象一個鄉村診所。村民有著非常理想化的特性，要麼健康要麼發燒。他們只有問診所的醫生的才能知道是否發燒。 聰明的醫生透過詢問病人的感覺診斷他們是否發燒。村民只回答他們感覺正常、頭暈或冷。

假設一個病人每天來到診所並告訴醫生他的感覺。醫生相信病人的健康狀況如同一個離散馬爾可夫鏈。病人的狀態有兩種 “健康” 和 “發燒”，但醫生不能直接觀察到，這意味著狀態對他是 “隱含” 的。每天病人會告訴醫生自己有以下幾種由他的健康狀態決定的感覺的一種：正常、冷或頭暈。這些是觀察結果。 整個系統為一個隱馬爾可夫模型 (HMM)。

醫生知道村民的總體健康狀況，還知道發燒和沒發燒的病人通常會抱怨什麼症狀。 換句話說，醫生知道隱馬爾可夫模型的引數。 這可以用 Python 語言表示如下:

states = ('Healthy', 'Fever')

observations = ('normal', 'cold', 'dizzy')

start_probability = {'Healthy': 0.6, 'Fever': 0.4}

transition_probability = {'Healthy' : {'Healthy': 0.7, 'Fever': 0.3},

'Fever' : {'Healthy': 0.4, 'Fever': 0.6},

}

emission_probability = {'Healthy' : {'normal': 0.5, 'cold': 0.4, 'dizzy': 0.1},

'Fever' : {'normal': 0.1, 'cold': 0.3, 'dizzy': 0.6},

}

在這段程式碼中，起始機率 startprobability 表示病人第一次到訪時醫生認為其所處的 HMM 狀態，他唯一知道的是病人傾向於是健康的。這裡用到的特定機率分佈不是均衡的，如轉移機率大約是 {'Healthy': 0.57, 'Fever': 0.43}。 轉移機率 transitionprobability 表示潛在的馬爾可夫鏈中健康狀態的變化。在這個例子中，當天健康的病人僅有 30% 的機會第二天會發燒。放射機率 emission_probability 表示每天病人感覺的可能性。假如他是健康的，50% 會感覺正常。如果他發燒了，有 60% 的可能感覺到頭暈。

病人連續三天看醫生，醫生發現第一天他感覺正常，第二天感覺冷，第三天感覺頭暈。 於是醫生產生了一個問題：怎樣的健康狀態序列最能夠解釋這些觀察結果。維特比演算法解答了這個問題。

def viterbi (obs, states, startp, transp, emit_p):

V =

path =

Initialize

for st in states:

V0st = startpst * emitpstobs0

pathst = st

Run Viterbi when t > 0

for t in range (1,len (obs)):

V.append ()

newpath =

for curr_st in states:

pathstocurr_st =

for prev_st in states:

pathstocurrst.append ((Vt-1prevst transpprevstcurrst emitpcurrstobst, prevst))

currprob, prevstate = max (pathstocurr_st)

Vtcurrst = currprob

newpathcurrst = pathprevstate + curr_st

No need to keep the old paths

path = newpath

for line in dptable (V):

print (line)

prob, end_state = max ((V-1st, st) for st in states)

return prob, pathend_state

def dptable (V):

Print a table of steps from dictionary

yield ' ' * 4 + ' '.join (states)

for t in range (len (V)):

yield ' '.format (t) + ' '.join ('{:.4f}'.format (Vtstate) for state in V0)

函式 viterbi 具有以下引數: obs 為觀察結果序列，例如 'normal', 'cold', 'dizzy'； states 為一組隱含狀態； startp 為起始狀態機率；transp 為轉移機率；而 emitp 為放射機率。 為了簡化程式碼，我們假設觀察序列 obs 非空且 transpij 和 emit_pij 對所有狀態 i,j 有定義。

在執行的例子中正向 / 維特比演算法使用如下:

def example :

return viterbi (observations,

states,

start_probability,

transition_probability,

emission_probability)

print (example )

維特比演算法揭示了觀察結果 'normal', 'cold', 'dizzy' 最有可能由狀態序列 'Healthy', 'Healthy', 'Fever' 產生。 換句話說，對於觀察到的活動，病人第一天感到正常，第二天感到冷時都是健康的，而第三天發燒了。

維特比演算法的計算過程可以直觀地由格圖表示。 維特比路徑本質上是穿過格式結構的最長路徑。 診所例子的格式結構如下，黑色加粗的是維特比路徑：

Image:Viterbi animated demo.gif|border|center|Animation of the trellis diagram for the Viterbi algorithm. After Day 3, the most likely path is 'Healthy', 'Healthy', 'Fever'

在實現維特比演算法時需注意許多程式語言使用浮點數計算，當 p 很小時可能會導致結果算術下溢。 避免這一問題的常用技巧是在整個計算過程中使用對數機率，在對數系統中也使用了同樣的技巧。 當演算法結束時，可以透過適當的冪運算獲得精確結果。

虛擬碼

首先是一些問題必要的設定。 設觀察值空間為 、 狀態空間為 、 觀察值序列為，為 轉移矩陣，其中 為從狀態 轉移到 的轉移機率、 為 放射矩陣 (emission matrix)，其中 為在狀態 觀察到 的機率、 大小為 的初始機率陣列 ， 為 的機率。 我們稱路徑 為生成觀察值 的狀態序列。

在這個動態規劃問題中，我們構造了兩個大小為 的二維表 。 的每個元素，，儲存生成 時最有可能的路徑 ， 的機率。 的每個元素，，儲存最有可能路徑，的 。

我們按 增序填充兩個表 。

,

輸入:

觀察空間 ，
狀態 ，
觀察序列 若在 時間觀察值為 ，則，* 大小為 的轉移矩陣 ， 為從狀態 到 的轉移機率，
大小為 的放射矩陣 ， 為狀態 觀察到 的機率，
大小為 的初始機率陣列 ， 為 的機率，

輸出:

最有可能的隱含狀態序列

function VITERBI ( ) :

for each state do

end for

for do

for each state do

end for

end for

for do

end for

return

end function

使用動態規劃的演算法

最長公共子序列
Floyd-Warshall 演算法