维特比算法

维特比算法（）是一种动态规划算法。它用于寻找最有可能产生观测事件序列的维特比路径——隐含状态序列，特别是在马尔可夫信息源上下文和隐马尔可夫模型中。
术语“维特比路径”和“维特比算法”也被用于寻找观察结果最有可能解释相关的动态规划算法。例如在统计句法分析中动态规划算法可以被用于发现最可能的上下文无关的派生（解析）的字符串，有时被称为“维特比分析”。
维特比算法由安德鲁·维特比于1967年提出，用于在数字通信链路中解卷积以消除噪音。此算法被广泛应用于CDMA和GSM数字蜂窝网络、拨号调制解调器、卫星、深空通信和802.11无线网络中解卷积码。现今也被常常用于语音识别、关键字识别、计算语言学和生物信息学中。例如在语音（语音识别）中，声音信号做为观察到的事件序列，而文本字符串，被看作是隐含的产生声音信号的原因，因此可对声音信号应用维特比算法寻找最有可能的文本字符串。
算法.
假设给定隐式马尔可夫模型（HMM）状态空间 formula_1，共有k个状态，初始状态 formula_2 的概率为 formula_3 ，从状态 formula_2 到状态 formula_5 的转移概率为 formula_6。 令观察到的输出为 formula_7。 产生观察结果的最有可能的状态序列 formula_8 由递推关系给出：
此处 formula_10 是前 formula_11 个最终状态为 formula_12 的观测结果最有可能对应的状态序列的概率。 通过保存向后指针记住在第二个等式中用到的状态 formula_13 可以获得维特比路径。声明一个函数 formula_14 ，它返回若 formula_15时计算 formula_10 用到的 formula_13 值 或若 formula_18时的formula_12 . 这样:
这里我们使用了的标准定义
算法复杂度为 formula_21
例子.
想象一个乡村诊所。村民有着非常理想化的特性，要么健康要么发烧。他们只有问诊所的医生的才能知道是否发烧。 聪明的医生通过询问病人的感觉诊断他们是否发烧。村民只回答他们感觉正常、头晕或冷。
假设一个病人每天来到诊所并告诉医生他的感觉。医生相信病人的健康状况如同一个离散马尔可夫链。病人的状态有两种“健康”和“发烧”，但医生不能直接观察到，这意味着状态对他是“隐含”的。每天病人会告诉医生自己有以下几种由他的健康状态决定的感觉的一种：正常、冷或头晕。这些是观察结果。 整个系统为一个隐马尔可夫模型(HMM)。
医生知道村民的总体健康状况，还知道发烧和没发烧的病人通常会抱怨什么症状。 换句话说，医生知道隐马尔可夫模型的参数。 这可以用Python语言表示如下:
states = ('Healthy', 'Fever')
observations = ('normal', 'cold', 'dizzy')
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},
在这段代码中, 起始概率codice_1 表示病人第一次到访时医生认为其所处的HMM状态，他唯一知道的是病人倾向于是健康的。这里用到的特定概率分布不是均衡的,如转移概率大约是codice_2。 转移概率codice_3表示潜在的马尔可夫链中健康状态的变化。在这个例子中，当天健康的病人仅有30%的机会第二天会发烧。放射概率codice_4表示每天病人感觉的可能性。假如他是健康的，50%会感觉正常。如果他发烧了，有60%的可能感觉到头晕。
病人连续三天看医生，医生发现第一天他感觉正常，第二天感觉冷，第三天感觉头晕。 于是医生产生了一个问题：怎样的健康状态序列最能够解释这些观察结果。维特比算法解答了这个问题。
def viterbi(obs, states, start_p, trans_p, emit_p):
 V = [{}]
 # Initialize
 for st in states:
 V[0][st] = start_p[st] * emit_p[st][obs[0]]
 path[st] = [st]
 # Run Viterbi when t &gt; 0
 for t in range(1,len(obs)):
 V.append({})
 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]])
函数codice_5 具有以下参数: codice_6 为观察结果序列, 例如 codice_7； codice_8 为一组隐含状态； codice_9 为起始状态概率; codice_10 为转移概率; 而 codice_11 为放射概率。 为了简化代码，我们假设观察序列 codice_6 非空且 codice_13 和 codice_14 对所有状态 i,j 有定义。
在运行的例子中正向/维特比算法使用如下:
def example():
 return viterbi(observations,
 states,
 start_probability,
 transition_probability,
 emission_probability)
print(example())
维特比算法揭示了观察结果 codice_7 最有可能由状态序列 codice_16产生。 换句话说，对于观察到的活动, 病人第一天感到正常，第二天感到冷时都是健康的，而第三天发烧了。
维特比算法的计算过程可以直观地由格图表示。 维特比路径本质上是穿过格式结构的最长路径。 诊所例子的格式结构如下, 黑色加粗的是维特比路径：
在实现维特比算法时需注意许多编程语言使用浮点数计算，当 p 很小时可能会导致结果算术下溢。 避免这一问题的常用技巧是在整个计算过程中使用对数概率，在对数系统中也使用了同样的技巧。 当算法结束时，可以通过适当的幂运算获得精确结果。
伪代码.
首先是一些问题必要的设置。 设观察值空间为 formula_22、 状态空间为 formula_23、 观察值序列为 formula_24, formula_25 为 formula_26转移矩阵，其中 formula_27 为从状态 formula_28 转移到 formula_29 的转移概率、 formula_30 为 formula_31 放射矩阵(emission matrix)，其中 formula_32 为在状态 formula_28 观察到 formula_34 的概率、 大小为 formula_35 的初始概率数组 formula_36 ， formula_37 为 formula_38 的概率。 我们称路径 formula_39 为生成观察值 formula_24 的状态序列。
在这个动态规划问题中, 我们构造了两个大小为 formula_41 的二维表 formula_42 。 formula_43 的每个元素, formula_44 ，保存生成 formula_45 时最有可能的路径 formula_46 ， formula_47 的概率。 formula_48 的每个元素, formula_49，保存最有可能路径 formula_50 , formula_51 的 formula_52。
我们按 formula_53 增序填充两个表 formula_54。
 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