在数据分析中,经常会遇到数据集中存在异常值(outliers)的情况。这些异常值可能会对协方差矩阵的估计产生严重影响,导致估计结果不准确。为了解决这个问题,可以使用一种稳健的协方差估计方法——最小协方差行列式估计器(Minimum Covariance Determinant Estimator,简称MCD)。
MCD估计器的核心思想是找到一个子集,这个子集的样本数量为\(\frac{n_{\text{samples}} + n_{\text{features}} + 1}{2}\)
,使得这个子集的样本协方差矩阵的行列式最小。这样,就可以找到一个“纯净”的样本子集,从而计算出稳健的均值和协方差估计值。在计算过程中,还会进行一个校正步骤,以补偿只从初始数据的一部分进行估计的事实,最终得到整个数据集位置和协方差的稳健估计。
MCD估计器由P.J.Rousseuw提出,它是一种具有高断裂点的稳健估计器,即它可以用于估计高度污染的数据集的协方差矩阵,直到\(\frac{n_{\text{samples}} - n_{\text{features}} - 1}{2}\)
个异常值。这种估计器特别适合于样本数量大于特征数量5倍的情况,即n_{\text{samples}} > 5n_{\text{features}}
。
在实际应用中,可以通过比较不同类型位置和协方差估计在污染的高斯分布数据集上的估计误差来评估MCD估计器的性能。具体来说,可以比较以下几种估计方法:
在下面的代码示例中,将生成一个包含异常值的数据集,并使用MCD估计器来估计数据集的位置和协方差。还将比较MCD估计器与完整数据集和纯净数据集的估计结果。
import matplotlib.font_manager
import matplotlib.pyplot as plt
import numpy as np
from sklearn.covariance import EmpiricalCovariance, MinCovDet
# 示例设置
n_samples = 80
n_features = 5
repeat = 10
range_n_outliers = np.concatenate(
(np.linspace(0, n_samples / 8, 5),
np.linspace(n_samples / 8, n_samples / 2, 5)[1:-1]),
).astype(int)
# 定义用于存储结果的数组
err_loc_mcd = np.zeros((range_n_outliers.size, repeat))
err_cov_mcd = np.zeros((range_n_outliers.size, repeat))
err_loc_emp_full = np.zeros((range_n_outliers.size, repeat))
err_cov_emp_full = np.zeros((range_n_outliers.size, repeat))
err_loc_emp_pure = np.zeros((range_n_outliers.size, repeat))
err_cov_emp_pure = np.zeros((range_n_outliers.size, repeat))
# 计算
for i, n_outliers in enumerate(range_n_outliers):
for j in range(repeat):
rng = np.random.RandomState(i * j)
# 生成数据
X = rng.randn(n_samples, n_features)
# 添加一些异常值
outliers_index = rng.permutation(n_samples)[:n_outliers]
outliers_offset = 10.0 * (np.random.randint(2, size=(n_outliers, n_features)) - 0.5)
X[outliers_index] += outliers_offset
inliers_mask = np.ones(n_samples).astype(bool)
inliers_mask[outliers_index] = False
# 拟合最小协方差行列式(MCD)稳健估计器到数据
mcd = MinCovDet().fit(X)
# 比较原始稳健估计与真实位置和协方差
err_loc_mcd[i, j] = np.sum(mcd.location_ ** 2)
err_cov_mcd[i, j] = mcd.error_norm(np.eye(n_features))
# 比较从完整数据集学到的估计器与真实参数
err_loc_emp_full[i, j] = np.sum(X.mean(0) ** 2)
err_cov_emp_full[i, j] = (EmpiricalCovariance().fit(X).error_norm(np.eye(n_features)))
# 与从纯净数据集学到的经验协方差进行比较(即“完美”的mcd)
pure_X = X[inliers_mask]
pure_location = pure_X.mean(0)
pure_emp_cov = EmpiricalCovariance().fit(pure_X)
err_loc_emp_pure[i, j] = np.sum(pure_location ** 2)
err_cov_emp_pure[i, j] = pure_emp_cov.error_norm(np.eye(n_features))
# 显示结果
font_prop = matplotlib.font_manager.FontProperties(size=11)
plt.subplot(2, 1, 1)
lw=2
plt.errorbar(range_n_outliers, err_loc_mcd.mean(1), yerr=err_loc_mcd.std(1) / np.sqrt(repeat), label="稳健位置", lw=lw, color="m")
plt.errorbar(range_n_outliers, err_loc_emp_full.mean(1), yerr=err_loc_emp_full.std(1) / np.sqrt(repeat), label="完整数据集均值", lw=lw, color="green")
plt.errorbar(range_n_outliers, err_loc_emp_pure.mean(1), yerr=err_loc_emp_pure.std(1) / np.sqrt(repeat), label="纯净数据集均值", lw=lw, color="black")
plt.title("异常值对位置估计的影响")
plt.ylabel(r"误差 ($||\mu - \hat{\mu}||_2^2$)")
plt.legend(loc="upper left", prop=font_prop)
plt.subplot(2, 1, 2)
x_size = range_n_outliers.size
plt.errorbar(range_n_outliers, err_cov_mcd.mean(1), yerr=err_cov_mcd.std(1), label="稳健协方差 (mcd)", color="m")
plt.errorbar(range_n_outliers[:(x_size//5 + 1)], err_cov_emp_full.mean(1)[:(x_size//5 + 1)], yerr=err_cov_emp_full.std(1)[:(x_size//5 + 1)], label="完整数据集经验协方差", color="green")
plt.plot(range_n_outliers[(x_size//5):(x_size//2 - 1)], err_cov_emp_full.mean(1)[(x_size//5):(x_size//2 - 1)], color="green", ls="--")
plt.errorbar(range_n_outliers, err_cov_emp_pure.mean(1), yerr=err_cov_emp_pure.std(1), label="纯净数据集经验协方差", color="black")
plt.title("异常值对协方差估计的影响")
plt.xlabel("污染量 (%)")
plt.ylabel("RMSE")
plt.legend(loc="upper center", prop=font_prop)
plt.show()
通过上述代码,可以看到,在数据集中存在异常值时,使用MCD估计器可以得到更加稳健的协方差估计结果。此外,还可以比较MCD估计器与完整数据集和纯净数据集的估计结果,以验证实现是否正确。