高斯混合基本原理
前面我们讲到了诸多的 分类算法(Classification Algorithm),虽然它们的数学原理差别很大,但它们其实都属于 有监督学习(Supervised Learning) 这一个类别当中。也就是说:它们在训练过程中需要数据拥有自己对应的标签。
而我们今天要讲的 高斯混合模型(Gaussian Mixture Model,简称 GMM),它是一个 聚类算法(Clustering Algorithm),而聚类算法不同于分类算法,它们属于 无监督学习(Unsupervised Learning) 这一类别。也就是说:高斯混合模型不要求数据有自己的标签,高斯混合模型会自动地将数据分为不同的类别。
我们来思考一个简单的问题:如果给你一些数据,你想用什么模型去拟合这些数据的分布呢?可能很多人的第一想法就是用正态分布去拟合,毕竟正态分布是生活中最为常见的分布。其实这个想法是正确的,但仍有缺陷:虽然正态分布是最常见的分布,但现实世界太过于复杂,我们不能确保一个正态分布就能拟合出所有的数据集。对此,我们可以试着想一想:如果用多个正态分布去拟合,效果会不会更好?没错,这正是高斯混合模型的出发点。
高斯加权混合
为了解决高斯模型的单峰性的问题,我们引入多个高斯模型的加权平均来拟合多峰数据:
由于我们只能观察到每个样本 的信息,而无法了解每个样本究竟属于哪个高斯分布,因此我们可以引入一个隐变量 ( 表示样本属于第 个高斯分布)来辅助我们的推导:
于是 可以写成:
最后可以得到:
值得注意的是:高斯混合模型并 不在意 每个数据点究竟属于哪个类别(只是推导过程关注于单个数据点)。它想要做的事情是让多个高斯模型去拟合整个数据集,从而去预测新数据属于哪哪个高斯分布。另外,高斯混合模型也 不能确定 究竟要用多少个高斯云(即高斯分布的图像)去拟合图像,因此要自己设置初始值 。

梯度下降局限
写出高斯混合模型的对数似然函数:
其中 。
对这个表达式直接通过求导,由于连加号的存在,会无法得到解析解。因此我们无法直接根据极大似然估计的原理对这个式子使用常见的梯度下降算法(这里哪怕用似然函数求导也无法得到解析解)。
EM 迭代算法
由于无法直接对含有隐变量的似然函数求导,所以梯度下降无法求解出 GMM 的极大似然估计。对此我们引入一个专门解决此类问题的算法:EM 算法。

证据下界
我们可以先假设 服从的分布为 ,于是有:
两边同时关于 同时计算期望:
由于 KL 散度始终大于 0 ,因此 ELBO(全称为 Evidence Lower Bound Optimization,中文译名为 证据下界 )是 的一个下界(至于什么是 KL 散度可以参考下面这个视频)。
流程介绍
EM 算法本质上是通过最大化 ELBO 来间接最大化对数似然函数。具体步骤分为 E-step 和 M-step。
- 寻找使得 KL 散度最小的 ,使得 ELBO 进一步逼近
- 寻找 的极大值点作为新参数
两者交替迭代,最终收敛到局部最优解。
E-step
E-step 的核心是求解对数似然函数的期望
将上面的式子稍微变形:
要使 ELBO 逼近 ,就要让 KL 散度最小,先通过当前参数 估计 ,得 ,于是有:
这里我们将 记为 :
M-step
M-step 的核心是最大化对数似然函数的期望
由于信息熵为常数项,因此最大化 等价于将对数似然 的期望最大化:
理论推导
以下推导部分参考自该视频
E-step
我们先用 琴声不等式(Jensen’s inequality) 放缩的方式求解出 ELBO:
根据琴声不等式的取等条件我们可知:
将 乘到等式的右侧可得:
因为 对变量 的积分为 1,因此我们将两边同时对 进行积分:
将上式重新代入琴声不等式的取等条件中:
于是我们就轻松地求解出 E-step 中的 了。
M-step
由于不等式已经取等,因此有:
由于后面的信息熵为常数,所以 的极大似然估计为:
收敛证明
EM 算法的流程并不复杂,但是还有一个很重要的问题需要我们思考:EM 算法收敛吗?如果 EM 算法无法正常收敛,那么这个算法的过程无论多么精美都没用。就让我们再证明一下 EM 算法的收敛性吧。
根据单调有界原理,如果数列单调递增且有界,那么该数列收敛。
首先我们来看看有界性
定义似然函数:
由于概率值有界,而有界函数的有限次线性组合仍然有界, 有界。
然后我们来看看单调性
不妨设函数 :
其中 可以是任意分布。
在 EM 算法执行之前,根据琴声不等式,对于任意的 ,有下列关系:
经过 E-step 的迭代后,因为 E-step 的目的就是让琴声不等式取等,所以有:
因为 M-step 的目标如下:
显然有下列关系:
回到最上面的关系,令
将上面所有步骤组成不等式链可得:
因此函数 具有单调性。
高斯混合代码实现
准备了这么多,终于可以来看一下 GMM 的代码了。
import numpy as npnp.random.seed(0)X1 = np.random.multivariate_normal([0,0], [[1,0],[0,1]], 100)X2 = np.random.multivariate_normal([5,5], [[1,0],[0,1]], 100)X = np.vstack([X1, X2])
# 计算多维高斯概率密度函数def gaussian_pdf(x, mean, cov): D = x.shape[0] cov_det = np.linalg.det(cov) cov_inv = np.linalg.inv(cov) norm_const = 1.0 / np.sqrt((2 * np.pi)**D * cov_det) diff = x - mean return norm_const * np.exp(-0.5 * diff.T @ cov_inv @ diff)
class GMM: def __init__(self, n_components, tol=1e-6, max_iter=100): self.K = n_components self.tol = tol self.max_iter = max_iter
def fit(self, X): # 初始化参数 N, _ = X.shape self.p = np.ones(self.K) / self.K self.mu = X[np.random.choice(N, self.K, replace=False)] self.Sigma = np.array([np.cov(X, rowvar=False)] * self.K)
log_likelihood_old = 0 for _ in range(self.max_iter): # E-step gamma = np.zeros((N, self.K)) for i in range(N): for k in range(self.K): gamma[i, k] = self.p[k] * gaussian_pdf(X[i], self.mu[k], self.Sigma[k]) gamma[i, :] /= np.sum(gamma[i, :])
# M-step N_k = np.sum(gamma, axis=0) self.p = N_k / N self.mu = (gamma.T @ X) / N_k[:, np.newaxis] for k in range(self.K): diff = X - self.mu[k] self.Sigma[k] = (gamma[:, k][:, np.newaxis] * diff).T @ diff / N_k[k]
# 计算对数似然函数判断是否收敛 log_likelihood = 0 for i in range(N): temp = 0 for k in range(self.K): temp += self.p[k] * gaussian_pdf(X[i], self.mu[k], self.Sigma[k]) log_likelihood += np.log(temp)
if np.abs(log_likelihood - log_likelihood_old) < self.tol: break log_likelihood_old = log_likelihood
return self
# 软预测函数 def predict_proba(self, X): N = X.shape[0] gamma = np.zeros((N, self.K)) for i in range(N): for k in range(self.K): gamma[i, k] = self.p[k] * gaussian_pdf(X[i], self.mu[k], self.Sigma[k]) gamma[i, :] /= np.sum(gamma[i, :]) return gamma
# 硬预测函数 def predict(self, X): return np.argmax(self.predict_proba(X), axis=1)
# 执行代码if __name__ == "__main__": gmm = GMM(n_components=2) gmm.fit(X)
labels = gmm.predict(X) print("混合系数 p:", gmm.p) print("均值 mu:", gmm.mu) print("协方差 Sigma:", gmm.Sigma)E-step
下面给出 E-step 的代码:
gamma = np.zeros((N, self.K))for i in range(N): for k in range(self.K): gamma[i, k] = self.p[k] * gaussian_pdf(X[i], self.mu[k], self.Sigma[k]) gamma[i, :] /= np.sum(gamma[i, :])E-step 的目标是计算每个数据点属于每个分量的 后验概率 ,因此有:
这里 通常称为 responsibility ,表示第 个高斯对 的责任。
M-step
下面给出 M-step 的代码:
N_k = np.sum(gamma, axis=0)self.p = N_k / Nself.mu = (gamma.T @ X) / N_k[:, np.newaxis]for k in range(self.K): diff = X - self.mu[k] self.Sigma[k] = (gamma[:, k][:, np.newaxis] * diff).T @ diff / N_k[k]M-step 的目的则是最大化 期望的完整数据对数似然:
将似然函数对每个参数分别求偏导可得 混合系数 、均值 和 协方差 的更新公式: