1.4 损失函数:把任务翻译成优化目标
1.3 节解决了「网络有没有能力表达复杂函数」,但一个能表达任意函数的网络,仍然不知道该向哪个方向学。损失函数承担的就是这件事:它把「任务做得好不好」翻译成一个标量,而梯度下降只认标量。
这一步看起来简单,却是实际项目里错得最多的环节之一。三个典型症状:分类任务用了 MSE,训练初期损失几乎不降;网络里写了 Softmax 又用了 CrossEntropyLoss,梯度被额外系数改写导致收敛变慢;多任务训练时几个损失的权重拍脑袋定,最后某个任务的主导了训练、其余任务被牺牲。它们都不是「网络结构」问题,而是目标定义问题。
这一节要做三件事:给出一个统一的视角(损失 = 负对数似然),把最常见的两种损失(交叉熵与 MSE)放进这个视角里;推导 softmax + 交叉熵那个异常简洁的梯度,说明它为什么是分类任务的标准搭配;最后处理真实数据里的三种糟糕情况——类别不均衡、标签噪声、多任务加权。
1.4.1 输出层与损失的配对
损失函数是把网络输出 与目标 映射为非负标量的函数 ;训练目标为
要做对这件事,必须先回答两个问题:输出层的值域是什么(网络吐出什么形式的数)、任务的概率模型是什么(这些数在解释什么)。两者决定了损失的形式,配对表如下。
| 任务 | 输出层 | 内部量 | 损失 | 对应的概率模型 |
|---|---|---|---|---|
| 多分类(互斥 类) | Softmax | logits | 交叉熵 | 类别分布 |
| 二分类 | Sigmoid(或单 logit + BCE) | 单个 logit | 二元交叉熵(BCE) | 伯努利分布 |
| 回归(实值) | 恒等(无激活) | MSE(或 L1 / Huber) | 高斯(噪声为加性正态) | |
| 带计数 / 正值的回归 | Softplus / exp | 正的尺度参数 | Poisson / Gamma 负对数似然 | 相应分布 |
| 多标签分类 | 每个标签独立 Sigmoid | 个 logit | 各项 BCE 之和 | 个独立伯努利 |
一类做法是死记「分类用交叉熵、回归用 MSE」,容易在遇到新任务时失效。更稳的做法是问三个问题:① 网络的输出在参数化哪个分布的什么参数? ② 该分布下观测到真值的对数似然是多少?③ 负对数似然就是损失。多分类:输出参数化类别分布的概率 → 负对数似然即交叉熵。回归:输出参数化高斯的均值、方差固定 → 负对数似然即 MSE(差一个常数与系数)。多标签:输出参数化 个独立伯努利 → 各项 BCE 之和。表里的每一行都可以这样现场推出来,不必背。
1.4.2 交叉熵与均方误差
交叉熵(cross-entropy):设 类,真值以 one-hot 或软标签 给出,网络经 softmax 输出 ,则
one-hot 时它退化为 ,即只有正确类别的概率被惩罚。
均方误差(mean squared error, MSE):设预测 、真值 ,则
平均绝对误差(MAE / L1):。Huber 损失在 小于阈值 时用平方、超过时用线性,兼得 MSE 的平滑与 MAE 的抗离群性。
one-hot 下只需盯一个数 。 时罚 , 时罚 , 时罚 , 时罚到 。对数把「差一点点」和「完全颠倒」区分得很开:模型把 99% 的置信度给了错误类别,会收到一个很大的梯度,逼它立刻改口。相比之下 MSE 对概率误差的惩罚是平方级的,在两端太平缓: 从 降到 ,MSE 只多罚了不到 。
两个方向各说一次:
回归不能用交叉熵:交叉熵的定义要求第二个参数是概率分布(非负且和为 )。连续量没有天然的类别概率,硬套离散化会引入人为的 bin 边界。而 MSE 来自一个明确的概率假设——观测噪声为加性高斯、且方差与输入无关。观测 、 时,负对数似然为
关于 最小化它,等价于最小化 ,即 MSE。因此「回归用 MSE」不是约定俗成,是高斯似然假设的直接结果;若噪声是重尾的(有离群点),高斯假设就不成立,此时该用 MAE(对应拉普拉斯噪声)或 Huber。
分类不用 MSE:一是概率模型不匹配(类别是离散分布,不是高斯);二是梯度质量差。即使把 softmax 输出接上 MSE,梯度里会带一个 因子,在模型最该被纠正的两个极端()梯度反而最小,训练被拖慢;交叉熵配 softmax 的梯度恰好是 (下一小节的推导),在最需要修正的地方最大。
在分类误差这个指标上,softmax + MSE 有时也能收敛到一个精度不错但是置信度校准很差的解:因为它不强迫概率逼近 0/1,只强迫预测接近标签。这类模型在需要校准概率的场景(排序、阈值决策、集成加权)会明显吃亏,而且训练早期更慢。工程判据是:只要任务是「从有限类别里选一个」,就用交叉熵;只有在做「回归到一个 区间内的软分数」且明确不需要概率语义时,才考虑 MSE。
1.4.3 统一视角:损失就是负对数似然
设数据由条件分布 生成, 个样本独立同分布。则最小化平均负对数似然(NLL)等价于最大化似然:
在类别分布假设下,上式右侧即交叉熵;在高斯假设下即 MSE(差常数项)。从这个视角看,所有的损失函数选择都是「假设数据服从哪种分布」的选择。
(i) 似然函数是各样本概率的乘积 。直接对乘积求导不便(乘积的导数是多因子求和),且大量小数相乘容易下溢。
(ii) 取对数:。对数函数严格单调递增,因此最大化乘积与最大化对数和给出同一个 。这解决了数值问题(把乘积变成和)与求导问题(求和可逐项求导)。
(iii) 取平均并加负号:。取平均使目标函数的量级与样本数无关(便于跨数据集比较、便于设定学习率);取负号把最大化变成最小化,以适配梯度下降。
∎因为它把乘法结构变成了加法结构。独立样本的概率要相乘,而「相乘」在优化里是最难处理的结构之一:一个样本的概率接近零,整条似然就接近零,梯度被它独占。取对数后每个样本的贡献是独立一项,可以逐样本求导、逐样本加权——这也是为什么工程上可以「对批内样本的损失取平均」,以及为什么可以针对单个样本重新加权(focal loss、样本重加权都靠这条性质)。
1.4.4 softmax + 交叉熵的梯度:一个值得记住的结果
设 logits ,,( 可为 one-hot 或软标签,只要 )。则
梯度不依赖 softmax 的显式导数,只依赖「预测减真值」这个残差——这就是这一对组合成为分类标准做法的根本原因。
第一步:softmax 的雅可比。 记 ,则 。对 求导要分两种情况:当 时 ;当 时 。合并写作 。
第二步:代入链式法则。 由 ,而 ,故
末步用到 (真值是分布)。
∎一般复合函数求导会留下两个因子的乘积(外层导数 × 内层导数),而这里内层导数 与外层导数 里都含 ,恰好除法把乘法约掉了。这不是巧合:softmax 是「指数族分布的规范形式」,交叉熵是它对数似然的自然配对,二者的导数在数学上必然互相抵消。工程含义非常实用:无论 logits 多大多小,梯度都落在 附近,不存在饱和放大或消失——分类任务的训练比回归任务稳定,一半功劳在这里。
三分类,某样本真值为类别 (即 ),某轮 logits 为 。
(1) 求 softmax 输出 ;(2) 求交叉熵损失;(3) 求梯度 ;(4) 若把 换成 (把第 3 类的分数抬到第 2 类之上),重算损失与梯度,并解释数值变化的含义。
(1) ,,,和 。故
(2) 真值为第 2 类,。(同一值在 (1) 中可直接读出:。)
(3) 梯度 。读法:第 1、3 类的 logit 都会被调低(正梯度,梯度下降沿负方向走),第 2 类会被调高(负梯度),且调整幅度与「错得有多离谱」成正比——第 1 类的 最大,所以它被压得最狠。
(4) 若 :(不变),,,梯度为 。损失从 涨到 、第 2 类的梯度项从 增到 ,说明把错误类别的分数抬高会同时加剧惩罚与修正力度。这正是交叉熵「错得越自信、罚得越狠」的数值形态。
∎1.4.5 交叉熵与 KL 散度
对任意两个分布 ,KL 散度满足
因此 。当 是给定的目标分布(如 one-hot 标签或软标签)时, 与模型参数无关,所以
它在做把模型分布拉向目标分布这件事,且只做这件事——因为目标分布自己的熵 是个常数,不影响优化方向。三个实用推论:① 两个模型在不同数据集上的交叉熵不可直接比较( 不同);② 在同一数据集上比较交叉熵与 KL 是等价的,所以「最小化 KL」和「最小化交叉熵」在训练中不必区分;③ 标签平滑把 从 one-hot 改成软分布,同时改变了 与最优解——这就是它既是正则手段、又会降低校准质量的原因(1.4.7)。
1.4.6 类别不均衡与焦点损失
二分类设定下,记 为模型给真值类别的预测概率(),焦点损失为
其中 为聚焦参数( 时退化为交叉熵), 为类别的加权系数。多分类形式为对每个样本用其真值类别的 计算同一表达式再平均。
交叉熵里每个样本的贡献是 ,一个「已经预测得很对」的样本()仍贡献 ;当成千上万这样的易分样本堆在一起,它们的总贡献会淹没少数难分样本。因子 做的事是: 接近 时把这一项压到接近零( 时 的权重), 小的时候几乎不压()。于是训练信号被自动重新分配到「难样本」上。这正是它被提出来处理检测任务中前景 / 背景极度不均衡的原因。
| 机制 | 手段 | 适用假设 | 风险 |
|---|---|---|---|
| 重采样 | 对少数类过采样 / 多数类欠采样 | 少数类样本量太少 | 过采样易过拟合;欠采样丢信息 |
| 类别权重 | 交叉熵各项乘 (逆频率) | 类间代价不对称 | 破坏概率校准,需重新校准 |
| 焦点损失 | 按「是否为易分样本」动态加权 | 存在大量易分样本淹没信号 | 过大时对噪声样本过度关注 |
| 重加权 / 重标注 | 学习每样本权重、清理标签 | 标签有噪声或难易差异极大 | 引入额外的偏差来源 |
- 把它当通用提点手段。类别均衡的数据上,焦点损失通常没有优势,甚至因为压低易分样本权重而降低有效样本量。它的定位是不均衡问题的专用工具。
- 与 一起乱调。两者作用不同: 调整类间相对权重(与逆频率重加权同类), 调整样本难易的相对权重。一起调会难以归因;建议先固定 (或设为均衡值)只调 ,观察「难样本的召回率」这一指标。
1.4.7 标签平滑与多任务损失加权
设 类、平滑系数 ,把 one-hot 标签替换为
然后用同一交叉熵公式 训练。
one-hot 标签要求模型对正确类别输出概率 1,也就要求正确类别的 logit 与其余类别差距趋于无穷——这只能靠把权重范数不断推大来实现。在有限数据上,这会让模型「过度自信」,并放大标签噪声的影响。标签平滑把目标从「确定分布」改成「带 平滑的分布」,给 logit 差距设了一个有限的最优值。
副作用同样清楚:它降低了概率校准质量。公开研究给出的解释是——平滑让不同类别的 logit 更紧地聚在一起,抹掉了「模型知道自己不确定」这一维信息,因此在知识蒸馏和置信度校准任务上,用平滑标签训练的老师可能反而不如不加平滑的老师。(该结论来自公开论文,具体实验设置需按原文核实。)
多任务训练时总损失常写成 。 的取法按「需要多少额外假设」排序:
| 方案 | 形式 | 依据 | 代价 / 风险 |
|---|---|---|---|
| 手动权重 | 人工调 | 无需额外假设,可控 | 需要反复试;任务数一多就难调 |
| 同尺度归一化 | 先各自除以初始步的损失值,再加权求和 | 让各任务起步量级一致 | 只是让起点公平,不保证终点平衡 |
| 梯度量级归一化 | 缩放 使各任务的梯度范数同阶 | 反向传播的实际尺度是梯度而非损失 | 需每步统计梯度范数,有额外开销 |
| 不确定性加权 | 为回归任务引入可学习的噪声方差 ,最小化 | 把每个任务的噪声视为同方差高斯、对 取极大似然 | 引入可学习参数;对分类任务的形式不同 |
最后一行值得记住的直觉: 是可学习的, 自动充当权重。噪声大(学不好)的任务权重自然被调小,同时 这一项防止 无限增大(否则退化为「忽略所有任务」)。具体公式与符号以原文为准,使用前需核实(见延伸阅读)。
1.4.8 代码:三种损失的等价关系与不均衡对照
import torch, torch.nn as nn, torch.nn.functional as F
torch.manual_seed(0)
logits = torch.tensor([[2.0, 1.0, 0.1]]) # 例 1.4.1 的 logits
target = torch.tensor([1]) # 真值为第 2 类(0 基索引)
# ① 交叉熵:框架把 log_softmax 与 NLL 融成一个数值稳定的算子
ce = F.cross_entropy(logits, target)
p = logits.softmax(-1) # 例 1.4.1:应为 (0.6590, 0.2424, 0.0986)
manual = -p[0, target].log() # 手算:-log(0.2424) = 1.4170
print("CE(框架) =", ce.item(), " 手算 =", manual.item())
# ② 梯度核对:定理 1.4.2 说 ∇_z = p − y
logits2 = logits.clone().requires_grad_(True)
F.cross_entropy(logits2, target).backward()
print("grad =", logits2.grad) # 应为 (0.6590, -0.7576, 0.0986)
print("p - y =", (p - F.one_hot(target, 3)).detach())
# ③ 交叉熵 = KL + 常数:在目标分布固定时,两者对参数的作用完全一致
logp = F.log_softmax(logits, dim=-1)
onehot = F.one_hot(target, 3).float()
kl = F.kl_div(logp, onehot, reduction="none").sum(-1) # KL(one-hot || p)
print("KL =", kl.item(), " CE − KL = H(one-hot) =", (ce - kl).item()) # 后者应为 0
# ④ 标签平滑:把 one-hot 软化成 (1−ε) 加 ε/C,最优置信度因此被压低
eps = 0.1
soft = (1 - eps) * onehot + eps / 3
ls = -(soft * logp).sum(-1).mean()
print("label-smoothed CE =", ls.item())
print("达到该损失时正确类的目标概率 =", (1 - eps) + eps / 3) # 0.9333
# ⑤ 极端不均衡:交叉熵与 focal loss 在“易分样本”上的权重差异
p_t = torch.tensor([0.9, 0.6, 0.2]) # 真值类别的预测概率
ce_each = -p_t.log()
focal_each = -(1 - p_t) ** 2 * p_t.log() # γ = 2
print("CE 每样本损失:", ce_each.tolist())
print("Focal 每样本损失:", focal_each.tolist())
print("易分样本占比 (相对 CE):", (focal_each[0] / ce_each[0]).item()) # 0.01 量级
1.4.9 损失曲线的读法:从「损失降不降」到「损失在说什么」
损失值本身只说明目标的绝对值,曲线的形状才携带诊断信息。下表把常见形状与病因对应起来(与 1.7.1的训练/验证曲线诊断配合使用)。
| 曲线形状 | 含义 | 首选检查 |
|---|---|---|
| 平稳单调下降 | 正常 | — |
| 前几百步几乎不动,之后才开始下降 | warmup 不足 / 初始化尺度过小 / 首层梯度被饱和吃掉 | 打印各层梯度范数(1.5.3);检查初始化(1.2.5) |
| 下降中突然跳到很大值 | 学习率过大 / 某个异常批 | 梯度的批内范数分布(个别样本的损失可能是其余样本的百倍) |
| 长期停在某个正值不动 | 任务与损失不匹配(如分类用了 MSE)或标签有系统性错误 | 用一小撮手工核对过的样本做一次过拟合测试(见下) |
| 不断下降但验证指标不涨 | 概率在变得更自信,尚未跨越决策边界;或评估指标与损失口径不一致 | 同时打印准确率 / F1 等任务指标 |
| 出现 NaN | 数值溢出(混合精度下尤甚)、学习率过大、除零 | 看 NaN 首次出现的位置:损失、梯度还是参数 |
- 初始损失核对: 类分类任务在随机初始化时,正确的初始交叉熵应接近 (如 10 类约 ,1000 类约 )。若初始损失明显偏离,说明输出层的初始化、softmax 的使用方式或标签编码有问题。这是最便宜的一致性检查,几十秒就能排除一整类 bug。
- 过拟合一小撮数据:取 个样本,关掉所有正则,训练几百步。损失必须能降到接近零。降不下去说明实现或容量层面有硬问题(模型太小、损失配错、梯度没传对),与「泛化」无关。
- 随机标签对照:把标签随机打乱后重训。若训练损失仍能降到接近零,说明模型容量足够记住随机标签(这本身正常);但若验证指标明显高于随机水平,说明评估流程里有信息泄漏(1.7.6)。
1.4.10 陷阱与勘误
Softmax+CrossEntropyLoss同时使用。框架的CrossEntropyLoss内部已包含log_softmax。再手动加一层 softmax 会让梯度被额外系数改写(等价于对已归一化的概率再做一次归一化),表现为收敛变慢、精度下降、概率严重过自信。判据:训练时loss.backward()前检查你喂给损失的是 logits 还是概率。- 把「损失收敛」当成「任务做好了」。损失只度量训练目标,不度量任务指标(准确率、F1、BLEU、mAP)。分类精度可能早已到顶而损失还在下降(概率在变得更自信);也可能损失在降而指标不涨(如类别不均衡下被多数类主导)。
- 回归任务默认用 MSE,却忘了检查离群点。MSE 对离群点的惩罚是平方级的,一个错标 10 倍的样本会主导整批梯度。稳妥做法是先画出目标值分布与残差分布,再决定 MSE / MAE / Huber。
- 多任务损失不加任何归一化。两个任务损失量级差 100 倍时,等价于只训一个任务。至少要把各任务损失的初始量级对齐(方法 1.4.1)。
- 把 focal loss 用于均衡数据(1.4.6的勘误)。
- 用测试集挑损失函数。损失函数属于模型设计的一部分,任何基于测试集的比较选择都会让报告的结果失真;应使用验证集,并保留测试集做最终一次性评估(见 1.7.6)。
- 把 BCE 的「混淆命名」搞混。
BCELoss期望概率输入(需要先过 Sigmoid),BCEWithLogitsLoss期望logits(内部含 Sigmoid)。混用是常见 bug 来源:用BCELoss却喂 logits,会让梯度在饱和区几乎消失。
Q1 一个多分类任务,网络末尾写了 Softmax,损失用了 CrossEntropyLoss,训练能跑但收敛慢。写出正确的改法,并说明为什么错误版本会变慢。
Q2 从最大似然的角度说明:为什么「回归用 MSE」不是约定,而是高斯噪声假设的推论?如果数据里有明显离群点,你会换成什么损失,对应什么噪声假设?
Q3 写出交叉熵与 KL 的关系式,并说明为什么「在固定数据集上,最小化交叉熵与最小化 KL 等价」,但「跨数据集比较交叉熵」却不合理。
Q4 焦点损失中的因子 在 与 时分别做什么?为什么这个设计能缓解类别不均衡?
参考要点
Q1 正确改法:去掉网络末尾的 Softmax,直接输出 logits 给 CrossEntropyLoss(或保留 softmax 而改用 NLLLoss 并先取 log,但这不推荐,因为数值稳定性差)。变慢的原因:网络输出的 再经过一次 softmax 后得到的 与 logits 的关系不再是「线性 + 指数归一化」,梯度里多出一层雅可比(),相当于对梯度做了额外压缩;同时在数学上它已经不是原始的交叉熵目标( 的熵被额外扭曲),最优解也不再是「 逼近真实类别分布」。
Q2 高斯似然的负对数含 ,最小化它关于 等价于最小化平方误差,故 MSE = 高斯噪声下的极大似然。离群点存在时高斯假设被违反(重尾),应换成 MAE(对应拉普拉斯噪声,即绝对偏差)、或 Huber(小误差平方、大误差线性,两个假设的分段折中)。判据是残差分布的尾部形态。
Q3 。目标分布 由数据集给定,与参数 无关,因此在同一数据集上 是常数,。跨数据集比较时 不同(如两个数据集的类别分布不同、标签平滑系数不同),因此交叉熵的绝对值不可比;要跨数据集比较模型,应该报告 KL 或归一化后的指标(如 perplexity 需要先说明词表与数据)。
Q4 (易分样本)时因子趋 ,该项损失被压到近乎消失;(难分样本)时因子趋 ,几乎保留原始交叉熵。因为不均衡数据中大多数样本是易分样本,交叉熵的总梯度会被它们占据;压低它们的权重后,少数难分样本与少数类样本的梯度占比显著提高,从而缓解「被易分样本淹没」的问题。 越大越激进,过大时会过度关注噪声样本。
- Goodfellow, Bengio & Courville, Deep Learning(2016,MIT Press)第 5 章与第 6 章——从最大似然推出常见损失的权威叙述。
- Szegedy et al., Rethinking the Inception Architecture for Computer Vision(2016,CVPR)——标签平滑在该文中被明确引入并解释(作为正则化手段的动机与效果,细节需按原文核实)。
- Müller, Kornblith & Hinton, When Does Label Smoothing Help?(2019,arXiv:1906.02629)——标签平滑损害置信度校准与知识蒸馏的实证研究。
- Lin, Goyal, Girshick, He & Dollár, Focal Loss for Dense Object Detection(2017,ICCV;arXiv:1708.02002)——焦点损失的提出与类别不均衡分析。
- Kendall, Gal & Cipolla, Multi-Task Learning Using Uncertainty to Weigh Losses for Scene Geometry and Semantics(2018,CVPR)——不确定性加权的多任务损失(公式细节需按原文核实)。
本节小结:损失函数是「任务」到「可优化标量」的翻译器,它的正确形式由输出层的值域与任务假设的概率模型共同决定;统一视角是「最小化负对数似然」。softmax + 交叉熵之所以成为分类默认,是因为它的梯度恰好是 ——不饱和、不放大、只依赖残差。交叉熵与 KL 只差一个常数熵项,因此训练中两者等价,但跨数据集比较需要谨慎。真实数据的三种麻烦各有对应工具:类别不均衡用重加权或焦点损失,标签噪声用标签平滑(以校准质量换取稳定性),多任务冲突用归一化或不确定性加权。下一节转向这一问题链的最后一环、也是整套范式里唯一需要真正推导的部分:这些梯度是怎么被算出来的。