泛化诊断考点:欠拟合与过拟合的识别、偏差—方差分解、L1 与 L2 正则化、早停与数据增强。通关标准:给定训练与验证误差的走势,三分钟内开出诊断书与药方。本章收官关,把前两关的模型与指标串成闭环。
关卡一(单选):训练集准确率 0.99,验证集准确率 0.71,最可能的诊断是:
A. 欠拟合 B. 过拟合 C. 数据泄漏 D. 学习率过大
关卡二(单选):关于 L1 与 L2 正则化,说法正确的是:
A. 两者都会把部分权重精确压成零
B. L1 倾向产生稀疏解,可顺带做特征选择;L2 把权重整体压小但不压成零
C. L2 产生稀疏解,L1 让权重分布更平滑
D. 正则化越强,验证误差一定越低
关卡三(简答):用偏差—方差语言解释"模型复杂度—验证误差"的 U 形曲线,并说明 K 折交叉验证观察到的大波动指向什么。
关卡四(判断):"发现过拟合后,第一反应应该是换更复杂的模型。"对吗?
关卡一选 B。 训练误差极低、验证误差明显落后,是教科书式的过拟合:模型把训练集的噪声也背了下来。C 也是"验证指标异常"的常见原因,但泄漏通常表现为验证集虚高而非双边劈叉;D 的症状是训练本身不收敛。临床问诊时要三个都排查,但题面给的证据链指向 B。
关卡二选 B。 L1 惩罚权重绝对值之和,约束区域是菱形,最优解容易被推到坐标轴上,产生精确为零的权重,天然稀疏;L2 惩罚权重平方和,约束区域是圆,解倾向于把权重整体摊小而非置零。D 错得最典型:正则强度过头会把模型压成欠拟合,验证误差先降后升,需要用验证集调强度。
关卡三:总误差 = 偏差平方 + 方差 + 不可约噪声。复杂度低时偏差主导(欠拟合区);复杂度高时方差主导(过拟合区);谷底是两者的平衡点。K 折各折指标波动大,说明模型对"喂哪部分数据"敏感,即方差大——药方自然指向降方差手段(更多数据、正则化、简化模型、集成)。
关卡四:错。 过拟合的病根正是容量过剩、数据不足,换更复杂的模型等于火上浇油。正确第一反应按成本排序:先加数据或做数据增强,再上正则化与早停,最后才考虑简化结构。
用一组多项式拟合实验把 U 形曲线亲手跑出来(纯标准库实现最小二乘):
# 多项式阶数扫描:亲手复现"复杂度-误差"U 形曲线 import random random.seed(3) # 真实规律是一次函数,叠加噪声 x = [i / 10 for i in range(20)] y = [2 * xi + 1 + random.gauss(0, 0.3) for xi in x] def fit_poly(xs, ys, deg): """最小二乘拟合,返回系数(幂次从高到低)""" n = deg + 1 A = [[sum(xi ** (i + j) for xi in xs) for j in range(n)] for i in range(n)] b = [sum(yi * xi ** i for xi, yi in zip(xs, ys)) for i in range(n)] for col in range(n): # 高斯消元 piv = max(range(col, n), key=lambda r: abs(A[r][col])) A[col], A[piv] = A[piv], A[col] b[col], b[piv] = b[piv], b[col] for r in range(col + 1, n): f = A[r][col] / A[col][col] for c in range(col, n): A[r][c] -= f * A[col][c] b[r] -= f * b[col] coef = [0.0] * n for r in range(n - 1, -1, -1): coef[r] = (b[r] - sum(A[r][c] * coef[c] for c in range(r + 1, n))) / A[r][r] return coef[::-1] def eval_poly(coef, x): return sum(c * x ** i for i, c in enumerate(coef[::-1])) def mse(coef, xs, ys): return sum((eval_poly(coef, xi) - yi) ** 2 for xi, yi in zip(xs, ys)) / len(xs) # 前 12 个点做训练,后 8 个做验证 train_x, train_y = x[:12], y[:12] val_x, val_y = x[12:], y[12:] for deg in [1, 3, 9]: w = fit_poly(train_x, train_y, deg) print(f"阶数 {deg}: 训练MSE={mse(w, train_x, train_y):.3f} 验证MSE={mse(w, val_x, val_y):.3f}") # 一次典型输出: # 阶数 1: 训练MSE=0.053 验证MSE=0.223 # 阶数 3: 训练MSE=0.051 验证MSE=1.695 # 阶数 9: 训练MSE=0.011 验证MSE=8534966265.889
阶数拉到九阶,训练误差继续下探(曲线几乎穿过每个训练点),验证误差直接爆表——这就是关卡一那组数字的生成机制。
再验证 L2 正则化的"压权重"效果(岭回归:在对角线上加惩罚):
# 岭回归示意:lambda 越大,权重范数越小 def ridge_norm(lam): xs, ys = train_x, train_y n = 2 # 一阶多项式两个系数 A = [[sum(xi ** (i + j) for xi in xs) + (lam if i == j else 0) for j in range(n)] for i in range(n)] b = [sum(yi * xi ** i for xi, yi in zip(xs, ys)) for i in range(n)] # 高斯消元同上(二元直接求解) det = A[0][0] * A[1][1] - A[0][1] * A[1][0] w0 = (b[0] * A[1][1] - A[0][1] * b[1]) / det w1 = (A[0][0] * b[1] - b[0] * A[1][0]) / det return w0, w1 for lam in [0.0, 0.5, 5.0, 50.0]: w0, w1 = ridge_norm(lam) print(f"lambda={lam:<5} 权重范数={(w0**2 + w1**2) ** 0.5:.3f} 斜率={w1:.3f}") # 一次典型输出: # lambda=0.0 权重范数=2.339 斜率=2.077 # lambda=0.5 权重范数=2.106 斜率=1.714 # lambda=5.0 权重范数=1.535 斜率=0.970 # lambda=50.0 权重范数=0.484 斜率=0.272
惩罚一路加码,权重范数单调收缩、斜率向零回缩——"L2 把权重整体压小"不再是背诵,而是你跑出来的事实。注意最后两行的另一个教训:正则过头,模型连真实规律(斜率约为二)都被压扁了,这正是关卡二 D 选项的错误机制。
易错点一:"训练集准确率高就是好模型"。高训练准确率既可能是学得好也可能是背书,必须与验证误差对照着读。汇报模型只报训练指标,是工程评审里的红线。
易错点二:把"偏差大方差小"背成固定搭配就套用。正确姿势是先看症状定区间:双边误差都高 → 偏差主导 → 加容量加特征;训练低验证高 → 方差主导 → 降容量加数据加正则。
变式一:题干换成"训练误差随轮数下降,验证误差先降后升",问干预手段。标准答法:早停——在验证误差回升的拐点附近停止训练,本质是用训练轮数当复杂度旋钮,等于免费的正则化。
变式二:面试追问"Dropout 属于哪种机制"。答:训练时随机屏蔽部分神经元,等效于训练指数级多个子网络的集成、推理时缩放还原,是深度学习里的方差压制手段;与 L1/L2 互补,常一起用。
变式三:问"数据增强为什么能治过拟合"。答:它扩充了有效训练分布、打碎了模型死记样本的路径,属于"加数据"药方的低成本版本;对图像的翻转裁剪、对文本的同义替换都是同一原理。
本章通关。带上"诊断书 + 药方"进入深度学习编队——那里梯度消失、死亡激活等新病种,本质上仍是本章泛化诊断思想的延伸。