网络是怎么“学“的:损失函数与梯度下降
上两篇我们把网络的架子搭好了——输入进去,一层层传,输出出来。但我们还差最关键的一步没讲:怎么让网络从"瞎蒙"变成"能分辨猫和狗"。
这篇就讲这个。先讲怎么量化错误(损失函数),再讲怎么根据这个错误去调整参数(梯度下降)。这两件事合在一起,就是机器学习的核心。
你得先知道自己有多烂
刚初始化的时候,网络上所有的 w 和 b 都是随机数。你给它一张猫的照片,它输出"0.3% 概率是猫"。基本等于瞎蒙。
怎么量化它蒙得有多烂?需要一个损失函数(Loss Function)。
损失函数要做的:接收网络的预测值和真实标签,输出一个正数。这个数越大,模型越烂;越小,模型越好。
训练的目标就一句话:让损失函数的输出尽量小。
回归问题用 MSE
先讲最简单的。你做一个房价预测模型,真实数据:
- 50 平米 → 150 万
- 80 平米 → 230 万
- 120 平米 → 310 万
你的模型预测:
- 50 平米 → 180 万(高了 30 万)
- 80 平米 → 200 万(低了 30 万)
- 120 平米 → 350 万(高了 40 万)
你直观感受是"差了几十万"。但需要的是一个数字来概括整体错误有多严重。
均方误差 MSE(Mean Squared Error):
MSE = (30² + 30² + 40²) / 3
= (900 + 900 + 1600) / 3
= 1133.33
每个样本的误差,平方,再求平均。
平方有两个好处:
- 正负误差不会互相抵消。一个高估一个低估,直接加起来会部分抵消,算出来好像没错。平方以后全变正数
- 大误差被指数级放大惩罚。差 40 的惩罚是 1600,差 30 的惩罚是 900,差 40 的惩罚是差 30 的近两倍。模型会被迫更关注离谱的输出
MAE(Mean Absolute Error)——用绝对值代替平方:
MAE = (30 + 30 + 40) / 3 = 33.33
MAE 不会放大离群点的惩罚。如果数据里有极端值,MAE 比 MSE 稳。反过来,如果大部分预测都比较准但偶尔出现离谱情况,MSE 会死磕那个异常点。
工业界的习惯:默认用 MSE,数据里离群点多换成 MAE。两个都试试也行。
分类问题不能用 MSE
你做一个猫狗识别。真实的标签是"猫"(用数字 0 表示),模型输出的是"是猫的概率"(一个 0~1 之间的数)。
如果真实是猫,模型预测"0.001% 概率是猫"——这个错误是灾难级的,你希望损失函数给一个极大的惩罚。
MSE 算一下:(1 - 0.001)² = 0.998。这数不大。
MSE 在分类问题上有两个毛病:
- 当模型错得很离谱的时候(预测接近 0 但真实是 1),输出接近 0 的神经元已经进入了 sigmoid 的"饱和区",梯度几乎为零,学不动
- 损失数值跟错误严重程度不匹配
**交叉熵(Cross Entropy)**专门解决这个。
二分类版本:
Loss = -[y·log(ŷ) + (1-y)·log(1-ŷ)]
y 是真实标签(0 或 1),ŷ 是预测概率。
如果真实是 1(是猫):
| 预测概率 ŷ | Loss | 解释 |
|---|---|---|
| 0.99 | 0.01 | 很准,几乎不罚 |
| 0.9 | 0.10 | 还可以 |
| 0.5 | 0.69 | 跟抛硬币差不多 |
| 0.01 | 4.60 | 错得离谱,爆炸式惩罚 |
不需要记这个公式。所有深度学习框架都有现成的 CrossEntropyLoss,你要知道的是为什么分类用交叉熵而不是 MSE——因为交叉熵"错了就狠狠打",梯度信号强,模型学得快。
多分类的情况(比如识别 0-9 十个数字),交叉熵的扩展版本叫 Softmax + Cross Entropy。PyTorch 的 nn.CrossEntropyLoss() 里已经内置了 Softmax 步骤,所以网络最后一层不要加 Softmax,直接输出原始分数就行。
损失函数怎么选,总结
| 问题类型 | 损失函数 | 输出层激活 |
|---|---|---|
| 回归(预测数值) | MSE 或 MAE | 不加激活(线性) |
| 二分类(是/否) | 二元交叉熵 | Sigmoid |
| 多分类(猫/狗/鸟) | 交叉熵 | Softmax(或直接用 raw logits) |
这张表够你用 90% 的场景了。
好了,知道损失值了,然后呢
假设网络有两万个参数(w1, w2, …, w20000),当前的损失值是 5.7。
你现在的任务是:调整这两万个参数,让损失值从 5.7 往下降。
怎么调?
直觉的办法是穷举——每个参数试几个值,排列组合。两万个参数,每个只试 2 个取值,组合数是 2 的 20000 次方。宇宙里的原子总数大约是 2 的 260 次方。你感受一下这个差距。
需要一个聪明的方法:梯度下降。
从一个参数讲起
假设我们只有一个参数 w,损失函数 L 关于 w 的图像是一条曲线。
我们在曲线上一个随机点,目标是走到最低点。眼睛能看到全景当然好走,但实际的问题是——你看不到整条曲线,你只能看到你脚下的那一个点。
导数在这个点告诉我们:朝 w 增大方向走,L 是变大还是变小。导数是正数 → L 在上升 → 应该往反方向走。导数是负数 → L 在下降 → 继续沿这个方向走。
所以,不管是正是负,一定是沿着导数的反方向走一步:
w_new = w_old - learning_rate × 导数
导数就是 gradient(梯度),所以这个操作叫"梯度下降"。
如果只有一个参数,梯度 = 导数。有两万个参数,梯度就是一个包含两万个数的向量,每个数表示你动一下对应的参数,损失值会怎么变。
不需要手动算这些导数。PyTorch 有了 loss.backward() 以后自动帮你把所有参数的梯度求出来。下一篇写代码的时候你就会看到,两行代码就搞定这件事。
学习率:步子迈多大
learning_rate 控制每一步的跨度。
太大:一步从半山坡蹦到对面山上去了。下次又蹦回来。loss 震荡不收敛。比如你从 lr=0.001 改成 lr=1,loss 可能从 2.3 蹦到 5.1 蹦到 0.8 蹦到 9.2,永远落不到谷底。
太小:一步挪一毫米。loss 确实在降,但降了一小时还在半山腰。
挑学习率不那么科学,就是试试 0.1、0.01、0.001、0.0001,看哪个能让 loss 在最初的几十个迭代里稳定下降。0.001 或 0.0003 是常见的起点。
还有一个技巧叫学习率衰减:刚开始步子大点,快速靠近谷底;后面步子变小,精细调整。现代的优化器(如 Adam)会帮你自动做类似的事情。
三种下降方式
数据量大的时候,每次算梯度之前要不要看全部数据?
批量梯度下降(BGD):每次把全部训练数据算一遍,得一个全局最准的梯度,然后更新一次参数。
准是准,但数据量一大慢到绝望。百万级数据算一次梯度可能要几分钟,而你可能需要几万次更新。现实中几乎没人用纯 BGD。
随机梯度下降(SGD):每次只看一条数据,马上更新参数。
快,但梯度方向非常不稳定——这条数据说"w 减小",下一条说"w 增大",参数被来回拉扯。最后能到目的地,但走得很颠簸。
小批量梯度下降(Mini-batch SGD):折中方案。每次看一小批数据(比如 64 条),算这批数据的平均梯度,然后更新。
既不慢,又不太颠。这是标准做法,你以后写"用 SGD 训练"指的其实就是 Mini-batch SGD。batch_size 常见值:32、64、128,取决于显存。
从 SGD 到 Adam
最基本的 SGD 有一个问题:在山谷地形里(一个方向很陡、另一个方向很缓),SGD 会在陡峭方向上反复震荡,而在平缓方向上进展缓慢。
后来出了动量法(Momentum):不光考虑当前的梯度,还"记住"之前的更新方向。就像一个小球从山坡上滚下来,越滚越快,但碰到局部上升也能靠惯性冲过去。同时能减少震荡。
再后来出了Adam:在动量法的基础上,还给每个参数分配了自适应的学习率——变化频繁的参数用较小的步长,变化稀疏的参数用较大的步长。
所以你现在入门,直接用 Adam 就行,不用调太多。等你的 loss 降不下去的时候,再切换到 SGD + Momentum 试试。这个建议够用到你第一个项目完成。
局部最优的真相
很多科普文章会拿三维地形图来吓唬你:梯度下降会困在一个小山坳里,永远看不到远处真正的深谷。
三维世界里确实是这样。但深度学习里的参数空间不是三维,而是几万维。
高维空间中,一个点要成为局部极小值,需要在每一个方向上都是最小值。也就是说,两万个参数各自的方向上都不能是下坡。这件事概率极低。我们真正困扰的更多是鞍点(有些方向上升、有些方向下降),但鞍点可以通过动量和自适应学习率冲过去。
实际训练中,loss 降不下去的原因绝大多数是:学习率不对、数据有问题、网络结构有问题、梯度消失/爆炸——不是"困在局部最优"了。
收一下
训练网络 = 做两件事的循环:
1. 前向传播:算一遍输出,得到损失值(知道有多烂)
2. 反向传播 + 梯度下降:算出每个参数的梯度,用 w_new = w_old - lr×梯度 更新
重复几千轮。
下一个问题:把"某个参数梯度 = -0.42"这个数算出来的过程,就是反向传播。这个推导过程有些繁琐(核心是链式法则,数学上不深但步骤多),入门阶段不需要精通。知道它等价于"把错误信号从输出层往回传,按贡献大小把责任摊到每个参数头上"就够了。下一篇我们放下理论,直接写代码跑起来。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)