算法本身理解是一回事,着手代码又是一回事,所以对于能够代码动手实现的,是很值得学习的,至少表明其深入理解了。
算法中的思路是从观测的序列得到最大概率的隐状态,关键就是这一时刻的隐状态能得出下一个时刻隐状态的概率以及这一时刻显状态的概率。
package sk.ml;
/**
* 维特比算法
*/
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)
{
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)
{
prob = V[obs.length - 1][y];
state = y;
}
}
return path[state];
}
}
5、网络上对HMM有两个比较经典的案例,一个是天气,一个是诊断。
诊断见知乎上文章:https://www.zhihu.com/question/20136144
package sk.ml;
import static sk.ml.DoctorExample.Status.*;
import static sk.ml.DoctorExample.Feel.*;
public class DoctorExample {
enum Status
{
Healthy,//健康
Fever,//感冒
}
enum Feel
{
normal,//舒服
cold,//冷
dizzy,//头晕
}
static int[] states = new int[]{Healthy.ordinal(), Fever.ordinal()};
static int[] observations = new int[]{normal.ordinal(), cold.ordinal(), dizzy.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.5, 0.4, 0.1},
{0.1, 0.3, 0.6},
};
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(Status.values()[r] + " ");
}
System.out.println();
}
}
执行结果:
Healthy Healthy Fever
package sk.ml;
import static sk.ml.WeatherExample.Weather.*;
import static sk.ml.WeatherExample.Activity.*;
public class WeatherExample {
enum Weather//隐状态
{
Rainy,//下雨
Sunny,//天晴
}
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
理解算法最好的就是代码和数学公式两边对应。