4.1.4 特征值与特征向量

- 直觉理解特征值和特征向量的含义
- 用可视化看到矩阵变换中的”特殊方向”
- 理解 PCA 降维为什么有效
- 用 NumPy 计算特征值和特征向量
先说一个很重要的学习预期
Section titled “先说一个很重要的学习预期”这一节很容易让新人一看到标题就紧张。 但这里真正要先学会的,不是完整线代课程里的所有推导,而是:
- 为什么会有“方向不变但长度改变”这种特殊向量
- 为什么这些特殊方向和 PCA、降维、信息保留直接相关
- 为什么 AI 里会反复碰到它们
所以你这一节的第一目标,不是把符号背熟, 而是先把“特殊方向”这层直觉真正建立起来。
先建立一张地图
Section titled “先建立一张地图”这一节最容易让新人觉得“名字很难、好像很高级”。其实你先记住下面这条线就够了:

所以这节课真正想解决的问题不是“背定义”,而是:
- 怎样在一堆变化里找到最重要的方向
- 为什么 PCA 能把数据压缩得更少丢信息
让本节不再吓人的术语表
Section titled “让本节不再吓人的术语表”| 术语 | 直白理解 | 为什么重要 |
|---|---|---|
λ / lambda | 特征向量被拉伸或缩短的倍数 | 如果 λ = 3,说明这个方向变换后长度变成 3 倍。 |
np.linalg.eig | NumPy 里计算特征值和特征向量的函数 | 它先返回特征值,再返回特征向量。 |
eigenvectors[:, i] | 第 i 个特征向量 | NumPy 把特征向量按“列”存放,不是按行存放,新人很容易漏掉。 |
| 协方差矩阵 | 描述不同特征如何一起变化的矩阵 | PCA 就是从这个矩阵里找重要方向。 |
eigvalsh | 专门求对称/厄米矩阵特征值的函数 | 协方差矩阵是对称矩阵,只求特征值时它更快也更稳定。 |
| PCA | Principal Component Analysis,主成分分析 | 一种降维方法,保留变化最大的方向。 |
看公式时先记住一句话:特征向量是方向,特征值表示这个方向有多强。
一、直觉理解
Section titled “一、直觉理解”矩阵变换中的”特殊方向”
Section titled “矩阵变换中的”特殊方向””上一节我们学了:矩阵 × 向量 = 新向量(方向和大小都可能改变)。
但有些向量很特殊——矩阵作用在它们身上后,方向不变,只有长度变化。
flowchart LR A["普通向量 v"] -->|"矩阵 A 作用"| B["方向和大小都变了"] C["特征向量 v"] -->|"矩阵 A 作用"| D["方向不变,大小变了 λ 倍"]
style A fill:#ffebee,stroke:#c62828,color:#333 style B fill:#ffebee,stroke:#c62828,color:#333 style C fill:#e8f5e9,stroke:#2e7d32,color:#333 style D fill:#e8f5e9,stroke:#2e7d32,color:#333用数学语言说:A × v = λ × v
- v 是特征向量(方向不变的那个向量)
- λ(lambda)是特征值(拉伸的倍数)
一个更适合新人的类比
Section titled “一个更适合新人的类比”你可以先把矩阵变换想成一阵风吹过很多根箭头:
- 大多数箭头会被吹偏
- 但有少数箭头恰好正对着这股风
这些箭头被吹了以后:
- 方向不会偏
- 只会变长或变短
这些“怎么吹都不偏”的特殊箭头, 就是特征向量。
可视化:哪些向量方向不变?
Section titled “可视化:哪些向量方向不变?”import numpy as npimport matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['Arial Unicode MS']plt.rcParams['axes.unicode_minus'] = False
# 定义一个矩阵A = np.array([[2, 1], [1, 2]])
# 计算特征值和特征向量eigenvalues, eigenvectors = np.linalg.eig(A)print(f"特征值: {eigenvalues}") # [3. 1.]print(f"特征向量:\n{eigenvectors}")
# 可视化:对很多方向的向量做变换,看看哪些方向不变fig, axes = plt.subplots(1, 2, figsize=(14, 6))
# 生成一组均匀分布的单位向量angles = np.linspace(0, 2*np.pi, 50, endpoint=False)unit_vectors = np.array([np.cos(angles), np.sin(angles)]) # 2×50
# 变换后的向量transformed = A @ unit_vectors # 2×50
# 画变换前后for ax, vectors, title in [(axes[0], unit_vectors, '变换前(单位圆)'), (axes[1], transformed, '变换后(椭圆)')]: # 画所有向量(灰色) for i in range(vectors.shape[1]): ax.plot([0, vectors[0, i]], [0, vectors[1, i]], 'gray', alpha=0.3)
# 高亮特征向量方向 for j in range(2): ev = eigenvectors[:, j] if ax == axes[0]: scale = 1 else: scale = eigenvalues[j] color = ['red', 'blue'][j] ax.quiver(0, 0, ev[0]*scale, ev[1]*scale, angles='xy', scale_units='xy', scale=1, color=color, width=0.01, label=f'特征向量 {j+1} (λ={eigenvalues[j]:.0f})')
ax.set_xlim(-4, 4) ax.set_ylim(-4, 4) ax.set_aspect('equal') ax.grid(True, alpha=0.3) ax.legend(fontsize=10) ax.set_title(title, fontsize=13)
plt.suptitle(f'矩阵 A = [[2,1],[1,2]] 的特征向量', fontsize=14)plt.tight_layout()plt.show()解读:
- 红色和蓝色箭头是特征向量的方向
- 变换后,单位圆变成了椭圆
- 但特征向量的方向没变,只是长度变了(分别变为 3 倍和 1 倍)
- 椭圆的长轴和短轴,恰好就是特征向量的方向
为什么这张图对新人特别重要?
Section titled “为什么这张图对新人特别重要?”因为它让“特征值 / 特征向量”第一次不再只是抽象名词。
你可以直接用肉眼看到:
- 大多数方向在变
- 只有少数方向很特别
- 这些方向刚好就是数据最值得保留或关注的方向
也就是说,这一节最关键的,不是先会算,而是先会看懂这种“特殊方向感”。
二、用 NumPy 计算特征值和特征向量
Section titled “二、用 NumPy 计算特征值和特征向量”A = np.array([[4, 2], [1, 3]])
# 一行搞定eigenvalues, eigenvectors = np.linalg.eig(A)
print("特征值:", eigenvalues) # [5. 2.]print("特征向量:\n", eigenvectors)# 每一列是一个特征向量# 第 1 个特征向量(对应 λ=5): eigenvectors[:, 0]# 第 2 个特征向量(对应 λ=2): eigenvectors[:, 1]预期输出:
特征值: [5. 2.]特征向量: [[ 0.89442719 -0.70710678] [ 0.4472136 0.70710678]]不同环境里,特征向量的符号可能整体反过来,例如 [0.707, -0.707] 变成 [-0.707, 0.707]。这仍然是同一条方向线,不算错误。
验证:A × v = λ × v
Section titled “验证:A × v = λ × v”for i in range(len(eigenvalues)): v = eigenvectors[:, i] # 第 i 个特征向量 lam = eigenvalues[i] # 第 i 个特征值
left = A @ v # 矩阵乘向量 right = lam * v # 特征值乘向量
print(f"\n特征值 λ={lam:.1f}, 特征向量 v={v.round(3)}") print(f" A @ v = {left.round(6)}") print(f" λ * v = {right.round(6)}") print(f" 相等? {np.allclose(left, right)}") # True这段验证比原始数字更重要。只要 A @ v 和 λ * v 几乎相等,就说明这一组特征值和特征向量真的满足定义。
对称矩阵的特殊性质
Section titled “对称矩阵的特殊性质”在 AI 中,我们经常遇到对称矩阵(如协方差矩阵)。对称矩阵有一个好性质:特征向量互相垂直。
# 协方差矩阵(对称矩阵的典型例子)rng = np.random.default_rng(seed=42)data = rng.normal(size=(100, 2))data[:, 1] = data[:, 0] * 0.8 + rng.normal(size=100) * 0.3 # 制造相关性
cov_matrix = np.cov(data.T)print(f"协方差矩阵(对称):\n{cov_matrix.round(3)}")
eigenvalues, eigenvectors = np.linalg.eig(cov_matrix)print(f"\n特征值: {eigenvalues.round(3)}")
# 验证特征向量垂直(点积 ≈ 0)dot = np.dot(eigenvectors[:, 0], eigenvectors[:, 1])print(f"两个特征向量的点积: {dot:.10f}") # ≈ 0(垂直)三、PCA 降维——特征值最重要的应用
Section titled “三、PCA 降维——特征值最重要的应用”先记住 PCA 的工作流
Section titled “先记住 PCA 的工作流”对新人来说,PCA 最好先记成一个流程,而不是一套抽象定义:
flowchart TD A["原始高维数据"] --> B["先做中心化"] B --> C["计算协方差矩阵"] C --> D["求特征值和特征向量"] D --> E["按特征值从大到小排序"] E --> F["保留最重要的前几个方向"] F --> G["把原始数据投影过去"] G --> H["得到更低维的新表示"]为什么需要降维?
Section titled “为什么需要降维?”flowchart TD A["原始数据<br/>100 个特征"] --> B{"特征太多"} B --> C["训练慢"] B --> D["容易过拟合"] B --> E["无法可视化"] B --> F["PCA 降维"] F --> G["保留最重要的方向<br/>比如降到 2-3 维"]
style A fill:#ffebee,stroke:#c62828,color:#333 style F fill:#e8f5e9,stroke:#2e7d32,color:#333 style G fill:#e8f5e9,stroke:#2e7d32,color:#333PCA 的直觉
Section titled “PCA 的直觉”PCA(主成分分析)的核心思想:
- 数据在不同方向上的”变化幅度”不同
- 特征值最大的方向 = 数据变化最大的方向 = 包含信息最多的方向
- 只保留前几个最重要的方向,丢弃不重要的方向 → 降维
一个更适合新人的说法
Section titled “一个更适合新人的说法”你可以先把 PCA 理解成:
- 数据原来站在很多坐标轴上
- 但真正最有信息的变化,通常只集中在少数几个方向
所以 PCA 做的事很像:
- 先重新摆正坐标轴
- 再只保留最有信息的那几根轴
这比一开始就记“协方差矩阵特征分解”要更容易进脑子。
# 生成有明显主方向的 2D 数据rng = np.random.default_rng(seed=42)n = 200x = rng.normal(size=n)y = 0.6 * x + rng.normal(size=n) * 0.3 # y 和 x 有关联data = np.column_stack([x, y])
# 计算协方差矩阵cov = np.cov(data.T)eigenvalues, eigenvectors = np.linalg.eig(cov)
# 按特征值从大到小排序idx = eigenvalues.argsort()[::-1]eigenvalues = eigenvalues[idx]eigenvectors = eigenvectors[:, idx]
print(f"特征值: {eigenvalues.round(3)}")print(f"方差占比: {(eigenvalues / eigenvalues.sum() * 100).round(1)}%")使用 seed=42 时的预期输出:
特征值: [1.06 0.068]方差占比: [94. 6.]%这表示第一个主方向保留了这个小数据集中约 94% 的变化,所以从 2D 降到 1D 时信息损失相对较少。
可视化:PCA 找到的方向
Section titled “可视化:PCA 找到的方向”fig, axes = plt.subplots(1, 2, figsize=(14, 6))
# 左图:原始数据 + 主成分方向ax = axes[0]ax.scatter(data[:, 0], data[:, 1], alpha=0.4, s=20, color='gray')
mean = data.mean(axis=0)colors = ['red', 'blue']labels = ['第 1 主成分(信息最多)', '第 2 主成分(信息较少)']
for i in range(2): direction = eigenvectors[:, i] * eigenvalues[i] * 2 ax.annotate('', xy=mean + direction, xytext=mean, arrowprops=dict(arrowstyle='->', color=colors[i], lw=3)) ax.annotate(labels[i], xy=mean + direction, fontsize=10, color=colors[i])
ax.set_aspect('equal')ax.grid(True, alpha=0.3)ax.set_title('原始 2D 数据 + PCA 方向')ax.set_xlabel('特征 1')ax.set_ylabel('特征 2')
# 右图:投影到第 1 主成分(降到 1D)projected = data @ eigenvectors[:, 0] # 投影到第 1 主成分ax = axes[1]ax.scatter(projected, np.zeros_like(projected), alpha=0.4, s=20, color='red')ax.set_title(f'降到 1D(保留了 {eigenvalues[0]/eigenvalues.sum()*100:.0f}% 的信息)')ax.set_xlabel('第 1 主成分')ax.set_yticks([])
plt.tight_layout()plt.show()解读:
- 红色箭头是第 1 主成分——数据变化最大的方向
- 蓝色箭头是第 2 主成分——变化较小的方向
- 如果只保留第 1 主成分(从 2D 降到 1D),仍然保留了大部分信息
用 scikit-learn 做 PCA
Section titled “用 scikit-learn 做 PCA”在实际项目中,我们通常直接用 scikit-learn 的 PCA:
python -m pip install --upgrade scikit-learnfrom sklearn.decomposition import PCAfrom sklearn.datasets import load_iris
# 加载经典鸢尾花数据集(4 个特征)iris = load_iris()X = iris.data # (150, 4)y = iris.target # 3 种花
print(f"原始维度: {X.shape}") # (150, 4)
# PCA 降到 2 维pca = PCA(n_components=2)X_2d = pca.fit_transform(X)print(f"降维后: {X_2d.shape}") # (150, 2)
# 各主成分的方差占比print(f"方差占比: {pca.explained_variance_ratio_.round(3)}")# [0.925, 0.053] → 前 2 个主成分保留了约 97.8% 的信息!
# 可视化plt.figure(figsize=(8, 6))for i, name in enumerate(iris.target_names): mask = y == i plt.scatter(X_2d[mask, 0], X_2d[mask, 1], label=name, s=40, alpha=0.7)
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 降维(4D → 2D)')plt.legend()plt.grid(True, alpha=0.3)plt.show()结果:4 维数据降到 2 维后,三种花依然能清晰分开!这说明 PCA 有效地保留了最重要的信息。
四、特征值的其他含义
Section titled “四、特征值的其他含义”特征值大小的含义
Section titled “特征值大小的含义”| 特征值 | 含义 |
|---|---|
| 大的特征值 | 数据在这个方向上变化大,信息量大 |
| 小的特征值 | 数据在这个方向上变化小,可以舍弃 |
| 特征值为 0 | 数据在这个方向上完全没有变化(冗余维度) |
| 负的特征值 | 矩阵在这个方向上做了”反转”(翻转方向) |
PCA 中最关键的指标——前 k 个特征值占总特征值的比例:
# 模拟一个高维数据集rng = np.random.default_rng(seed=42)n_features = 20X = rng.normal(size=(200, n_features))# 让前几个特征有很强的信号X[:, :3] = X[:, :3] * 5
# 计算协方差矩阵的特征值cov = np.cov(X.T)eigenvalues = np.linalg.eigvalsh(cov) # eigvalsh 用于对称矩阵,更快eigenvalues = eigenvalues[::-1] # 从大到小排列
# 方差占比variance_ratio = eigenvalues / eigenvalues.sum()cumulative_ratio = np.cumsum(variance_ratio)
# 画 Scree Plot(碎石图)fig, axes = plt.subplots(1, 2, figsize=(14, 5))
axes[0].bar(range(1, 21), variance_ratio * 100, color='steelblue')axes[0].set_xlabel('主成分编号')axes[0].set_ylabel('方差占比 (%)')axes[0].set_title('各主成分的方差占比')
axes[1].plot(range(1, 21), cumulative_ratio * 100, 'o-', color='coral')axes[1].axhline(y=95, color='gray', linestyle='--', label='95% 阈值')axes[1].set_xlabel('主成分个数')axes[1].set_ylabel('累计方差占比 (%)')axes[1].set_title('累计方差占比(选几个主成分?)')axes[1].legend()
plt.tight_layout()plt.show()解读:通过”碎石图”可以判断保留几个主成分就够了——通常选择累计方差达到 95% 的点。
学到这里,下一节该带着什么问题走?
Section titled “学到这里,下一节该带着什么问题走?”看完这节以后,最值得带去后面的是这几个问题:
- 如果我们已经知道怎么找“重要方向”,那更抽象地说,什么叫一个空间的维度?
- 一组向量到底什么时候算“冗余”?
- 为什么 SVD 会成为以后很多 AI 方法里的通用工具?
这三个问题,正好会把你自然带到:
flowchart LR A["矩阵 A"] --> B["特征值分解"] B --> C["特征值 λ<br/>方向的重要性"] B --> D["特征向量 v<br/>重要的方向"] C --> PCA["PCA 降维"] D --> PCA
style A fill:#e3f2fd,stroke:#1565c0,color:#333 style PCA fill:#e8f5e9,stroke:#2e7d32,color:#333学完这一页,至少保留这张证据卡:
- 数学对象
- 向量、矩阵、特征值、基或向量空间概念
- 数值示例
- 用于计算它的简单数字或 NumPy 片段
- 可视化或输出
- 形状、变换后的点、相似度分数、特征方向或投影
- AI 关联
- 这里出现在 embeddings、批次、PCA、神经层或注意力中
- 期望产出
- 计算过程,以及一句把它和 AI 操作联系起来的话
| 概念 | 直觉理解 | NumPy 实现 |
|---|---|---|
| 特征向量 | 矩阵变换下方向不变的向量 | np.linalg.eig(A)[1] |
| 特征值 | 特征向量被拉伸的倍数 | np.linalg.eig(A)[0] |
| PCA | 找数据变化最大的方向,降维 | sklearn.decomposition.PCA |
| 方差占比 | 每个主成分保留了多少信息 | pca.explained_variance_ratio_ |
练习 1:计算特征值
Section titled “练习 1:计算特征值”用 NumPy 计算以下矩阵的特征值和特征向量,并验证 A × v = λ × v:
A = np.array([[3, 1], [0, 2]])练习 2:可视化特征向量
Section titled “练习 2:可视化特征向量”对矩阵 A = [[1, 2], [0, 3]],画出:
- 一组均匀分布的单位向量(圆)
- 矩阵变换后的结果(椭圆)
- 标出特征向量的方向
练习 3:PCA 实战
Section titled “练习 3:PCA 实战”使用 scikit-learn 的 load_digits() 手写数字数据集(64 维),用 PCA 降到 2 维并可视化,看看不同数字能否区分开。
如果你还没有安装 scikit-learn,先运行:
python -m pip install --upgrade scikit-learnfrom sklearn.datasets import load_digitsfrom sklearn.decomposition import PCA
digits = load_digits()X = digits.data # (1797, 64)y = digits.target # 0~9
# 你的代码:PCA 降维 + 可视化操作参考与检查点
- 对
A=[[3,1],[0,2]],特征值是3和2。特征值3的一个有效特征向量是[1,0];特征值2的一个有效特征向量与[-1,1]成比例。 - 验证方式是检查
A @ v和lambda * v在数值上相同,允许浮点误差,也允许特征向量整体缩放。 - digits 数据集 PCA 到 2D 后,不同数字应有部分聚类,但不会完美分开。解释时要说明 PCA 保留的是高方差方向,不是类别标签。