維特比算法

維特比算法(英語:Viterbi algorithm)是一種動態規劃算法。它用於尋找最有可能產生觀測事件序列維特比路徑——隱含狀態序列,特別是在馬爾可夫信息源上下文和隱馬爾可夫模型中。

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

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

算法

假設給定隱式馬爾可夫模型(HMM)狀態空間  ,共有k個狀態,初始狀態   的概率為   ,從狀態   到狀態   的轉移概率為  。 令觀察到的輸出為  。 產生觀察結果的最有可能的狀態序列   由遞推關係給出:[2]

 

此處   是前   個最終狀態為   的觀測結果最有可能對應的狀態序列的概率。 通過保存向後指針記住在第二個等式中用到的狀態   可以獲得維特比路徑。聲明一個函數   ,它返回若  時計算   用到的   值 或若  時的  . 這樣:

 

這裡我們使用了arg max英語arg max的標準定義 算法複雜度為  

例子

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

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

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

 
Graphical representation of the given HMM

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

def viterbi(obs, states, start_p, trans_p, emit_p):
    V = [{}]
    path = {}

    # Initialize
    for st in states:
        V[0][st] = start_p[st] * emit_p[st][obs[0]]
        path[st] = [st]

    # Run Viterbi when t > 0
    for t in range(1,len(obs)):
        V.append({})
        newpath = {}

        for curr_st in states:
            paths_to_curr_st = []
            for prev_st in states:
                paths_to_curr_st.append((V[t-1][prev_st] * trans_p[prev_st][curr_st] * emit_p[curr_st][obs[t]], prev_st))
            curr_prob, prev_state = max(paths_to_curr_st)
            V[t][curr_st] = curr_prob
            newpath[curr_st] = path[prev_state] + [curr_st]

        # No need to keep the old paths
        path = newpath

    for line in dptable(V):
        print(line)
    prob, end_state = max([(V[-1][st], st) for st in states])
    return prob, path[end_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(V[t][state]) for state in V[0]])

函數viterbi 具有以下參數: obs 為觀察結果序列, 例如 ['normal', 'cold', 'dizzy']states 為一組隱含狀態; start_p 為起始狀態概率; trans_p 為轉移概率; 而 emit_p 為放射概率。 為了簡化代碼,我們假設觀察序列 obs 非空且 trans_p[i][j]emit_p[i][j] 對所有狀態 i,j 有定義。

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

def example():
    return viterbi(observations,
                   states,
                   start_probability,
                   transition_probability,
                   emission_probability)
print(example())

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

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

 
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( O, S, π, Y, A, B ) : X
     for each state si do
         T1[i,1] ← πi·Biy1
         T2[i,1] ← 0
     end for
     for i2,3,...,T do
         for each state sj do
              
              
         end for
     end for
      
     xT ← szT
     for iT,T-1,...,2 do
         zi-1T2[zi,i]
         xi-1szi-1
     end for
     return X
 end function

使用動態規劃的算法

注釋

  1. ^ 29 Apr 2005, G. David Forney Jr: The Viterbi Algorithm: A Personal History. [2012-11-23]. (原始內容存檔於2016-06-02). 
  2. ^ Xing E, slide 11

參考資料