python+HMM之维特比解码

HMM 回顾

《统计学习方法》 p.174

隐马尔科夫模型(HMM)有三个基本的问题

  • (1)概率计算问题。给定模型 λ=(A,B,Pi) 和观测序列 O(o1,o2,...,oT) ,计算在模型 λ 下观测序列 O 的概率 P(O|λ)
  • (2)学习问题。已知观测序列 O(o1,o2,...,oT) ,估计模型 λ=(A,B,Pi) 的参数,使得在该模型下观测序列概率 P(O|λ) 最大。即用极大似然估计的方法估计参数。
  • (3)预测问题,也称为解码问题。已知模型 λ=(A,B,Pi) 和观测序列 O(o1,o2,...,oT) ,求对给定观测序列条件概率 P(I|O) 最大的状态序列 I=(i1,i2,...,iT) ,即给定观测序列,求最有可能的对应的状态序列。

维特比译码

《统计学习方法》 p.186

问题类型:

(3)预测问题。已知模型 lambda = (A, B, Pi) 和观测序列,求最优的状态序列.

问题描述:

一共有三个盒子 <=> 状态集合 Q = {1,2,3}
盒子里边装着 红色 和 白色 两种颜色的球 <=> 观测集合 V = {红,白}
从盒子中有放回的取 3 次球,颜色分别为 O=(红,白,红),问取球的盒子顺序最可能是什么?

注意:代码中所有下标从 0 开始,而课本中问题描述是从 1 开始。

import numpy as np

# 模型参数
A = np.asarray([[0.5, 0.2, 0.3], [0.3, 0.5, 0.2], [0.2, 0.3, 0.5]]) # 转移矩阵
B = np.asarray([[0.5, 0.5], [0.4, 0.6], [0.7, 0.3]])
Pi = np.asarray([0.2, 0.4, 0.4]).transpose()

O = np.asarray([0,1,0]) 
T = O.shape[0]
N = A.shape[0]   # 状态数

p_nodes = Pi * B[:, O[0]]     # 记录每个节点的路径概率
path_nodes = list()           # 记录每个节点的路径
# 计初始化路径
for node in xrange(N):
    path_nodes.append([node])
# T 个时刻
for step in xrange(1, T):
    for this_node in xrange(N):   # 计算每个节点的新概率
        p_news = list()
        for last_node in xrange(N):
            p_trans = A[last_node, this_node]  # 转移概率
            p_out = B[this_node, O[step]]       # 输出概率
            p_new = p_nodes[last_node] * p_trans * p_out
            p_news.append(p_new)
        p_nodes[this_node] = np.max(p_news)    # 更新节点路径概率
        last_index = np.argmax(p_news)         # 更新节点路径
        temp = path_nodes[last_index][:]
        temp.append(this_node)
        path_nodes[this_node] = temp

print p_nodes     # 最有一步每个节点的概率
print path_nodes  
max_index = np.argmax(p_nodes)
max_path = path_nodes[max_index]
print max_path   # 最优路径
[ 0.00756  0.01008  0.0147 ]
[[2, 2, 0], [2, 2, 1], [2, 2, 2]]
[2, 2, 2]

实际项目中,一般都是通过对概率取对数,将乘法转为加法进行求解的。但是,要注意转移概率为 0 的特殊情况,下面不考虑这种情况。

##############################
# 维特比译码
# 《统计学习方法》 p.186
#############################

# 模型参数
A = np.asarray([[0.5, 0.2, 0.3], [0.3, 0.5, 0.2], [0.2, 0.3, 0.5]]) # 转移矩阵
B = np.asarray([[0.5, 0.5], [0.4, 0.6], [0.7, 0.3]])
Pi = np.asarray([0.2, 0.4, 0.4]).transpose()
A = np.log(A)
B = np.log(B)
Pi = np.log(Pi)

O = np.asarray([0,1,0])  # 观测序列


def Viterbi_decode(A, B, Pi, O):
    """维特比解码,所有概率为对数概率。
    输入:
        A: N×N 的转移矩阵
        B: N×M 的输出矩阵
        Pi: list, 初始状态概率分布
        O: list, 观测序列
    返回:
        max_path: list, 最优路径。
    """
    T = O.shape[0]
    N = A.shape[0]   # 状态数
    p_nodes = Pi + B[:, O[0]]     # 记录每个节点的路径概率
    path_nodes = list()           # 记录每个节点的路径
    # 计初始化路径
    for node in xrange(N):
        path_nodes.append([node])
    # T 个时刻
    for step in xrange(1, T):
        for this_node in xrange(N):   # 计算每个节点的新概率
            p_news = list()
            for last_node in xrange(N):
                p_trans = A[last_node, this_node]  # 转移概率
                p_out = B[this_node, O[step]]       # 输出概率
                p_new = p_nodes[last_node] + p_trans + p_out
                p_news.append(p_new)
            p_nodes[this_node] = np.max(p_news)    # 更新节点路径概率
            last_index = np.argmax(p_news)         # 更新节点路径
            temp = path_nodes[last_index][:]
            temp.append(this_node)
            path_nodes[this_node] = temp
    max_index = np.argmax(p_nodes)
    max_path = path_nodes[max_index]        
    return max_path

max_path = Viterbi_decode(A, B, Pi, O)
print max_path
[2, 2, 2]

在 TensorFlow入门(六) 双端 LSTM 实现序列标注(分词)最后部分使用了维特比解码。和这里有些区别:

  • 初始路径概率 pnodes=Pi+B[:,O[0]] , 改成使用 Bi-LSTM 模型输出的分类概率
  • 输出概率 pout=B[thisnode,O[step]] , 改成使用 Bi-LSTM 模型输出的分类概率

你可能感兴趣的:(python,机器学习)