假设有3枚硬币,分别记做A,B,C。这些硬币正面出现的概率分别是π,p和q。进行如下掷硬币实验:先掷硬币A,根据其结果选出硬币B或C,正面选B,反面选硬币C;然后投掷选重中的硬币,出现正面记作1,反面记作0;独立地重复n次(n=10),结果为1111110000
我们只能观察投掷硬币的结果,而不知其过程,估计这三个参数π,p和q。
可以看到投掷硬币时到底选择了B或者C是未知的。我们设隐藏变量Z 来指示来自于哪个硬币, Z = { z 1 , z 2 , … , z n } Z=\{z_1,z_2,…,z_n\} Z={z1,z2,…,zn},令 θ = { π , p , q } θ=\{π,p,q\} θ={π,p,q},观察数据 X = x 1 , x 2 , … , x n X={x_1,x_2,…,x_n} X=x1,x2,…,xn。
写出生成一个硬币时的概率:
P ( x ∣ θ ) = ∑ z P ( x , z ∣ θ ) = ∑ z P ( z ∣ π ) P ( x ∣ z , θ ) = π p x ( 1 − p ) 1 − x + ( 1 − π ) q x ( 1 − q ) 1 − x \begin{aligned}P(x|θ) &=∑zP(x,z|θ) = ∑zP(z|π)P(x|z,θ) \\ &= πp^x(1−p)^{1−x}+(1−π)q^x(1−q)^{1−x} \end{aligned} P(x∣θ)=∑zP(x,z∣θ)=∑zP(z∣π)P(x∣z,θ)=πpx(1−p)1−x+(1−π)qx(1−q)1−x
有了一个硬币的概率,我们就可以写出所有观察数据的log似然函数:
L ( θ ∣ X ) = l o g P ( X ∣ θ ) = ∑ j = 1 n l o g [ π p x j ( 1 − p ) 1 − x j + ( 1 − π ) q x j ( 1 − q ) 1 − x j ] \begin{aligned} L(θ|X)=logP(X|θ)=∑_{j=1}^nlog[πp^{x_j}(1−p)^{1−x_j} + (1−π)q^{x_j}(1−q)^{1−x_j}] \end{aligned} L(θ∣X)=logP(X∣θ)=j=1∑nlog[πpxj(1−p)1−xj+(1−π)qxj(1−q)1−xj]
然后求似然函数最大值
θ ∗ = a r g m a x L ( θ ∣ X ) θ^*= arg\quad maxL(θ|X) θ∗=argmaxL(θ∣X)
其中 L ( θ ∣ X ) = l o g P ( X ∣ θ ) = l o g ∑ Z P ( X , Z ∣ θ ) L(θ|X)=logP(X|θ)=log∑_ZP(X,Z|θ) L(θ∣X)=logP(X∣θ)=log∑ZP(X,Z∣θ)。因为 l o g log log里面带着加和所以这个极大似然是求不出解析解的。 如果我们知道隐藏变量Z的话,求以下的似然会容易很多:
L(θ|X,Z)=logP(X,Z|θ)
但是隐藏变量Z的值谁也不知道,所以我们转用EM算法求它的后验概率期望 E Z ∣ X , θ ( L ( θ ∣ X , Z ) ) E_{Z|X,θ}(L(θ|X,Z)) EZ∣X,θ(L(θ∣X,Z))的最大值。
def cal_u(pi1,p1,q1,xi):
return pi1*math.pow(p1,xi) * math.pow(1-p1,1-xi)/\
float(pi1* math.pow(p1,xi) * math.pow(1-p1,1-xi)+
(1-pi1) * math.pow(q1,xi) * math.pow(1-q1,1-xi))
def cal_u(pi, p, q, xi):
"""
u值计算
:param pi: 下一次迭代开始的 pi
:param p: 下一次迭代开始的 p
:param q: 下一次迭代开始的 q
:param xi: 观察数据第i个值,从0开始
:return:
"""
return pi * math.pow(p, xi) * math.pow(1 - p, 1 - xi) / \
float(pi * math.pow(p, xi) * math.pow(1 - p, 1 - xi) +
(1 - pi) * math.pow(q, xi) * math.pow(1 - q, 1 - xi))
def e_step(pi,p,q,x):
"""
e步计算
:param pi: 下一次迭代开始的 pi
:param p: 下一次迭代开始的 p
:param q: 下一次迭代开始的 q
:param x: 观察数据
:return:
"""
return [cal_u(pi,p,q,xi) for xi in x]
算法首先选取参数初始值 θ ( 0 ) = π ( 0 ) , p ( 0 ) , q ( 0 ) θ(0)=π(0),p(0),q(0) θ(0)=π(0),p(0),q(0),然后迭代到收敛为止
# -*- coding: utf-8 -*-
import math
def cal_u(pi, p, q, xi):
"""
u值计算
:param pi: 下一次迭代开始的 pi
:param p: 下一次迭代开始的 p
:param q: 下一次迭代开始的 q
:param xi: 观察数据第i个值,从0开始
:return:
"""
return pi * math.pow(p, xi) * math.pow(1 - p, 1 - xi) / \
float(pi * math.pow(p, xi) * math.pow(1 - p, 1 - xi) +
(1 - pi) * math.pow(q, xi) * math.pow(1 - q, 1 - xi))
def e_step(pi,p,q,x):
"""
e步计算
:param pi: 下一次迭代开始的 pi
:param p: 下一次迭代开始的 p
:param q: 下一次迭代开始的 q
:param x: 观察数据
:return:
"""
return [cal_u(pi,p,q,xi) for xi in x]
def m_step(u,x):
"""
m步计算
:param u: m步计算的u
:param x: 观察数据
:return:
"""
pi1=sum(u)/len(u)
p1=sum([u[i]*x[i] for i in range(len(u))]) / sum(u)
q1=sum([(1-u[i])*x[i] for i in range(len(u))]) / sum([1-u[i] for i in range(len(u))])
return [pi1,p1,q1]
def run(observed_x, start_pi, start_p, start_q, iter_num):
"""
:param observed_x: 观察数据
:param start_pi: 下一次迭代开始的pi $\pi$
:param start_p: 下一次迭代开始的p
:param start_q: 下一次迭代开始的q
:param iter_num: 迭代次数
:return:
"""
for i in range(iter_num):
u=e_step(start_pi, start_p, start_q, observed_x)
print (i,[start_pi,start_p,start_q])
if [start_pi,start_p,start_q]==m_step(u, observed_x):
break
else:
[start_pi,start_p,start_q]=m_step(u, observed_x)
if __name__ =="__main__":
# 观察数据
x = [1, 1, 0, 1, 0, 0, 1, 0, 1, 1]
# 初始化 pi,p q
[pi, p, q] = [0.4, 0.6, 0.7]
# 迭代计算
run(x,pi,p,q,100)
运行结果如下
0 [0.4, 0.6, 0.7]
1 [0.40641711229946526, 0.5368421052631579, 0.6432432432432431]
2 [0.40641711229946537, 0.5368421052631579, 0.6432432432432431]
3 [0.40641711229946537, 0.536842105263158, 0.6432432432432431]