损失函数的设计哲学——从直觉理解为什么损失函数要长这样

如果你写过几个 PyTorch 训练脚本,大概已经对 nn.MSELossnn.CrossEntropyLoss 这两个名字烂熟于心。你可能也知道"回归用 MSE,分类用 CrossEntropy"这条经验法则,甚至背过交叉熵的公式:

$$
\mathcal{L}{CE} = -\sum{c} y_c \log(p_c)
$$

但你有没有停下来想过——为什么?

为什么回归不直接用绝对误差(L1),非要用平方(L2)?为什么分类不用 MSE 做损失?为什么交叉熵里的那个 $\log$ 恰到好处,换成别的就不行?

这些问题背后不是拍脑袋的巧合。每一种损失函数的设计,都是对人类认知目标的一种数学建模。这篇博客想做的,就是把几个最常用的损失函数从设计动机到数学形式拆一遍——不是为了背公式,而是为了理解如果换你来设计,你也会这么想


1. 先想清楚:损失函数到底在做什么

在说具体函数之前,先明确一个最基础的问题。

训练一个模型,本质是一个 优化问题

$$
\theta^* = \arg\min_\theta \mathbb{E}{(x,y)\sim\mathcal{D}} [\mathcal{L}(f\theta(x), y)]
$$

损失函数 $\mathcal{L}$ 是你的评判标准——模型输出 $\hat{y}$ 离真实标签 $y$ 有多远。梯度下降就是沿着这个"有多远"的方向,一步步把模型参数调得让输出更接近标签。

但"有多远"这个距离,不是你说了算,是损失函数说了算。同一个预测结果,用不同损失函数测出来的"距离"天差地别,从而导致梯度方向和大小也天差地别。

所以损失函数设计的本质是:我们想让模型在什么方向上、以多快的速度更新参数

这是全文的核心视角,带着它看后面的每个损失函数。


2. 回归的起点:L1 和 L2 损失

2.1 L2 Loss(MSE)——惩罚大错误

$$
\mathcal{L}_{L2} = (y - \hat{y})^2
$$

L2 损失是最直观的想法:预测值和真实值越远,损失越大。而且因为平方的关系,偏离越大,惩罚增速越快

这意味着什么?

  • 一个误差为 3 的样本,损失是 9
  • 一个误差为 1 的样本,损失是 1

前者对梯度的贡献是后者的 9 倍(绝对值差是 3 倍,平方后是 9 倍)。所以 MSE 会拼命优先消灭大误差样本,哪怕牺牲其他样本的精度。

这合理吗? 要看场景。如果你的数据噪声服从高斯分布(正态分布),MLE(最大似然估计)推导出来的刚好就是 MSE——那它不仅是合理的,还是理论最优的。但如果数据有离群点,MSE 会把大量精力花在拟合那一个离群点上,导致整体模型变差。

2.2 L1 Loss(MAE)——不怕离群点

$$
\mathcal{L}_{L1} = |y - \hat{y}|
$$

L1 损失对误差的惩罚是线性的。一个误差 3 的样本,损失就是 3;误差 1 就是 1。梯度恒定是 ±1(不考虑符号),不受误差大小影响。

所以 L1 的好处是对离群点不敏感——一个离群点不会把整个梯度带偏。但也正因为梯度恒定,在误差接近零时,模型仍然以同样速率更新参数,这导致它在最优解附近容易震荡,收敛不如 MSE 平滑。

2.3 Huber Loss——取两者之长

$$
\mathcal{L}_{Huber} =
\begin{cases}
\frac{1}{2}(y - \hat{y})^2 & \text{如果 } |y - \hat{y}| \leq \delta \
\delta|y - \hat{y}| - \frac{1}{2}\delta^2 & \text{其他}
\end{cases}
$$

Huber 的设计思路非常清晰:小误差用平方、大误差用线性

  • 当误差小($<\delta$),它行为像 MSE,梯度线性减小,收敛平稳
  • 当误差大($>\delta$),它行为像 L1,梯度被截断在 $\pm\delta$,不被离群点带偏

这个 $\delta$ 是你要设的超参数——它决定了"多大算大误差"。它的存在本身就体现了设计者的思考:同一个损失函数在不同尺度下应该有不同行为

Huber 损失的梯度:

$$
\frac{\partial \mathcal{L}_{Huber}}{\partial \hat{y}} =
\begin{cases}
\hat{y} - y & \text{如果 } |y - \hat{y}| \leq \delta \
\delta \cdot \text{sign}(\hat{y} - y) & \text{其他}
\end{cases}
$$

看这个梯度形式,你会发现它对大误差的惩罚是有上限的——不管你是差了 100 还是 1000,梯度最大就是 $\delta$。这对含有噪声数据的回归任务非常合适,也是为什么很多强化学习的 value 网络使用 Huber Loss(比如 DQN 就用它来稳定 TD-error 的梯度)。


3. 分类任务的核心:CrossEntropy 为什么比 MSE 好

3.1 先试一下:分类用 MSE 会怎样

假设一个二分类问题,标签 $y \in {0, 1}$,模型输出 $\hat{y} \in [0, 1]$(sigmoid 之后)。

用 MSE:

$$
\mathcal{L}_{MSE} = (y - \hat{y})^2
$$

看梯度怎么传。令模型输出为 $z$(sigmoid 之前的 logit),sigmoid 为 $\sigma(z) = 1/(1+e^{-z})$,那么 $\hat{y} = \sigma(z)$。

用链式法则:

$$
\frac{\partial \mathcal{L}_{MSE}}{\partial z} = 2(\hat{y} - y) \cdot \sigma’(z)
$$

而 sigmoid 的导数有个致命特性:当 $\hat{y}$ 接近 0 或 1 时,$\sigma’(z)$ 接近 0

这意味着什么?当模型的预测已经很确定(无论对错)的时候,梯度会变得非常非常小——学习几乎停滞

想象一个模型预测 $\hat{y}=0.99$,但真实标签是 $y=1$。虽然预测已经很接近正确值了,但 $\sigma’(z)$ 大约只有 0.01 量级,梯度被这个因子"吃掉",更新极慢。更糟糕的是,如果模型预测 $\hat{y}=0.99$,真实标签是 $y=0$,它本应该大幅度调整,但因为 $\sigma’(z)$ 太小,同样学不动。

这就是 MSE 在分类任务中的梯度消失问题

3.2 交叉熵的设计:让梯度正比于误差

二分类交叉熵(Binary Cross-Entropy, BCE):

$$
\mathcal{L}_{BCE} = -[y\log(\hat{y}) + (1-y)\log(1-\hat{y})]
$$

为什么中间有个 $\log$?来看梯度:

对正样本($y=1$),损失简化为 $-\log(\hat{y})$,梯度:

$$
\frac{\partial \mathcal{L}_{BCE}}{\partial z} = -(1 - \sigma(z)) = \hat{y} - 1
$$

对负样本($y=0$),损失简化为 $-\log(1-\hat{y})$,梯度:

$$
\frac{\partial \mathcal{L}_{BCE}}{\partial z} = \sigma(z) = \hat{y}
$$

统一写成:

$$
\frac{\partial \mathcal{L}_{BCE}}{\partial z} = \hat{y} - y
$$

梯度直接正比于预测和标签的差值——完全不依赖 $\sigma’(z)$。模型错的离谱时,梯度就大;快对了,梯度就小。

相比 MSE 中梯度被 $\sigma’(z)$ 压缩的问题,交叉熵的设计优雅地避开了这个陷阱。

多分类版本就是 softmax + 交叉熵:

$$
\mathcal{L}{CE} = -\sum{c=1}^{C} y_c \log\left(\frac{e^{z_c}}{\sum_{j}e^{z_j}}\right)
$$

梯度形式同样简洁。对正确类别 $c^*$ 的 logit:

$$
\frac{\partial \mathcal{L}{CE}}{\partial z{c^}} = p_{c^} - 1
$$

对其他类别 $c \neq c^*$:

$$
\frac{\partial \mathcal{L}{CE}}{\partial z{c}} = p_{c}
$$

非常干净。每个梯度的方向都告诉模型:正确类别的 logit 往上拉,其他类别的 logit 往下压,幅度正比于当前的置信度偏差。

3.3 从信息论角度看交叉熵

交叉熵还有一个更底层的解释视角:

$$
H(p, q) = -\sum_{x} p(x) \log q(x)
$$

这衡量的是用分布 q 来编码分布 p 的信息时,需要的额外比特数。最小化交叉熵就是在让模型分布 $q$ 尽可能接近真实分布 $p$——也就是在做 KL 散度最小化。

这个视角给了一个直觉:分类问题的本质不是"让数值接近",而是"让分布匹配"。用 MSE 去做分类,本质上是在用欧氏距离衡量分布差异,它不知道正确概率和错误概率之间的关系。而交叉熵天然懂。


4. 对比损失:当任务涉及"相似性"

4.1 Contrastive Loss——拉近正对,推远负对

$$
\mathcal{L}_{Contrastive} = (1-y)\cdot d^2 + y\cdot \max(0, m-d)^2
$$

其中 $d$ 是两个样本嵌入的欧氏距离,$y=1$ 表示这对样本相似(正对),$y=0$ 表示不相似(负对),$m$ 是 margin。

这个函数的设计哲学很直接:

  • 正对:让它们靠近,距离的平方就是惩罚项
  • 负对:只用管"离得不够远的"——距离超过 margin $m$ 的负对不产生任何损失

为什么要设这个 margin?不是所有负对都应该被推得无限远。你只需要它们分离到一定程度就够了。没有 margin 的话,模型会试图把所有负对推到无限远,但这是不必要的——就像你不需要把"猫"和"桌子"的向量表示隔到天边,只要它们不混淆就行。

4.2 Triplet Loss——你比我近就行

$$
\mathcal{L}_{Triplet} = \max(0, d(a,p) - d(a,n) + m)
$$

这里有一个 anchor 样本 $a$,一个正样本 $p$,一个负样本 $n$。损失只在负样本比正样本距离 anchor 还近(加上 margin)时产生。

设计逻辑:只要正对已经比负对近,我就不管了。不需要绝对距离小,只需要相对距离对。这种"相对而非绝对"的思路在人脸识别、行人重识别等任务中效果显著。

对比一下:Contrastive Loss 对每个样本对独立施压,Triplet Loss 对关系施压。它们的区别很像监督学习与排序学习的区别——前者关心绝对值,后者关心相对值。


5. KL 散度:当你想让分布靠近但不完全相等时

$$
D_{KL}(p \parallel q) = \sum_{x} p(x) \log\frac{p(x)}{q(x)}
$$

KL 散度最值得提的一点是:它不是对称的。$D_{KL}(p\parallel q) \neq D_{KL}(q\parallel p)$。

这个不对称性恰恰是设计者的意图:

  • 前向 KL($D_{KL}(p\parallel q)$):在 $p$ 有密度的地方,$q$ 必须有密度,否则损失极大。这叫 mode-covering——$q$ 会试图覆盖 $p$ 的所有峰值,哪怕这导致它在峰值之间分配概率。
  • 反向 KL($D_{KL}(q\parallel p)$):在 $q$ 有密度而 $p$ 没有的地方,损失极大。这叫 mode-seeking——$q$ 会只拟合 $p$ 的主要模式,放弃次要模式,产生一个更集中的分布。

你在用 VAE 时用的是前向 KL($D_{KL}(q(z|x) \parallel p(z))$),让后验分布逼近先验。而在强化学习的策略优化(如 TRPO/PPO)中,你限制新旧策略的 KL 散度,用的方向取决于具体公式。

这个不对称性不只是数学上的小细节——它决定了算法的行为是"覆盖"还是"聚焦",设计时选哪个方向看你的任务需要哪种特性。


6. 从 MLE 统一视角看损失函数

讲到这里,你会发现很多损失函数都可以从**最大似然估计(MLE)**的角度统一理解:

  • 假设 $p(y|\hat{y}) = \mathcal{N}(\hat{y}, \sigma^2)$ → MLE 给出 MSE
  • 假设 $p(y|\hat{y}) = \text{Laplace}(\hat{y}, b)$ → MLE 给出 L1
  • 假设 $p(y|\hat{y}) = \text{Bernoulli}(\hat{y})$ → MLE 给出 BCE
  • 假设 $p(y|\hat{y}) = \text{Categorical}(\hat{y})$ → MLE 给出 CrossEntropy

每种损失函数背后都隐含了一个关于数据分布的假设。 你选择损失函数时,本质上是在选择你相信数据服从哪种分布。

这不是学术上的马后炮——当你面对一个新问题,不知道用什么损失函数时,问自己:“我的数据噪声长什么样?” 零星的离群点?高斯噪声?离散类别?你的答案就是你的损失函数。


总结

损失函数 适合场景 核心设计思想
MSE (L2) 高斯噪声的回归 平方惩罚,优先消灭大误差
MAE (L1) 含离群点的回归 线性惩罚,不受离群点主导
Huber 通用回归 小误差L2 + 大误差L1
CrossEntropy 分类 梯度正比于误差,无梯度消失
Contrastive 度量学习 正对拉近、负对推远到margin
Triplet 细粒度相似性 只需相对距离,不要绝对距离
KL Divergence 分布匹配 不对称性设计,选覆盖还是聚焦

所有损失函数的共同目标都是让模型朝着正确的方向更新参数。区别在于:它们定义了"正确"的标准,而这个标准,决定了你模型最终学到的行为。


下篇预告:优化器的直觉——SGD 为什么要有动量,Adam 又为什么好用。