从协方差矩阵到降维可视化:PCA 的数学原理与应用
这篇文章系统介绍了主成分分析(PCA)的原理与实践。首先讲解了 PCA 的数学基础,包括数据中心化、协方差矩阵与特征值分解;然后展示了如何使用 scikit-learn 对鸢尾花数据集进行 PCA 降维、可视化协方差矩阵、特征值、二维投影及累计解释方差;最后总结了 PCA 在高维可视化、数据压缩、去噪和特征提取等场景的应用,为理解和实践线性降维提供了完整指导。
从协方差矩阵到降维可视化:PCA 的数学原理与应用
这篇文章系统介绍了主成分分析(PCA)的原理与实践。首先讲解了 PCA 的数学基础,包括数据中心化、协方差矩阵与特征值分解;然后展示了如何使用 scikit-learn 对鸢尾花数据集进行 PCA 降维、可视化协方差矩阵、特征值、二维投影及累计解释方差;最后总结了 PCA 在高维可视化、数据压缩、去噪和特征提取等场景的应用,为理解和实践线性降维提供了完整指导。
1.PCA 概述
主成分分析(Principal Component Analysis, PCA)是一种经典的降维方法,广泛用于数据压缩、特征提取、可视化和噪声消除等场景。
它的核心思想是:通过线性变换,将原始高维数据映射到一组新的、相互正交的主成分坐标轴上,并按方差大小排序。前几个主成分往往能够保留绝大部分数据信息,从而实现高效的数据表示。换句话说,PCA 的目标是:寻找能够最大化数据方差的方向,并将数据投影到这些方向上。
2. 数学原理
假设数据集为:
X=x1,x2,…,xn,xi∈Rd X = {x_1, x_2, \ldots, x_n}, \quad x_i \in \mathbb{R}^d X=x1,x2,…,xn,xi∈Rd
2.1 数据中心化
为了消除偏移,先对数据去均值化:
X~=X−μ,μ=1n∑i=1nxi \tilde{X} = X - \mu, \quad \mu = \frac{1}{n}\sum_{i=1}^n x_i X~=X−μ,μ=n1i=1∑nxi
2.2 协方差矩阵
计算特征之间的协方差:
C=1nX~TX~ C = \frac{1}{n} \tilde{X}^T \tilde{X} C=n1X~TX~
2.3 特征值分解
对协方差矩阵进行特征分解:
Cvi=λivi C v_i = \lambda_i v_i Cvi=λivi
其中,λi\lambda_iλi 表示第 iii 个主成分的方差大小,viv_ivi 为对应的主成分方向。
2.4 保留前 k 个主成分
选择最大的 kkk 个特征值对应的特征向量,组成投影矩阵:
W=[v1,v2,…,vk] W = [v_1, v_2, \ldots, v_k] W=[v1,v2,…,vk]
降维结果为:
Z=X~W Z = \tilde{X} W Z=X~W
这样,数据被映射到低维空间,同时保留了尽可能多的信息。
3. scikit-learn 实现与可视化
以经典的 鸢尾花数据集 为例,演示 PCA 的完整流程。
3.1 数据加载与准备
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.decomposition import PCA
from sklearn.datasets import load_iris
from matplotlib.patches import Ellipse
sns.set_theme(style="whitegrid", font="SimHei", rc={"axes.unicode_minus": False})
# 加载数据
data = load_iris()
X = data.data
y = data.target
3.2 协方差矩阵可视化
协方差矩阵反映了不同特征之间的相关性,是 PCA 的数学核心。
# 数据中心化
X_centered = X - np.mean(X, axis=0)
# 协方差矩阵
cov_matrix = np.cov(X_centered.T)
# ========== 协方差矩阵热力图 ==========
plt.figure(figsize=(6, 5))
sns.heatmap(cov_matrix, annot=True, fmt=".2f", cmap="Blues",
xticklabels=data.feature_names, yticklabels=data.feature_names, cbar=True)
# 调整 X 轴刻度旋转角度
plt.xticks(rotation=30, ha="right")
plt.title("协方差矩阵热力图", fontsize=14)
plt.tight_layout()
plt.show()

3.3 特征值与方差贡献
特征值表示各主成分的重要性。特征值越大,说明该主成分在数据表示中越关键。
# ========== 特征值大小 ==========
eig_vals, eig_vecs = np.linalg.eig(cov_matrix)
plt.figure(figsize=(6, 4))
bars = plt.bar(
range(1, len(eig_vals)+1),
eig_vals,
color=sns.color_palette("Blues", len(eig_vals)),
edgecolor="k", alpha=0.8
)
plt.xlabel("主成分", fontsize=12)
plt.ylabel("特征值(方差大小)", fontsize=12)
plt.title("各主成分的特征值", fontsize=14)
# 设置 x 轴标签为 PC1, PC2 ...
plt.xticks(range(1, len(eig_vals)+1), [f"PC{i}" for i in range(1, len(eig_vals)+1)])
# 添加淡化的 y 轴网格
plt.grid(axis="y", alpha=0.3, linestyle="--")
# 在柱子顶部添加数值
for bar in bars:
plt.text(
bar.get_x() + bar.get_width()/2, bar.get_height(),
f"{bar.get_height():.2f}",
ha='center', va='bottom', fontsize=10
)
plt.tight_layout()
plt.show()

3.4 二维降维结果
将原始四维数据降到二维后,不同类别的分布在新坐标系下更加清晰可分。
# ========== PCA二维降维结果 ==========
pca = PCA(n_components=4)
X_pca = pca.fit_transform(X)
plt.figure(figsize=(10, 6))
markers = ['o', 's', '^']
palette = sns.color_palette("Set2", len(np.unique(y)))
for label, marker, color in zip(np.unique(y), markers, palette):
plt.scatter(
X_pca[y==label, 0], X_pca[y==label, 1],
label=data.target_names[label],
marker=marker, color=color, alpha=0.8, edgecolor="k", s=70
)
# 添加置信椭圆
cov = np.cov(X_pca[y==label, 0], X_pca[y==label, 1])
mean = np.mean(X_pca[y==label, :2], axis=0)
eigvals, eigvecs = np.linalg.eigh(cov)
angle = np.degrees(np.arctan2(*eigvecs[:,0][::-1]))
ellipse = Ellipse(
xy=mean, width=2*np.sqrt(eigvals[0]), height=2*np.sqrt(eigvals[1]),
angle=angle, edgecolor=color, fc=color, lw=2, alpha=0.3, linestyle="--"
)
plt.gca().add_patch(ellipse)
plt.xlabel(f"主成分1 (解释方差 {pca.explained_variance_ratio_[0]:.2%})", fontsize=12)
plt.ylabel(f"主成分2 (解释方差 {pca.explained_variance_ratio_[1]:.2%})", fontsize=12)
plt.title("PCA二维降维结果", fontsize=14)
plt.gca().set_aspect("equal", adjustable="datalim")
plt.grid(alpha=0.5, linestyle="--") # 淡化网格
plt.legend(frameon=True, fontsize=12, loc="lower right") # 图例放右侧
plt.tight_layout()
plt.show()

3.5 累计解释方差
累计解释方差曲线展示了保留信息的程度。实际应用中,常选择能解释 80%~95% 方差 的前几个主成分作为降维结果。
# ========== PCA解释方差分析(双y轴优化版) ==========
fig, ax1 = plt.subplots(figsize=(8,5))
# 左轴:单个解释方差(条形图)
bars = ax1.bar(
components, explained_var, alpha=0.7,
color=sns.color_palette("Blues", len(components)),
label="单个解释方差"
)
ax1.set_ylabel("单个解释方差比例", fontsize=12)
ax1.set_xlabel("主成分", fontsize=12)
ax1.set_xticks(components)
ax1.set_xticklabels([f"PC{i}" for i in components])
# 在条形上添加百分比
for bar, ev in zip(bars, explained_var):
ax1.text(
bar.get_x() + bar.get_width()/2, bar.get_height() + 0.01,
f"{ev:.2%}", ha="center", va="bottom", fontsize=9
)
# 右轴:累计解释方差(折线图)
ax2 = ax1.twinx()
ax2.plot(
components, cumulative_var, marker='o', color="darkorange", linewidth=2,
label="累计解释方差"
)
ax2.set_ylabel("累计解释方差比例", fontsize=12)
# 在累计曲线上添加数值
for i, cv in enumerate(cumulative_var):
ax2.text(
components[i], cv + 0.001, f"{cv:.2%}",
ha="center", va="bottom", fontsize=9, color="black"
)
# 美化
ax1.grid(axis="y", alpha=0.3, linestyle="--")
plt.grid(alpha=0.7, linestyle="--") # 淡化网格
fig.suptitle("PCA解释方差分析", fontsize=14)
fig.tight_layout()
plt.show()

4. 应用场景
- 高维可视化:将复杂的高维数据映射到二维/三维空间,便于展示和分析。
- 数据压缩:减少存储空间和计算开销。
- 去噪处理:舍弃小方差成分,提高模型鲁棒性。
- 特征提取:得到更加紧凑和有代表性的特征集。
5. 总结
PCA 是一种经典的线性降维方法,核心思想是最大化数据投影的方差。在 scikit-learn 中,PCA 的实现非常简洁,仅需几行代码即可完成降维与可视化。
在实际应用中,PCA 常作为数据预处理的重要步骤,广泛用于特征工程、建模前的数据压缩以及可视化分析,为后续的数据挖掘与建模奠定基础。
更多推荐



所有评论(0)