60 个点、10 维、各列共享一个大公共因子。 把解约束在前 k 个主成分张成的子空间里,测试误差是 U 形。 k 等于 1 时 0.3010,k 等于 2 时 0.1480,k 等于 10(全空间)时 0.1931。 最低点在 k 等于 2,因为真值就落在前两个主成分上。约束砍掉的不是参数, 是「参数能去的方向」。
第 3 章的最小二乘是无约束的:θ 想去哪就去哪。 问题在于,数据一病态,它就往方差最大的方向跑。 这一节的约束最硬:规定解只能落在某个子空间里。 4.2 会把硬约束换成软约束(罚项),两者在数学上是一回事。
一、为什么需要约束
回顾 3.2 的结论:无约束最小二乘的方差是噪声方差乘 (A 转置 A) 的逆。 一旦两列基接近共线,这个逆的对角元就爆掉。 Demo 里 10 个特征共享一个大公共因子, 条件数是 170,系数范数 2.06,而真值只有两个非零分量、范数 1.94。 范数看着差不多,但它是 10 个分量凑出来的,每个分量单独看都不可信。 约束的作用不是让训练误差更小——它一定会让训练误差变大。 它的作用是「不让解跑进那些数据管不住的方向」。 用偏差换方差,这就是全部逻辑。 换个说法:无约束解在「数据多、特征少、彼此不相关」时已经是最优的,加约束只会更差。 只有当数据撑不住参数个数时,约束才开始赚。
二、约束怎么写:一个投影矩阵
教材的写法是:θ 等于 P 乘 θ 撇,P 是一个投影矩阵。
展开就是把解限制在 P 的值域里。
代入目标函数,对 θ 撇求导,解出 θ 等于 P 乘 (P 转置 A 转置 A P) 的逆乘 P 转置 A 转置 y。
这个式子看着吓人,实际只是「先把数据投到子空间里,再做一次普通最小二乘」。
代码里根本不用写这个公式。
只要把 A 乘上 P(或者乘上子空间的一组正交基),然后照常调 lstsq。
Demo 里就是 z = Xc @ Vk 再 lstsq(z, y)。
P 取什么,是这个方法唯一的设计决策。
最常用的是主成分方向:取 Xc 的奇异值分解里前 k 个右奇异向量。
这叫主成分回归——先降维再回归。
为什么主成分是个好选择?因为它按方差排序。
方差大的方向是数据说得清的方向,方差小的方向是数据几乎没信息、却最容易让系数乱跑的方向。
砍掉后者,等于主动放弃「拟合这部分噪声的权利」。
三、实测:U 形只在测试集上出现
| 子空间维数 k | 训练 MSE | 测试 MSE |
|---|---|---|
| 1 | 0.5237 | 0.3010 |
| 2 | 0.1564 | 0.1480 |
| 3 | 0.1514 | 0.1532 |
| 5 | 0.1427 | 0.1751 |
| 8 | 0.1355 | 0.1888 |
| 10 | 0.1341 | 0.1931 |
| 训练那一列从 0.5237 一路降到 0.1341,没有一个反弹。 | ||
| 测试那一列从 0.3010 降到 0.1480 再升到 0.1931。 | ||
| 同一个 k,两列的方向完全不同——这就是为什么必须有留出数据。 | ||
| k 等于 1 为什么这么差?因为真值是两个方向的组合,只留一个方向,它只能画出一个影子。 | ||
| 这时候的问题是偏差,不是方差。 | ||
| k 等于 10 为什么又变差?因为 10 个参数里有 8 个在拟合噪声。 | ||
| 这时候的问题是方差。 | ||
| U 形两端是两种病,中间才是健康区。 | ||
| 注意主成分方差的落差:第一位 541.9,第三位只有 10.8。 | ||
| 这个数量级差就是「主成分能用来筛方向」的原因——差得越大,砍得越放心。 | ||
| 如果所有方向的方差都差不多,主成分就没有筛选能力,这个方法也就没用了。 |
四、它的边界
主成分回归的假设是「方差大的方向就是重要的方向」。 这个假设在真实数据里经常错。 一个方差很小、但和 y 高度相关的方向,会被它直接砍掉——那是致命的。 这是它和偏最小二乘的区别:后者在找方向时会看 y。 本书没讲偏最小二乘,但这个区别值得知道:无监督降维和「为预测而降维」不是一回事。 还有一个边界是「k 怎么选」。 Demo 里 k 等于 2 最好,是因为我知道真值。真实场景只有交叉验证能告诉你。 4.3 节会把这套流程走一遍。
五、动手 Demo:部分空间约束:k 等于 2 时测试 0.148,全空间 0.193
60 个训练点、10 维,各列共享一个大公共因子,所以彼此高度相关。 真值故意设在前两个主成分方向上,系数 1.6 与负 1.1。 对每个 k,先把中心化的数据投到前 k 个主成分上,再做普通最小二乘,最后在 400 个留出点上算误差。
mu = _X.mean(0)
Xc = _X - mu
U, S, Vt = np.linalg.svd(Xc, full_matrices=False)
for k in (1, 2, 3, 5, 8, 10):
Vk = Vt[:k].T # 前 k 个主成分方向
z = Xc @ Vk # 把数据投进子空间
a = np.linalg.lstsq(z, _Y, rcond=None)[0]
print(k, np.mean((z @ a - _Y) ** 2),
np.mean((( _XT - mu) @ Vk @ a - _YT) ** 2))
实跑输出:
--------------------------------------------------------------------
部分空间约束:k 等于 2 时测试 0.148,全空间 0.193
--------------------------------------------------------------------
数据 60 个训练点,10 维,各列共享一个大公共因子
真值 落在前两个主成分方向上,系数 1.6 与负 1.1
约束方式 解只能落在前 k 个主成分张成的子空间里
主成分方差 前三个 541.9、14.6、10.8
子空间维数 k 训练 MSE 测试 MSE
---------------------------------
1 0.5237 0.3010
2 0.1564 0.1480
3 0.1514 0.1532
5 0.1427 0.1751
8 0.1355 0.1888
10 0.1341 0.1931
**先看两端:k 等于 1 时测试 0.3010,k 等于 10(全空间)时 0.1931。**
**k 等于 1 是约束太狠:只留一个方向,真值那两个方向被压成了影子。**
**k 等于 10 是根本没有约束:10 个参数里有 8 个在拟合噪声。**
**最低点是 k 等于 2,测试 0.1480。**
**为什么恰好是 2?因为真值就落在前两个主成分上——这不是巧合,是信息本身。**
**主成分方差那一行的落差更值得看:第一位 541.9,第三位只有 10.8。**
**后面那几个方向方差只有十几,最小二乘却会在那儿放上不小的系数,把噪声也吃进去。**
**所以砍掉它们,砍的不是参数,是「参数能去的方向」。**
**教材把它写成「解必须落在 P 的值域里」,P 就是前 k 个主成分的投影矩阵。**
**注意 k 从 3 加到 10,训练误差从 0.1514 掉到 0.1341,测试误差却从 0.1532 涨到 0.1931。**
**训练和测试反向,就是过拟合最直白的形态。**
要说明:**数据的相关性是我造的**(十维共用一个大公共因子),**真值也故意放在主成分上**,所以答案这么干净。
**真实数据里主成分方向不会这么对齐真值,k 只能靠交叉验证选。**
注意这里根本没有出现 P 那个公式。 因为「乘上正交基 Vk」和「乘上投影矩阵 P」在数值上是同一件事,而前者更稳。 Vk 的列是标准正交的,条件数天然是 1。 中心化那一步不能省。 SVD 分解的是 Xc 而不是 X:不中心化的话,第一个主成分会是「均值方向」,那是数据的整体偏移, 不是变化的方向。 训练误差随 k 单调降,和 3.2 那条性质一致:k 变大等于子空间变大,只加不减。 所以训练那一列不能用来选 k,这一点和第 3 章完全一样。 k 等于 2 恰好最优,是我设计的结果——我把真值放在了前两个主成分上。 真实数据没有这么听话:真值可能落在第 7 个主成分上,而那个方向方差很小。 那时候主成分回归会做出很自信的错答案。 要说明:数据的相关性是我造的(十维共用一个大公共因子)。 换成真实数据,主成分方向不会这么对齐真值,k 只能靠交叉验证选。
30 秒实验
- 把真值改成落在第 8、第 9 个主成分上(方差最小的那些),再跑一遍。 主成分回归会选哪个 k?它会不会彻底错过信号?
- 把 k 从 2 改到 4,看测试误差从 0.1480 涨到多少。涨的那部分,是方差还是偏差?
- 不做中心化直接 SVD,看第一个主成分变成什么方向。
用
Vt[0] @ Xc.T打印一下投影值,你会发现它几乎是常数。
常见误区
- 「加约束是为了防止过拟合」。这句话对,但太笼统。 精确的说法是:加约束把「解能去的方向」砍掉,用偏差换方差。 约束一定会让训练误差变大,如果它没变大,说明约束根本没起作用。
- 「主成分回归是标准做法」。 它的前提是「方差大等于重要」,这在很多领域不成立。 如果一个关键变量方差很小(比如一个很少触发的告警标志),它会被第一个砍掉。
- 「k 越大越好,反正训练误差在降」。 训练误差单调降,说明它不能用来选 k。 Demo 里 k 从 2 到 10,训练降了 0.02,测试涨了 0.045。
- 「子空间约束和岭回归是一回事」。 两者都在限制解,但方式不同:子空间约束是硬砍(某些方向直接禁止), 岭回归是软压(所有方向都缩,靠 λ 控制缩多少)。 结果上它们可以很接近,性质上不一样——第 4.2 节细说。
自测三问(点开对答案)
1. 为什么砍掉方差小的方向能改善测试误差? —— **因为那些方向上数据几乎没有信息,但最小二乘照样会在那里放上系数。** **放上去的系数拟合的是噪声,换一批数据就失效,这就是方差。** **砍掉它们等于承认「这些方向我不管了」,代价是偏差,收益是方差。** 2. 如果在真实业务里,某个重要变量的方差很小,主成分回归会怎样? —— **它会把这个变量所在的方向当成噪声砍掉,然后给出很自信的错答案。** **因为主成分只看 X 的方差,不看 X 和 y 的关系。** **这种错误很难从误差指标上看出来——训练误差和测试误差可能都还挺好。** 3. 子空间维数 k 和 4.2 的 λ,本质上是同一个东西吗? —— **都是「复杂度旋钮」,但方向相反:k 越大越复杂,λ 越大越简单。** **数学上,岭回归的解可以写成「对所有方向按奇异值加权收缩」,** **而子空间约束是「前 k 个权重 1、后面权重 0」。** **所以子空间约束是岭回归的一种极端版本:权重只有 0 和 1。**硬约束有个毛病: k 必须是整数,方向和取舍是断的。下一节换成软约束—— 每个方向都保留,但按重要性不同程度地压小。 这就是 ℓ2 约束,也就是岭回归。
- ☐ (本节完,下一节 4.2 4.2)