
Loss 不是训练日志上的数字。Loss 是模型学习的方向盘。

1. 模型本来不知道对错
模型只会输出数字。图片进来,它吐出 logits;文本进来,它吐出分数;表格进来,它吐出预测值。
这些数字没有天然意义。Loss 的作用,就是把“预测”和“真实答案”放在一起比较,变成一个可以反向传播的标量。
Loss 小,说明预测更接近目标。Loss 大,说明偏离目标更远。
一句话:Loss 把业务目标翻译成数学目标。
2. Loss 在训练循环中的位置
训练不是模型自己变聪明。训练是一个闭环:先预测,再计算损失,再反传梯度,最后更新参数。
这条链路从 loss.backward() 开始往回走。没有 Loss,Autograd 不知道该从哪一个标量出发。
所以训练代码里最关键的不是 optimizer.step(),而是你到底定义了什么 Loss。
3. 先按任务选 Loss,不要先背 API
选 Loss 的第一原则:先看任务。
预测连续数值,是回归。预测一个类别,是多分类。预测每个标签是否存在,是多标签。
Loss 选错,模型就会沿着错误目标优化。表面上 loss 也会下降,但业务结果可能完全不对。

4. 回归 Loss:预测一个数,惩罚一个距离
回归任务最直观:预测值和真实值之间有一个距离。
MSELoss 用平方惩罚误差。错得越大,惩罚越重。它适合大多数普通回归任务。
L1Loss 用绝对值惩罚误差。它对异常点更稳,不容易被极端样本带偏。
HuberLoss 是折中:小误差像 MSE,大误差像 L1。

回归记忆法:MSE 怕大错,L1 不怕异常点,Huber 两边都要。
5. 多分类 Loss:CrossEntropyLoss
多分类的目标是:多个类别中,选一个正确答案。
CrossEntropyLoss 的输入是 logits。logits 是模型的原始分数,不要求大于 0,也不要求加起来等于 1。
最常用的 target 是类别索引。例如 0 表示猫,1 表示狗,2 表示鸟。
不要提前对 logits 做 softmax。CrossEntropyLoss 内部已经包含稳定版的 LogSoftmax 和 NLLLoss 逻辑。

6. CrossEntropyLoss 的核心:LogSoftmax + NLLLoss
Softmax 把 logits 变成概率。LogSoftmax 是在概率上取 log,但会用更稳定的方式计算。
NLLLoss 的意思是 Negative Log Likelihood,也就是“正确类别的 log 概率取负”。
如果正确类别的分数高,Loss 小。
如果错误类别的分数高,Loss 大。
所以 CrossEntropyLoss 的本质不是“看最大值对不对”,而是惩罚整个类别分布。
7. shape 规则:大多数报错都来自这里
CrossEntropyLoss 有两种 target 形式。
第一种是类别索引:input 是 [N, C],target 是 [N],target 必须是 long。这个最常用,性能也更好。
第二种是类别概率:input 和 target 形状相同,target 是 float。它适合 soft label、label smoothing 和知识蒸馏。
如果你把类别索引写成 [N, 1],或者把 target 写成 float,就很容易报错或走错逻辑。

8. 二分类与多标签:BCEWithLogitsLoss
二分类不是一定要用 CrossEntropyLoss。
如果模型只输出一个 logit,表示“是正类的倾向”,就用 BCEWithLogitsLoss。
多标签也用 BCEWithLogitsLoss。因为每个标签都是独立判断:这张图可以既有猫,也有狗,也可能是室外。
BCEWithLogitsLoss 把 Sigmoid 和 BCELoss 合成一个函数,数值上更稳定。

口诀:互斥类别用 CrossEntropyLoss;独立标签用 BCEWithLogitsLoss。
9. 四个常用调节参数
reduction 控制输出形态。none 返回每个位置的 Loss,mean 返回平均值,sum 返回总和。默认用 mean。
weight 是类别权重。类别不平衡时,少数类可以给更高权重。
ignore_index 用来忽略某些标签。语义分割里,边界、无效像素、未标注区域经常要忽略。
label_smoothing 会把硬标签变软。它让模型不要过度自信,分类任务常用。

10. Loss 也是 nn.Module
从源码看,PyTorch 的很多 Loss 类都继承自 _Loss,而 _Loss 又继承自 Module。
_WeightedLoss 会把 weight 注册成 buffer。这样 weight 会跟着 state_dict 保存,也会跟着 .to(device) 迁移。
以 CrossEntropyLoss 为例,它的 forward 只做一件事:把 input、target、weight、ignore_index、reduction、label_smoothing 传给 F.cross_entropy。
functional.cross_entropy 再处理 torch_function 分发、旧参数兼容和 reduction 枚举,最后调用 torch._C._nn.cross_entropy_loss。
这个 _C._nn 入口已经进入 C++ 绑定。真正高性能计算在底层 kernel 中完成,Autograd 记录相关计算,等待 loss.backward() 触发反向传播。

源码主线:Module 负责封装,functional 负责调度,C++/ATen 负责真正计算。
11. 实战常见坑
第一个坑:CrossEntropyLoss 前手动 softmax。这样会重复处理概率,梯度也更弱。
第二个坑:target 的 dtype 错。CrossEntropyLoss 的类别索引 target 要 long;BCEWithLogitsLoss 的 target 通常是 float。
第三个坑:把多标签当多分类。多分类只能选一个类,多标签可以同时选多个类。
第四个坑:只看 loss 数值,不看业务指标。不同 reduction、batch size、类别权重,都会改变 loss 的量级。

12. 总结
Loss 是训练目标。你怎么定义 Loss,模型就怎么学习。
回归任务先看 MSELoss、L1Loss、HuberLoss。
多分类默认用 CrossEntropyLoss,直接输入 logits。
二分类和多标签默认用 BCEWithLogitsLoss,直接输入 logits。
源码上,Loss 类本质仍然是 nn.Module,forward 通常下沉到 torch.nn.functional,再进入 C++/ATen kernel。

内容来源:《PyTorch 深度修炼》损失函数:模型到底是怎么知道自己错了:功能变化与行业影响解析_热闻岛






