用Java实现的求解HMM的维特比算法,开源在Git上:https://github.com/hankcs/Viterbi。代码本身没什么新意,看到Git上没有好用的Viterbi的Java实现,所以补个缺。特点是简单好懂,一个方法搞定。调用简单,往compute方法里填充HMM的五元组就能得到最佳标注序列。
附赠一个对经典天气预测问题的求解,问题的描述和思路详见前文:
package com.hankcs.algorithm;
import static com.hankcs.algorithm.Main.Weather.*;
import static com.hankcs.algorithm.Main.Activity.*;
public class Main
{
static enum Weather
{
Rainy,
Sunny,
}
static enum Activity
{
walk,
shop,
clean,
}
static int[] states = new int[]{Rainy.ordinal(), Sunny.ordinal()};
static int[] observations = new int[]{walk.ordinal(), shop.ordinal(), clean.ordinal()};
static double[] start_probability = new double[]{0.6, 0.4};
static double[][] transititon_probability = new double[][]{
{0.7, 0.3},
{0.4, 0.6},
};
static double[][] emission_probability = new double[][]{
{0.1, 0.4, 0.5},
{0.6, 0.3, 0.1},
};
public static void main(String[] args)
{
int[] result = Viterbi.compute(observations, states, start_probability, transititon_probability,
emission_probability);
for (int r : result)
{
System.out.print(Weather.values()[r] + " ");
}
System.out.println();
}
}
输出:Sunny Rainy Rainy
顺便贴一个源码:
package com.hankcs.algorithm;
/**
* 维特比算法
* @author hankcs
*/
public class Viterbi
{
/**
* 求解HMM模型
* @param obs 观测序列
* @param states 隐状态
* @param start_p 初始概率(隐状态)
* @param trans_p 转移概率(隐状态)
* @param emit_p 发射概率 (隐状态表现为显状态的概率)
* @return 最可能的序列
*/
public static int[] compute(int[] obs, int[] states, double[] start_p, double[][] trans_p, double[][] emit_p)
{
double[][] V = new double[obs.length][states.length];
int[][] path = new int[states.length][obs.length];
for (int y : states)
{
V[0][y] = start_p[y] * emit_p[y][obs[0]];
path[y][0] = y;
}
for (int t = 1; t < obs.length; ++t)
{
int[][] newpath = new int[states.length][obs.length];
for (int y : states)
{
double prob = -1;
int state;
for (int y0 : states)
{
//我加的注释:V[t,隐状态Sj] = V[t-1,隐状态Si] * trans_p(隐状态Si->隐状态Sj) * emit_p(obs[t] | 隐状态Si)
double nprob = V[t - 1][y0] * trans_p[y0][y] * emit_p[y][obs[t]];
if (nprob > prob)
{
prob = nprob;
state = y0;
// 记录最大概率
V[t][y] = prob;
// 记录路径
System.arraycopy(path[state], 0, newpath[y], 0, t);
newpath[y][t] = y;
}
}
}
path = newpath;
}
double prob = -1;
int state = 0;
for (int y : states)
{
if (V[obs.length - 1][y] > prob)//我加的注释:最后一步v值决定最大可能隐状态序列
{
prob = V[obs.length - 1][y];
state = y;
}
}
return path[state];
}
}
对,你没看错,就这么点代码。