上一期结尾留了个尾巴:多项式阶数一高,系数就冲到天文数字——训练集 R² 完美,测试集一塌糊涂。当时说"解法是正则化,给 (XᵀX) 加一个惩罚项 λI",今天把这个尾巴收掉。
本期主角是岭回归(Ridge Regression)。它要治两类病:一类是第14期见过的共线性(XᵀX 快解不出来了),一类是上一期的高阶过拟合(系数爆炸)。一个 λ,两个都治。
01
病根:系数开始发疯
先回顾第14期那批学生数据——8 个学生,两个自变量:每周学习时间 x₁、每天睡眠时间 x₂,因变量是考试成绩 y:
上期算出来:两个自变量相关性 corr(x₁, x₂) = 0.9836,VIF = 30.65,用正规方程解出的系数是:
β = [62.42, 4.78, −3.39]
注意 β₂ = −3.39。翻译成人话:睡眠时间越长,成绩越低——每天多睡 1 小时,成绩掉 3.39 分。这显然不合理,学习时间和睡眠时间本来就都跟成绩正相关。
为什么冒出个负号?因为 x₁ 和 x₂ 几乎是同一条线:学习时间长的学生,睡眠时间也长(corr = 0.98)。两个几乎一样的变量并排放进模型,它不知道该把功劳算给谁,只好一个给很大的正系数,另一个给负系数去抵消。结果就是系数又大、符号又乱、换一批数据就翻天覆地。
💡 共线性的三大症状:系数符号反常、数值巨大、轻微扰动就剧变。根因是 XᵀX 接近奇异——正规方程里的 (XᵀX)-1把一个很小的扰动放大成了巨大的误差。
02
给系数画一个圈
上一期我们说过,RSS(残差平方和)在参数空间里是一张"地形图"。把 β₁、β₂ 当坐标轴,画出 RSS 的等高线:
左图是关键:等高线不是一个个同心圆,而是一条又窄又长的沟(像个峡谷)。OLS 解落在沟底(红星)。问题在于——这条沟太窄太长,谷底虽然明确,但只要数据抖一下,解就会沿着沟壁滑出去很远。这就是系数不稳定的几何画面。
怎么把解"拴住"?给它加一条约束:不许系数长得太大。
min RSS subject to β₁² + β₂² ≤ t²
意思是:在"系数不超出半径 t 的圆"这个范围内,找 RSS 最小的点。几何上就是——把 RSS 等高线和这个圆画在一起,解落在圆与等高线的切点上(右图)。
右图里:红五星是 OLS 解(在沟底,离原点很远,‖β‖ = 5.86);绿圈是约束边界;紫点是岭回归解(落在圆上,‖β‖ = 3.44)。把 λ = 6 代进去,解从 [62.42, 4.78, −3.39] 变成 [44.18, 3.35, +0.79]——睡眠时间的系数从负变正了,符合常识。
这个"带约束"的问题,用拉格朗日乘子一转,就等价于在 RSS 后面加一项惩罚:
min RSS + λ·‖β‖²
约束半径 t 和惩罚强度 λ 一一对应:t 越小(圈越小),λ 越大(罚得越狠)。所以"画个圈"和"加个 λ"是同一件事的两种说法。
一句话:岭回归 = 在原目标上加一条"系数别太大"的约束,把解从窄沟底拽回原点附近。系数变小了,却换来了稳定和正确的符号。
03
换个角度:λ 把小特征值抬起来
还有一个更本质的视角。上期的正规方程是 β = (XᵀX)-1Xᵀy。岭回归把公式改了一个字:
β = (XᵀX + λI)-1Xᵀy
就是在 XᵀX 的对角线上加一个 λ。别小看这一下——它直接改变了 XᵀX 的特征值。
特征值可以理解成"数据在某个方向上的铺开程度"。这批学生数据的相关矩阵特征值是:
大方向 1.9836,小方向只有 0.0164——两者差了两个数量级。条件数(最大特征值 ÷ 最小特征值)高达 120.6。
小的那个 0.0164 说明:有个方向上数据几乎没铺开(两个变量几乎重合),那个方向的系数就极其不稳定——这正是共线性的病根。
现在在对角线上加 λI,特征值整体平移:变成 [0.0164+λ, 1.9836+λ]。取 λ = 0.5 看效果:
看关键:λ 对大特征值几乎没影响(涨 25%),却把小特征值抬高了 31 倍。大方向该怎样还怎样,病态的小方向被补平了——条件数从 120.6 降到 4.8,矩阵从"快解不出来"变成"良态"。
顺带解决"不可逆"。只要 λ > 0,XᵀX + λI 的特征值全部 ≥ λ > 0,一定满秩、一定可逆——哪怕原来的 XᵀX 是奇异的。这也是第13期 SVD 里"伪逆"干的事:给奇异值兜个底。
04
L2 的圆 vs L1 的菱形
上一章的约束是 β₁² + β₂² ≤ t²,这叫 L2 惩罚(系数平方和)。还有一种常见选择:L1 惩罚,用 |β₁| + |β₂| ≤ t。两者的约束区域形状完全不同:
左图(L2):约束是一个圆,圆滑、没有尖角。等高线切在圆弧上的某个点,解被收缩,但一般不会正好落到坐标轴上——所以系数变小,却很少变成 0。
右图(L1):约束是一个菱形,四个角是尖的。等高线很容易切在尖角上,而尖角正好落在坐标轴上——于是某些系数直接变成 0。这就是 Lasso 能"自动选特征"的几何原因。
💡 岭回归用 L2,好处是解稳定、处处可导、有闭式解;代价是不会把系数压成 0——它保留所有特征,只是让它们"变小"。真要挑特征,得请 Lasso 或弹性网出场(下一期讲)。
05
岭迹图:λ 越大,系数越往 0 缩
把 λ 从极小调到极大,观察三个系数各自怎么变,画成图就是岭迹图:
横轴是 log10(λ),纵轴是系数值。三条线读几个关键位置:
看 λ 极大那一端(最右边):β₁、β₂ 几乎贴着 0,只剩截距 β₀ 还站着(74.40)。这时模型退化成"不管投入多少,都用平均值预测"。换句话说,λ 是一个"收缩力度"旋钮:λ→0 完全不收缩(等于 OLS),λ→∞ 把系数全压平(等于平均值)。
注意截距 β₀ 从头到尾没被压到 0(61.65 → 74.40)。因为岭回归只惩罚斜率系数,不惩罚截距——否则模型连数据的整体水平都对不上了。所以计算前要先把每列特征中心化(减去各自均值),截距才能被单独拎出来、不参与惩罚。
三条系数曲线起起伏伏、汇聚成一条"山岭"——这就是"岭回归"名字的由来。这张图本身也是选 λ 的工具:看哪条曲线在哪个 λ 之后开始趋于平稳,就在那里附近取值。
06
救活过拟合:20 家门店,15 次多项式
再看另一类病——上一期的高阶过拟合。这次换一批更大的数据:20 家门店的广告投入 x 与销售额 y(真实关系大约是 y ≈ 25 + 10√x,边际回报递减):
用 15 次多项式拟合:X = [1, x, x², …, x¹⁵],16 个参数,却只有 20 个样本。训练集和测试集对比:
左图(OLS):曲线在训练点上完美穿行(train R² = 0.982),但一到测试点就疯了——测试集 R² = −0.423,比"直接用平均值猜"还差。右图(岭回归,λ≈0.37):曲线平顺地贴着大趋势,train R² = 0.827(略降),但测试集 R² 回到 +0.882。
为什么 OLS 会崩?看系数就明白了:
左图是 OLS 的 15 个系数(对数刻度):最大的冲到 7.78×10¹⁰——几百亿。右图是岭回归的系数:全部被压到 10⁰ 附近(最大 6.88)。系数大到几百亿,只要 x 稍微一动,预测值就剧烈摆动——这正是过拟合的样子。
训练 R² 从 0.982 掉到 0.827,看着像"变差了",但测试 R² 从 −0.423 升到 0.882——这才是真本事。宁可训练时"差一点",也要在没见过的新数据上"稳一点"。这就是正则化的核心交换:牺牲一点训练精度,换一大截泛化能力。
07
λ 怎么选:留一交叉验证
λ 是人为设定的旋钮,选大了欠拟合、选小了没用。怎么定?——交叉验证。
最省数据的一种叫留一交叉验证(LOO-CV):每次拿掉 1 个样本当"临时测试",用剩下 19 个训练,去预测那个被拿掉的样本,记下误差;对 20 个样本各做一遍,平均误差就是当前 λ 的得分。把 λ 从 10⁻⁴ 扫到 10⁴,每个 λ 算一次,画出来是一条 U 形曲线:
蓝线是 LOO-CV 误差,读三个位置:
曲线两头高、中间低,最低点就是最优 λ ≈ 0.37。绿线(测试集 R²)也印证了这一点——它在同一个位置附近达到最高。把选出的 λ 代回去,就是第 06 章那条平顺的岭回归曲线。
实现起来几行 numpy 就够(注意中心化和 solve 的写法):
import numpy as np
# 岭回归:中心化 -> 截距不参与惩罚
def ridge_fit(Z, y, lam):
Zc = Z - Z.mean(axis=0)
yc = y - y.mean()
p = Z.shape[1]
# solve 比 inv 更稳更快
coef = np.linalg.solve(Zc.T @ Zc + lam * np.eye(p), Zc.T @ yc)
return y.mean() - Z.mean(axis=0) @ coef, coef
# 留一交叉验证误差
def loo_cv_mse(Z, y, lam):
n = len(y)
errs = []
for i in range(n):
mask = np.ones(n, dtype=bool); mask[i] = False
itc, cf = ridge_fit(Z[mask], y[mask], lam)
errs.append((y[i] - (itc + Z[i] @ cf)) ** 2)
return np.mean(errs)
# 扫描 λ,取 CV 误差最低点
lams = np.logspace(-4, 4, 120)
mse = [loo_cv_mse(Z, y, l) for l in lams]
lam_best = lams[int(np.argmin(mse))] # ≈ 0.37
💡 为什么不用训练 R² 选 λ?因为训练 R² 会骗你——λ 越小、模型越复杂,训练 R² 越高,但它衡量的是"背题能力",不是"考试能力"。CV 误差衡量的是泛化,所以它能找到真正的甜点。这和上一期"看 test R² 不只看 train R²"是同一条原则。
08
总结与边界
把这一期串起来:
① 共线性 → XᵀX 接近奇异 → 系数爆炸、符号错乱,根因是小特征值太小。
② 岭回归 = (XᵀX + λI)-1Xᵀy,等价于给系数加一个 ‖β‖² ≤ t 的圆约束。
③ 从特征值看:加 λI 把小特征值抬起来,条件数从 120.6 降到 4.8。
④ L2 圆 → 收缩不归零;L1 菱形 → 尖角切点使系数归零(Lasso 自动选特征)。
⑤ λ 用交叉验证选:太小过拟合、太大欠拟合,最低点就是甜点。
岭回归也有边界:
① 不产生稀疏解。它保留所有特征(只是变小),要做特征选择得用 Lasso / 弹性网。
② 用偏差换方差。系数被"压小"意味着引入了偏差(bias)——这是偏差-方差权衡的另一面:牺牲无偏,换来更小的方差。
③ 必须先标准化。若各特征量纲差很多(一个以万元计、一个以个数计),惩罚对量纲大的特征不公平,结果会失真。
④ λ 依赖数据。不同问题的最优 λ 天差地别,没有万能值,必须现场交叉验证。
最后回顾数学建模系列这条主线