原创:维特比算法
看了宗成庆博士的《统计自然语言处理(中文信息处理)》的第六章,对维特比算法有着非常精辟的讲解。把其中的讲解上传上来,个人感觉比较正统。
今天用Java实现了这个算法,也可以转换为C代码:
package com.nlp.hmm.algorithm.viterbi; /** * 维特比算法 * * @author XueQiang Tong * @date 2017/04/25 */ public class Viterbi { /** * @param obs * 观察输出序列 * @param state * 状态序列(隐式) * @param start_p * 初始概率 * @param trans_p * 状态转换概率 * @param emit_p * 发射概率 * @return 返回最优状态路径(这个路径具有最好的解释能力) */ public static int[] compute(int obs[], int state[], double[] start_p, double[][] trans_p, double[][] emit_p) { double v[][] = new double[obs.length][state.length];// viterbi值矩阵 int path[][] = new int[state.length][obs.length];// 存储路径 // 1.初始化时间序列为0的节点( t=0) for (int s : state) { v[obs[0]][s] = start_p[s] * emit_p[s][obs[0]]; path[s][obs[0]] = s; } // 2.开始递归计算viterbi矩阵(从t=1开始) for (int t = 1; t < obs.length; ++t) { int newpath[][] = new int[state.length][obs.length];// 每次迭代时,定义一个新的存储path,因为每个时间序列的path都依赖于 // 上个序列的path // 对该时间序列上的每个state节点进行迭代,每个节点都与上个序列的所有state交互 for (int s : state) { double maxValue = -1.0; int label = -1; for (int s0 : state) {// 寻找出备选路径 double viterbiValue = v[t - 1][s0] * trans_p[s0][s] * emit_p[s][obs[t]]; if (viterbiValue > maxValue) { maxValue = viterbiValue; label = s0; // 记录最大概率 v[t][s] = maxValue; // 记录路径 System.arraycopy(path[label], 0, newpath[s], 0, t); newpath[s][t] = s; } } } path = newpath; } // 找出最后一个时间序列上的viterbi值最大的state,从而找出最优路径 double maxValue = -1.0; int label = -1; for (int s : state) { if (v[obs.length - 1][s] > maxValue) { maxValue = v[obs.length - 1][s]; label = s; } } return path[label]; } }
测试文件:
1 package com.nlp.hmm.algorithm.viterbi; 2 3 4 public class ViterbiTest { 5 6 public static void main(String[] args) { 7 int[] obs = {0,1,2};//0--walk,1--shop,2--clean 8 int[] states = {0,1};//0--rainy,1--sunny 9 double[] start_p = {0.6,0.4};//0.6--rainy,0.4--sunny 10 double[][] trans_p = new double[states.length][states.length];//0--rainy,1--sunny 11 trans_p[0][0] = 0.7; 12 trans_p[0][1] = 0.3; 13 trans_p[1][0] = 0.4; 14 trans_p[1][1] = 0.6; 15 double[][] emit_p = new double[states.length][obs.length];//dimension1:rainy 0,sunny 1;dimension2:walk 0 shop 1 clean 2 16 emit_p[0][0] = 0.1; 17 emit_p[0][1] = 0.4; 18 emit_p[0][2] = 0.5; 19 emit_p[1][0] = 0.6; 20 emit_p[1][1] = 0.3; 21 emit_p[1][2] = 0.1; 22 int []path = Viterbi.compute(obs, states, start_p, trans_p, emit_p); 23 for (int pa:path){ 24 System.out.print(pa+" "); 25 } 26 } 27 28 }
输出结果为1,0,0,就是sunny,rainy,rainy。对比了一下,结果没问题,逻辑也没问题。维特比算法和前向搜索算法有着共同点,两者解决的hmm问题不一样。
下面是本人的python实现代码:
import numpy as np def viterbi(obs,state,start_prob,transition_prob,emit_prob): vit = np.array(np.zeros(shape = [len(obs),len(state)],dtype = np.float64)) path = np.array(np.zeros(shape = [len(state),len(obs)],dtype = np.int32)) #初始化 for i in range(len(state)): vit[0,i] = start_prob[i] * emit_prob[i][0] path[i][0] = state[i] for t in range(1,len(obs)): newPath = np.zeros_like(path) v = np.dot(np.reshape(vit[t-1],[len(state),1]),np.reshape(emit_prob[:,t],[1,len(state)])) * transition_prob vi = np.max(v,axis = 0) vit[t,:] = vi[:] pa = np.argmax(v,axis = 0) for i in range(len(pa)): label = pa[i] newPath[i,:] = path[label,:] newPath[i][t] = state[i] path = newPath return path[np.argmax(vit[len(obs)-1,:]),:]