高斯混合模型
高斯混合模型
高斯混合模型是多个高斯概率密度函数(二维即正态分布曲线)的线性组合。而混合高斯的曲线是由若干个单高斯函数叠加而成的,即任何一个曲线,无论多么复杂,我们都可以用若干个高斯曲线来无限逼近它,这就是高斯混合模型的基本思想。
在最简单的情况下,GMM 可用于以与 K-Means 相同的方式聚类,我们还是使用前几个实验中也用到的简单可聚类的数据集来查看 GMM 效果:
from sklearn.datasets.samples_generator import make_blobs
from sklearn.mixture import GaussianMixture as GMM
import matplotlib.pyplot as plt
%matplotlib inline
# 导入高斯混合估计器
X, y_true = make_blobs(n_samples=400, centers=4,
cluster_std=0.60, random_state=0) # 数据生成
# n_components 表示聚类数
gmm = GMM(n_components=4).fit(X)
labels = gmm.predict(X) # 得到预测标签
plt.scatter(X[:, 0], X[:, 1], c=labels, s=40, cmap='viridis') # 绘制聚类彩色图
GMM作为密度估计
尽管 GMM 通常被归类为聚类算法,但从根本上说它是一种密度估计算法。也就是说,GMM 在某些数据上的结果更适合作为描述数据分布的生成概率模型。
例如,假设我们在特定分布中有一些一维数据:
import numpy as np
plt.style.use('seaborn') # 样式美化
np.random.seed(2) # 随机数种子为 2
x = np.concatenate([np.random.normal(0, 2, 2000),
np.random.normal(5, 5, 2000),
np.random.normal(3, 0.5, 600)]) # 数组拼接
# 绘制直方图
plt.hist(x, 80, density=True)
plt.xlim(-10, 20)
我们用高斯混合模型得到上图的近似密度:
# 导入高斯混合估计器
from sklearn.mixture import GaussianMixture as GMM
X = x[:, np.newaxis] # 增加一个维度成二维
clf = GMM(4, max_iter=500, random_state=3).fit(X)
# 组件(高斯分布)个数为 4,最大迭代次数 100
xpdf = np.linspace(-10, 20, 1000) # 生成曲线 x 数据
density = np.array([np.exp(clf.score([[xp]])) for xp in xpdf]) # 得到模型拟合密度
plt.hist(x, 80, density=True, alpha=0.5)
plt.plot(xpdf, density, '-r')
plt.xlim(-10, 20)
上图中的红线即是我们通过 4 个高斯分布混合模拟出的密度。我们将那 4 个高斯分布绘制出来,可以直观上看看是怎么混合的:
from scipy import stats
plt.hist(x, 80, density=True, alpha=0.3) # 原数据直方图
plt.plot(xpdf, density, '-r') # 混合高斯模型估计密度
# 绘制 4 个高斯函数
for i in range(clf.n_components):
pdf = clf.weights_[i] * stats.norm(clf.means_[i, 0],
np.sqrt(clf.covariances_[i, 0])).pdf(xpdf)
plt.fill(xpdf, pdf, facecolor='gray',
edgecolor='none', alpha=0.3) # 填充函数
plt.xlim(-10, 20)
与 K-Means 一样,高斯混合模型使用期望最大化方法拟合这些单独的高斯分布,并使用后验概率来计算加权均值和协方差。另外,该算法可证明收敛于最优值(尽管最优值不一定是全局的)。
GMM 组件数量
在上面的模型中,我们设定的组件值(高斯分布个数)为 4,是通过原数据直方图大概判断的,你可以试试将组件个数参数改得更大一点,试试拟合效果。理论上该参数越大,拟合出的曲线越接近直方图趋势变化,但此参数并不是越大越好,容易过拟合。
给定一个模型,我们可以使用 赤池信息准则(Akaike Information Criterion,AIC)或者 贝叶斯信息准则(Bayesian Information Criterion,BIC)来评估其对数据的拟合程度,scikit-learn 的 GMM 估计器包含计算这两者的内置方法:
print(clf.bic(X)) # 计算 BIC 值
print(clf.aic(X)) # 计算 AIC 值
让我们看看在上面的数据集中,使用 AIC 和 BIC 确定 GMM 组件数量:
# 组件数从 1-9 的模型
n_estimators = np.arange(1, 10)
clfs = [GMM(n, max_iter=1000).fit(X) for n in n_estimators]
bics = [clf.bic(X) for clf in clfs]
aics = [clf.aic(X) for clf in clfs]
plt.plot(n_estimators, bics, label='BIC')
plt.plot(n_estimators, aics, label='AIC')
plt.legend()
从图中看到,理论上 AIC 和 BIC 值越小越好,但是如果值变化不大的情况下,模型复杂度越小的模型越优,所以 GMM 组件数确定在 5-7 是最合适的。
GMM 用于异常值检测
GMM 是所谓的生成模型:它是一个概率模型,可以从中生成数据集。数据生成模型的一个应用是异常值检测:我们可以简单地评估生成模型下每个点的可能性,可能性较低的点(取决于您自己的偏差/方差偏好)可以标记为异常值。
让我们通过定义带有一些异常值的新数据集来了解一下:
np.random.seed(0)
print(len(x))
# 4600 个数中生成 20 个异常值位数并排序
true_outliers = np.sort(np.random.randint(0, len(x), 20))
y = x.copy() # 得到 x 的浅复制
y[true_outliers] += 50 * np.random.randn(20)
# 将20个原数据加上 0-50 的正态分布的值变成异常值
# 用加上了20个异常值的数据进行模型拟合
clf = GMM(4, max_iter=500, random_state=0).fit(y[:, np.newaxis])
xpdf = np.linspace(-10, 20, 1000)
density_noise = np.array([np.exp(clf.score([[xp]]))
for xp in xpdf]) # 得到带有噪声的密度估计值
plt.hist(y, 80, density=True, alpha=0.5) # 绘制直方图
plt.plot(xpdf, density_noise, '-r') # 红线绘制噪声密度估计
plt.xlim(-15, 30)
需要注意的是,生成的 true_outliers 是 20 个标为异常值的点在 4600 个数中的位置,是 [0-4600) 的 20 个整数。
现在让我们评估模型下每个点的对数似然,并将它们作为 y 的函数进行绘制:
# 得到对数似然值
log_likelihood = np.array([clf.score_samples([[yy]]) for yy in y])
plt.plot(y, log_likelihood, '.k') # 注意横坐标是 y 值
上图是我们绘制的带有噪声的 y 值和其对应的对数似然值,可以看到对数似然值 < -8 的点比较稀疏,这里我们将对数似然值 < -9 判断为异常值:
# 得到检测异常值位数
detected_outliers = np.where(log_likelihood < -9)[0]
print("true outliers:") # 查看真实异常值位数
print(true_outliers)
print("\ndetected outliers:") # 查看检测异常值位数
print(detected_outliers)
该算法错过了其中的一些点,这是可以预料的,因为异常值 y 值的生成加上了 0-50 的正态分布值,可能其噪声添加接近于 0。
以下是遗漏的异常值位数:
set(true_outliers) - set(detected_outliers)
以下是被虚假标记为异常值的非异常值位数:
set(detected_outliers) - set(true_outliers)
其他密度估计器
还有一些有用的密度估计器是核密度估计,可通过 sklearn.neighbors.KernelDensity 获得。 在某些方面,这可以看作是 GMM 的一般化,其中在每个训练点的位置都放置了一个高斯。
# 导入核密度估计器
from sklearn.neighbors import KernelDensity
# bandwidth = 0.15
kde = KernelDensity(0.15).fit(x[:, None])
density_kde = np.exp(kde.score_samples(xpdf[:, None])) # 得到密度估计值
plt.hist(x, 80, density=True, alpha=0.5) # 绘制直方图
plt.plot(xpdf, density, '-b', label='GMM') # 蓝线绘制 GMM 密度估计曲线
plt.plot(xpdf, density_kde, '-r', label='KDE') # 红线绘制核密度估计曲线
plt.xlim(-10, 20)
plt.legend()
所有这些密度估计模型都可以看作是数据的生成模型:也就是说,该模型告诉我们如何创建更多适合模型的数据。