我们知道,统计模型中的参数都需要根据观测数据集(训练数据)来进行估计。但是,在有些场景下,观测数据集中包含的信息不完整,有缺失,此时就不太容易去估计相应的参数。EM 算法就是针对这种问题的的方法。
桌子上放着一个盒子,其中有两种类型的硬币 A A A 和$ B$。随机从盒子中抓取硬币是 A A A 的概率为 τ \tau τ, 硬币 A A A 和 B B B 正面朝上的概率分别是 p 1 p_1 p1 和 p 2 p_2 p2。接下来,估计 p 1 p_1 p1、 p 2 p_2 p2、 τ \tau τ的值。
假设得到的完整的观测数据如下表,1 表示正面朝上,0 表示反面朝上。
数据 | 硬币 |
---|---|
1 | A |
0 | A |
1 | B |
1 | A |
0 | B |
估计参数:
数据 | 硬币 A 概率 | 硬币 B 概率 |
---|---|---|
1 | 0.6 | 0.4 |
0 | 0.4 | 0.6 |
1 | 0.6 | 0.4 |
1 | 0.6 | 0.4 |
0 | 0.4 | 0.6 |
估计参数
假设:每次不知道抛掷的是哪个硬币、也不知道可能硬币的概率,如下表所示:
数据 | 硬币 | 硬币 A 概率 | 硬币 B 概率 |
---|---|---|---|
1 | ? | ? | ? |
0 | ? | ? | ? |
1 | ? | ? | ? |
1 | ? | ? | ? |
0 | ? | ? | ? |
我们需要在包含缺失信息、隐藏变量的观测数据中去估计 p 1 p_1 p1、 p 2 p_2 p2、 τ \tau τ 这三个参数。这样的问题有个特点:
对于这样的问题,我们可以假设一组参数,并在此参数的基础上,根据观测数据去估计隐藏变量的分布信息,然后再根据新的隐藏变量信息反过来去更新参数。这个过程可以反复进行,但是什么时候停止这个循环的过程?
我们可以设定一个条件,观测数据的对数似然值最大。即:当 p 1 p_1 p1、 p 2 p_2 p2、 τ \tau τ 是什么值的时候,这组观测数据出现的可性能最大。例如:
数据 | 硬币 A | 硬币 B |
---|---|---|
1 | τ * p1 | (1 – τ ) * p2 |
0 | τ * (1 – p1) | (1 – τ ) * (1 – p2) |
1 | τ * p1 | (1 – τ ) * p2 |
1 | τ * p1 | (1 – τ ) * p2 |
0 | τ * (1 – p1) | (1 – τ ) * (1 – p2) |
每个样本的对数似然值计算公式:
ℓ ( x t ) = log ( τ ⋅ p 1 x t ( 1 − p 1 ) 1 − x t + ( 1 − τ ) ⋅ p 2 x t ( 1 − p 2 ) 1 − x t ) \ell(x_t) = \log\left(\tau \cdot p_1^{x_t}(1-p_1)^{1-x_t} + (1-\tau) \cdot p_2^{x_t}(1-p_2)^{1-x_t}\right) ℓ(xt)=log(τ⋅p1xt(1−p1)1−xt+(1−τ)⋅p2xt(1−p2)1−xt)
其中:
上面提到的解决思路就是 EM 算法的思想:
E 步:
数据 | 硬币 A 概率 | 硬币 B 概率 |
---|---|---|
1 | γ 0 A = τ p 1 τ p 1 + ( 1 − τ ) p 2 \gamma_{0}^{A}=\dfrac{\tau p_1}{\tau p_1+(1-\tau)p_2} γ0A=τp1+(1−τ)p2τp1 | γ 0 B = 1 − γ 0 A \gamma_{0}^{B}=1-\gamma_{0}^{A} γ0B=1−γ0A |
0 | γ 1 A = τ ( 1 − p 1 ) τ ( 1 − p 1 ) + ( 1 − τ ) ( 1 − p 2 ) \gamma_{1}^{A}=\dfrac{\tau (1-p_1)}{\tau(1 -p_1)+(1-\tau)(1-p_2)} γ1A=τ(1−p1)+(1−τ)(1−p2)τ(1−p1) | γ 1 B = 1 − γ 1 A \gamma_{1}^{B}=1-\gamma_{1}^{A} γ1B=1−γ1A |
1 | … | … |
1 | … | … |
0 | … | … |
M 步:
更新 τ \tau τ(硬币A选择概率)
τ = 1 N ∑ i = 1 N γ i A \tau=\dfrac{1}{N}\displaystyle \sum_{i=1}^{N}\gamma_{i}^{A} τ=N1i=1∑NγiA
更新 p 1 p_1 p1(硬币A正面朝上的概率)
p 1 = ∑ i = 1 N γ i A ⋅ x i ∑ i = 1 N γ i A \displaystyle p_1=\dfrac{\sum^{N}_{i=1}\gamma_{i}^{A}\cdot x_i}{\sum_{i=1}^{N}\gamma_{i}^{A}} p1=∑i=1NγiA∑i=1NγiA⋅xi
更新 p 2 p_2 p2(硬币B正面朝上的概率)
p 2 = ∑ i = 1 N γ i B ⋅ x i ∑ i = 1 N γ i B \displaystyle p2=\dfrac{\sum^{N}_{i=1}\gamma_{i}^{B}\cdot x_i}{\sum_{i=1}^{N}\gamma_{i}^{B}} p2=∑i=1NγiB∑i=1NγiB⋅xi
EM 重复执行 E 步和 M 步,直至收敛:
注意:EM 算法可能收敛到局部最优解,对初始值敏感,且收敛速度较慢。因此,实际应用时,需要进行一些改进来提升其性能。
接下来,我们来观察下上面提到问题的计算过程:
import numpy as np
def e_step(coins, pi, p1, p2):
resp = []
for coin in coins:
a = pi * p1 ** coin * (1 - p1) ** (1 - coin)
b = (1 - pi) * p2 ** coin * (1 - p2) ** (1 - coin)
ap = a / (a + b)
bp = b / (a + b)
resp.append((ap, bp))
return np.array(resp)
def m_step(coins, resp):
pi = sum(resp[:, 0]) / len(resp[:, 0])
p1 = np.dot(coins, resp[:, 0]) / sum(resp[:, 0])
p2 = np.dot(coins, resp[:, 1]) / sum(resp[:, 1])
return pi, p1, p2
def em(coins):
# 初始化参数
pi, p1, p2 = 0.5, 0.6, 0.4
# 迭代计算
for _ in range(3):
# 打印参数
print('pi: %.5f p1: %.5f p2: %.5f' % (pi, p1, p2))
resp = e_step(coins, pi, p1, p2)
# 输出概率
print(resp)
print('-' * 30)
pi, p1, p2 = m_step(coins, resp)
if __name__ == '__main__':
coins = [1, 0, 1, 1, 0]
em(coins)
输出如下
pi: 0.50000 p1: 0.60000 p2: 0.40000
[[0.6 0.4]
[0.4 0.6]
[0.6 0.4]
[0.6 0.4]
[0.4 0.6]]
------------------------------
pi: 0.52000 p1: 0.69231 p2: 0.50000
[[0.6 0.4]
[0.4 0.6]
[0.6 0.4]
[0.6 0.4]
[0.4 0.6]]
------------------------------
pi: 0.52000 p1: 0.69231 p2: 0.50000
[[0.6 0.4]
[0.4 0.6]
[0.6 0.4]
[0.6 0.4]
[0.4 0.6]]
------------------------------
我们发现,迭代一次之后参数就不再发生变化,这是因为我们的问题、观测数据都过于简单,在较为复杂的问题中,需要迭代更多的步骤才能收敛。