viterbi-algorithm 维特比算法的例子解析

维特比算法的目的:

寻找最可能的隐藏状态序列(Finding most probable sequence of hidden states)


关于原理的讲解可以参考下面两篇文章,讲的比较清楚
小白给小白详解维特比算法1.
小白给小白详解维特比算法2.


本文通过分析维特比算法的例子,来学习该算法

  1. 定义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 = {}


  1. 后面天气的情况都是根据前一天天气概率×转移概率×发射概率得到
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(单个词)
  • 明明实体识别:给定一个词的序列,找出最可能的标签序列

本文讲的可能不是很清楚,有不对的地方,还请不吝指教。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值