上图表示最原始的通信模型,一般现在通信模型包含信源,信道,接收者,信息,上下文和编码,发送者对信息上下文进行编码得到一种能够在信道中传播的信号,比如语音或者电话线的调制信号,假设信息发送的信息是 q i , q 2 , ⋯   , q N q_i,q_2,\cdots,q_N qi,q2,⋯,qN,然后通过媒体传播到接收方,接接收端接收到的信号是 o 1 , o 2 , . ⋯   , o T o_1,o_2,.\cdots,o_T o1,o2,.⋯,oT,在接收方,根据事先约定好的方法,将编码的信号还原成发送的消息。
而自然语言处理也可以理解成一个通信系统。语音识别,实际上就是听着对介质传播过来的声音进行大脑解码,猜测说话者要表达的意思,这个过程也就是通信系统中接受端根据接收到的信号去分析、理解、还原发送端传送过来的信息。
在通信系统中,只需要从所有源信息中找到最可能产生观测信号 o 1 , o 2 , . ⋯   , o T o_1,o_2,.\cdots,o_T o1,o2,.⋯,oT的那一个信息,也就是根绝观测信号来推测信号源发送的信息 q i , q 2 , ⋯   , q N q_i,q_2,\cdots,q_N qi,q2,⋯,qN了。用概率论语言来描述也就是,在已知 o 1 , o 2 , . ⋯   , o T o_1,o_2,.\cdots,o_T o1,o2,.⋯,oT的情况下,求得令条件概率 P ( q i , q 2 , ⋯   , q N ∣ o 1 , o 2 , . ⋯   , o ) P(q_i,q_2,\cdots,q_N|o_1,o_2,.\cdots,o_) P(qi,q2,⋯,qN∣o1,o2,.⋯,o)达到最大值的信息串 q i , q 2 , ⋯   , q N q_i,q_2,\cdots,q_N qi,q2,⋯,qN。
从这个角度看,围绕HMM有三个基本的问题
HMM是可用于标注问题的关于时序的模型,描述由一个隐藏的马尔可夫链随机生成不可观测的状态随机序列(上图第二行),再由各个状态生成一个观测而产生观测随机序列(上图第一行)的过程,其中上图第二行表示状态序列,上图第一行表示观测序列。【序列的每一个位置可以看作一个时刻】。注:标注问题是给定观测的序列预测其对应的标记序列
首先,HMM是基于两个基本假设的
首先我们需要明确的是,概率计算是在知道模型参数 λ \lambda λ和观测序列 O O O的条件下,计算产生该观测序列的概率,即 P ( O ∣ λ ) P(O|\lambda) P(O∣λ),最直接的方法就是应用概率计算公式进行推导,从上一节EM算法的总结中我们知道了隐变量这个概念,引入隐变量的一个条件就是不影响原来的边缘分布,也就是对隐变量进行积分能够将隐变量积分掉。在这个问题中,我们知道生成状态序列的概率,从状态序列产生观测序列的概率,以及初始概率分布,但是就是不知道那个状态序列 I I I是什么。这就可以看作概率计算中的一个隐变量,于是根据概率计算公式就有如下表达:
在模型训练计算参数 λ \lambda λ的时候,我们需要的是最大化似然函数,也就是 a r g m a x λ P ( O ∣ λ ) argmax_{\lambda}P(O|\lambda) argmaxλP(O∣λ),如果是上述概率计算公式的话,计算量非常大,是 O ( T N T ) O(TN^T) O(TNT), T T T表示序列的长度,实现上是不可行的,因此发展出如下有效算法。
如上图,引自徐亦达老师HMM的课件,左边表示前向算法,右边表示后向算法,为保持符号一致,输出序列改为 o i o_i oi表示。
前向概率定义:给定HMM模型 λ \lambda λ,定义到时刻 t t t部分观测序列为 o 1 , o 2 , ⋯   , o t o_1,o_2,\cdots,o_t o1,o2,⋯,ot,且状态为 q i q_i qi的概率为前向概率,记作 α t ( i ) = P ( o 1 , o 2 , ⋯   , o t , i t = q i ∣ λ ) \alpha_t(i)=P(o_1,o_2,\cdots,o_t,i_t=q_i|\lambda) αt(i)=P(o1,o2,⋯,ot,it=qi∣λ)注意,这只是一个定义,根据这个定义可以递归求得前向概率 α t ( i ) \alpha_t(i) αt(i)以及观测序列概率 P ( O ∣ λ ) P(O|\lambda) P(O∣λ)
所以根据这个递推公式,我们只需要先计算初值 α 1 ( i ) \alpha_1(i) α1(i) 然后对 t = 1 , 2 , ⋯   , T − 1 t=1,2,\cdots,T-1 t=1,2,⋯,T−1根据递推公式进行递推,最后对所有可能的初始状态进行积分就可以得到 P ( O ∣ λ ) = ∑ i = 1 N α T ( i ) P(O|\lambda)=\sum_{i=1}^N\alpha_T(i) P(O∣λ)=∑i=1NαT(i)。
后向概率定义:给定HMM模型 λ \lambda λ,定义在时刻 t t t状态为 q t q_t qt的条件下,从 t + ! t+! t+!到T的部分观测序列 o t + 1 , o t + 2 , ⋯   , o T o_{t+1},o_{t+2},\cdots,o_T ot+1,ot+2,⋯,oT的概率为后向概率: β t ( i ) = P ( o t + 1 , o t + 2 , ⋯   , o T ∣ i t = q t , λ ) \beta_t(i) = P(o_{t+1},o_{t+2},\cdots,o_T|i_t=q_t,\lambda) βt(i)=P(ot+1,ot+2,⋯,oT∣it=qt,λ)
同理,这也只是一个定义,根据该定义可以递推 P ( O ∣ λ ) P(O|\lambda) P(O∣λ)
第一种直接运用概率计算,但是需要大量的标记数据,代价很大,因此最常用的还是EM算法,在这里叫做Baum-Welch算法,看书就可以看到这里和上一节总结的EM算法十分相似,并且这里的隐变量实际上已经给出就是 I I I,看书完全能明白,就不做总结了。
维特比算法是一个特殊但是应用最广的动态规划算法,利用动态规划可以解决任何一个图中的最短路径问题。维特比算法是针对一个特殊的图——篱笆网络的有向图最短路径问题提出的。他之所以重要,是因为凡是使用HMM描述的问题都可以用它来解码,包括今天的数字通信,语音识别,机器翻译,拼音转汉字,分词等等。
统计学习方法中根据前向和后向算法推导了单个输出的最大可能概率,但是我们需要预测最可能的一个状态序列,所以整个问题就变成 q 1 , q 2 , ⋯   , q N = A r g M a x q ∈ Q P ( q 1 , q 2 , ⋯   , q N ∣ o 1 , o 2 , ⋯   , o N ) q_1,q_2,\cdots,q_N = ArgMax_{q\in Q}P(q_1,q_2,\cdots,q_N|o_1,o_2,\cdots,o_N) q1,q2,⋯,qN=ArgMaxq∈QP(q1,q2,⋯,qN∣o1,o2,⋯,oN)这时,我们是有观测序列的,但是我们不知道状态序列,也就是每个状态 q 1 q_1 q1实际上可能有N个取值,为了简化,我们取几个进行说明,这个优化问题可以展开成一个篱笆网络
从第一个状态到最后一个状态的任何一条路径都可能产生我们观察到额输出序列 O O O,这些路径的可能性可能不一样,我们要做的就是周到最可能的这条路径。维特比提出了一种与状态数目成正比的算法。
其基础可以概括为三点
10.1 10.2 10.3
# -*-coding:utf-8-*-
import numpy as np
def sum(L):
sumValue = 0.0
for i in range(0, len(L)):
sumValue += L[i]
return sumValue
def max(L):
target = 0
maxValue = L[target]
for i in range(1, len(L)):
if (L[i] > maxValue):
target = i
maxValue = L[i]
return maxValue, target + 1
def printTable(table, type="float"):
if (type == "float"):
for i in range(0, len(table)):
for j in range(0, len(table[i])):
print ("%-16f" % (table[i][j]),)
print ("\n")
elif (type == "int"):
for i in range(0, len(table)):
for j in range(0, len(table[i])):
print ("%-2d" % (table[i][j]),)
print ("\n")
def initTable(x, y, type="float"):
table = []
if (type == "float"):
initValue = 0.0
elif (type == "int"):
initValue = 0
for i in range(0, x):
temp = []
for j in range(0, y):
temp.append(initValue)
table.append(temp)
return np.array(table)
def forwardV(A, B, pi, Object, mod="forward"):
T = len(Object)
NumState = len(A)
NodeTable = initTable(NumState, T)
if (mod == "viterbi"):
NodePath = initTable(NumState, T, type="int")
for i in range(0, NumState):
NodeTable[i][0] = pi[i] * B[i][Object[0]]
for t in range(1, T):
for j in range(0, NumState):
temp = []
for i in range(0, NumState):
temp.append(NodeTable[i][t - 1] * A[i][j] * B[j][Object[t]])
if (mod == "forward"):
NodeTable[j][t] = sum(temp)
elif (mod == "viterbi"):
NodeTable[j][t], NodePath[j][t] = max(temp)
if (mod == "forward"):
print (u"前向算法alpha值记录表:")
printTable(NodeTable)
p = 0.0
for i in range(0, NumState):
p += NodeTable[i][T - 1]
return NodeTable, p
elif (mod == "viterbi"):
print (u"viterbi算法结点值记录表:")
printTable(NodeTable)
print (u"viterbi算法路径记录表:")
printTable(NodePath, type="int")
target = 0
maxValue = NodeTable[target][T - 1]
for i in range(1, NumState):
if NodeTable[i][T - 1] > maxValue:
target = i
maxValue = NodeTable[i][T - 1]
print (u"viterbi算法最终状态:", target + 1, "\n")
StateSequeues = [0] * T
StateSequeues[T - 1] = target + 1
for i in range(1, T):
StateSequeues[T - i - 1] = NodePath[StateSequeues[T - i] - 1][T - i]
return StateSequeues
def backward(A, B, pi, Object):
T = len(Object)
NumState = len(A)
NodeTable = initTable(NumState, T)
for i in range(0, NumState):
NodeTable[i][T - 1] = 1
for t in range(1, T):
for i in range(0, NumState):
temp = []
for j in range(0, NumState):
temp.append(NodeTable[j][T - t] * A[i][j] * B[j][Object[T - t]])
NodeTable[i][T - t - 1] = sum(temp)
print (u"后向算法beta值记录表:")
printTable(NodeTable)
p = 0.0
for i in range(0, NumState):
p += pi[i] * B[i][Object[0]] * NodeTable[i][0]
return NodeTable, p
# ---------------------------------------------------------------------#
# forwardV函数实现前向算法和viterbi算法
# backward函数实现后向算法的计算
# 前向后向结合计算特殊点概率,只需取两个记录表中相应的alpha和beta值即可
# ---------------------------------------------------------------------#
print (u"习题10.1和10.3")
A1 = [[0.5, 0.2, 0.3], [0.3, 0.5, 0.2], [0.2, 0.3, 0.5]]
B1 = [[0.5, 0.5], [0.4, 0.6], [0.7, 0.3]]
pi1 = [0.2, 0.4, 0.4]
Object1 = [0, 1, 0, 1]
# 习题10.1
table, P = backward(A1, B1, pi1, Object1)
print (u"(10.1)通过后向算法计算可得 P(O|lambda)=", P, "\n")
# 习题10.3
I = forwardV(A1, B1, pi1, Object1, mod="viterbi")
print (u"(10.3)通过viterbi算法求得最优路径 I=", I, "\n")
print (u"#-----------------------------------------------------#")
# ---------------------------------------------------------------------#
print (u"习题10.2")
A2 = [[0.5, 0.1, 0.4], [0.3, 0.5, 0.2], [0.2, 0.2, 0.6]]
B2 = [[0.5, 0.5], [0.4, 0.6], [0.7, 0.3]]
pi2 = [0.2, 0.3, 0.5]
Object2 = [0, 1, 0, 0, 1, 0, 1, 1]
F, pf = forwardV(A2, B2, pi2, Object2, mod="forward")
B, pb = backward(A2, B2, pi2, Object2)
# 习题10.2
# P(i4=q3|O,lambda)=P(i4=q3,O|lambda)/P(O|lambda)=alpha4(3)*beta4(3)/P(O|lamda)
# pf=pb,任选一个
# 1-based索引转换为0-based索引
print (u"P(O|lambda)=", pf)
P2 = F[3 - 1][4 - 1] * B[3 - 1][4 - 1] / pf
print (u"(10.2)通过前向后向概率计算可得 P(i4=q3|O,lambda)=", P2, "\n")
10.4 试用前向概率和后向概率推导 P ( O ∣ λ ) = ∑ i = 1 N ∑ j = 1 N α t ( i ) a i j b j ( o t + 1 ) β t + 1 ( j ) , t = 1 , 2 , . . . , T − 1 P(O|\lambda)=\sum_{i=1}^N\sum_{j=1}^N\alpha_t(i)a_{ij}b_j(o_{t+1})\beta_{t+1}(j),{\quad}t=1,2,...,T-1 P(O∣λ)=∑i=1N∑j=1Nαt(i)aijbj(ot+1)βt+1(j),t=1,2,...,T−1
10.5 比较维特比算法中变量 δ \delta δ的计算和前向算法中变量 α \alpha α α \alpha α。
计算变量α的时候直接对上个的结果进行数值计算,而计算变量δ需要在上个结果计算的基础上选择最大值。