直觉:模型为什么会"背答案"

给一个学生十道题和标准答案,他可能学会解题方法(泛化),也可能死记每道题的答案(过拟合)。考新题时,前者答得好,后者一塌糊涂。机器学习里,过拟合就是模型把训练集里的噪声、偶然规律也当成信号记了下来;欠拟合则是模型连训练集里的真实规律都没抓住。前者训练误差很低、测试误差很高;后者两者都高。

我们要的是中间那个甜点。理解这个甜点的经典框架,就是偏差-方差分解。

机制与公式:偏差-方差分解

设真实关系为 y=f(x)+εy = f(x) + \varepsilon,噪声 ε\varepsilon 均值为 0、方差为 σ2\sigma^2。我们用训练集 DD 训练出预测器 f^D(x)\hat f_D(x)。在某个测试点 xx 上,对所有可能的训练集 DD 取期望,平方误差可以精确分解为:

\mathbb{E}_D\big[(y - \hat f_D(x))^2\big] = \underbrace{\big(f(x) - \mathbb{E}_D[\hat f_D(x)]\big)^2}_{\text{偏差}^2} + \underbrace{\mathbb{E}_D\big[(\hat f_D(x) - \mathbb{E}_D[\hat f_D(x)])^2\big]}_{\text{方差}} + \underbrace{\sigma^2}_{\text{不可约误差}}

三项的含义:

  • 偏差:模型平均预测与真值的系统性偏差。模型太简单(如用直线拟合曲线)→ 高偏差 → 欠拟合。
  • 方差:训练集换一批,模型预测会抖动多少。模型太灵活(高容量)→ 对训练集的噪声敏感 → 高方差 → 过拟合。
  • 不可约误差 σ2\sigma^2:数据本身的噪声下界,再好的模型也消不掉。

提升模型容量(更多参数、更高多项式阶、更深的树)通常降低偏差、抬高方差。总误差是两者之和的 U 形曲线,最优容量在底部。这就是"权衡"二字的来源:你不能同时把两项压到最低。

正则化:在损失里给复杂度加价

正则化的本质,是在优化目标里给"模型复杂度"加一项惩罚,逼模型在拟合数据和保持简单之间折中——也就是主动牺牲一点偏差换取更大的方差下降。

最常见的是 L2(岭回归)和 L1(Lasso):

LL2=i(yiy^i)2+λw22,LL1=i(yiy^i)2+λw1\mathcal{L}_{\text{L2}} = \sum_i (y_i - \hat y_i)^2 + \lambda \|w\|_2^2, \qquad \mathcal{L}_{\text{L1}} = \sum_i (y_i - \hat y_i)^2 + \lambda \|w\|_1

  • L2 把权重整体往 0 收缩(shrinkage),但很少压到恰好为 0;它对应在权重上加了一个高斯先验(MAP 视角)。
  • L1 倾向产生稀疏解(很多权重恰好为 0),因此自带特征选择;它对应拉普拉斯先验。几何上,L1 的约束区域是带尖角的菱形,等高线更容易在坐标轴上相切,于是某些维度被压到 0。

λ\lambda 是旋钮:λ0\lambda \to 0 退化为无正则(高方差风险),λ\lambda \to \infty 把权重全压死(高偏差)。它本身要靠验证集或交叉验证来选,不能用训练集选。

代码:用验证曲线找甜点

下面用岭回归在不同 λ\lambda 下扫一遍,观察训练/验证误差的剪刀差,定位过拟合区与欠拟合区:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import numpy as np
from sklearn.linear_model import Ridge
from sklearn.model_selection import cross_val_score
from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import make_pipeline

rng = np.random.default_rng(0)
X = np.sort(rng.uniform(-3, 3, 60)).reshape(-1, 1)
y = np.sin(X).ravel() + 0.3 * rng.standard_normal(60) # 真信号 + 噪声

for lam in [1e-4, 1e-1, 1e0, 1e2]:
model = make_pipeline(PolynomialFeatures(12), Ridge(alpha=lam))
# neg MSE:越接近 0 越好;lam 太小 -> 过拟合,太大 -> 欠拟合
cv = -cross_val_score(model, X, y, scoring="neg_mean_squared_error", cv=5).mean()
print(f"lambda={lam:<7} cv_mse={cv:.3f}")

典型现象:λ\lambda 很小时验证 MSE 偏大(高方差/过拟合),λ\lambda 很大时也偏大(高偏差/欠拟合),中间出现最小值。

工程权衡与其他正则手段

正则化远不止 L1/L2。深度学习里常用的还有:

  • Dropout:训练时随机置零一部分激活,相当于对指数级子网络做隐式集成,降低神经元间的"共适应"。推理时关闭并按比例缩放。
  • Early stopping:监控验证损失,连续若干 epoch 不下降就停。它隐式限制了权重远离初始化的距离,效果上类似 L2。
  • 数据增强:通过对输入做保持标签不变的变换(翻转、裁剪、加噪),等价于扩充数据分布,是降低方差最直接的手段。
  • 权重衰减(weight decay):在 SGD 中等价于 L2,但在 Adam 等自适应优化器里两者并不等价(这就是 AdamW 把 weight decay 从梯度里解耦出来的原因,一个经典踩坑点)。
  • Batch/Layer Norm:主要为稳定训练,但也带来一定正则副作用。

工程上的几条经验:

  • 更多高质量数据通常胜过更强正则。 数据增多会把方差项整体压下来,让你能安全地用更大容量。
  • 正则强度与模型容量要联调。 大模型 + 强正则,往往比小模型 + 弱正则更稳。
  • 不要用训练误差判断过拟合。 永远盯验证集。训练误差持续下降而验证误差掉头向上,是过拟合的标准信号。

常见误区

  • “参数越多必然过拟合” —— 不一定。过参数化的深网在合适正则与优化下仍能泛化("双下降"现象表明,越过插值点后测试误差可能再次下降),经典 U 形曲线只是故事的一部分。
  • “加了 L2 就不会过拟合” —— λ\lambda 选错照样过拟合或欠拟合,正则不是开关而是连续旋钮。
  • 用测试集调 λ\lambda —— 这会让测试集信息泄漏进模型选择,测试指标变得乐观失真。λ\lambda 必须用独立验证集选。

小结

过拟合与欠拟合是同一枚硬币的两面,偏差-方差分解给了它们精确的数学坐标:总误差 = 偏差² + 方差 + 不可约噪声,而模型容量在偏差和方差之间做权衡,最优点在 U 形曲线底部。正则化(L1/L2/Dropout/early stopping/数据增强)的统一逻辑,是花一点偏差买回更多方差。落地时记住三件事:盯验证集、把正则强度当连续旋钮和容量联调、能加数据就加数据。