维特比算法的目的:
寻找最可能的隐藏状态序列(Finding most probable sequence of hidden states)
关于原理的讲解可以参考下面两篇文章,讲的比较清楚
小白给小白详解维特比算法1.
小白给小白详解维特比算法2.
本文通过分析维特比算法的例子,来学习该算法
- 定义HMM的五个重要元素,
# 隐藏序列 S
states = ("Rainy", "Sunny")
# 观测序列 K
observations = ('walk', 'shop', 'clean')
# 初始概率 π
start_probability = {'Rainy': 0.6, "Sunny": 0.4}
# 转移概率 A
transition_probability = {
'Rainy': {"Rainy": 0.7, "Sunny": 0.3},
"Sunny": {"Rainy": 0.4, "Sunny": 0.6}
}
# 发射概率 B
emission_probability = {
"Rainy": {"walk": 0.1, "shop": 0.4, "clean": 0.5},
"Sunny": {"walk": 0.6, "shop": 0.3, "clean": 0.1},
}
2.定义初始化状态
# 路径概率表 V[时间][隐状态] = 概率
V = [{}]
# 一个中间变量,代表当前状态是哪个隐状态
path = {}
# 初始化初始状态 t=0
for y in states:
V[0][y] = start_p[y] * emit_p[y][obs[0]]
path[y] = [y]
V[时间][天气] = 概率
观测序列(observations)得到的结果 观测对象第一天是散步
计算第一天的天气:
V[第一天][天晴] = 初始概率[天晴] * 发射概率[散步] = 0.4 * 0.6 = 0.24
V[第一天][下雨] = 初始概率[下雨] * 发射概率[散步] = 0.6 * 0.1 = 0.06
计算结果可见,第一天天晴的概率比较大。
此时初始的路径 path = {}
- 后面天气的情况都是根据前一天天气概率×转移概率×发射概率得到
def vierbi(obs, states, start_p, trans_p, emit_p):
"""
:param obs: 观察序列 K
:param states: 隐藏状态 S
:param start_p: 初始概率 π
:param trans_p: 转移概率 A
:param emit_p: 发射概率 B
:return:
"""
# 路径概率表 V[时间][隐状态] = 概率
V = [{}]
# 一个中间变量,代表当前状态是哪个隐状态
path = {}
# 初始化初始状态 t=0
for y in states:
V[0][y] = start_p[y] * emit_p[y][obs[0]]
path[y] = [y]
# 对t>0 跑一遍维特比算法
for t in range(1, len(obs)):
V.append({})
newpath = {}
for y in states:
# 概率 隐状态 = 前状态是y0的概率 * y0转移到y的概率 * y表现为当前状态的概率
(prob, state) = max([(V[t - 1][y0] * trans_p[y0][y] * emit_p[y][obs[t]], y0) for y0 in states])
# 记录最大概率
V[t][y] = prob
# 记录路径
newpath[y] = path[state] + [y]
# 不需要保存旧路径
path = newpath
print_dptable(V)
(prob, state) = max([(V[len(obs) - 1][y], y) for y in states])
return prob, path[state]
第二天: 由观测序列可得 observations[1] = ‘购物’
① 首先看计算第二天下雨的概率
- V[第二天][下雨] = V[第一天][下雨] * 转移概率[下雨][下雨] * 发射概率[下雨][购物] = 0.06 * 0.7 * 0.4 = 0.0168
- V[第二天][下雨] = V[第一天][天晴] * 转移概率[天晴][下雨] * 发射概率[下雨][购物] = 0.24 * 0.4 * 0.4 = 0.0384
同样在这里取最大概率
V[第二天][下雨] = 0.0384
得到的新路径 newpath[下雨] = [晴天,下雨]
②然后计算第二天天晴的概率
- V[第二天][天晴] = V[第一天][下雨] * 转移概率[下雨][天晴] * 发射概率[天晴][购物] = 0.06 * 0.3 * 0.3 = 0.0054
- V[第二天][天晴] = V[第一天][天晴] * 转移概率[天晴][天晴] * 发射概率[天晴][购物] = 0.24 * 0.6 * 0.3 = 0.0432
V[第二天][天晴] = 0.0432
得到的新路径 newpath[晴天] = [晴天,晴天]
第二天计算完成之后得到前两天的路径
paht={雨天:[晴天,下雨],晴天:[晴天,晴天]}
以同样的方式得到第三天的结果
observations[1] = 打扫
- V[第三天][下雨] = V[第二天][下雨] * 转移概率[下雨][下雨] * 发射概率[下雨][打扫] = 0.0384 * 0.7 * 0.5 = 0.01344
- V[第三天][下雨] = V[第二天][天晴] * 转移概率[天晴][下雨] * 发射概率[下雨][打扫] = 0.0432 * 0.4 * 0.5 = 0.00864
V[第三天][下雨] = 0.01344
newpath[下雨] = [晴天,下雨,下雨] - V[第三天][天晴] = V[第二天][下雨] * 转移概率[下雨][天晴] * 发射概率[天晴][打扫] = 0.0384* 0.3 * 0.1 = 0.001152
- V[第三天][天晴] = V[第二天][天晴] * 转移概率[天晴][天晴] * 发射概率[天晴][打扫] = 0.0432 * 0.6 * 0.1 = 0.002592
V[第三天][天晴] = 0.002592
newpath[晴天] = [晴天,晴天,晴天]
把得到的newpath赋值给path
最后通过找出V[第三天]中天气最大的那个概率得到天气的情况
因此第三天的天气为V[第三天][下雨] = 0.01344
由此可反推得到路径path[下雨] = [晴天,下雨,下雨]
图形表示如下,第二天之后的天气概率计算后取最大值,根据第三天天气的最大概率再反推天气的路径
上面就是维特比算法实现的详解,下面是完整代码。
# -*- coding:utf-8 -*-
# @Time :2019/5/27 15:30
# @author :ding
# @filename :vertibi.py
"""
维特比算法的实现
HMM 五个重要元素
S 隐藏序列的集合
K 输出状态或观测状态的集合
π对应隐藏状态的的初始概率
A 隐藏状态的转移概率 是一个N*M的概率矩阵
B 影厂状态到观测状态的混淆矩阵,是一个N*M的发射概率的矩阵
"""
# 隐藏序列 S
states = ("Rainy", "Sunny")
# 观测序列 K
observations = ('walk', 'shop', 'clean')
# 初始概率 π
start_probability = {'Rainy': 0.6, "Sunny": 0.4}
# 转移概率 A
transition_probability = {
'Rainy': {"Rainy": 0.7, "Sunny": 0.3},
"Sunny": {"Rainy": 0.4, "Sunny": 0.6}
}
# 发射概率 B
emission_probability = {
"Rainy": {"walk": 0.1, "shop": 0.4, "clean": 0.5},
"Sunny": {"walk": 0.6, "shop": 0.3, "clean": 0.1},
}
def print_dptable(V):
print(" ")
for i in range(len(V)): print("%7d" % i, end="")
print()
for y in V[0].keys():
print("%.5s: " % y, end=" ")
for t in range(len(V)):
print("%.7s" % V[t][y], end=" ")
print()
def vierbi(obs, states, start_p, trans_p, emit_p):
"""
:param obs: 观察序列 K
:param states: 隐藏状态 S
:param start_p: 初始概率 π
:param trans_p: 转移概率 A
:param emit_p: 发射概率 B
:return:
"""
# 路径概率表 V[时间][隐状态] = 概率
V = [{}]
# 一个中间变量,代表当前状态是哪个隐状态
path = {}
# 初始化初始状态 t=0
for y in states:
V[0][y] = start_p[y] * emit_p[y][obs[0]]
path[y] = [y]
# 对t>0 跑一遍维特比算法
for t in range(1, len(obs)):
V.append({})
newpath = {}
for y in states:
# 概率 隐状态 = 前状态是y0的概率 * y0转移到y的概率 * y表现为当前状态的概率
(prob, state) = max([(V[t - 1][y0] * trans_p[y0][y] * emit_p[y][obs[t]], y0) for y0 in states])
# 记录最大概率
V[t][y] = prob
# 记录路径
newpath[y] = path[state] + [y]
# 不需要保存旧路径
path = newpath
print_dptable(V)
(prob, state) = max([(V[len(obs) - 1][y], y) for y in states])
return prob, path[state]
def test():
return vierbi(observations,
states,
start_probability,
transition_probability,
emission_probability
)
print(test())
打印结果如下
维特比算法在NLP方面有很多的应用
- 词性标注:给定一个词的序列,找出最可能的词性序列
- 分词:给定一个字的序列,找出最可能的标签序列,可利用BMES这些标签来分词B(开头)M(中间词)E(结尾)S(单个词)
- 明明实体识别:给定一个词的序列,找出最可能的标签序列
本文讲的可能不是很清楚,有不对的地方,还请不吝指教。