HanLP — HMM隐马尔可夫模型 -- 维特比(Viterbi)算法 --示例代码 - Java
Viterbi 维特比算法解决的是篱笆型的图的最短路径问题,图的节点按列组织,每列的节点数量可以不一样,每一列的节点只能和相邻列的节点相连,不能跨列相连,节点之间有着不同的距离,距离的值就不在
题目背景
从前有个村儿,村里的人的身体情况只有两种可能:健康、发烧
。
假设这个村儿的人没有体温计或者百度这种神奇东西,他唯一判断他身体情况的途径就是到村头我的偶像金正月的小诊所询问。月儿通过询问村民的感觉,判断她的病情,再假设村民只会回答正常、头晕或冷
。
有一天村里奥巴驴就去月儿那去询问了。
- 第一天她告诉月儿她感觉正常。
- 第二天她告诉月儿感觉有点冷。
- 第三天她告诉月儿感觉有点头晕。
那么问题来了,月儿如何根据阿驴的描述的情况,推断出这三天中阿驴的一个身体状态呢?
已知情况
隐含的身体状态 = {健康,发烧}
可观察的感觉状态 = {正常,冷,头晕}
月儿预判的阿驴身体状态的概率分布(初始状态矩阵) = {健康:0.6,发烧:0.4}
月儿认为的阿驴身体健康状态的转换概率分布(转移矩阵) =
{
健康->健康: 0.7 ,
健康->发烧: 0.3 ,
发烧->健康:0.4 ,
发烧->发烧: 0.6
}
月儿认为的在相应健康状况条件下,阿驴的感觉的概率分布(发射矩阵) =
{
健康,正常:0.5 ,冷 :0.4 ,头晕: 0.1 ;
发烧,正常:0.1 ,冷 :0.3 ,头晕: 0.6
}
由上面我们可以发现,HMM的三要素都齐备了,下面就是解决问题了。
阿驴连续三天的身体感觉依次是: 正常、冷、头晕 。
过程:
第一天的时候,对每一个状态(健康或者发烧),分别求出第一天身体感觉正常的概率:P(第一天健康) = P(正常|健康)P(健康|初始情况) = 0.5 * 0.6 = 0.3 P(第一天发烧) = P(正常|发烧)P(发烧|初始情况) = 0.1 * 0.4 = 0.04
第二天的时候,对每个状态,分别求在第一天状态为健康或者发烧情况下观察到冷的最大概率。在维特比算法中,我们先要求得路径的单个路径的最大概率,然后再乘上观测概率。P(第二天健康) = max{0.30.7, 0.040.4}0.4=0.30.70.4=0.084 此时我们需要记录概率最大的路径的前一个状态,即0.084路径的前一个状态,我们在小本本上记下,第一天健康。 P(第二天发烧)=max{0.30.3, 0.040.6}0.3=0.027, 同样的在0.027这个路径上,第一天也是健康的。
第三天的时候,跟第二天一样。P(第三天健康)=max{0.0840.7, 0.0270.4}0.1=0.00588,在这条路径上,第二天是健康的。P(第三天发烧)=max{0.0840.3, 0.0270.6}0.6=0.01512,在这条路径上,第二天是健康的。
最后一天的状态概率分布即为最优路径的概率,即P(最优)=0.01512,这样我们可以得到最优路径的终点,是发烧
由最优路径开始回溯。请看我们的小本本,在求得第三天发烧概率的时候,我们的小本本上面写的是第二天健康,好了,第二天就应该是健康的状态,然后在第二天健康的情况下,我们记录的第一天是健康的。这样,我们的状态序列逆推出来了。即为:健康,健康,发烧
简略的画个图吧:
这儿的箭头指向就是一个回溯查询小本本的过程,我们在编写算法的时候,其实也得注意,每一个概率最大的单条路径上都要把前一个状态记录下来。
代码
Viterbi
package com.vipsoft.viterbi;
/**
* 维特比算法
* @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)
{
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];
}
}
DoctorExample
package com.vipsoft.viterbi;
import static com.vipsoft.viterbi.DoctorExample.Feel.cold;
import static com.vipsoft.viterbi.DoctorExample.Feel.dizzy;
import static com.vipsoft.viterbi.DoctorExample.Feel.normal;
import static com.vipsoft.viterbi.DoctorExample.Status.Fever;
import static com.vipsoft.viterbi.DoctorExample.Status.Healthy;
public class DoctorExample
{
enum Status
{
/**
* 健康
*/
Healthy,
/**
* 发热
*/
Fever,
}
enum Feel
{
/**
* 正常
*/
normal,
/**
* 冷
*/
cold,
/**
* 头晕
*/
dizzy,
}
static int[] states = new int[]{Healthy.ordinal(), Fever.ordinal()};
/**
* 初始矩阵
* { 健康:0.6 , 发烧: 0.4 }
*/
static double[] start_probability = new double[]{0.6, 0.4};
/**
* 转移矩阵
* {
* 健康->健康:0.7 ,
* 健康->发烧:0.3 ,
* 发烧->健康:0.4 ,
* 发烧->发烧:0.6
* }
*/
static double[][] transititon_probability = new double[][]{
{0.7, 0.3},
{0.4, 0.6},
};
/**
* 发射矩阵
* {
* 健康,正常:0.5 ,冷 :0.4 ,头晕: 0.1 ;
* 发烧,正常:0.1 ,冷 :0.3 ,头晕: 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[] observations = new int[]{normal.ordinal(), cold.ordinal(), dizzy.ordinal()};
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();
}
}
源码:https://gitee.com/VipSoft/VipBoot/tree/develop/vipsoft-viterbi/src/main/java/com/vipsoft/viterbi
引用:https://www.zhihu.com/question/20136144 里的问题回答,正好是 HanLP 的Demo示例
HanLP — HMM隐马尔可夫模型 -- 维特比(Viterbi)算法 --示例代码 - Java的更多相关文章
- HMM隐马尔可夫模型(词语粘合)
HMM用于自然语言处理(NLP)中文分词,是用来描述一个含有隐含未知参数的马尔可夫过程,其目的是希望通过求解这些隐含的参数来进行实体识别,说简单些也就是起到词语粘合的作用. HMM隐马尔可夫模型包括: ...
- HMM隐马尔可夫模型来龙去脉(一)
目录 隐马尔可夫模型HMM学习导航 一.认识贝叶斯网络 1.概念原理介绍 2.举例解析 二.马尔可夫模型 1.概念原理介绍 2.举例解析 三.隐马尔可夫模型 1.概念原理介绍 2.举例解析 四.隐马尔 ...
- HMM隐马尔可夫模型来龙去脉(二)
目录 前言 预备知识 一.估计问题 1.问题推导 2.前向算法/后向算法 二.序列问题 1.问题推导 2.维特比算法 三.参数估计问题 1.问题推导 2.期望最大化算法(前向后向算法) 总结 前言 H ...
- 隐马尔可夫模型的前向算法(java实现),今天奉上
隐马尔可夫模型的前向算法(手动实现),今天奉上,由于研究生期间,实现的时候没有多加注释,这里为了让更好的人进入自然语言处理领域,特此,将前向算法奉上,具体公式可参考52nlp的HMN系列博客. 参考了 ...
- HMM隐马尔科夫模型
这是一个非常重要的模型,凡是学统计学.机器学习.数据挖掘的人都应该彻底搞懂. python包: hmmlearn 0.2.0 https://github.com/hmmlearn/hmmlearn ...
- 机器学习-HMM隐马尔可夫模型-笔记
HMM定义 1)隐马尔科夫模型 (HMM, Hidden Markov Model) 可用标注问题,在语音识别. NLP .生物信息.模式识别等领域被实践证明是有效的算法. 2)HMM 是关于时序的概 ...
- 自然语言处理(1)-HMM隐马尔科夫模型基础概念(一)
隐马尔科夫模型HMM 序言 文本序列标注是自然语言处理中非常重要的一环,我先接触到的是CRF(条件随机场模型)用于解决相关问题,因此希望能够对CRF有一个全面的理解,但是由于在学习过程中发现一个算法像 ...
- ZH奶酪:隐马尔可夫模型学习小记——forward算法+viterbi算法+forward-backward算法(Baum-welch算法)
网上关于HMM的学习资料.博客有很多,基本都是左边摘抄一点,右边摘抄一点,这里一个图,那里一个图,公式中有的变量说不清道不明,学起来很费劲. 经过浏览几篇博文(其实有的地方写的也比较乱),在7张4开的 ...
- HMM:隐马尔科夫模型-维特比算法
http://blog.csdn.net/pipisorry/article/details/50731584 目标-解决HMM的基本问题之二:给定观察序列O=O1,O2,-OT以及模型λ,如何选择一 ...
- HMM 隐马尔科夫模型
参考如下博客: http://www.52nlp.cn/itenyh%E7%89%88-%E7%94%A8hmm%E5%81%9A%E4%B8%AD%E6%96%87%E5%88%86%E8%AF%8 ...
随机推荐
- 节能减排 | AIRIOT智慧工厂节能管理解决方案
工厂作为高能耗的生产型企业,降低能耗和提升资源利用率方面就显得很重要,对实施国家倡导的节能降耗.绿色发展有着很大程度上的必要性.然而,工厂能源管理从传统手段向智能化升级转型的过程中,企业也不可避免的面 ...
- 低代码平台解密:探秘MQTT协议的应用之道
前言 低代码平台作为当今快速发展的技术之一,为开发人员提供了更高效.更简便的工具和方法,以快速构建和部署应用程序.而MQTT协议作为物联网领域的重要通信协议,在低代码平台上的应用也日益受到关注,今天小 ...
- C语言根据公式Cnm= 算出从n个不同元素中取出m个元素(m≤n)的组合数。建议定义和调用函数fact(n)计算n!,其中n的类型是int,函数类型是double。
/* 开发者:慢蜗牛 开发时间:2020.5.28 程序功能:开发公式Cnm */ #include<stdio.h> long fact_1(int m); long fact_2(in ...
- 关于Delphi
# 关于Delphi ··Delphi中使用的面向对象pascal编程语言. ··Pascal语言最初由瑞士苏黎士理工学院的尼古拉斯-沃斯(Niklaus Wirth)教授在1971年设计. ··19 ...
- [UOJ216][UNR#2 2A] Jakarta Skyscrapers
印尼首都雅加达市有 $10^{18}$ 座摩天楼,它们排列成一条直线,我们从左到右依次将它们编号为 $1$ 到 $10^{18}$ .除了这 $10^{18}$ 座摩天楼外,雅加达市没有其他摩天楼. ...
- 累加器Adder
① java8引⼊的,相⽐较是⼀个⽐较新的类 ② ⾼并发下LogAdder⽐AtomicLog效率⾼,不过本质是空间换时间 ③ 竞争激烈的时候,LongAdder把不同线程对应到不同的Cell上进⾏修 ...
- 【纯手工打造】时间戳转换工具(python)
1.背景 最近发现一个事情,如果日志中的时间戳,需要我们转换成时间,增加可读性.或者将时间转换成时间戳,来配置时间.相信大多人和我一样,都是打开网页,搜索在线时间戳转换工具,然后复制粘贴进去.个人认为 ...
- LeetCode456:132模式(单调栈)
解题思路:根据题意,我们首先首先要找到所有的极大值点,同时记录当前极大值点的左边的最小值.遍历所有点,看是否能够满足132条件.虽然记录极大值点的地方可以优化,减小比较的次数,但是由于我们不知道极大值 ...
- dada的GCD ( jxnu acm新生选拔赛)
1007 dada的GCD,输入格式描述有误,已修正 dada的GCD Time Limit : 3000/1000ms (Java/Other) Memory Limit : 65535/327 ...
- PersistenceException、ReflectionException、IllegalArgumentException、wrapException持久性异常 反射异常 非法参数异常 包装异常
PersistenceException.ReflectionException.IllegalArgumentException.wrapException wrapException 持久性异常 ...