学习隐马尔可夫模型时,最大的困难便是一堆公式与实际问题对应不上号。原因可能还是在于对概率论的理解太表面,且隐马尔可夫模型考虑了时间因素,显然这样的随机过程一时半会是难以形象的理解的。因此,本文采用先举例,后定义公式的方式来学习隐马尔可夫模型。
当然,我们还是首先需要知道隐马尔可夫模型(HMM)在统计学习中的地位和应用。参考书本《统计学习方法》的介绍,隐马尔可夫是可用于标准问题的统计学习模型,描述由隐藏的马尔可夫链随机生成观测序列的过程,属于生成模型。本文首先介绍隐马尔可夫模型的基本概念,然后分别叙述隐马尔可夫模型的概率计算算法、学习算法以及预测算法。隐马尔可夫模型在语音识别、自然语言处理、生物信息、模式识别等领域有着广泛的应用。
问题阐述
参看《统计学习方法》信息抽取例子。
输入:At Microsoft Research, we have an insatiable curiosity and the desire to create new technology that will help define the computing experience.
输出:At/O Microsoft/B Research/E, we/O have/O an/O insatiable/B curiosity/E and/O the/O desire/BE to/O create/O new/B technology/E that/O will/O help/O define/O the/O computing/B experience/E.
这是在干嘛。。。如果我们是人,这个问题是说,给你一个输入,该输入为一句完整的句子,我们要从中找出名词短语来(PS:/后的符号是一个标注,由计算输出,暂时可以不去看它)。OK,这有什么困难的,根据我们所学的英语知识,寻找连续两个名词,再人为的判断下它是否符合为意义的名词即可。再做进一步退化,我们不知道该单词的意思,只知道词性,我们该怎么做?行吧,标出该句子中每个单词的词性,如介词,副词,名词,连续2个or N个名词组成的词组可能为名词短语。可并不是所有的名词短语是有意义的啊,我们还是需要大量的英文知识储藏才能解决这个问题。
现在我们来考虑计算机如何解决该问题。目前来看,计算机并不知道各英语单词的词性,这不难,给它一个词性对应表,让它暴力判断下即可。可问题又来了,哪怕在成千上万的英语单词中,能够寻找到对应的词性,可连续2个or N个单词组成的词组如何判断是有意义的呢?显然,想要再找对应的名词短语映射表,那简直就是天文数字,无从下手。
机械的求解方法无法解决,这时候统计学就派上了用场,标注问题便是解决该问题的有效手段。标注问题分为学习和标注两个过程,首先给定一个训练数据集
继续回到上述问题,假设我们有了一堆输入输出数据,标注问题便能够通过数学概率模型来建立一种学习模型,训练出有效的参数,根据这个模型来预测输入值对应的输出值。如下图:
那么该问题就完全就可以用统计学习的方法来解决,我们假设要对文章进行标注。那么每个英文单词是一个观测,英文句子是一个观测序列,标记表示名词名词短语的“开始”、“结束”、“其他”(分别以B,E,O表示),标记序列表示英文句子中基本名词短语的所在位置。
也就是说,针对观测序列,即我们的输入语句,我们可以对应的给每个单词进行标注,标注符号可以自由定义,套用前面最蠢的办法,我们可以训练出一个词性标注器,即at对应的介词,Microsoft对应的名词,再来判断名词短语。但既然我们可以人为定义标注符号,解决该问题并不需要词性标注这一过程,而是直接根据学习系统去判断名词短语的开始和结束,或者其他。只要输入的训练数据均符合上述约定即可。
OK,我们解释了标注问题用来解决什么样的实际问题,但刚才提到的数学模型却没有明确,其背后使用了统计概率的相关知识,而一个最常见的模型便是我们的隐马尔可夫模型。相比信息抽取的例子,要理解隐马尔可夫模型是干嘛的,显然还是困难的,我们不妨引用一篇关于HMM博文(请点击这里)的例子来进一步阐述隐马尔可夫模型的原理。
刚才我们说了信息抽取的例子,其实仔细回想一下,为何需要隐马尔可夫模型,“隐”这个概念是相当重要的。藏于物理世界中的往往是表象,而真正的隐藏状态是不可见的,我们往往需要通过观察大量的表象来总结规律,从而能够确定事物的隐含状态,达到认识事物的本质。而事物的隐含状态与事物的表象存在着一定的关联关系,我们可以通过统计的方法来确立它们之间的关联。如观察海藻湿度的状态,能否确定是雨天还是晴天。这个例子可能偏颇,需明确一下,海藻的湿度为可见状态,而天气状态为隐藏状态(假设盲人能够感知海藻湿度,却无法观察天气)。
1.海藻模型
我们先来定义观测概率矩阵:
回归海藻例子,刚才提到了我们可以观察海藻的湿度来确定隐含状态。那么我们现定义海藻湿度分为四个等级为”dry”,”dryish”,”damp”,”soggy”.显而易见,不同的天气状况我们能观测到海藻不同湿度的概率是不同的,以表格的形式如下:
我们先来看看最一般的马尔科夫定义,马尔可夫链是随机变量 X1,X2,...XN 的一个数列。这些变量的范围,即他们所有可能取值的集合,被称为“状态空间”,而 Xn 的值则是在时间 n 的状态。如果 Xn+1 对于过去状态的条件概率分布仅是 Xn 的一个函数,则
所以隐马尔可夫模型做了最基本的假设,即当前状态只与前一个状态有关。即
有了时间不动性假设,我们就可以统计概率了,即假设历史上只出现了三种天气状态,分别为”sunny”,”cloudy”,’rainy’.那么根据某个时间段内,可以统计出如”sunny” → ”cloudy”的频率,这里的频率随着统计量可以理解为概率。那么,我们又可以列一个表格来描述从不同的状态转移至另一状态,如下表
这时有了状态转移概率和观测概率矩阵,我们可以搬出书中关于隐马尔可夫模型的定义了。等一下,作为一个系统我该从哪里开始运行?或者计算从哪里开始算起呢?显然我们还缺一个要素,初始状态概率向量 π ,即 π=(πi)
隐马尔可夫模型是关于时序的概率模型,描述由一个隐藏的马尔可夫链随机生成不可观测的状态随机序列,再由各个状态生成一个观测而产生观测随机序列的过程。隐藏的马尔可夫链随机生成的状态的序列,称为状态序列;每个状态生成一个观测,而由此产生的观测的随机序列,称为观测序列。序列的每一个位置又可以看作是一个时刻。
如上图所示,第一层为不可观测状态,第二层为观测状态。在海藻模型中,盲人每天第一件事情便是感知海藻的湿度,即每天记录海藻的状态。假设盲人连续记录了三天,海藻的观测序列为(“dry”,”damp”,”soggy”),那由我们的隐马尔可夫模型知道,第一天海藻状态为”dry”可能最佳隐藏状态为”sunny”;第二天海藻状态为”damp”可能最佳隐藏状态为”cloudy”;第三天海藻状态为”soggy”可能最佳隐藏状态为”rainy”。这是根据人为经验去判断的,但whatever,人其实就是一个自我学习系统,我们训练出的规则可能相对简单,由此我们得到了天气的隐藏状态序列为(“sunny”,”cloudy”,”rainy”)。
继续隐马尔可夫模型的形式定义:
设Q是所有可能的状态集合,V是所有可能的观测的集合。
你可能疑问了 O/I ,对应的观测状态O下,I序列的概率一定是最大嘛?即 P(I|O) 的概率一定是最大吗?或者你又想了,为什么在某天开始的观察序列一定是 O={dry,damp,soggy} ,从而引申出另外一个问题,假设给出了 λ=(A,B,π) 的模型,你咋确定从某一时刻起一定是该观测序列,而不是其他。即求 P(O|λ) 的概率。
这里便引出了隐马尔可夫模型的3个基本问题的其中两个,概率计算问题和预测问题。
海藻模型中提出了在已知模型 λ=(A,B,π) 求解 P(O|λ) 的问题。我们就给它来求解一把。
1.穷举搜索
问题求解当没有好的办法时,我们骑着驴也要找马,咋办呢,穷举呗。刚才在海藻模型中,根据观察序列寻找了一个隐藏序列。该概率可以表示为
P(O|I,λ) 。显然 I 序列的个数不只一个,3种天气状态,对应3天序列, I 的总数为 3×3×3=27 个,假设有N中状态,对应T个观察序列,那么 I 的总数为 NT 个。
从图中也能看的非常清楚,27种序列一一列举出来后,进行分别计算。对固定的状态序列 I={i1,i2,...,iT} ,观测序列 O={o1,o2,...,oT} 的概率是 P(O|I,λ) ,即
有了 I 出现的概率,和在 I 出现的条件下, O 出现的概率后,我们就可以求得联合概率 P(O,I|λ) ,即
在程序中有空间换时间的思想,其实在数学公式里也是通用的,我们可以采用中间变量来记录结果,假设能够利用该中间变量来计算后续节点,那么很明显可以极大的简化计算次数。因为,我们无需重新计算先前路径。
2.递归思想之前向算法
在这之前我们需要定义节点之间路径的数学含义和节点的有向边汇聚的数学含义。这是我们后续做理论推到的基础,也是理解前向算法和后向算法等价的基础。假设sunny有初始值 sunny=0.6 ,指向cloudy,且该路径附上权值 pathsunny→cloudy=0.375 ,那么经过该路径连接的两个节点关系为 cloudy=sunny∗pathsunny→cloudy ,即有向路径可以代表“乘法”。同样的,指向cloudy有多条路径,即 cloudy=0.2,rainy=0.2 ,我们定义有向边汇聚到同一点,则该点表示为
书中直接给出了定义:
3.反向算法
反向算法也是一种求解 P(O|λ) 的一种方法,但它的物理含义却并没有那么清晰,或者说根本很难确定它实际的物理含义,但我们可以简单证明下反向算法和前向算法所得出的结果是等价的。知乎上同样有一篇关于反向传播答疑贴,请参看这里
我们先来举一个简单的例子,假如我们知道C商户有A类货物和B类货物,A类货物50元/斤,B类货物30元/斤,A类货物卖出了3斤,卖给了2个人。B类货物分别卖出了5斤给2个人,8斤给1个人,请问C商户总过卖了多少钱。小学数学题,答案很简单, C=50∗3∗2+(30∗5∗2+30∗8∗1)=840元
其实,除了直接进行求解外,我们可以把这个问题用节点来进行建模,如上图,我们根据有向边和有向边汇聚建立了如上各节点,同样的,我们能求出 C=840元 。好了,我们现在要开始反向传播算法了,令C=1,注意这里的节点资源均为1,即刚才的前向算法是令A=1,B=1,求出C。现在反向传播的做法便是令C=1,OK,现在跟着剪头相反方向进行计算,由上至下,第二层节点的值分别为2和1,第三层节点的值分别2*3和 (2∗5+1∗8) ,最后一层节点 A=6∗50=300,B=18∗30=540 ,由此得A货物卖出了300元,B货物卖出了540元,总共也卖出了840元。所以说,不管自下而上求出各节点,还是自上而下求出的各节点,只要最终对各节点和进行累加,所得到的结果是一样的。
我们来看看隐马尔可夫模型反向算法是如何定义的,给定隐马尔可夫模型 λ ,定义在时刻t状态为 qi 的条件下,从t+1到T的部分观测序列为 ot+1,ot+2,...,oT 的概率为后项概率,记作
算法(观测序列概率的后向算法)
输入:隐马尔可夫模型 λ ,观测序列 O ;
输出:观测序列概率 P(O|λ)
(1)βT(i)=1,i=1,2,...,N
类似于在商户卖货问题中令最终节点C=1
(2) 对 t=T−1,T−2,...,1
βt(i)=∑j=1Naijbj(ot+1βt+1(j)),i=1,2,...,N
其中 aij 直观的理解,就是对有向边进行了反向,而 β 就是反向算法过程中的中间变而已,我们也给它赋予了实际的物理含义。
(3)P(O|λ)=∑i=1Nπibi(o1)β1(i)
类似于对商户问题的起始节点进行求和。
我们再来理解下 βt(i) 的定义,状态 it=qi 为 βt(i) 的条件了,这里和前向算法中存在了微小的差异,来看书上的图:
从图中其实可以很明显的看出,虽然是反向算法,但我们可以从前向的角度去理解该式子,由于t时刻,不同的 qi 对应的 aij 是不同的,所以 P(ot+1,ot+2,...,oT|λ) 除了跟模型参数有关,还跟 qi 相关。
步骤1.初始化后向概率,对最终时刻的所有状态 qi 规定 βT(i)=1. 步骤2.是后向概率的递推公式。如上图所示,为了计算在时刻t状态为 qi 条件下时刻t+1之后的观测序列为 ot+1,ot+2,...,oT 的后向概率 βt(i) ,只需要考虑在时刻t+1所有可能的N个状态 qj 的转移概率,以及在此状态下的观测 ot+1 的观测概率,然后考虑状态 qj 之后的观测序列的后向概率。步骤3.求 P(O|λ) 的思路与步骤2.一致,只是初始概率 πi 代替转移概率。
回归实际问题,我们现在能够根据给定的模型参数 λ 计算出 P(O|λ) 的值,本文对标注问题进行阐述时引出了信息抽取的一个例子,它就是典型的预测问题,给定一连串输入序列,我们需要对每个单词进行标注,从而使得整个隐藏序列对于观测序列来讲概率最大。即 P(I|O) 最大。
1.维特比算法
维特比算法实际是用动态规划解隐马尔可夫模型预测问题,即用动态规划求概率最大路径(最优路径),这时一条路径对应着一个状态序列。
我们先来看《统计学习算法》中,对维特比算法的人工计算实例,从而加深对维特比算法的理解。
例. 输入模型 λ=(A,B,π)
A=⎡⎣⎢0.50.30.20.20.50.30.30.20.5⎤⎦⎥
B=⎡⎣⎢0.50.40.70.50.60.3⎤⎦⎥
π=(0.2,0.4,0.4)T
已知观测序列O={红,白,红},试求最优状态序列,即最优路径 I∗=(i∗1,i∗2,i∗3)
假设我们对动态规划的原理不甚了解,没关系,我们只需要知道动态规划的其中一个原理,即如果最优路径在时刻t通过节点 i∗t ,那么这一路径从节点 i∗t 到终点 i∗T 的部分路径,对于从 i∗t 到 i∗T 所有可能的部分路径来说,必须是最优的。
在t=1时刻,对于每一个状态 i,i=1,2,3, 求状态为 i 观测 o1 为红的概率,为了方便书写,我们记作 δ1(i) ,则
由此回过头来再看看书中更一般的定义,首先导入变量 δ和ψ. (求解最优路径的中间变量和用来记录路径节点的指针)定义在时刻t状态为i的所有单个路径 (i1,i2,...,it) 中概率最大值为
其实条件概率和联合概率之间有它们的关系,省略共同的影响参数 λ ,可得
由定义可得变量 δ 的递推公式:
算法(维特比算法)
输入:模型 λ=(A,B,π) 和观测 O=(o1,o2,...,ot) ;
输出:最优路径 I∗=(i∗1,i∗2,...,i∗T)
(1) 初始化
δ1(i)=πibi(o1),i=1,2,...,N
ψ1(i)=0,i=1,2,...,N
(2) 递推。对 t=2,3,...,T
δt(i)=max1≤j≤N[δt−1(j)aji]bi(ot),i=1,2,...,N
ψt(i)=argmax1≤j≤N[δt−1aji],i=1,2,..,N
(3) 终止
P∗=max1≤i≤NδT(i)
i∗T=argmax1≤i≤N[δT(i)]
最优路径回溯。对 t=T−1,T−2,...,1
i∗t=ψt+1(i∗t+1)
求得最优路径 I∗=(i∗1,i∗2,...,i∗T)
在预测问题上,我们用到了贝叶斯的思考哲学,更多的内容请参看博文-数学之美番外篇:平凡而又神奇的贝叶斯方法。针对学习算法,由于它内容繁杂且用到EM算法,因此待我学习完EM算法后,再进行梳理。
在海藻模型中我们定义了一堆状态转移矩阵,观测概率矩阵和初始状态概率向量。在python中数据如下:(打开文本编辑器,新建文本test.py)
observations = ('dry','dryish','damp','soggy')
start_probability ={'sunny':0.6,'cloudy':0.2,'rainy':0.2}
transition_probability = {
'sunny':{'sunny':0.5,'cloudy':0.375,'rainy':0.125},
'cloudy':{'sunny':0.25,'cloudy'0.125:,'rainy':0.625},
'rainy':{'sunny':0.25,'cloudy':0.375,'rainy':0.375}
}
emission_probability ={
'sunny':{'dry':0.6,'dryish':0.2,'damp':0.15,'soggy':0.05},
'cloudy':{'dry':0.25,'dryish':0.25,'damp':0.25,'soggy':0.25},
'rainy':{'dry':0.05,'dryish':,0.1'damp':0.35,'soggy':0.50}
}
定义HMM模型:(打开文本编辑器,新建文件hmm.py)
class HMM:
def __init__(self,A,B,pi):
self.A =A
self.B =B
self.pi =pi
def _forward(self,obs_seq):
# 取A = N x N
N = self.A.shape[0]
T = len(obs_seq)
F = zeros((N,T))
# alpha = pi*b
F[:,0] = self.pi *self.B[:,obs_seq[0]]
for t in range(1,T):
for n in range(N):
# 计算第t时,第n个状态的前向概率
F[n,t] = dot(F[:,t-1], (self.A[:,n])) * self.B[n, obs_seq[t]]
return F
F 即为我们在前向算法中定义的 αt(i).
def _backward(self,obs_seq):
N = self.A.shape[0]
T = len(obs_seq)
X = zeros((N,T))
# 表示X矩阵的最后一列
X[:,-1:] =1
for t in reversed(range(T-1)):
for n in range(N):
# 边权值为a_ji
X[n,t] = sum(X[:,t+1] * self.A[n,:] * self.B[:,obs_seq[t+1]])
return X
测试前向算法和后向算法数据的一致性:(在test.py中添加)
def gengerate_index_map(labels):
index_label = {}
label_index = {}
i =0
for l in labels:
index_label[i] =l
label_index[l] =i
i+=1
return label_index,index_label
states_label_index,states_index_label = gengerate_index_map(states)
observations_label_index,observations_index_label = gengerate_index_map(observations)
def convert_observations_to_index(observations,label_index):
list =[]
for o in observations:
list.append(label_index[o])
return list
def convert_map_to_matrix(map,label_index1,label_index2):
m = empty((len(label_index1),len(label_index2)),dtype = float)
for line in map:
for col in map[line]:
m[label_index1[line]][label_index2[col]] = map[line][col]
return m
def convert_map_to_vector(map,label_index):
v = empty(len(map),dtype = float)
for e in map:
v[label_index[e]] =map[e]
return v
A = convert_map_to_matrix(transition_probability,states_label_index,states_label_index)
print (A)
B = convert_map_to_matrix(emission_probability,states_label_index,observations_label_index)
print (B)
observations_index = convert_observations_to_index(observations,observations_label_index)
print (observations_index)
pi = convert_map_to_vector(start_probability,states_label_index)
print (pi)
h = HMM(A,B,pi)
# 人为定义的海藻状态序列
obs_seq = ('dry','damp','soggy')
obs_seq_index = convert_observations_to_index(obs_seq,observations_label_index)
# 计算P(o|lambda)
F = h._forward(obs_seq_index)
print ("forward: P(O|lambda) = %f" %sum(F[:,-1]))
X = h._backward(obs_seq_index)
print ("backward: P(O|lambda) = %f" %sum(X[:,0]*pi*B[:,0]))
前向,后向算法输出的结果为:(答案一致)
forward: P(O|lambda) = 0.026441
backward: P(O|lambda) = 0.026441
def viterbi(self,obs_seq):
N = self.A.shape[0]
T = len(obs_seq)
prev = zeros((T-1,N),dtype = int)
V = zeros((N,T))
V[:,0] = self.pi * self.B[:,obs_seq[0]]
for t in range(1,T):
for n in range(N):
# 计算delta(j)*a_ji
seq_probs = V[:,t-1] * self.A[:,n] * self.B[n,obs_seq[t]]
# 记录最大状态转移过程
prev[t-1,n] = argmax(seq_probs)
V[n,t] = max(seq_probs)
return V,prev
def build_viterbi_path(self,prev,last_state):
"""
returns a state path ending in last_state in reverse order.
"""
T = len(prev)
yield(last_state)
# 从T-1开始,每次下降1
for i in range(T-1,-1,-1):
yield(prev[i,last_state])
last_state = prev[i,last_state]
def state_path(self,obs_seq):
V,prev = self.viterbi(obs_seq)
# build state path with greatest probability
last_state = argmax(V[:,-1])
path = list(self.build_viterbi_path(prev,last_state))
return V[last_state,-1],reversed(path)
输出结果:(在test.py中添加)
# 计算P(I|o)
p,ss = h.state_path(obs_seq_index)
path = []
for s in ss:
path.append(states_index_label[s])
print("最有可能的隐藏序列为:" ,path)
print("viterbi: P(I|O) =%f"% p)
result:
输入观察序列为:['dry','damp','soggy']
最有可能的隐藏序列为: ['sunny', 'cloudy', 'rainy']
viterbi: P(I|O) =0.010547
至此,维特比算法也呈现完毕,从输入和输出对应的关系来看,dry对应的sunny,damp对应的cloudy,soggy对应的rainy,还是相当符合实际情况滴。
关于隐马尔可夫模型概率计算问题和预测问题已经全部阐述完毕了,理论结合实践,在该模型的学习过程中,理解了反向算法和前向算法的一致性,在算法执行效率上,代替了暴力求解,采用递归思想和动态规划的原理来降低计算时间复杂度,理解了数学中的中间变量即为递归原型。