损失函数:模型真正优化的东西


文档摘要

损失函数:模型真正优化的东西 本节摘要:你的网络做出了预测,真值却另有说法。它究竟错得有多离谱?那个数就是损失(Loss)。挑错损失函数,模型会去优化一个完全错误的东西。模型优化的从来不是准确率、不是 F1、不是你汇报给老板的指标——它优化的只有损失函数。优化器取损失的梯度,调权重让那个数变小;如果损失没抓住你在乎的东西,模型就会找出数学上最省力的办法去满足它,而那条路几乎从来不是你想要的。一个在分类任务上最小化 MSE 的模型,会自信地对所有输入预测 0.5——它在最小化损失,也毫无用处。换成交叉熵,同样的模型被迫把预测推向 0 或 1,因为 −log(0.5) = 0.693 是个糟糕的损失,而 −log(0.99) = 0.01 重奖自信正确的预测。

损失函数:模型真正优化的东西

本节摘要:你的网络做出了预测,真值却另有说法。它究竟错得有多离谱?那个数就是损失(Loss)。挑错损失函数,模型会去优化一个完全错误的东西。模型优化的从来不是准确率、不是 F1、不是你汇报给老板的指标——它优化的只有损失函数。优化器取损失的梯度,调权重让那个数变小;如果损失没抓住你在乎的东西,模型就会找出数学上最省力的办法去满足它,而那条路几乎从来不是你想要的。一个在分类任务上最小化 MSE 的模型,会自信地对所有输入预测 0.5——它在最小化损失,也毫无用处。换成交叉熵,同样的模型被迫把预测推向 0 或 1,因为 −log(0.5) = 0.693 是个糟糕的损失,而 −log(0.99) = 0.01 重奖自信正确的预测。损失函数的选择,就是「会学的模型」与「钻指标空子的模型」之间的分野。本节将从零实现 MSE、二分类/多分类交叉熵、对比损失(InfoNCE),讲透标签平滑与 focal loss,给出按任务选损失的决策树。

学习目标

阅读完本节,你应当能够:

  1. 从零实现 MSE、二分类交叉熵、多分类交叉熵、对比损失(InfoNCE)及其梯度
  2. 通过演示「对所有输入预测 0.5」的失败模式,解释 MSE 为何不适合分类
  3. 应用标签平滑,描述它如何防止过度自信的预测。
  4. 为回归、二分类、多分类、嵌入学习选择正确的损失函数

一、问题与直觉

在分类任务上最小化 MSE 的模型,会自信地对所有输入预测 0.5。它在最小化损失,也毫无用处。

损失函数是模型真正优化的唯一目标——不是准确率、不是 F1、不是你汇报的指标。优化器取损失的梯度、调权重让那个数变小。如果损失没抓住你在乎的东西,模型会找出数学上最省力的方式去满足它,而那条路几乎从来不是你想要的。

具体例子:二分类,两类各 50%,你用 MSE。模型对每个输入都预测 0.5,平均 MSE 是 0.25,这是不真正学任何东西时的最小可能值——模型零区分能力,却技术上把损失最小化了。换成交叉熵,同样模型被迫把预测推向 0 或 1,因为 −log(0.5) = 0.693 是个糟糕损失,而 −log(0.99) = 0.01 重奖自信正确的预测。

更糟的是,自监督学习里你连标签都没有。对比损失完全定义了学习信号:什么算相似、什么算不同、该把它们推开多远。对比损失搞错,你的嵌入会坍缩成一个点——每个输入都映射到同一个向量,技术上是零损失,实际上一文不值。

均方误差(MSE)

回归的默认。算预测与目标的平方差,对所有样本求平均。

MSE = (1/n) * sum((y_pred - y_true)^2)

为什么要平方:它对大误差做二次惩罚。误差为 2 的代价是误差为 1 的 4 倍,误差为 10 的代价是 100 倍。这让 MSE 对异常值敏感——一个错得离谱的预测会主导损失。MSE 对预测的梯度:

dMSE/dy_pred = (2/n) * (y_pred - y_true)

与误差线性相关:误差越大梯度越大。对回归这是优点(大误差需大修正),对分类是缺陷(你希望对自信的错误答案做指数级惩罚,而非线性)。

交叉熵损失

分类的损失函数,根植于信息论——度量预测分布与真实分布的差异。

二分类交叉熵(BCE):

BCE = -(y * log(p) + (1 - y) * log(1 - p))

y 是真标签(0 或 1),p 是预测概率。−log(p) 为何有效:真标签为 1 时预测 p = 0.99,损失 −log(0.99) = 0.01;预测 p = 0.01,损失 −log(0.01) = 4.6。这 460 倍的差距就是交叉熵的力量——它狠狠惩罚自信的错误,几乎不罚自信的正确。梯度讲同样的故事:

dBCE/dp = -(y/p) + (1-y)/(1-p)

y = 1 且 p 接近零时,梯度是 −1/p,趋向负无穷——模型收到巨大信号去纠错;p 接近 1 时梯度极小,已正确,没什么可修。

多分类交叉熵(CCE): 配 one-hot 目标。

CCE = -sum(y_i * log(p_i))

只有真实类别对损失有贡献(其它 y_i 都是 0)。10 类里正确类得概率 0.1(随机猜测),损失 −log(0.1) = 2.3;得 0.9,损失 −log(0.9) = 0.105。模型学会把概率质量集中到正确答案上。

MSE 为何不适合分类

MSE 梯度在预测接近 0 或 1 时(因 sigmoid 饱和)变平;交叉熵梯度补偿了这一点——−log 抵消了 sigmoid 的平坦区,在最需要的地方给出强梯度。

标签平滑

标准 one-hot 标签说「这是 100% 的类 3,其它全是 0%」,这说法太满。标签平滑把它软化:

smooth_label = (1 - alpha) * one_hot + alpha / num_classes

alpha = 0.1、10 类时:目标不再是 [0, 0, 1, 0, ...],而是 [0.01, 0.01, 0.91, 0.01, ...]。模型目标从 1.0 降到 0.91。

为何有效:要让 softmax 输出恰好 1.0,模型得把 logits 推到无穷,这导致过度自信、损害泛化、使模型对分布漂移脆弱。标签平滑把目标封顶在 0.9(alpha=0.1),把 logits 留在合理范围。GPT 及多数现代模型都用标签平滑或其等价物。

对比损失

没有标签、没有类别,只有成对输入和一个问题:它俩相似还是不同?

SimCLR 式对比损失(NT-Xent / InfoNCE): 取一张图,造两个增强视图(裁剪、旋转、颜色抖动),它们是「正对」——嵌入应相似;batch 里其它图都是「负对」——嵌入应不同。

L = -log(exp(sim(z_i, z_j) / tau) / sum(exp(sim(z_i, z_k) / tau)))

sim() 是余弦相似度,z_i 与 z_j 是正对,求和遍历所有负样本,tau(温度)控制分布锐度——温度越低,负样本越难,分离越激进。batch 256 意味着每个正对有 255 个负样本,温度 tau = 0.07(SimCLR 默认),损失看起来像在相似度上做 softmax——它要正对的相似度在 256 个选项里最高。

三元组损失: 取三输入——锚点、正样本(同类)、负样本(异类)。

L = max(0, d(anchor, positive) - d(anchor, negative) + margin)

margin(典型 0.2~1.0)强制正负距离有最小间距。若负样本已经够远,损失为零——没梯度、不更新。这使训练高效,但需要精心做三元组挖掘(挑接近锚点的难负样本)。

Focal Loss

为不平衡数据而生。标准交叉熵对所有已正确分类的样本一视同仁,focal loss 给易分样本降权:

FL = -alpha * (1 - p_t)^gamma * log(p_t)

p_t 是真实类的预测概率,gamma 控制聚焦程度。gamma = 0 时退化为标准交叉熵;gamma = 2(默认)时:

  • 易分样本(p_t = 0.9):权重 = 0.1² = 0.01,几乎忽略。
  • 难分样本(p_t = 0.1):权重 = 0.9² = 0.81,完整梯度信号。

focal loss 由 Lin 等人为目标检测提出——那里 99% 的候选区是背景(易负样本),没有 focal loss,模型淹没在易背景样本里,学不会检测物体;有了它,模型把容量集中到要紧的难、模糊样本上。

损失函数决策树

二、从零实现

完整代码见原课程 phases/03-deep-learning-core/05-loss-functions/code/ 相应文件。

Step 1:MSE 及其梯度

def mse(predictions, targets): n = len(predictions) total = 0.0 for p, t in zip(predictions, targets): total += (p - t) ** 2 return total / n def mse_gradient(predictions, targets): n = len(predictions) return [2.0 * (p - t) / n for p, t in zip(predictions, targets)]

Step 2:二分类交叉熵

log(0) 是真问题:若模型对正样本预测恰为 0,log(0) = 负无穷,必须 clip。

def binary_cross_entropy(predictions, targets, eps=1e-15): n = len(predictions) total = 0.0 for p, t in zip(predictions, targets): p_clipped = max(eps, min(1 - eps, p)) total += -(t * math.log(p_clipped) + (1 - t) * math.log(1 - p_clipped)) return total / n

Step 3:多分类交叉熵配 softmax

def softmax(logits): max_val = max(logits) exps = [math.exp(x - max_val) for x in logits] total = sum(exps) return [e / total for e in exps] def categorical_cross_entropy(logits, target_index, eps=1e-15): probs = softmax(logits) p = max(eps, probs[target_index]) return -math.log(p) def cce_gradient(logits, target_index): probs = softmax(logits) grads = list(probs) grads[target_index] -= 1.0 return grads

softmax + 交叉熵的梯度化简得极漂亮:真实类是(预测概率 − 1),其它类是(预测概率)。这种优雅的化简不是巧合,正是 softmax 与交叉熵成对出现的原因。

Step 4:标签平滑

def label_smoothed_cce(logits, target_index, num_classes, alpha=0.1, eps=1e-15): probs = softmax(logits) loss = 0.0 for i in range(num_classes): if i == target_index: smooth_target = 1.0 - alpha + alpha / num_classes else: smooth_target = alpha / num_classes p = max(eps, probs[i]) loss += -smooth_target * math.log(p) return loss

Step 5:对比损失(简化版 InfoNCE)

基于余弦相似度,在正对相似度与负对相似度上做带温度的 softmax。

Step 6:MSE vs 交叉熵的分类对决

用第 04 节的两层网络(圆形数据集),分别配 MSE 与 BCE 训练,眼看交叉熵收敛更快、最终精度更高。这是「损失函数选错,模型钻空子」的最直观证据。

三、框架对比

PyTorch 内置全部标准损失,数值稳定性已处理:

import torch import torch.nn as nn import torch.nn.functional as F predictions = torch.tensor([0.9, 0.1, 0.7], requires_grad=True) targets = torch.tensor([1.0, 0.0, 1.0]) mse_loss = F.mse_loss(predictions, targets) bce_loss = F.binary_cross_entropy(predictions, targets) logits = torch.randn(4, 10) labels = torch.tensor([3, 7, 1, 9]) ce_loss = F.cross_entropy(logits, labels) ce_smooth = F.cross_entropy(logits, labels, label_smoothing=0.1)

务必用 F.cross_entropy(而不是 F.nll_loss 配手写 softmax)。它把 log-softmax 与负对数似然合并在一次数值稳定的运算里。先 softmax 再取 log 稳定性更差——大指数相减会丢精度。对比学习大多用自定义实现或 lightlypytorch-metric-learning 等库,核心循环一致:算两两相似度、在正负样本上做 softmax、反向传播。

四、可复用产物

本节产出(位于原课程 outputs/):

  • prompt-loss-function-selector.md:一个可复用提示,帮你选对损失函数。
  • prompt-loss-debugger.md:一个诊断提示,当损失曲线看着不对时用。

五、练习

  1. Easy:实现 Huber 损失(光滑 L1)——小误差用 MSE,大误差用 MAE。用预测 y = sin(x) 的回归网络,在 5% 目标掺噪声(异常值)时比较 MSE 与 Huber 的最终测试误差。
  2. Medium:把 focal loss 加进二分类训练循环。造不平衡数据(90% 类 0、10% 类 1),200 轮后比较标准 BCE 与 focal loss(gamma=2)在少数类召回率上的表现。
  3. Hard:实现带半难负样本挖掘的三元组损失。为 5 个类生成二维嵌入数据,对每个锚点找「仍比正样本远」的最难负样本(半难),与随机选三元组比较收敛。

本节要点回顾

  1. 损失函数是模型真正优化的唯一目标:不是准确率、不是 F1,选错损失,模型会钻空子。
  2. MSE 适合回归:对大误差二次惩罚,梯度与误差线性,但对异常值敏感。
  3. MSE 不适合分类:在 sigmoid 饱和区梯度变平,模型会自信地预测 0.5 来最小化损失,毫无区分力。
  4. 交叉熵是分类首选:−log(p) 重罚自信的错误、轻罚自信的正确,梯度在最需要处最强。
  5. softmax + 交叉熵梯度优雅:真实类(预测 − 1)、其它类(预测),这正是二者成对的原因。
  6. 标签平滑防过度自信:把 one-hot 软化,封顶 logits,GPT 等现代模型都用它。
  7. 对比损失定义自监督信号:InfoNCE 在正负对相似度上做带温度的 softmax,搞错会导致嵌入坍缩成一个点。
  8. focal loss 应对不平衡:(1 − p_t)^gamma 给易分样本降权,让模型聚焦难样本,目标检测的标准武器。
  9. 按任务选损失:回归 → MSE/Huber,二分类 → BCE,多分类 → 交叉熵(+ 标签平滑),不平衡 → focal,嵌入学习 → InfoNCE/三元组。

下一节,我们进入优化器——梯度告诉你方向,优化器决定走多远、多快,从 SGD 一路搭到 AdamW。


发布者: 作者: Rohit Gupta 转发
评论区 (0)
U