深入浅出EM算法

EM(Expectation Maximization)算法主要用来估计含隐变量的模型参数。

我们先讲解EM算法的一般推导,然后列出EM算法的两个特例,k-Means和高斯混合模型。
参考资料:
http://www.cnblogs.com/jerrylead/archive/2011/04/06/2006936.html
这个链接讲的很详细,我只是复述总结一下,再解决一些遗留问题。
在讲EM之前,我们先谈谈Jensen不等式这个东西

Jensen不等式

回顾凸函数的定义,假设
( 1 a ) f ( x 1 ) + a f ( x 2 ) f ( ( 1 a ) x 1 + a x 2 )
对任意的 a [ 0 , 1 ] 和任意的在定义域内的 x 1 , x 2 都成立,那么 f ( x ) 是一个凸函数。
Jensen不等式实际上是凸函数定义的形式上的推广
如果 f ( x ) 是一个凸函数,那么有
Eq. 1
E [ f ( x ) ] f ( E [ x ] )

这个实际上也是由凸函数的定义来的,一张图表明Eq. 1的物理意义:

定理1:如果 f ( x ) 是严格凸的,那么 E [ f ( x ) ] = f ( E [ x ] ) 的充要条件是 x = E [ x ] ,也就是说 x 是一个常量。

ok,说完了Jensen不等式,我们开始讲解EM算法。

EM算法原理

含隐变量模型的对数似然:
l ( θ )
= i = 1 n l o g   P ( x ; θ ) —– (1)
= i = 1 n l o g   j = 1 k P ( x , z j ; θ ) —– (2)
= i = 1 n l o g   j = 1 k P ( z j ) P ( x , z j ; θ ) P ( z j ) —– (3)
i = 1 n j = 1 k P ( z j ) l o g   P ( x , z j ; θ ) P ( z j ) —–(4)
这里 z j 是隐变量,比如在聚类方法中, z j 可能是类别属性,也可能是其他隐变量。其中,
第(1)步到第(2)步:联合概率之和等于边缘概率,概率学基础知识。
第(2)步到第(3)步:分子分母乘一个相等的数,小学知识,其中 P ( z j ) 表示 z j 的概率质量函数,而且 i = 1 k P ( z i ) = 1
第(3)步到第(4)步:Jensen不等式。在这里:
j = 1 k P ( z j ) P ( x , z j ; θ ) P ( z j ) = E [ P ( x , z j ; θ ) P ( z j ) ] , 概率统计知识,
f ( ) = l o g ( )
由于对数函数是一个凹函数,因此由 f ( E [ x ] ) E [ f ( x ) ] 可得(注意到,这里符号反的)
f ( E [ P ( x , z j ; θ ) P ( z j ) ] ) = l o g   j = 1 k P ( z j ) P ( x , z j ; θ ) P ( z j )
E [ f ( P ( x , z j ; θ ) P ( z j ) ) ] = j = 1 k P ( z j ) l o g   P ( x , z j ; θ ) P ( z j )

我们看到,实际上 E [ f ( x ) ] l ( θ ) 的下界,因为我们要极大化 l ( θ ) 。因此我们需要使得 E [ f ( x ) ] 极大,从而逼近 l ( θ ) 。根据定理1,由于 l o g ( ) 是严格凹的,因此 l ( θ ) i = 1 n j = 1 k P ( z j ) l o g   P ( x , z j ; θ ) P ( z j ) 中的等式成立当且仅当 P ( x , z ; θ ) P ( z ) 是一个常量,不妨令

P ( x , z j ; θ ) P ( z j ) = c , j = 1 , , k

那么,由于 P ( z j ) = 1 ,因此 j = 1 k P ( x , z j ; θ ) = c ,所以
P ( z j ) = P ( x , z j ; θ ) c = P ( x , z j ; θ ) j = 1 k P ( x , z j ; θ ) = P ( x , z j ; θ ) P ( x ; θ ) = P ( z j | x ; θ )

这样,我们就通过极大化下界使得其逼近 l ( θ ) ,这也就是EM算法中的E步,其实本质上就是计算 z j 的后验概率。

在EM算法中的M步里面,因为已经给定了 P ( z j ) ,所以M步直接极大化 θ 的对数似然:
Eq. 2
θ := a r g m a x θ i = 1 n j = 1 k P ( z j ) l o g   P ( x , z j ; θ ) P ( z j ) .

所以,我们可以给出EM算法的通用形式:

Randomly initialize theta
while (unconvergence) do
    for j from 1 to k do
        P(z_j) := P(z_j | x; theta)
    update theta according to Eq. 2

我们可以定义
J ( θ , P ( z ) ) = i = 1 n j = 1 k P ( z j ) l o g   P ( x i , z j ; θ ) P ( z j )
EM算法可以看成是对函数 J 的坐标上升,即先固定 θ ,求 J 关于 P ( z ) 的极大;然后固定 P ( z ) ,求 J 关于 θ 的极大。

k-Means

作为EM算法的特例,k-Means是必须要讲的。

k-Means算法的流程如下:

  1. Initialize k centroids μ 1 , , μ k R n randomly.
  2. Repeat follow steps until convergence
    3. for each x i do c i := a r g m i n 1 j k | | x i μ j | | 2
    4. for each μ j do μ j := i = 1 n 1 { c i = j } x i i = 1 n 1 { c i = j }

这里 J ( μ , c ) = i = 1 n | | x i μ c i | | 2 ,所以我们可以认为k-Means是对函数 J 的坐标上升。这里 J 不是凸函数,因此可能得到局部最优解。

高斯混合模型—GMM

高斯混合模型的基本假设是观测数据由多个不同参数的高斯分布所生成。因此假设观测数据为 ( x 1 , , x n ) , x i R m ,那么
P ( x ; θ )
= i = 1 k α i N ( x | μ i , Σ i )
= i = 1 k α i [ 1 | Σ i | 1 2 ( 2 π ) m 2 e 1 2 ( x μ i ) T Σ i 1 ( x μ i ) ]

其中, α i = 1 。在这里,我们自然而然的想到,利用MLE估计参数
θ = ( α 1 , μ 1 , Σ 1 ; ; α k , μ k , Σ k )
那么对数似然并使其极大化:
m a x   l ( θ ) = m a x   i = 1 n l o g P ( x i ; θ )
s . t . i = 1 k α i = 1 , α i 0 , i = 1 , , k
这篇文章https://blog.csdn.net/xmu_jupiter/article/details/50889023提到说上式不好求解,因为求偏导麻烦,这很麻烦吗?根据链式法则不是很好求吗?我们来求解一下:
α i l = i = 1 n 1 | Σ i | 1 2 ( 2 π ) m 2 e 1 2 ( x i μ i ) T Σ i 1 ( x i μ i ) P ( x i ; θ )

μ i l = i = 1 n α i | Σ i | 1 2 ( 2 π ) m 2 e 1 2 ( x μ i ) T Σ i 1 ( x i μ i ) Σ i 1 ( x i μ i ) P ( x i ; θ )
为简单起见,假设变量间相互独立,那么 Σ i = d i a g ( σ i 1 2 , σ i 2 2 , , σ i m 2 ) 是一个对角阵,
显然有 | Σ i | 1 2 = σ i 1 σ i 2 σ i m
g ( Σ i ) = α i | Σ i | 1 2 ( 2 π ) m 2 = α i ( 2 π ) m 2 l = 1 m σ i l 1 , h ( Σ i ) = e 1 2 ( x μ i ) T Σ i 1 ( x μ i ) .
涉及到矩阵行列式及逆矩阵对变量求导,推导可得:
g σ i j = α i ( 2 π ) m 2 ( l = 1 m σ i l ) 1 σ i j 1
h σ i j = e 1 2 ( x μ i ) T Σ i 1 ( x μ i ) ( x j μ i j ) 2 σ i j 3
σ i j l = i = 1 n g σ i j h + h σ i j g P ( x i ; θ )

利用坐标上升的方法可得到参数的迭代更新公式:

Randomly initialize theta
while (unconvergence) do
for i from 1 to k do
α i ( j + 1 ) := α i ( j ) + λ α i l
for l from 1 to m do
μ i l ( j + 1 ) := μ i ( j ) + λ μ i l l
σ i l ( j + 1 ) := σ i ( j ) + λ σ i l l
endforl
endfori
// Normalize α to satisfy constraint.
normalize α i ( j + 1 ) := α i ( j + 1 ) i = 1 k α i ( j + 1 ) for each i from 1 to k
endwhile
从理论上来说,我觉得这样求解没问题,那么从实际实现来看又会如何呢?

用Python实现了Gaussian混合模型的坐标上升方法的代码:

#!/usr/local/bin/python3
# -*- coding: utf-8 -*-

from sklearn.datasets.samples_generator import make_blobs
import numpy as np
from operator import mul
import math
from functools import reduce
from pandas import DataFrame
from matplotlib import pyplot as plt


def norm_distribution_value(x, mu, sigma):
    k, d = np.shape(mu)
    t_normalizations = np.zeros(k)
    for i in range(k):
        t_normalizations[i] = math.exp(-0.5 * sum(
            (x[j] - mu[i, j])**2 / sigma[i, j]**2 for j in range(d)))
        t_normalizations[i] /= (reduce(mul, sigma[i]) * (2 * math.pi)**(d / 2))
    return t_normalizations


def gradient_alpha(X, alpha_old, mu, sigma):
    k = len(alpha_old)  # How many gaussian distributions
    n, d = np.shape(X)  # The data shape
    grad_alpha = np.zeros(k)
    for x in X:
        t_value = norm_distribution_value(x, mu, sigma)
        p = sum(alpha_old * t_value)
        #print("ok", p)
        if np.all(t_value / p == t_value / p):
            grad_alpha += t_value / p
    return grad_alpha


def gradient_mu(X, alpha, mu_old, sigma):
    k = len(alpha)  # How many gaussian distributions
    n, d = np.shape(X)  # The data shape
    grad_mu = np.zeros(np.shape(mu_old))
    for x in X:
        t_value = norm_distribution_value(x, mu_old, sigma)
        for i in range(k):
            t_re = alpha[i] * t_value[i] * \
                (x - mu_old[i]) / (sigma[i]**2) / sum(t_value * alpha)
            if np.all(t_re == t_re):
                grad_mu[i] +=  t_re
    return grad_mu

def gradient_sigma(X, alpha, mu, sigma_old):
    k = len(alpha)  # How many gaussian distributions
    n, d = np.shape(X)  # The data shape
    grad_sigma = np.zeros(np.shape(sigma_old))
    for x in X:
        t_value = norm_distribution_value(x, mu, sigma_old)
        for i in range(k):
            g = alpha[i] / (2 * math.pi)**(d / 2) / (reduce(mul, sigma_old[i]))
            g_grad = -g / sigma_old[i]
            h = math.exp(-0.5 * sum(
                (x[j] - mu[i, j])**2 / sigma_old[i, j]**2 for j in range(d)))
            h_grad = h * (x - mu[i])**2 / sigma_old[i]**3
            t_re = (h * g_grad + g * h_grad) / sum(alpha * t_value)
            if np.all(t_re == t_re):
                grad_sigma[i] += t_re
    return grad_sigma

def gaussian_mixture_model_gradient_ascent(X, k=3, learn_rate=0.1):
    # Initialize
    n, d = np.shape(X)
    alpha = np.random.random_sample(k)
    mu = np.random.random_sample((k, d))
    sigma = np.random.random_sample((k, d))
    alpha = alpha / sum(alpha)  # Normalization
    ite_num = 0
    while ite_num < 100:
        alpha += learn_rate * gradient_alpha(X, alpha, mu, sigma)
        alpha = alpha / sum(alpha)
        print(alpha)
        mu += learn_rate * gradient_mu(X, alpha, mu, sigma)
        print(mu)
        sigma += learn_rate * gradient_sigma(X, alpha, mu, sigma)
        print(sigma)
        ite_num += 1
        print('iteration: ', ite_num)
    return alpha, mu, sigma

def gaussian_cluster(x, alpha, mu, sigma):
    k = len(alpha)
    d = len(x)
    p = np.zeros(k)
    for i in range(k):
        p[i] = alpha[i] / reduce(mul, sigma[i]) * math.exp(-0.5 * sum(
            (x[j] - mu[i, j])**2 / sigma[i, j]**2 for j in range(d)))
    return np.argmax(p)


X, y = make_blobs(n_samples=100, centers=2, n_features=2)
alpha, mu, sigma = gaussian_mixture_model_gradient_ascent(X,k=2)

p_y = np.zeros(len(X))
for i in range(len(X)):
    p_y[i] = gaussian_cluster(X[i], alpha, mu, sigma)

df = DataFrame(dict(x1=X[:, 0], x2=X[:, 1], label=p_y))
df.to_csv('gaussianTestData.csv')
colors = {0: 'red', 1: 'blue'}
fig, ax = plt.subplots()

grouped = df.groupby('label')
for key, group in grouped:
    group.plot(ax=ax, kind='scatter', x='x1',
               y='x2', label=key, color=colors[key])
plt.show()

发现只有在极少部分情况下,才能收敛到一个全局最优解。这是因为优化目标并不是一个凸函数,参数个数也比较多。因此很大程度上坐标上升无法保证得到一个较为满意的结果。

猜你喜欢

转载自blog.csdn.net/wuyanxue/article/details/80465774