维特比算法 Viterbi

维特比算法

  • 一种动态规划算法(动态规划 Dynamic Programming,是运筹学的一个分支,是求解决策过程最优化的过程。)

  • 用于寻找最有可能产生观测事件序列的-维特比路径-隐含状态序列

  • 特别是在马尔可夫信息源上下文和隐马尔可夫模型中


 在计算机科学领域,应用动态规划的思想解决的最基本的一个问题就是:寻找有向无环图(篱笆网络Lattice)中两个点之间的最短路径。

从两个点之间找一条最短的路径,除了遍历完所有路径,还有什么更好的方法?答案:viterbi (维特比)算法。


 

 

A→E 的最短路径 

穷举法(遍历):每条路径需要计算4次加法,共3×3×3=27条路径,共4×27=108次计算。 

维特比算法需要多少次计算?


AB

6,7, 5


ABC

C1: 6+5=11,7+4=11,5+4=09,  最终选择A→B3→C1,抛弃其他到C1的路径,长度09
C2: 6+6=12,7+3=10,5+6=11,  最终选择A→B2→C2,抛弃其他到C2的路径,长度10
C3: 6+9=15,7+7=14,5+6=11,  最终选择A→B3→C3,抛弃其他到C3的路径,长度11


A(B)CD

            

D1:9+7=16,10+5=15,11+5=16,最终选择A→B2→C2→D1,抛弃其他到D1的路径,长度15
D2:9+8=17,10+4=14,11+7=18,最终选择A→B2→C2→D2,抛弃其他到D2的路径,长度14
D3:9+3=12,10+3=13,11+6=17,最终选择A→B3→C1→D3,抛弃其他到D3的路径,长度12


A(BC)DE

                                

15+4=19

14+8=22

12+5=17

选择D3

完整路径: A→B3→C1→D3→E


 

计算的次数大约是多少?

从A到B有3条路径,每一条需要算到每一个C的最短距离,所以是:2(路径加法数)×3(A→B)×3(B→C)=18,

确定了每一个到C的最短距离,剩下的事情就可以从C开始考虑:每一个C需要确定到每一个D的最短距离:2(路径加法数)×3(C个数)×3(D个数)=18,

最终再加上D到E的距离:2×3=6

总计:18+18+3=39

远远小于穷举法的108次


 

核心思想:步步为营

每一步把最短路径找出来,以后就不必再重新计算这一步,节约了时间。

类似于DB优化中的选择尽量先做,没必要带着一堆没用的数据“负重前行”。


 

初始状态:

P(身体状态=正常)=0.7
P(身体状态=轻感冒)=0.2
P(身体状态=重感冒)=0.1

 


 

 状态转移矩阵

 

 


 

 

 

发散矩阵


 观测序列:(咳嗽、发烧、拉肚子、活蹦乱跳)


 问题:

已知观测序列,求这个人的状态序列


 

代码解决问题:

# https://blog.csdn.net/Luzichang/article/details/91365752

import math
# 状态的样本空间
states = ('没毛病','轻感冒','重感冒')
# 观测的样本空间
observations = ( '活跃', '咳嗽','发烧','腹泻')
#observations = ( '读书', '读书','读书','读书')
#observations = ( '打球', '打球','打球','打球')
#observations = ( '访友', '访友','访友','访友')
#observations = ( '访友', '访友','读书','访友', '访友','访友','读书')
# 起始个状态概率
start_probability = {'没毛病':0.7, '轻感冒':0.2,'重感冒':0.1}
# 状态转移概率
transition_probability = {
  '没毛病': {'没毛病':0.7, '轻感冒':0.2,'重感冒':0.1},
  '轻感冒': {'没毛病':0.4, '轻感冒':0.4,'重感冒':0.2},
  '重感冒': {'没毛病':0.2, '轻感冒':0.5,'重感冒':0.3},
}
# 状态->观测的发散概率
emission_probability = {
  '没毛病': { '活跃': 0.7,'咳嗽': 0.1, '发烧': 0.0, '腹泻': 0.2},
  '轻感冒': { '活跃': 0.5,'咳嗽': 0.2, '发烧': 0.2, '腹泻': 0.1},
  '重感冒': { '活跃': 0.3,'咳嗽': 0.2, '发烧': 0.3, '腹泻': 0.2},
}
# 计算以E为底的幂
def E(x):
  #return math.pow(math.e,x)
  return x
 
 
def display_result(observations,result_m):#较为友好清晰的显示结果
  # 从结果中找出最佳路径
  #print(result_m)
  infered_states = []
  final = len(result_m) - 1
  (p, pre_state), final_state = max(zip(result_m[final].values(), result_m[final].keys()))
  infered_states.insert(0, final_state)
  infered_states.insert(0, pre_state)
  for t in range(final - 1, 0, -1):
    _, pre_state = result_m[t][pre_state]
    infered_states.insert(0, pre_state)
  print(format("Viterbi Result", "=^80s"))
  head = format("观测序列", " ^7s")
  head += format("推断状态", " ^16s")
  for s in states:
    head += format(s, " ^15s")
  print(head)
  print(format("", "-^80s"))
 
  for obs,result,infered_state in zip(observations,result_m,infered_states):
    item = format(obs," ^10s")
    item += format(infered_state," ^18s")
    for s in states:
      item += format(result[s][0]," >12.8f")
      if infered_state == s:
        item += "(*)"
      else:
        item +="   "
 
    print(item)
  print(format("", "=^80s"))
 
 
 
def viterbi(obs, states, start_p, trans_p, emit_p):
 
  result_m = [{}] # 存放结果,每一个元素是一个字典,每一个字典的形式是 state:(p,pre_state)
                  # 其中state,p分别是当前状态下的概率值,pre_state表示该值由上一次的那个状态计算得到
  for s in states:  # 对于每一个状态
    result_m[0][s] = (E(start_p[s]*emit_p[s][obs[0]]),None) # 把第一个观测节点对应的各状态值计算出来
    #print('11',result_m[0][s])
  for t in range(1,len(obs)):
    result_m.append({})  # 准备t时刻的结果存放字典,形式同上
 
    for s in states: # 对于每一个t时刻状态s,获取t-1时刻每个状态s0的p,结合由s0转化为s的转移概率和s状态至obs的发散概率
                     # 计算t时刻s状态的最大概率,并记录该概率的来源状态s0
                     # max()内部比较的是一个tuple:(p,s0),max比较tuple内的第一个元素值
      result_m[t][s] = max([(E(result_m[t-1][s0][0]*trans_p[s0][s]*emit_p[s][obs[t]]),s0) for s0 in states])
      #print(result_m[t][s])
  return result_m    # 所有结果(包括最佳路径)都在这里,但直观的最佳路径还需要依此结果单独生成,在显示的时候生成
 
 
if __name__ == "__main__":
    result_m = viterbi( observations,
                        states,
                        start_probability,
                        transition_probability,
                        emission_probability)
    display_result(observations,result_m)

输出结果如下:

=================================Viterbi Result=================================
 观测序列        推断状态            没毛病            轻感冒            重感冒      
--------------------------------------------------------------------------------
    活跃           没毛病          0.49000000(*)  0.10000000     0.03000000
    咳嗽           轻感冒          0.03430000     0.01960000(*)  0.00980000
    发烧           轻感冒          0.00000000     0.00156800(*)  0.00117600
    腹泻           没毛病          0.00012544(*)  0.00006272     0.00007056
================================================================================

 结果分析: 

递推过程,顺向。选择每一步最短路径(最大概率),然后进行下一步。步步为营。

注意:“每一步的最短路径”不是“最终的最短路径”,“最终的最短路径”是回溯得到的。

回溯过程,逆向。找到最终结果的最大概率,然后逆流而上,继续找上一步的最大概率,形成一条链路,这条链路就是“最终的最短路径”。


 REF:

 

 

 

 

————————————————

REF:

https://blog.csdn.net/athemeroy/article/details/79339546

https://www.zhihu.com/question/20136144

 

 

 

 

posted on 2020-07-05 18:39  HBU_DAVID  阅读(1005)  评论(0编辑  收藏  举报

导航