🎯 本课核心要解决的两大问题
正则化(Regularization):模型在训练集上太“死记硬背”了(过拟合),我们怎么给它套个紧箍咒,让它在新数据上也能考高分?
优化(Optimization):上一课我们有了损失函数(痛苦指标),这节课我们具体怎么下山——怎么一步步修改权重 WW,让损失降到最低?
第一部分:正则化(Regularization)—— 给模型戴上“紧箍咒”
1. 为什么要正则化?(奥卡姆剃刀原则)
白话解释:如果两个模型在训练集上都得了100分(完全猜对),我们优先选那个更简单的模型。如果简单模型实在搞不定,再换复杂的。
2. 损失函数的新公式(加刑!)
现在损失函数变成两部分:
总损失=数据损失(Data Loss)+λ×正则化损失(Regularization Loss)
数据损失:模型在训练集上猜得有多痛苦(越大越差)。
正则化损失:模型自己带了多少“累赘”(权重越大,惩罚越重)。
λ(Lambda):正则化强度。这是一个超参数(和KNN里的K一样,要用验证集来调)。
λ=0:完全不管,随便模型怎么复杂。
λ=∞(无穷大):模型敢有任何权重,直接罚死,模型变成全0(啥也学不到)。
3. 两大常用正则化武器:L1 和 L2(一定要分清!)
假设我们有一个权重向量 w=[0.5,−0.5,1.0,0.0]:
| 正则化类型 | 数学公式 | 计算示例(代入w) | 性格特点(几何直观) |
|---|---|---|---|
| L2 正则化(岭回归) | 0.25+0.25+1.0+0=1.5 | “和稀泥”:它讨厌大数字,逼迫所有权重都尽量小且分散。因为它平方了,大数字(1.0)的惩罚(1.0)比小数字(0.5)的惩罚(0.25)大得多。 | |
| L1 正则化(Lasso) | 0.5+0.5+1.0+0=2.0 | “极端洁癖”:它只算绝对值,会让大量不重要的特征权重直接变成0(稀疏矩阵)。用来做特征选择特别好。 |
🎨灵魂画图(L1 vs L2 的等高线):
想象在二维坐标轴(权重 w1w1, w2w2)上:
L2的等高线是个圆形。它跟损失函数相交时,很容易切在坐标轴上非零的地方,所以权重多为非零小值。
L1的等高线是个菱形(正方形旋转45度)。它的尖角顶在坐标轴上,所以很多权重会被强制切成0。
选哪个?
如果特征数量巨大(比如文本分析),认为很多特征没用 → 用L1(砍掉无用特征)。
如果特征本来就很重要(比如图像像素),认为大家都有用 → 用L2(让大家平分权重)。
第二部分:优化(Optimization)—— 怎么找到最好的 W?
1. 最蠢但最直观的办法:随机搜索(Random Search)
做法:生成几千组随机的 W,挨个算损失,选损失最小的那个。
效果:在CIFAR-10(10类)上,瞎蒙准确率是10%,随机搜索最好能到15.5%。
结论:比瞎蒙强点,但跟傻子差不多,绝不可取。
2. 聪明办法:沿着斜坡往下滚(梯度下降)
既然我们要求损失函数 L(W) 的最小值,而高维空间我们想象不出来,我们看一维情况(只有一个权重 x):
导数的定义(斜率):
导数大于0,函数在上升;导数小于0,函数在下降。
在多维空间(有N个权重)中:
我们把每个权重的偏导数()拼成一个向量,这就是梯度(Gradient)。
梯度的方向:指向函数值上升最快的方向。
我们想要下降,所以参数更新公式是:
学习率×梯度
3. 梯度下降的代码骨架(最基础的版本)
# 伪代码 while True: # 1. 根据当前权重w,计算损失函数关于w的梯度 dw = compute_gradient(loss_function, data, w) # 2. 沿着梯度的反方向,迈出一步(步长 = learning_rate) w -= learning_rate * dw第三部分:深入梯度下降的痛点(为什么需要高级优化器?)
最基本的梯度下降(Batch Gradient Descent)有两个致命缺点:
痛点1:计算太慢
如果训练集有100万张图,算一次梯度要跑遍100万张,太慢。
解法:随机梯度下降(SGD)
每次只随机抽一小批(Mini-batch)数据(比如256张)来算梯度,用这个梯度代表全局梯度。
痛点2:在“陡峭峡谷”中左右横跳(高条件数)
图示:
text
⛰️ 山脊(陡峭方向) ↑ 振荡巨大 ←—————→ (我们想去的平坦方向,但走得极慢)
如果损失函数的形状像个长条形峡谷(一个方向特别陡,另一个方向特别平),SGD会在陡坡上左右震荡,在平坡上像蜗牛爬。
痛点3:陷入局部极小值(Local Minima)和鞍点(Saddle Point)
局部极小值:掉进小坑里,以为到底了,其实旁边还有个深谷。
鞍点(这是高维噩梦!):
在二维图上像个马鞍,一个方向向上,一个方向向下,梯度在此处为0。
关键认知:在深度学习(高维空间)中,局部极小值很少见,满大街都是鞍点!因为要让所有方向都向上太难了,只要有某个方向是平的或向下的,梯度就是0,SGD就卡死在这里不动了。
第四部分:四大优化器进化史
为了解决上述痛点,优化器经历了四代进化。我们把dx当成当前算出来的梯度。
🥇 第一代:Vanilla SGD(原始版)
# 缺点:在峡谷震荡,在鞍点卡死 x -= learning_rate * dx🥈 第二代:SGD + Momentum(动量法)—— 像推铁球下山
核心思想:不只看当前的梯度,还要积累历史速度(惯性)。如果一直往一个方向走,速度越来越快,能冲过鞍点和小坑。
# 先定义速度 v,初始为0 v = 0 while True: dx = compute_gradient(x) # 更新速度:旧速度*动量系数 + 当前梯度 v = mu * v - learning_rate * dx # mu通常取0.9 x += v # 更新参数作用:在峡谷的陡峭方向,梯度正负震荡,累加起来抵消了;在平坦方向,一直有微小的正梯度,越积越大,嗖的一下就冲出去了!
🥉 第三代:RMSProp(自适应学习率)—— 给每个参数单独调步长
核心思想:梯度大的参数(陡峭方向),步长调小点防止震荡;梯度小的参数(平坦方向),步长调大点加速前进。
# 缓存梯度平方的移动平均 grad_squared = 0 while True: dx = compute_gradient(x) # 累加平方梯度(哪边陡,哪边累加得大) grad_squared = decay_rate * grad_squared + (1 - decay_rate) * dx * dx # 更新参数:除以 sqrt(累加平方),陡峭方向除以大数,步长变小;平坦方向除以小数,步长变大 x -= learning_rate * dx / (np.sqrt(grad_squared) + 1e-7) # 1e-7防止除以0🏆 第四代:Adam(目前最主流,Momentum + RMSProp 的私生子)
核心思想:既有动量(存历史梯度),又有自适应学习率(存历史平方梯度)。
⚠️偏差修正(Bias Correction),这是Adam防止刚开始训练时步子迈太大的关键!
没有偏差修正的Adam(起步会爆炸!):
first_moment = 0 # 一阶动量(带速度) second_moment = 0 # 二阶动量(自适应) while True: dx = compute_gradient(x) first_moment = beta1 * first_moment + (1 - beta1) * dx # 类似Momentum second_moment = beta2 * second_moment + (1 - beta2) * dx*dx # 类似RMSProp x -= lr * first_moment / (np.sqrt(second_moment) + 1e-7)问题:因为first_moment和second_moment初始都是0,在训练刚开始(t=1)时,算出来的值会被初始化为极小的数,导致分母极小,除以它后第一步步长巨大无比,直接飞出银河系!
✅ 加了偏差修正:
first_moment = 0 second_moment = 0 for t in range(1, num_iterations): dx = compute_gradient(x) first_moment = beta1 * first_moment + (1 - beta1) * dx second_moment = beta2 * second_moment + (1 - beta2) * dx * dx # 🔥 关键修正:除以 (1 - beta^t),随着t增大,分母趋近于1,修正逐渐消失 first_unbias = first_moment / (1 - beta1 ** t) second_unbias = second_moment / (1 - beta2 ** t) x -= learning_rate * first_unbias / (np.sqrt(second_unbias) + 1e-7)结论:Adam通常默认参数
beta1=0.9,beta2=0.999,learning_rate=1e-3。在99%的CV任务中,先无脑上Adam,效果都不差。
第五部分:学习率(Learning Rate)—— 你迈的步子有多大?
学习率是最重要的超参数,没有之一!
太高(10−110−1):步子太大,在谷底左右横跳,甚至越过山丘,Loss直接爆掉(变成NaN)。
太低(10−610−6):步子太小,像蚂蚁爬山,训练100年也不收敛。
理想(10−3∼10−410−3∼10−4):稳定下降。
学习率衰减(Learning Rate Schedules)—— 训练到一半要“慢下来”
刚开始可以大步流星,快到底部时如果步子还很大,就会在最优解附近转圈,永远落不下去。所以:
阶梯式衰减(Step Decay):每训练30轮(Epoch),学习率直接除以10(比如ResNet的经典操作)。
余弦退火(Cosine Annealing):像余弦曲线一样平滑地从最大值降到0。
线性预热(Linear Warmup):刚开始用极小的学习率(比如最大值的10%)跑几轮,防止Adam起步爆炸,再慢慢升上去。
第六部分:正则化与优化器的关系(AdamW 小科普)
在普通的SGD中,L2正则化(λwλw)和权重衰减(Weight Decay)是等价的。
但在Adam中,因为自适应学习率的存在,L2正则化会失效(梯度大的被缩小,梯度小的被放大,导致L2惩罚不均)。
现代正解:用AdamW。它不把正则化加在梯度里,而是直接在更新参数的最后,硬生生把权重乘一个小于1的系数(如0.995),这叫权重衰减,干净又利落。
💡 第三课终极认知闭环
正则化:给损失函数加刑(L1稀疏,L2平滑),λλ 控制惩罚强度,防止模型在训练集上作弊。
优化本质:利用梯度(导数向量)指引方向,负梯度下山。
SGD太笨:遇到峡谷震荡,遇到鞍点卡死,计算全数据集太慢。
Momentum:加个惯性,冲过鞍点,抵消震荡。
RMSProp:自适应学习率,陡坡慢点走,平坡快点走。
Adam(集大成者)= Momentum + RMSProp +偏差修正(防止起步炸),当下CV默认首选。
学习率策略:配合预热和衰减,让模型最后稳稳落进谷底。
代码避坑指南:
在PyTorch里,一行
torch.optim.Adam(model.parameters(), lr=1e-3, weight_decay=1e-4)就把Adam和L2正则化全搞定了。如果训练时Loss突然变成
nan,99%是学习率设太高了,赶紧降低一个数量级。