从协方差矩阵到降维可视化: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,xiRd

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=1nxi

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 常作为数据预处理的重要步骤,广泛用于特征工程、建模前的数据压缩以及可视化分析,为后续的数据挖掘与建模奠定基础。

Logo

永洪科技,致力于打造全球领先的数据技术厂商,具备从数据应用方案咨询、BI、AIGC智能分析、数字孪生、数据资产、数据治理、数据实施的端到端大数据价值服务能力。

更多推荐