查看全课大纲(第 68 / 82 节)
  1. 1.函数与数据泛化
  2. 2.向量:从几何直觉到高维空间
  3. 3.矩阵与张量
  4. 4.特征值与特征向量:矩阵变换的不动方向
  5. 5.凸函数:从向量求导的矩阵记号到好优化的函数形状
  6. 6.概率与统计:机器学习的数学基础
  7. 7.独立性与随机变量:贝叶斯公式与二项分布
  8. 8.期望、方差与协方差:随机变量的数字画像
  9. 9.联合概率:当一条轴不够用的时候
  10. 10.概率分布(一):从伯努利到伽马函数
  11. 11.概率分布(二):贝塔、泊松、正态与两个贝叶斯故事
  12. 12.机器学习的模型:找输入与输出的关系
  13. 13.机器学习的应用
  14. 14.过拟合
  15. 15.机器学习的分类
  16. 16.有监督学习:从房价预测说起
  17. 17.损失函数
  18. 18.梯度下降(上):更新规则与学习速率
  19. 19.梯度下降(2)
  20. 20.牛顿方法
  21. 21.KNN简介
  22. 22.距离测算
  23. 23.朴素贝叶斯
  24. 24.朴素贝叶斯的实际应用
  25. 25.信息熵:决策树构建的度量基础
  26. 26.信息增益与决策树的构建
  27. 27.ID3算法
  28. 28.决策树节点的划分
  29. 29.CART树
  30. 30.Bagging
  31. 31.Boosting:自适应提升算法
  32. 32.Gradient Boosting
  33. 33.线性模型:回归与分类的同一副骨架
  34. 34.最小二乘:把最优参数直接解出来
  35. 35.最小二乘法的几何与概率视角
  36. 36.极大似然:给最小二乘一个概率出身
  37. 37.逻辑回归(1):把线性模型接到分类上
  38. 38.逻辑回归(2):损失函数与多分类
  39. 39.---- 手造数据: 5 个样本, 2 个特征; x2 几乎 = 2*x1 (偏差手工指定, 无随机) ----
  40. 40.讲师例: 向量 "23014" -- 三把范数尺子量同一个向量
  41. 41.优化方法(1):坐标下降与前向选择
  42. 42.优化方法(2):前向策略与最小角回归
  43. 43.拉格朗日乘子法(1):从无约束到等式约束
  44. 44.拉格朗日乘子法(2):等式约束下的平行条件与拉格朗日函数
  45. 45.拉格朗日乘子法(3):不等式约束与 KKT 条件
  46. 46.最大间隔:把分类问题变成优化问题
  47. 47.拉格朗日对偶问题:把 w 和 b 全部换成 α
  48. 48.支持向量:边界上的少数派
  49. 49.SVM 计算过程:一笔到底算完,再走进核方法
  50. 50.贝叶斯公式:概率即信念
  51. 51.隐含变量和概率模型:给看不见的原因建模
  52. 52.EM 算法:期望与最大化的迭代
  53. 53.琴生不等式:EM 的数学钥匙
  54. 54.三个硬币问题:EM 的复盘与衔接
  55. 55.高斯混合模型:用隐变量解释双峰数据
  56. 56.隐马尔可夫模型
  57. 57.隐马尔可夫模型(2):前向算法与动态混合模型
  58. 58.隐马尔可夫模型在 NLP 中的运用
  59. 59.EM 算法:把「为什么收敛」推到底
  60. 60.HMM 在 NLP 中的运用:从分词到机器翻译
  61. 61.HMM 参数估计:Baum-Welch 的三块拉格朗日
  62. 62.高维下的高斯模型
  63. 63.主题模型:从词袋到隐含主题
  64. 64.Dirichlet 分布(1)
  65. 65.Dirichlet 分布(2)
  66. 66.统计学习内容回顾
  67. 67.K-means 算法
  68. 68.PCA 降维
  69. 69.SVD 降维
  70. 70.从聚类到流形
  71. 71.局部线性嵌入
  72. 72.随机邻域嵌入:SNE 与 t-SNE
  73. 73.无参模型:让数据自己说话
  74. 74.高斯过程:函数空间里的正态分布
  75. 75.高斯过程(2):噪声、正则化与核的线性组合
  76. 76.神经网络简介:从感知器到自编码器
  77. 77.反向传播:把误差一层层传回去
  78. 78.卷积神经网络(CNN):让特征自己学出来
  79. 79.循环神经网络(RNN):给网络装上记忆
  80. 80.深度学习回顾:静态一类,动态一类
  81. 81.增强学习(1):蒙特卡洛、树搜索与 Q-learning
  82. 82.强化学习(2):从乌鸦吃核桃到井字棋的 value 回传

PCA 降维

约 28 分钟

📺 正在播放小象官方高清录播(支持倍速与清晰度调节)

小象实战讲义 · 机器学习算法精讲

上一节我们讲完了最简单的聚类算法 K-means。无监督学习还有很大的一块——降维(dimension reduction)。面对动辄成千上万维的数据,我们能不能「乘一个矩阵」就把它压小、还尽量不丢信息?本节讲最经典、应用最广的降维算法:主成分分析(PCA)。它要做的事一句话就能说清——找到数据分布最「开阔」的那条轴,把所有点投影上去。

💡 核心导读

  • 降维 = 乘一个矩阵:pp 维数据乘上一个 d×pd \times p 的变换矩阵 GG(d≪pd \ll p),本质是高维到低维的线性投影。
  • 为什么压:对抗维度的诅咒(curse of dimensionality)、剔除冗余与噪声维度——信号处理里大部分信号都是冗余的。
  • PCA 的直觉:二维数据投影到一维,往长轴上映射是好选择(不同点还散得开),往短轴上映射是差选择(一堆点挤成一个点)。
  • 四步算法:去中心化 → 算协方差矩阵 → 特征值分解 → 取最大特征值对应的特征向量做投影。
  • 零损失的极限:当数据完美落在一条线上,第二特征值恰为 0——一个维度被另一个完全线性表出,压掉它一点信息都不损失。

为何需要降维:乘一个矩阵的事

先把降维这件事的形态看清楚。给定最原始的数据——original data,它是 pp 维空间里的高维数据。我们可以给它乘上一个矩阵 GG——一个 transform 矩阵,维度是 d×pd \times p。矩阵乘上去之后,它就变成了一个 dd 维的数据。当然我们认为 dd 是远小于 pp 的。

y=Gx,G∈Rd×p,d≪py = G x, \qquad G \in \mathbb{R}^{d \times p}, \quad d \ll p

这种形式,就是通过一种线性的方式,把原先的 xx 压缩到 yy——f:x→yf: x \rightarrow y,把高维空间的数据映射到低维空间。这种线性变换其实有个特别简单的描述:在高维空间里,通过投影的方式把数据投到低维空间。大家可以想象:二维空间里的一个面,可以映射到某条线上;线再映射到线段上——一个 shadow(影子),这就是投影。

那做这个压缩的目的是什么?两点:

  1. 对抗维度的诅咒。在高维空间里数据太繁琐,有 curse of dimensionality(维度的诅咒),所以我们希望尽量保留原有的信息、尽量减少信息的损失,再去做一个很好的线性映射。
  2. 关键信息往往藏在低维里。很多情况下你会发现,数据虽然很高维,但起到关键作用的部分往往是低维的。信号处理里有个经验:大部分信号其实都是冗余信号——所以才有了稀疏编码(Sparsity Coding)、后来的压缩感知(Compressive Sensing),它们都是想在复杂信号里找到最重要、最核心的那部分。

而 GG 的选择本身就是一门学问。假设 pp 和 dd 都已经确定——从 pp 维压到 dd 维——怎么压?选什么样的矩阵? 这就需要一个标准,这个标准就是减少信息的损失。我们这一节要讲的 PCA,它定的标准是:保持原有数据的方差最大。

哪些维度是「头发长度」:数据里的冗余

什么时候压得动、什么时候压不动?讲师用了一个贯穿全段的例子——预测小学生的考试成绩。

先看哪些维度是纯噪声。头发的颜色——越黑的小朋友学得越好?你会发现没有什么关联。那头发的长度呢?听起来也没有关联,但你拿实际数据一算,还真有正相关:长头发的小朋友,一般学习好一点。但这并不是头发长度的功劳——它侧面上暴露了另一件事:长头发的小朋友经常是小女孩,而小学生阶段的小女孩特别听话、特别认真,考试这种形式特别适合她们,所以成绩好一些。主要原因是性别,还真不是头发长度。你想想:同样都是小女孩,有长发有短发——你区分不出头发更长的就成绩更好。

所以在这种程度上,有些维度会引入更多的噪声。对于预测学习成绩这件事,头发长度、头发颜色这两个维度就可以全部压缩掉。压缩有两种方式:一种直接剔除;另一种是在线性变换里把这个维度的权重减小。

权重是什么意思?把 pp 维数据的每一维排开:第一维、第二维、第三维……一直到第 pp 维。乘上矩阵 GG 的时候,GG 的每一行就是一个权重向量 w1,w2,…,wdw_1, w_2, \ldots, w_d,每个分量对应一个原始维度。权重的大小,就代表你对那个维度特征的重视程度。假如第三维是「每天学习时间」——重要——那对应的权重 w3w_3 就会比较大;第一维是「头发长度」——不重要——那对应的 w1w_1 可能就只有 0.005 甚至更小。压缩时,不重要的维度就被压到一个很小的空间里去。也可以理解为:在调整、旋转这个子空间的时候,偏向重要的特征方向去旋转,而不是偏向不重要的。

(此处原为课件图示,现用文字描述:一份数据在两个维度上作散点。三种典型形态——第一种,一个维度上的取值完全随机,散点铺满整个平面,两个维度互不表示;第二种,两个维度明显负相关,散点呈一条下倾的带状;第三种,散点几乎完美落在一条直线上。)

散点图上通常有三种形态:

  • 随机:两个维度各走各的,谁也说不清谁;
  • 负相关:讲师举的例子是玩游戏时长与学习成绩——玩游戏时间越长、成绩越差,不绝对、不是严格的反比,但趋势明显。某些压缩会损失一些信息,但大量的信息得以保留;
  • 完美落线:几乎可以完美地落在一条线上。这时其中一个维度的信息,可以完全用另外一个(或几个)维度的线性变换表示——这个维度就是冗余(redundant)的,直接通过映射去掉它。反过来,第一种形态里一个特征没办法用其他特征表示,每个信息都很重要。(讲师讲到这先说「A 的冗余信息比较多」,马上自我纠正:「啊,错了 Sorry,是 C 的」——完美落线的那种才是冗余最重的。)

再感受一下高维数据有多吓人。人脸图像是高维像素矩阵,但这都不算高——想象一个视频:一秒 24 帧,每一帧假设是 50×8050 \times 80 的(讲师口算先说成 400,当场纠正:Sorry,50×8050 \times 80 是 4000 个点),每个点有 RGB 三个通道,那就是一帧 12000 个数据,一秒再乘上 24——接近 30 万个数据。这么庞大的数据,怎么有效地压缩、还能看到图像里最重要的内容?这就是我们要研究的部分。做 face recognition 的人都知道:同一个人在不同表情、不同光照、不同角度下,还是那一个人——我们要把高维空间里最核心的部分提取出来。识别一个人的时候,面目特征比「这张照片的光照特点」重要得多。

(至于「哪些特征重要」这件事,讲师顺带提了一句:现在也可以自动地通过深度学习去学到——与本课程 11.4 里对深度学习的诚实对照呼应;PCA 则代表了另一条路:用数学标准显式地把这个「重要」定义出来。)

往长轴上投影:PCA 的直觉

第一个要讲的降维算法叫 Principal Component Analysis。principle 是「主」,component 是「成分、部分」——找到最重要的组成部分来分析,中文叫主成分分析法。它是一个非常 classical 的 multivariate method(多变量分析)——统计里叫多变量分析或高维变量分析,机器学习里很多最原始的东西,都是从 Multivariate Analysis 这边来的。

它是卡尔·皮尔逊(Karl Pearson)1901 年首先研究的,后来 Harold Hotelling 在 1933 年独立发展。皮尔逊这个人特别有意思:他 1911 年在 UCL(University College London)建立了世界上第一个大学统计系——他意识到统计可以应用到政治、经济、社会、文化、水文、天文等各个方面,是很重要的应用数学分支。到今天,UCL 仍然是英国计算机、统计方向最好的学校之一:DeepMind 的创始人 Hassabis 就是在 UCL 读的博士,David Silver、DeepMind 的很多人,都跟 UCL 和 Edinburgh 有很大关联。英国最好的学校当然是牛津剑桥,但做 AI、机器学习的很多人,来源恰恰是 UCL、Edinburgh 这些学校。

那 PCA 的道理是什么?拿最简单的二维数据来看:把它压缩到一维,投到哪条线上?大家可以先想一个极端问题:如果压缩到零维,是什么东西?——不用想了,所有东西都是一个点。零维空间就是一个点,甚至你没有数据、做了压缩之后还是这个点。一维空间是一条线,线上有些区域有数据、有些没有。二维是个面。现在我们要把面里的数据映射到一条线上去,还得保住数据的基本特性——什么样的映射算好?

原始空间里不同的数据,映射之后还是不同的点——这就代表原始数据的某些特性得以保留。往哪个方向映射能做到这一点?你会发现:往长轴方向映射时,原本不同的数据点大多还散得开;而往短轴(比如横着的那条线)方向映射,数据全被压进一个很小的区域——实际上原始空间里可能有 10 个、8 个点被映射到了同一个点上;而往长轴上映射,只有大概两三个、四五个点撞到一起。往长轴上映射是更好的选择,往短轴上是差的选择——这就是我们的直觉。(二维是这样,三维就是在空间中的一堆点里找一个最合适的面,把点都映射到那个平面上去。)

把这个直觉翻成数学标准就是课件里给 PCA 的两条准则:投影后保留的方差最大化;同时最小二乘重构误差最小化(从投影还原回去,丢的平方误差最小)。有了直觉,接下来就是怎么通过数学的方法,把这个「最好的方向」学出来。

PCA 的四步:均值、协方差、特征分解、投影

在讲步骤之前,先回顾一件老事:线性变换主要就是通过矩阵乘积来压缩,而矩阵最重要的两个特征,就是它的特征向量和特征值。给定矩阵 AA、向量 vv,乘上矩阵变成 AvAv;如果 vv 恰好落在 AA 的特征向量方向上,那么乘上 AA 就代表在这个方向上扩大 λ\lambda 倍——这个倍数就是对应的特征值。(这是整个课程第一次课讲线性代数时给过的图景,忘记的同学可以回去复习。)

第一步:减去均值(去中心化)

给定了最原始的数据——比如 11 个数据(课件里的例子是 N=11N=11 个对象),每个是二维的。第一件事:求它的均值。均值 μ\mu 本身是一个二维向量,比如大概在 (2,2)(2, 2) 这个位置;然后每一个数据都减去均值。减完之后,均值就变成了 (0,0)(0,0),所有数据都围着原点基本均等地分布。

第二步:算协方差矩阵

现在要求这个新矩阵(每个点都减完均值之后)的协方差矩阵 CC(covariance matrix)。先回忆协方差怎么写。方差 variance 是:

Var(x)=E[(x−E[x])(x−E[x])]\mathrm{Var}(x) = E\big[(x - E[x])(x - E[x])\big]

协方差就是把它推广到两个变量——「co」表示两者相差相交的关系:

cov(x,y)=E[(x−E[x])⊤(y−E[y])]\mathrm{cov}(x, y) = E\big[(x - E[x])^{\top}(y - E[y])\big]

现在是两个维度 XX、YY,就可以求一个 2×22 \times 2 的协方差矩阵。板书上是这么摆的——四个元素:

C=(a11a12a21a22)=(Var(X)cov(X,Y)cov(Y,X)Var(Y))C = \begin{pmatrix} a_{11} & a_{12} \\ a_{21} & a_{22} \end{pmatrix} = \begin{pmatrix} \mathrm{Var}(X) & \mathrm{cov}(X, Y) \\ \mathrm{cov}(Y, X) & \mathrm{Var}(Y) \end{pmatrix}

对角线 a11a_{11}、a22a_{22} 是各自的方差;a12a_{12}、a21a_{21} 是两个方向的协方差。XX 跟 YY 的协方差和 YY 跟 XX 的是一样的——因为这两部分可以互相调换、左右相乘——所以这个矩阵一定是对称的。板书上这个例子:非对角是 0.615 和 0.615(对称),一个对角线元素 0.716 代表 XX 方向自己的方差。

第三步:特征值分解

有了协方差矩阵,做一件非常简单的事:求它的特征向量和特征值。(讲师说「求它的特征值和特征根」,随即自我纠正——特征根就是特征值的旧称,同一个东西。)这个矩阵最多有两个特征对。板书例子的完整数字如下(对角 0.716 与非对角 0.615 为逐字稿口播数字,另一个对角 0.616 由板书特征值 1.28 与 0.049 反推重建):

C=(0.6160.6150.6150.716)C = \begin{pmatrix} 0.616 & 0.615 \\ 0.615 & 0.716 \end{pmatrix}

用代码把这个板书例子的特征分解完整算一遍——2×22 \times 2 对称矩阵用迹(trace)和行列式(det)的求根公式手算即可,不依赖任何库:

import math

# 板书例:11 个二维数据减去均值后的协方差矩阵
# (0.615 / 0.716 为逐字稿口播数字,0.616 由特征值 1.28 与 0.049 反推重建)
C = [[0.616, 0.615],
     [0.615, 0.716]]

# 对称性检查:X 跟 Y 的协方差 = Y 跟 X 的协方差
print(f"对称性检查: C[0][1] = {C[0][1]}, C[1][0] = {C[1][0]}")

# 2x2 特征值:lambda^2 - trace*lambda + det = 0 的求根公式
trace = C[0][0] + C[1][1]
det = C[0][0] * C[1][1] - C[0][1] * C[1][0]
disc = math.sqrt(trace * trace - 4 * det)
lambda1 = (trace + disc) / 2
lambda2 = (trace - disc) / 2
print(f"trace = {trace:.4f}, det = {det:.4f}")
print(f"lambda1 = {lambda1:.4f}   (讲师板书: 约 1.28)")
print(f"lambda2 = {lambda2:.4f}   (板书: 约 0.049, 口播读作 0.49)")

# 特征向量 v1: 解 (C - lambda1*I) v = 0, 取 v = [C12, lambda1 - C11] 再归一化
v1 = [C[0][1], lambda1 - C[0][0]]
norm = math.sqrt(v1[0] ** 2 + v1[1] ** 2)
v1 = [x / norm for x in v1]
v2 = [-v1[1], v1[0]]  # 与 v1 正交的第二个特征向量
print(f"v1 = [{v1[0]:.4f}, {v1[1]:.4f}]   (课件 Page 11: 约 [-0.677, -0.735], 同一条方向线)")
print(f"v2 = [{v2[0]:.4f}, {v2[1]:.4f}]")

# 三项核验: 单位长度 / 互相垂直 / Cv = lambda*v 回代
print(f"|v1| = {math.sqrt(v1[0]**2 + v1[1]**2):.4f}, |v2| = {math.sqrt(v2[0]**2 + v2[1]**2):.4f}")
print(f"v1.v2 = {v1[0]*v2[0] + v1[1]*v2[1]:.4f}")
Cv1 = [C[0][0]*v1[0] + C[0][1]*v1[1], C[1][0]*v1[0] + C[1][1]*v1[1]]
print(f"Cv1 = [{Cv1[0]:.4f}, {Cv1[1]:.4f}] = lambda1*v1 = [{lambda1*v1[0]:.4f}, {lambda1*v1[1]:.4f}]")

# 方差分配: 两个特征值之和 = 总方差, lambda1 就是投影到 v1 后的方差
print(f"第一主成分保留的方差比例: {lambda1 / trace:.4f}")
对称性检查: C[0][1] = 0.615, C[1][0] = 0.615
trace = 1.3320, det = 0.0628
lambda1 = 1.2830   (讲师板书: 约 1.28)
lambda2 = 0.0490   (板书: 约 0.049, 口播读作 0.49)
v1 = [0.6779, 0.7352]   (课件 Page 11: 约 [-0.677, -0.735], 同一条方向线)
v2 = [-0.7352, 0.6779]
|v1| = 1.0000, |v2| = 1.0000
v1.v2 = 0.0000
Cv1 = [0.8697, 0.9433] = lambda1*v1 = [0.8697, 0.9433]
第一主成分保留的方差比例: 0.9632

几何上:协方差矩阵的两个特征向量 v1v_1、v2v_2,长度代表对应 λ\lambda 的大小——λ1\lambda_1 对应的方向比 λ2\lambda_2 的长(实算 1.2830 对 0.0490)。我们要投的,就是 v1v_1 这个方向。(课件 Page 11 印的 v1≈[−0.677,−0.735]v_1 \approx [-0.677, -0.735],与实算的 [0.678,0.735][0.678, 0.735] 是同一条方向线的两端——特征向量取正向取负向是同一个方向;逐字稿口播「0.677 和 −0.753」里的 0.753 是 0.735 的口误。)

第四步:排序、选方向、投影

特征值从大到小排列:3×33 \times 3 的问题就是 λ1,λ2,λ3\lambda_1, \lambda_2, \lambda_3 从大到小排。板书例只有两个值:1.28 和 0.049——很显然 1.28 大,保留 v1v_1 这个方向,每个数据乘上 v1v_1,就压缩到一维空间里去了。刚才那 11 个数据,都比较完美地映射到了 v1v_1 这条线上——这条线保证投影后的方差最大;往横线上、往其他方向上映射,都不是好选择。

  • 三维压到一维:取最大的 λ1\lambda_1 对应的 v1v_1;
  • 三维压到二维:去掉最小的 λ3\lambda_3,保留 λ1,λ2\lambda_1, \lambda_2 对应的 v1,v2v_1, v_2——它们两个构成的那个矩阵,就是我们刚才说的转移矩阵 GG,所有数据乘上它,就映射到 v1,v2v_1, v_2 所代表的新二维空间。

原始数据多少维、要压到多少维,就选多少个最大的 λ\lambda 对应的特征向量。 而且 v1v_1 和 v2v_2 一定是垂直的——对称矩阵不同特征值的特征向量相乘为 0,orthogonal(上面的实算也验证了 v1⋅v2=0v_1 \cdot v_2 = 0)。课件还补了一个很好的性质:如果保留的维数 mm 等于数据的秩 rank(X),就没有任何信息损失——下面用代码亲眼看一次这个「零损失」什么时候发生。

均值、方差、标准差:一段直觉回顾

把这几个量在几何上再看一眼:均值相当于期望,就是这一堆点的中心点。方差呢?每一个点到中心点做一个距离,把所有距离的平均值求出来——那是标准差;这些距离平均的平方,就是方差。一堆数据、一个中心点、每个点拉一条线到中心——这就是均值和方差该有的画面感。

完美落线:冗余维度的零损失压缩

回到前面三种数据形态里「完美落线」的那一种——它值得用代码亲手验证一次。取 4 个点,恰好落在 y=2xy = 2x 这条直线上(一维信息用两维存放),完整走一遍 PCA 的四步:

import math

# 「完美落线」数据: 4 个点恰好在 y = 2x 上
X = [[0, 0], [1, 2], [2, 4], [3, 6]]
N = len(X)

# 第一步: 去中心化
mean = [sum(X[i][j] for i in range(N)) / N for j in range(2)]
Xc = [[X[i][j] - mean[j] for j in range(2)] for i in range(N)]
print("去中心化后的数据:")
for row in Xc:
    print(" ", [f"{v:.1f}" for v in row])

# 第二步: 协方差矩阵 (N-1 分母)
C = [[sum(Xc[k][i] * Xc[k][j] for k in range(N)) / (N - 1) for j in range(2)] for i in range(2)]
print("协方差矩阵:")
for row in C:
    print(" ", [f"{v:.4f}" for v in row])

# 第三步: 特征分解 (2x2 求根公式)
trace = C[0][0] + C[1][1]
det = C[0][0] * C[1][1] - C[0][1] * C[1][0]
disc = math.sqrt(trace * trace - 4 * det)
lambda1, lambda2 = (trace + disc) / 2, (trace - disc) / 2
print(f"lambda1 = {lambda1:.4f}, lambda2 = {lambda2:.4f}")

# 第四步: 投影到第一主成分
v1 = [C[0][1], lambda1 - C[0][0]]
norm = math.sqrt(v1[0] ** 2 + v1[1] ** 2)
v1 = [x / norm for x in v1]
print(f"v1 = [{v1[0]:.4f}, {v1[1]:.4f}]   (正是 y=2x 方向的单位向量 (1,2)/sqrt(5))")
Y = [sum(Xc[i][j] * v1[j] for j in range(2)) for i in range(N)]
print(f"压缩到 1 维后的数据: {[f'{y:.4f}' for y in Y]}")

# 对比: 给 y 加一点噪声再算一遍
Xn = [[0, 0.1], [1, 1.9], [2, 4.1], [3, 5.9]]
meann = [sum(Xn[i][j] for i in range(N)) / N for j in range(2)]
Xcn = [[Xn[i][j] - meann[j] for j in range(2)] for i in range(N)]
Cn = [[sum(Xcn[k][i] * Xcn[k][j] for k in range(N)) / (N - 1) for j in range(2)] for i in range(2)]
tracen = Cn[0][0] + Cn[1][1]
detn = Cn[0][0] * Cn[1][1] - Cn[0][1] * Cn[1][0]
discn = math.sqrt(tracen * tracen - 4 * detn)
l1n, l2n = (tracen + discn) / 2, (tracen - discn) / 2
print(f"加噪声后: lambda1 = {l1n:.4f}, lambda2 = {l2n:.4f}   (微小的 lambda2 就是噪声的方差)")
print(f"第一主成分保留的方差比例: {l1n / tracen:.4f}   (大量信息保留, 压掉的只是噪声)")
去中心化后的数据:
  ['-1.5', '-3.0']
  ['-0.5', '-1.0']
  ['0.5', '1.0']
  ['1.5', '3.0']
协方差矩阵:
  ['1.6667', '3.3333']
  ['3.3333', '6.6667']
lambda1 = 8.3333, lambda2 = 0.0000
v1 = [0.4472, 0.8944]   (正是 y=2x 方向的单位向量 (1,2)/sqrt(5))
压缩到 1 维后的数据: ['-3.3541', '-1.1180', '1.1180', '3.3541']
加噪声后: lambda1 = 8.0778, lambda2 = 0.0022   (微小的 lambda2 就是噪声的方差)
第一主成分保留的方差比例: 0.9997   (大量信息保留, 压掉的只是噪声)

三个看点:

  • λ2=0\lambda_2 = 0(实算印出 0.0000——真实值是 10−1610^{-16} 量级的浮点毛边,比第一特征值小 16 个数量级;8.3 节见过它落成 −0.0000-0.0000 的兄弟,本质都是机器零)。协方差矩阵是奇异的——yy 这一维的信息完全被 xx 线性表出(y=2xy = 2x),这就是「完美落线」的数学形态:保留维数 m=1m = 1 恰好等于数据的秩 rank,压缩到一维,零信息损失。
  • v1=(0.4472,0.8944)v_1 = (0.4472, 0.8944) 正是直线方向 (1,2)/5(1,2)/\sqrt{5} 的单位向量——PCA 找到的「最重要的方向」,就是数据铺开的那条线。
  • 加了一点噪声(yy 各偏 ±0.1\pm 0.1)之后,λ2\lambda_2 变成微小的 0.0022——噪声被隔离在第二主成分里,第一主成分保留 99.97% 的方差。这就是「压缩损失少量信息、保留大量信息」的定量版本。

手写数字降到二维:PCA 真的管用

课件里还有一段 pseudo code,讲师明说不展开细讲——「任何代码里边都会有这种实现」。但例子值得看:用 PCA 做手写数字(MNIST 那样的数据)的投影,把每个手写数字的高维像素降到二维。你会发现,即使这么复杂的问题压到二维空间里:这一片区域是 3、那一块是 1、这边是 0、4 在那边、5 在中间——大的区域仍然各自聚在一起,在二维空间里仍然能看出这些数据的特征。这说明两件事:第一,这些数据之间的特征本身比较明显;第二,我们确实找到了一个比较好的投影方向。

PCA 用特征值分解把「最重要的方向」找了出来;而实际计算中它还有一个更稳定的数值搭档——奇异值分解(SVD),它跟降维的关系比这里展示的更深。这正是下一节的主角。

📝 动手练一练

  1. 概念辨析:三份数据的协方差矩阵如下,哪一份存在「一个维度可以完全用另一个维度线性表示」的关系?哪一份压缩到一维会零信息损失?为什么剩下那份每个维度都动不得?

C甲=(1.0001.0),C乙=(1.0−0.9−0.91.0),C丙=(1.01.01.01.0)C_{\text{甲}} = \begin{pmatrix} 1.0 & 0 \\ 0 & 1.0 \end{pmatrix}, \quad C_{\text{乙}} = \begin{pmatrix} 1.0 & -0.9 \\ -0.9 & 1.0 \end{pmatrix}, \quad C_{\text{丙}} = \begin{pmatrix} 1.0 & 1.0 \\ 1.0 & 1.0 \end{pmatrix}

👉 点击查看参考答案

丙存在完全线性表出的关系:两行完全相同,行列式 1.0×1.0−1.0×1.0=01.0 \times 1.0 - 1.0 \times 1.0 = 0,矩阵奇异,λ2=0\lambda_2 = 0——正是代码里「完美落线」的形态(y=xy = x),压缩到一维零信息损失(保留维数等于秩)。乙是强负相关(「玩游戏时长 vs 成绩」的形态):λ\lambda 一大一小,压到一维会损失少量信息但保留大部分。甲两个维度互不相关、方差相等,任何一个维度都无法用另一个表示——每个信息都重要,硬压到一维就要丢掉整整一半的方差。

  1. 手动演算:对协方差矩阵 C=(2112)C = \begin{pmatrix} 2 & 1 \\ 1 & 2 \end{pmatrix} 手算特征值和特征向量,并把去中心化后的数据点 (1,1)(1, 1) 投影到第一主成分上。
👉 点击查看参考答案

迹 =4= 4,行列式 =2×2−1×1=3= 2 \times 2 - 1 \times 1 = 3。特征方程 λ2−4λ+3=0\lambda^2 - 4\lambda + 3 = 0,即 (λ−1)(λ−3)=0(\lambda - 1)(\lambda - 3) = 0,得 λ1=3,λ2=1\lambda_1 = 3, \lambda_2 = 1。对 λ1=3\lambda_1 = 3:(C−3I)v=0(C - 3I)v = 0 给出 −v1+v2=0-v_1 + v_2 = 0,即 v1=v2v_1 = v_2,归一化后 v1=(1,1)/2=(0.7071,0.7071)v_1 = (1, 1)/\sqrt{2} = (0.7071, 0.7071);v2v_2 与之正交:(0.7071,−0.7071)(0.7071, -0.7071)。投影:(1,1)⋅v1=2/2=2≈1.4142(1,1) \cdot v_1 = 2/\sqrt{2} = \sqrt{2} \approx 1.4142。投影后方差为 λ1=3\lambda_1 = 3,占比 3/(3+1)=75%3 / (3+1) = 75\%。

本章小结

PCA 是无监督降维里最经典的方法:先把数据减去均值(去中心化),算出对称的协方差矩阵,做特征值分解,把特征值从大到小排序,取前若干个最大特征值对应的特征向量构成投影矩阵——每一个特征值,就是数据投影到对应特征向量方向后的方差;要压到多少维,就取多少个。它的准则是双生的:投影后保留的方差最大、最小二乘重构误差最小。数据越「完美落线」,压得越无损;数据越随机,每个维度越动不得。

📋 行动清单

  • 复习第一课:特征值、特征向量的几何含义(Av=λvAv = \lambda v:乘矩阵等于在特征向量方向上伸缩 λ\lambda 倍)——讲师原话「忘记的话可以看我们整个课程的第一次课」。
  • 手动演算:把练一练第 2 题完整推一遍,再用本节的求根公式代码核对自己的手算结果。
  • 思考应用:找一份你熟悉的数据(用户行为、图像、成绩单),想想哪些维度是「头发长度」式的伪相关维度?哪些是「玩游戏时长」式的强相关维度?PCA 会把哪一维压掉?
  • 预习下一节:SVD(奇异值分解)——PCA 在实际计算里最稳定的数值搭档,也是下一节的主角。

—— 小象教研组

配套资料 · 直接下载

课件、代码、数据集等配套资料全部免费,点击即可直接下载,无需分享或解锁。

  • 第16章课件:无监督学习(k-means/PCA/SVD/Isomap)
    下载