在统计学中,对于高斯分布的数据,可以通过计算马氏距离来衡量一个观测值与分布模式之间的距离。马氏距离的计算公式为:
d_{(\mu,\Sigma)}(x_i)^2 = (x_i - \mu)^T\Sigma^{-1}(x_i - \mu)
其中,\(\mu\) 和 \(\Sigma\) 分别代表数据的均值和协方差矩阵。在实际应用中,通常使用估计值来代替真实的 \(\mu\) 和 \(\Sigma\)。然而,标准的协方差最大似然估计(MLE)对数据集中的异常值非常敏感,这同样会影响基于MLE的马氏距离的准确性。因此,使用稳健的协方差估计方法,如最小协方差行列式(MCD)估计器,可以提高对数据集中错误观测的抵抗力,并更准确地反映观测的真实结构。
最小协方差行列式(MCD)估计器是一种稳健的协方差估计方法,它能够在高度污染的数据集中估计协方差矩阵,即使数据集中存在多达 \(\frac{n_{\text{samples}}-n_{\text{features}}-1}{2}\) 个异常值。MCD的核心思想是找到 \(\frac{n_{\text{samples}}+n_{\text{features}}+1}{2}\) 个观测值,使得它们的样本协方差行列式最小,从而得到一个“纯净”的观测子集,用于计算位置和协方差的标准化估计。MCD由P.J.Rousseuw首次提出。
本例展示了马氏距离如何受到异常数据的影响。当使用基于标准协方差MLE的马氏距离时,从污染分布中抽取的观测值与来自真实高斯分布的观测值无法区分。而使用基于MCD的马氏距离时,这两个群体变得可以区分。相关应用包括异常值检测、观测值排序和聚类。
首先,生成了一个包含125个样本和2个特征的数据集。两个特征都是以0为均值的高斯分布,其中特征1的标准差为2,特征2的标准差为1。接下来,用标准差分别为1和7的高斯异常样本替换了25个样本。
import numpy as np
# 为了一致的结果
np.random.seed(7)
n_samples = 125
n_outliers = 25
n_features = 2
# 生成形状为(125, 2)的高斯数据
gen_cov = np.eye(n_features)
gen_cov[0, 0] = 2.0
X = np.dot(np.random.randn(n_samples, n_features), gen_cov)
# 添加一些异常值
outliers_cov = np.eye(n_features)
outliers_cov[np.arange(1, n_features), np.arange(1, n_features)] = 7.0
X[-n_outliers:] = np.dot(np.random.randn(n_outliers, n_features), outliers_cov)
接下来,对数据拟合了基于MCD和MLE的协方差估计器,并打印了估计的协方差矩阵。注意到,基于MLE的估计器估计的特征2的方差(7.5)比基于MCD的稳健估计器(1.2)高得多。这表明,基于MCD的稳健估计器对异常样本的抵抗力更强,这些异常样本被设计为在特征2上具有更大的方差。
import matplotlib.pyplot as plt
from sklearn.covariance import EmpiricalCovariance, MinCovDet
# 拟合数据的MCD稳健估计器
robust_cov = MinCovDet().fit(X)
# 拟合数据的MLE估计器
emp_cov = EmpiricalCovariance().fit(X)
print("估计的协方差矩阵:\nMCD (稳健):\n{}\nMLE:\n{}".format(robust_cov.covariance_, emp_cov.covariance_))
为了更直观地展示差异,绘制了两种方法计算的马氏距离的等高线图。注意,基于稳健MCD的马氏距离更贴合内部黑点,而基于MLE的距离更受异常红点的影响。
最后,强调了基于MCD的马氏距离在区分异常值方面的能力。取马氏距离的立方根,得到近似正态分布(如Wilson和Hilferty所建议),然后使用箱线图绘制内部样本和异常样本的值。对于基于稳健MCD的马氏距离,异常样本的分布与内部样本的分布更加分离。
# 计算样本的MLE马氏距离的立方根
emp_mahal = emp_cov.mahalanobis(X - np.mean(X, 0))**(1/3)
# 绘制箱线图
plt.boxplot([emp_mahal[:-n_outliers], emp_mahal[-n_outliers:]], widths=0.25)
# 绘制个别样本
plt.plot(np.full(n_samples-n_outliers, 1.26), emp_mahal[:-n_outliers], "+k", markeredgewidth=1)
plt.plot(np.full(n_outliers, 2.26), emp_mahal[-n_outliers:], "+k", markeredgewidth=1)
plt.show()