EM算法的简介
EM算法由两步组成:E步和M步,是最常用的迭代算法。
本文主要参考了李航博士的《统计学习方法》
在此基础上主要依据EM算法原理补充了三硬币模型的推导。
1.EM算法的原理
1.1从一个例子开始
三硬币模型
假设有3枚硬币,分别记作A,B和C。 这些硬币正面向上的概率分别是 和 。进行如下抛硬币试验:
1、先抛硬币A, 根据其结果选出硬币B或者硬币C,正面选硬币B,反面选硬币C;
2、然后掷选出的硬币,抛硬币的结果,出现正面记作1,出现反面记作0;
3、独立重复次试验(这里,n=10),观测结果如下:
假设只能观测到掷硬币的结果,不能观测掷硬币的过程。问如何估计三硬币正面出现的概率,即三硬币模型参数。
对于单次观测结果,三硬币模型可以写作:
其中,是第个观测结果1或0;随机变量是隐变量,表示未观测到的掷硬币A的结果;是模型参数。
方便起见,观测数据可以表示为,隐变量数据可以表示为。观测数据的似然函数可以表示为:
即:
这个问题没有办法直接解析,只能用迭代的方法解决。下面我们先看看EM算法的推导,之后重新再对这个问题进行推导。
1.2 EM算法推导
1.2.1 Jensen 不等式说明
首先说明一下什么是凸函数:
粗糙一点理解,如果函数的二阶导数为正数,那么这个函数就是凸函数:比如开口向上的二次函数就是典型的凸函数。
若有凸函数,且在函数中取自变量点集,且取对应,满足,
则有:
当时,的二阶导数,故可对运用Jessen不等式。
如果是对于运用Jessen不等式,不等式方向要变号。
1.2.2 EM算法推导
求解型入(1)式的问题,我们取对数似然函数,也就是对对数似然函数求取极大值:
运用迭代的思想解决这个问题,假设在第次迭代后的估计值是。因为想要求取最大对数似然,所以我们希望,并逐步达到极大值,也就是它们的差值达到最大值:
利用Jensen不等式,得到其下界:
令,则求取的是:
因为后一项中无项,故:
因为:
设:
则:
EM算法的总结:
E步(求隐变量):给定观测数据和当前的参数估计,求取隐变量的条件概率分布;
M步:将隐变量当做已知量,求的极大化的
E步和M步重复执行,直到收敛。
1.3 三硬币模型继续推导
1.3.1 三硬币模型的隐变量以及完全数据的对数似然函数
我们已知:
设来自掷硬币B的概率为, 则来自C的概率为,且。即参数为模型的隐变量。
于是完全数据的似然函数可以表示为:
相应的对数似然函数为:
1.3.2 E步:确定函数
因为EM算法是迭代算法,设第次迭代的参数估计值为,又因为隐变量代表观测数据来自B的概率,所以第次隐变量:
求取:
将带入则可以得到:
1.3.2 M步
得到了函数,接下来就是极大化参数:
1.求解:
求取极值,令等式右边为0:
左右两边同时乘得到:
则:
2.接下来求解:
求取极值,令等式右边为0:
左右两边同时乘得到:
则:
3.最后用同样的方法得到:
1.3.2 参数空间
1.模型参数
: 硬币A正面的概率,在此模型中是一个float类型的数值
: 硬币B正面的概率,在此模型中是一个float类型的数值
:硬币C正面的概率,在此模型中是一个float类型的数值
2.隐变量
: 最后观测值到底来源于B还是C,是一个一维向量
,其中代表第次抛硬币B的概率。
1.4 EM算法的收敛性
证明EM算法的收敛,只需要证明是单调递增的即可:
证明:
由于:
取对数化简得:
前两项有,对后两项进行计算:
也即后面两项小于等于0,所以
得证。
2 三银币模型的Python实现
2.1 模型实现
import numpy as np
np.random.seed(0)
class ThreeCoinsMode(object):
def __init__(self, n_epoch=5):
"""
运用EM算法求解三银币模型
:param n_epoch: 迭代次数
"""
self.n_epoch = n_epoch
self.params = {'pi': None, 'p': None, 'q': None, 'mu': None}
def __init_params(self, n):
"""
对参数初始化操作
:param n: 观测样本个数
:return:
"""
self.params = {'pi': np.random.rand(1),
'p': np.random.rand(1),
'q': np.random.rand(1),
'mu': np.random.rand(n)}
# self.params = {'pi': [0.5],
# 'p': [0.5],
# 'q': [0.5],
# 'mu': np.random.rand(n)}
def E_step(self, y, n):
"""
E步:跟新隐变量mu
:param y: 观测样本
:param n: 观测样本个数
:return:
"""
pi = self.params['pi'][0]
p = self.params['p'][0]
q = self.params['q'][0]
for i in range(n):
self.params['mu'][i] = (pi * pow(p, y[i]) * pow(1-p, 1-y[i])) / (pi * pow(p, y[i]) * pow(1-p, 1-y[i]) + (1-pi) * pow(q, y[i]) * pow(1-q, 1-y[i]))
def M_step(self, y, n):
"""
M步:跟新模型参数
:param y: 观测样本
:param n: 观测样本个数
:return:
"""
mu = self.params['mu']
self.params['pi'][0] = sum(mu) / n
self.params['p'][0] = sum([mu[i] * y[i] for i in range(n)]) / sum(mu)
self.params['q'][0] = sum([(1-mu[i]) * y[i] for i in range(n)]) / sum([1-mu_i for mu_i in mu])
def fit(self, y):
"""
模型入口
:param y: 观测样本
:return:
"""
n = len(y)
self.__init_params(n)
print(0, self.params['pi'], self.params['p'], self.params['q'])
for i in range(self.n_epoch):
self.E_step(y, n)
self.M_step(y, n)
print(i+1, self.params['pi'], self.params['p'], self.params['q'])
2.2 模型测试结果
def run_three_coins_model():
y = [1, 1, 0, 1, 0, 0, 1, 0, 1, 1]
tcm = ThreeCoinsMode()
tcm.fit(y)
运行结果:
0 [0.5488135] [0.71518937] [0.60276338]
1 [0.54076424] [0.65541668] [0.53474516]
2 [0.54076424] [0.65541668] [0.53474516]
3 [0.54076424] [0.65541668] [0.53474516]
4 [0.54076424] [0.65541668] [0.53474516]
5 [0.54076424] [0.65541668] [0.53474516]
参考文献:
《统计学习方法》 李航著