1. 引言:应对“维度灾难”的智慧

在上一篇关于K-Means聚类的文章中,我们悄然完成了一个关键步骤:使用PCA将4维的鸢尾花数据降到2维进行可视化。这个看似简单的操作背后,隐藏着一个强大而优雅的思想——主成分分析(Principal Component Analysis, PCA)

想象一下,你面对一个拥有成百上千个特征的数据集。这些特征之间可能存在复杂的相关性冗余信息。直接处理如此高维的数据,不仅计算成本高昂,还容易导致模型过拟合,这就是所谓的**“维度灾难”**。

PCA的核心使命是:在尽可能保留原始数据信息的前提下,找到一个新的、更少维度的坐标系统,使得数据在这些新坐标(称为主成分)上的投影方差最大化。它不是简单地删除特征,而是通过线性变换创造一组新的、互不相关的特征(主成分),并按其携带的信息量(方差)排序。

  • K-Means 解决了 “数据点如何分组” 的问题。
  • PCA 解决了 “如何用更少的维度描述数据的主要结构” 的问题。
  • 二者结合:PCA为K-Means提供了降维后的、去除冗余的、计算效率更高的输入数据,同时其主成分方向的解释性也为聚类结果提供了洞察。在上一节的可视化中,正是PCA的二维投影让我们“看”到了K-Means的效果。

本文将带你:

  1. 理解PCA背后的方差最大化线性代数直觉。
  2. 掌握PCA的完整计算步骤
  3. 使用scikit-learn,在鸢尾花数据集上从头实践PCA。
  4. 关键联系:深入演示PCA如何作为K-Means等下游任务的“预处理引擎”,并分析其带来的影响。

2. 目录

3. 环境与数据准备

环境配置与之前保持一致。我们将继续使用鸢尾花数据集

pip install numpy pandas matplotlib scikit-learn
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
from sklearn.cluster import KMeans
from sklearn.svm import SVC
from sklearn.metrics import accuracy_score, silhouette_score
from sklearn.model_selection import train_test_split
import warnings
warnings.filterwarnings(‘ignore’)

# 加载数据集
iris = datasets.load_iris()
X = iris.data
y = iris.target
feature_names = iris.feature_names

# 关键:PCA前必须进行标准化!
# 因为PCA基于方差,如果特征尺度不同,大方差特征将主导主成分方向。
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

print(“数据集:鸢尾花 (150 samples, 4 features))
print(“特征:”, feature_names)

4. PCA核心原理简述

4.1 直觉:寻找数据伸展的方向

想象数据点分布在n维空间中。PCA要做的,就是找到一个方向(向量),使得数据点投影到这个方向上后,分布得最“散开”(即投影点的方差最大)。这个方向被称为第一主成分(PC1)

然后,在与第一主成分正交(垂直)的所有方向中,再次找到一个方差最大的方向,即第二主成分(PC2)。以此类推。这些主成分彼此正交,且按其承载的方差(信息量)从大到小排列。

4.2 数学步骤:从协方差到特征向量

PCA的计算可以清晰地分解为以下步骤:

  1. 标准化数据:确保每个特征均值为0,方差为1(我们已做)。
  2. 计算协方差矩阵:对于 d d d个特征,其协方差矩阵 Σ \Sigma Σ是一个 d × d d \times d d×d的对称矩阵,描述了特征两两之间的相关性。
    Σ = 1 n − 1 X T X \Sigma = \frac{1}{n-1} X^T X Σ=n11XTX
    (其中 X X X是中心化后的数据矩阵,每行一个样本,每列一个特征)
  3. 对协方差矩阵进行特征分解:求解 Σ v = λ v \Sigma v = \lambda v Σv=λv,得到特征值 λ i \lambda_i λi和对应的特征向量 v i v_i vi
    • 特征向量:就是主成分的方向。每个特征向量定义了新坐标系的一个轴。
    • 特征值:表示数据在对应特征向量方向上的方差大小。特征值越大,该主成分越重要。
  4. 选择主成分并投影:将特征值从大到小排序,选择前 k k k个最大的特征值对应的特征向量,组成投影矩阵 W W W。将原始数据 X X X投影到新空间: Z = X W Z = X W Z=XW,得到降维后的数据 Z Z Z

4.3 评估:如何选择主成分数量k?

我们通过累积解释方差比来决定 k k k
累积方差比 = ∑ i = 1 k λ i ∑ i = 1 d λ i \text{累积方差比} = \frac{\sum_{i=1}^{k} \lambda_i}{\sum_{i=1}^{d} \lambda_i} 累积方差比=i=1dλii=1kλi
我们通常设定一个阈值(如95%),选择累积方差比首次超过该阈值的 k k k值。这保留了绝大部分信息。

5. 代码实现详解

5.1 从零开始理解:手动实现PCA

为了深入理解原理,我们先用numpy手动实现一次PCA。

# 手动实现PCA
def my_pca(X, n_components):
    # 步骤1: 数据已标准化(X_scaled),计算协方差矩阵
    cov_matrix = np.cov(X, rowvar=False) # rowvar=False表示每列是一个特征

    # 步骤2: 特征分解
    eigenvalues, eigenvectors = np.linalg.eigh(cov_matrix) # eigh专用于对称矩阵,返回有序特征值

    # 步骤3: 按特征值从大到小排序(eigh返回的是升序)
    sorted_indices = np.argsort(eigenvalues)[::-1]
    eigenvalues_sorted = eigenvalues[sorted_indices]
    eigenvectors_sorted = eigenvectors[:, sorted_indices]

    # 步骤4: 选择前k个特征向量
    components = eigenvectors_sorted[:, :n_components]

    # 步骤5: 投影
    X_projected = np.dot(X, components)

    # 计算解释方差比
    explained_variance_ratio = eigenvalues_sorted[:n_components] / np.sum(eigenvalues_sorted)

    return X_projected, explained_variance_ratio, components

# 手动实现2维PCA
X_pca_manual, evr_manual, components_manual = my_pca(X_scaled, n_components=2)
print(“手动实现PCA完成。”)
print(f”前2个主成分解释的方差比:{evr_manual})
print(f”累计解释方差比:{np.sum(evr_manual):.4f})

5.2 使用scikit-learn高效实现

在实践中,我们直接使用sklearn.decomposition.PCA

# 使用sklearn实现PCA
pca = PCA(n_components=2) # 降至2维
X_pca_sklearn = pca.fit_transform(X_scaled)

# 访问关键属性
print(“使用sklearn PCA:”)
print(f”主成分(特征向量)形状:{pca.components_.shape}) # (2, 4)
print(f”各主成分的解释方差比:{pca.explained_variance_ratio_})
print(f”累计解释方差比:{np.sum(pca.explained_variance_ratio_):.4f})

# 查看所有主成分的方差,用于绘制“累积方差图”
pca_full = PCA().fit(X_scaled) # 训练一个保留所有主成分的PCA

5.3 主成分的可视化与解释

# 绘制“累积解释方差比”图(碎石图)
plt.figure(figsize=(12, 5))

plt.subplot(1, 2, 1)
plt.plot(range(1, len(pca_full.explained_variance_ratio_) + 1),
         np.cumsum(pca_full.explained_variance_ratio_), ‘bo-)
plt.xlabel(‘主成分数量’)
plt.ylabel(‘累积解释方差比’)
plt.title(‘PCA 累积解释方差’)
plt.axhline(y=0.95, color=‘r’, linestyle=--, label=95% 阈值’)
plt.legend()
plt.grid(True)

# 绘制前两个主成分构成的二维空间
plt.subplot(1, 2, 2)
scatter = plt.scatter(X_pca_sklearn[:, 0], X_pca_sklearn[:, 1], c=y, cmap=‘viridis’, edgecolor=‘k’, s=60, alpha=0.8)
plt.xlabel(f‘第一主成分 (PC1, {pca.explained_variance_ratio_[0]*100:.1f}%))
plt.ylabel(f’第二主成分 (PC2, {pca.explained_variance_ratio_[1]*100:.1f}%))
plt.title(‘PCA降维后的数据空间’)
plt.colorbar(scatter, label=‘真实类别’)
plt.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

# 解释主成分:每个主成分是原始特征的线性组合
loadings = pd.DataFrame(pca.components_.T, columns=[‘PC1’, ‘PC2’], index=feature_names)
print(“\n主成分载荷矩阵 (每个主成分由原始特征线性组合的系数):)
print(loadings.round(3))

图表解读

  1. 左图:前两个主成分就解释了约95%的方差,印证了二维可视化是有效的。
  2. 右图:在PCA构造的新空间中,三个类别的数据点被清晰地分开了。
  3. 载荷矩阵:例如,PC1的系数在“花瓣长度”和“花瓣宽度”上很大且为正,说明PC1主要代表了“花瓣大小”的信息。这为数据降维提供了可解释性

6. 关键应用:PCA为K-Means与SVM赋能

这是本文与上几篇文章产生联系的核心。我们具体评估PCA预处理对下游任务的影响。

6.1 PCA预处理对K-Means聚类的影响

我们比较在原始4维数据PCA降维后的2维数据上运行K-Means的效果。

# 情景1: 在原始4维标准化数据上进行K-Means
kmeans_orig = KMeans(n_clusters=3, init=‘k-means++, n_init=10, random_state=42)
labels_orig = kmeans_orig.fit_predict(X_scaled)
sil_orig = silhouette_score(X_scaled, labels_orig)

# 情景2: 在PCA降维后的2维数据上进行K-Means
kmeans_pca = KMeans(n_clusters=3, init=‘k-means++, n_init=10, random_state=42)
labels_pca = kmeans_pca.fit_predict(X_pca_sklearn)
sil_pca = silhouette_score(X_pca_sklearn, labels_pca)

print(“K-Means聚类评估 (轮廓系数,越接近1越好):)
print(f”在原始4维数据上聚类的轮廓系数: {sil_orig:.4f})
print(f”在PCA降维(2D)数据上聚类的轮廓系数: {sil_pca:.4f})

# 可视化对比
plt.figure(figsize=(14, 6))
plt.subplot(1, 2, 1)
plt.scatter(X_pca_sklearn[:, 0], X_pca_sklearn[:, 1], c=labels_orig, cmap=‘Set1’, edgecolor=‘k’, s=60, alpha=0.8)
plt.title(‘K-Means on 4D Data (projected to 2D for viewing))
plt.xlabel(‘PC1’); plt.ylabel(‘PC2’); plt.grid(True, alpha=0.3)

plt.subplot(1, 2, 2)
plt.scatter(X_pca_sklearn[:, 0], X_pca_sklearn[:, 1], c=labels_pca, cmap=‘Set1’, edgecolor=‘k’, s=60, alpha=0.8)
plt.title(‘K-Means on 2D PCA Data’)
plt.xlabel(‘PC1’); plt.ylabel(‘PC2’); plt.grid(True, alpha=0.3)
plt.show()

分析

  • 效果:在鸢尾花数据集上,PCA降维后聚类效果可能略好或持平。这是因为去除了噪声和冗余维度,使簇的结构更清晰。
  • 效率:对于海量高维数据,PCA降维能大幅提升K-Means的计算速度。
  • 结果:两种聚类的标签分配可能高度一致,但PCA后的结果通常更符合人类在二维空间的直观观察。

6.2 PCA预处理对SVM分类的影响

# 划分数据集(保持一致)
X_train_orig, X_test_orig, y_train, y_test = train_test_split(
    X_scaled, y, test_size=0.3, random_state=42)

# 在原始4维数据上训练SVM
svm_orig = SVC(kernel=‘rbf’, C=1.0, random_state=42)
svm_orig.fit(X_train_orig, y_train)
acc_orig = accuracy_score(y_test, svm_orig.predict(X_test_orig))

# 对训练集和测试集分别进行PCA降维(重要:必须在训练集上fit,在测试集上transform)
pca_svm = PCA(n_components=2)
X_train_pca = pca_svm.fit_transform(X_train_orig)
X_test_pca = pca_svm.transform(X_test_orig)

# 在PCA降维后的2维数据上训练SVM
svm_pca = SVC(kernel=‘rbf’, C=1.0, random_state=42)
svm_pca.fit(X_train_pca, y_train)
acc_pca = accuracy_score(y_test, svm_pca.predict(X_test_pca))

print(“SVM分类准确率对比:)
print(f”在原始4维数据上: {acc_orig:.4f})
print(f”在PCA降维(2D)数据上: {acc_pca:.4f})

# 可视化PCA降维后SVM的决策边界(仅用2个特征)
h = .02
x_min, x_max = X_train_pca[:, 0].min() - 1, X_train_pca[:, 0].max() + 1
y_min, y_max = X_train_pca[:, 1].min() - 1, X_train_pca[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
Z = svm_pca.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)

plt.figure(figsize=(10, 8))
plt.contourf(xx, yy, Z, alpha=0.4, cmap=plt.cm.coolwarm)
plt.scatter(X_train_pca[:, 0], X_train_pca[:, 1], c=y_train, edgecolors=‘k’, cmap=plt.cm.coolwarm, label=‘训练集’)
plt.scatter(X_test_pca[:, 0], X_test_pca[:, 1], c=‘yellow’, edgecolors=‘k’, marker=^, s=100, label=‘测试集’)
plt.xlabel(f‘PC1 ({pca_svm.explained_variance_ratio_[0]*100:.1f}%))
plt.ylabel(f‘PC2 ({pca_svm.explained_variance_ratio_[1]*100:.1f}%))
plt.title(‘SVM on PCA-reduced Data’)
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()

分析

  • 准确率:在鸢尾花这种小数据集上,降维后SVM准确率可能略有下降,因为丢失了少量信息。但可视化和解释性大幅提升。
  • 核心价值:在真正的高维数据(如图像像素、文本TF-IDF)中,PCA降维对SVM至关重要。它能:
    1. 避免维度灾难,提高训练速度。
    2. 去除噪声和特征相关性,有时能提升模型泛化能力。
    3. 提供可解释的特征(主成分),便于理解模型。

7. 总结与完整代码获取

本文我们系统性地学习了PCA这一无监督降维的基石算法:

  1. 理解了PCA的几何直觉与数学原理:通过寻找方差最大的正交方向进行投影。
  2. 手动实现并对比了sklearn的PCA,加深了对计算步骤的理解。
  3. 掌握了如何使用“累积方差图”来选择主成分数量
  4. 关键联系:通过具体实验,展示了PCA作为特征提取和降维工具,如何高效地为K-Means聚类SVM分类等下游任务赋能。

PCA不仅是降维工具,更是数据探索、特征工程和模型可解释性的利器。它与K-Means、SVM等算法的结合,构成了一个完整的机器学习流水线:数据标准化 -> PCA降维 -> 聚类/分类建模 -> 结果分析与解释

本文的完整代码已整合在上文各步骤中,你可以直接复制运行,并尝试修改n_components、使用不同的数据集(如fetch_olivetti_faces)来观察PCA的效果。

8. 参考文献

  1. Pearson, K. (1901). LIII. On lines and planes of closest fit to systems of points in space. The London, Edinburgh, and Dublin Philosophical Magazine and Journal of Science, 2(11), 559-572.
  2. Hotelling, H. (1933). Analysis of a complex of statistical variables into principal components. Journal of educational psychology, 24(6), 417.
  3. Scikit-learn官方文档 - PCA: https://scikit-learn.org/stable/modules/generated/sklearn.decomposition.PCA.html
  4. Jolliffe, I. T. (2002). Principal component analysis. Springer Series in Statistics.
  5. Shlens, J. (2014). A tutorial on principal component analysis. arXiv preprint arXiv:1404.1100.

更多推荐