马上又是一年情人节,小雷还是孤单一人。朋友劝小雷说:“你还在期待爱情,但是先得解决婚姻呀 ”。
于是,小雷就这样走上了漫漫的相亲路。
一张略显空旷的餐桌,坐着的女孩就是这次的相亲对象。
女孩早早的就来了,比约定的时间早了不少。她低着头,在玩儿手机,看不清楚是什么表情。
见我来了,她抬起头来,眼神很是友好,她轻声说:“你坐”
简单的自我介绍,我们的聊天就开始了。
从工作聊到学生时代,又从家里趣事聊到业余爱好。聊到尽兴处,她笑着歪了歪头(撒娇),眼睛布灵布灵的。
走的时候,她主动留下了联系方式,还回头对我做了个鬼脸(撒娇)
回去以后,小雷坐在电脑旁边,回想这一天,忽然就想知道女孩对他的第一印象如何。
恰巧身旁放了一本李航写的《统计学习方法》,小雷随手翻到“隐马尔可夫模型”这一节,一个叫做Baum-Welch的算法引起了他的注意。
这个算法是干什么用的呢?看官你还记不记得我们曾经说过,隐马尔可夫模型(HMM)里有三个基本问题,第三个问题叫做:
这个算法就是为了解决参数估计问题而出现的。而小雷很关心的第一印象不就是初始状态的概率分布嘛。说到这里,你可能想问,能不能把小雷的相亲用HMM来建模呢?当然能了!
话不多说,且看下文:
小雷跟女孩以后的关系我们定义在status的列表里:status=[“相处”,”拜拜”]
总结女孩的行为,一共有四种:”撒娇”,”低头玩儿手机”,”眼神很友好”,”主动留下联系方式”。我们把这放入名叫observations的列表里以备后用。
状态转移概率A表示了小雷和女孩以后关系的变化。
符号发射概率B 表示了女孩在不同第一印象下的表现。
初始状态的概率分布Pi表示女孩对小雷第一印象的概率,现在压根不知道,所以好坏对半分,P(相处)=0.5,P(拜拜)=0.5
观察序列O表示女孩实际的行为序列,即:【玩儿手机,眼神很是友好,撒娇,眼神很友好,主动留下联系方式,撒娇】
你看,我们目前的这些参数都是任取的,除了B有点不同外,别的好像都是0.5。为什么这样做呢?这是因为我们待会儿要做的事就是重新估计这些参数哇,所以它们的初值是多少并不重要~
status=["相处","拜拜"]
observations=["撒娇","低头玩儿手机","眼神很友好","主动留下联系方式"]
A={"相处":{"相处":0.5,"拜拜":0.5},"女":{"相处":0.5,"拜拜":0.5}}
B={"相处":{"撒娇":0.4,"低头玩儿手机":0.1,"眼神很友好":0.3,"主动留下联系方式":0.2},"拜拜":{"撒娇":0.1,"低头玩儿手机":0.5,"眼神很友好":0.2,"主动留下联系方式":0.2}}
Pi=[0.5,0.5]
O=[observations[1],observations[2],observations[0],observations[2],observations[3],observations[0]]
1.为什么要用EM算法呢?
原因有二:
一是Baum-Welch算法就是从EM算法演变出来的,算是同根同源。
二是如果产生观察序列O的状态序列 I 已知的话,我们采用最大似然估计来计算,才不会用EM算法呢!
I 就是下图红色虚线的部分:
理想是丰满的,现实是残酷的
我们并不知道 I 是什么,正如小雷不知道女孩在下一刻内心想的是继续相处还是说拜拜一样。
所以在这种局面下,只有用期望的次数来代替实际次数方可,因此期望最大化算法(Expectation maximization,EM)算法应运而生。
2.什么是EM算法呢?
EM算法是通过不断求解下界的极大化逼近求解对数似然函数极大化的算法
EM算法可以分为两步进行:
可以看到,不难发现这其中最为关键的就是Q函数了,可是它为什么是这个样子呢?横空出世?
3.Q函数是如何用数学推导出来的?
曾记否?EM算法的目标是求解对数似然函数极大化。即极大化
注:最后一步的推导即全概率公式
现在假设第i次迭代后 θ 的估计值为 θ(i) ,达到极大值后, L(θ)>L(θ(i))
因此:
Jensen不等式告诉我们:
则前面的公式可以推导为:
在EM算法里,我们回顾了 Q 函数以及它的推导过程,而Baum-Welch算法也是从一个 Q 开始的,可谓是一个 Q 引发的种种风云。
定义 Q 之前,我们先来看看 α 在干什么。
隐马尔可夫模型(HMM)中,那个不起眼的 α 终于有了它不一样的意义,
1.E-步骤:
将 α 代入 Q 函数,得
3.前向后向:
你知道吗,Baum-Welch算法又被称为前向后向算法,这是为什么呢?
原因在于,为了方便计算,我们定义了一个叫做 ξ 的变量,这个 ξ 里需要两个参数:前向变量 α 和后向变量 β 。还记得前向变量 α 和后向变量 β 是怎么算出来的吗?就是前向算法和后向算法分别计算出来的哦。提醒你一句:前向算法的思路就是“量变引起质变”,算法细节请移步链接内容。
既然说到前向变量 α 和后向变量 β ,不妨我们将它实现吧:
#前向算法
def forward(self,O):
row=self.A.shape[0]
col=len(O)
alpha=numpy.zeros((row,col))
#初值
alpha[:,0]=self.Pi*self.B[:,O[0]]
#递推
for t in range(1,col):
for i in range(row):
alpha[i,t]=numpy.dot(alpha[:,t-1],self.A[:,i])*self.B[i,O[t]]
#终止
return alpha
#后向算法
def backward(self,O):
row=self.A.shape[0]
col=len(O)
beta=numpy.zeros((row,col))
#初值
beta[:,-1:]=1
#递推
for t in reversed(range(col-1)):
for i in range(row):
beta[i,t]=numpy.sum(self.A[i,:]*self.B[:,O[t+1]]*beta[:,t+1])
#终止
return beta
好了, α 和 β 都有了,我们该说说 ξ 是怎么定义的了:
ξ(i,j) 表示在给定HMM参数 θ(i) 的条件下,在时间t位于状态 si ,时间t+1位于状态 sj 的概率。如图所示:
此外,在时间t位于状态 si 的概率为
这就是Baum-Welch算法了,我们用程序进行模拟:
#前向-后向算法(Baum-Welch算法):由 EM算法 & HMM 结合形成
def baum_welch(self,O,e=0.05):
row=self.A.shape[0]
col=len(O)
done=False
while not done:
zeta=numpy.zeros((row,row,col-1))
alpha=self.forward(O)
beta=self.backward(O)
#EM算法:由 E-步骤 和 M-步骤 组成
#E-步骤:计算期望值zeta和gamma
for t in range(col-1):
#分母部分
denominator=numpy.dot(numpy.dot(alpha[:,t],self.A)*self.B[:,O[t+1]],beta[:,t+1])
for i in range(row):
#分子部分以及zeta的值
numerator=alpha[i,t]*self.A[i,:]*self.B[:,O[t+1]]*beta[:,t+1]
zeta[i,:,t]=numerator/denominator
gamma=numpy.sum(zeta,axis=1)
final_numerator=(alpha[:,col-1]*beta[:,col-1]).reshape(-1,1)
final=final_numerator/numpy.sum(final_numerator)
gamma=numpy.hstack((gamma,final))
#M-步骤:重新估计参数Pi,A,B
newPi=gamma[:,0]
newA=numpy.sum(zeta,axis=2)/numpy.sum(gamma[:,:-1],axis=1)
newB=numpy.copy(self.B)
b_denominator=numpy.sum(gamma,axis=1)
temp_matrix=numpy.zeros((1,len(O)))
for k in range(self.B.shape[1]):
for t in range(len(O)):
if O[t]==k:
temp_matrix[0][t]=1
newB[:,k]=numpy.sum(gamma*temp_matrix,axis=1)/b_denominator
#终止阀值
if numpy.max(abs(self.Pi-newPi))and numpy.max(abs(self.A-newA))and numpy.max(abs(self.B-newB)).A=newA
self.B=newB
self.Pi=newPi
return self.Pi
计算结果如下:
[ 0.97705701 0.02294299]
第一项表示相处的概率,第二项表示拜拜的概率,从结果上看,P(相处)>P(拜拜)。
这么说来,女孩对小雷的第一印象是相当不错的 ☺
”相亲记“之从EM算法到Baum-Welch算法
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import numpy
'''
Created on 2017年2月9日
@author: 薛沛雷
'''
class HMM:
def __init__(self,A,B,Pi):
self.A=A
self.B=B
self.Pi=Pi
#前向算法
def forward(self,O):
row=self.A.shape[0]
col=len(O)
alpha=numpy.zeros((row,col))
#初值
alpha[:,0]=self.Pi*self.B[:,O[0]]
#递推
for t in range(1,col):
for i in range(row):
alpha[i,t]=numpy.dot(alpha[:,t-1],self.A[:,i])*self.B[i,O[t]]
#终止
return alpha
#后向算法
def backward(self,O):
row=self.A.shape[0]
col=len(O)
beta=numpy.zeros((row,col))
#初值
beta[:,-1:]=1
#递推
for t in reversed(range(col-1)):
for i in range(row):
beta[i,t]=numpy.sum(self.A[i,:]*self.B[:,O[t+1]]*beta[:,t+1])
#终止
return beta
#前向-后向算法(Baum-Welch算法):由 EM算法 & HMM 结合形成
def baum_welch(self,O,e=0.05):
row=self.A.shape[0]
col=len(O)
done=False
while not done:
zeta=numpy.zeros((row,row,col-1))
alpha=self.forward(O)
beta=self.backward(O)
#EM算法:由 E-步骤 和 M-步骤 组成
#E-步骤:计算期望值zeta和gamma
for t in range(col-1):
#分母部分
denominator=numpy.dot(numpy.dot(alpha[:,t],self.A)*self.B[:,O[t+1]],beta[:,t+1])
for i in range(row):
#分子部分以及zeta的值
numerator=alpha[i,t]*self.A[i,:]*self.B[:,O[t+1]]*beta[:,t+1]
zeta[i,:,t]=numerator/denominator
gamma=numpy.sum(zeta,axis=1)
final_numerator=(alpha[:,col-1]*beta[:,col-1]).reshape(-1,1)
final=final_numerator/numpy.sum(final_numerator)
gamma=numpy.hstack((gamma,final))
#M-步骤:重新估计参数Pi,A,B
newPi=gamma[:,0]
newA=numpy.sum(zeta,axis=2)/numpy.sum(gamma[:,:-1],axis=1)
newB=numpy.copy(self.B)
b_denominator=numpy.sum(gamma,axis=1)
temp_matrix=numpy.zeros((1,len(O)))
for k in range(self.B.shape[1]):
for t in range(len(O)):
if O[t]==k:
temp_matrix[0][t]=1
newB[:,k]=numpy.sum(gamma*temp_matrix,axis=1)/b_denominator
#终止阀值
if numpy.max(abs(self.Pi-newPi))and numpy.max(abs(self.A-newA))and numpy.max(abs(self.B-newB))True
self.A=newA
self.B=newB
self.Pi=newPi
return self.Pi
#将字典转化为矩阵
def matrix(X,index1,index2):
#初始化为0矩阵
m = numpy.zeros((len(index1),len(index2)))
for row in X:
for col in X[row]:
#转化
m[index1.index(row)][index2.index(col)]=X[row][col]
return m
if __name__ == "__main__":
#初始化,随机的给参数A,B,Pi赋值
status=["相处","拜拜"]
observations=["撒娇","低头玩儿手机","眼神很友好","主动留下联系方式"] #撒娇:小拳拳捶你胸口
A={"相处":{"相处":0.5,"拜拜":0.5},"拜拜":{"相处":0.5,"拜拜":0.5}}
B={"相处":{"撒娇":0.4,"低头玩儿手机":0.1,"眼神很友好":0.3,"主动留下联系方式":0.2},"拜拜":{"撒娇":0.1,"低头玩儿手机":0.5,"眼神很友好":0.2,"主动留下联系方式":0.2}}
Pi=[0.5,0.5]
O=[1,2,0,2,3,0]
A=matrix(A,status,status)
B=matrix(B,status,observations)
hmm=HMM(A,B,Pi)
print(hmm.baum_welch(O))
[1] 李航.统计学习方法[M].北京:清华大学出版社,2012:155-184
[2] 宗成庆.统计自然语言处理[M].北京:清华大学出版社,2008:116-119
[3] 隐马尔可夫模型[Online]. Available:http://www.hankcs.com/ml/hidden-markov-model.html
学习越深入,越觉得学无止境。
昨日读到毛泽东的一首四言诗,有感于此,摘录如下:
四言诗《奋斗自勉》
与天奋斗,其乐无穷!
与地奋斗,其乐无穷!
与人奋斗,其乐无穷!