观文听傑

返回

上一篇建立了训练、验证、测试三者的边界。边界解决了“用什么数据评价”,但还没有回答“训练时到底优化什么”。

垃圾邮件分类最终只看预测对不对,最直观的目标似乎就是准确率(Accuracy)。可真实训练通常优化二元交叉熵(Binary Cross-Entropy,BCE),而不是准确率。为什么不直接优化真正关心的指标?

今天只讲透一个核心问题:怎样把离散的分类对错,变成可优化、能表达置信程度的损失?

01 准确率为什么不能直接指导更新?#

二分类器先输出一个实数分数 zz,再用阈值产生类别:

y^=1[z0]\hat y=\mathbb{1}[z\ge 0]
  • zRz\in\mathbb{R}:模型尚未阈值化的原始分数。
  • 1[]\mathbb{1}[\cdot]:条件成立时为 1,否则为 0。
  • y^{0,1}\hat y\in\{0,1\}:最终预测类别。

把预测错误记为 1、正确记为 0,得到 0-1 损失(0-1 Loss)

L0/1(y^,y)=1[y^y]L_{0/1}(\hat y,y)=\mathbb{1}[\hat y\ne y]

它正好对应“错了多少个”,却有一个致命问题:分数在阈值同一侧移动时,损失完全不变。

真实标签 y = 1

损失 1  ◄───────────────○
                       z=0
损失 0                  ○────────────────► z
                -2  -1  0  1  2

z 从 -2 增加到 -0.01:仍然预测 0,损失一直是 1
z 从  0.01 增加到  5:仍然预测 1,损失一直是 0
text

除阈值处外,它对 zz 的导数几乎处处为 0;阈值处又不可导。梯度无法告诉模型参数应该向哪个方向移动、移动多少。

02 从分数到概率:Sigmoid 做了什么?#

我们希望模型不仅说“0 或 1”,还表达相信正类的程度。对线性二分类器:

z=Xw+bz=Xw+b

若一批数据包含 BB 个样本、每个样本有 DD 个特征:

X:      [B, D]
w:      [D, 1]
b:      [1]       可广播到每个样本
z:      [B, 1]
y:      [B, 1]
text

zz对数几率分数(Logit),取值没有上下界。用 S 形函数(Sigmoid Function) 将它压到 (0,1)(0,1)

p=σ(z)=11+ezp=\sigma(z)=\frac{1}{1+e^{-z}}

我们把 pp 解释为模型估计的 P(y=1x)P(y=1\mid x)。若 p0.5p\ge 0.5,等价于 z0z\ge 0,预测正类。

logit zz正类概率 pp直觉
2.197-2.1970.100.10强烈倾向负类
000.500.50无法区分
2.1972.1970.900.90强烈倾向正类

概率保留了阈值丢掉的信息:0.510.99 都预测正类,但后者确信得多。

03 二元交叉熵怎样惩罚“自信地犯错”?#

若真实标签 y=1y=1,希望预测概率 pp 越大越好,可以使用 logp-\log p;若 y=0y=0,希望 1p1-p 越大越好,可以使用 log(1p)-\log(1-p)。合并后就是 BCE:

L(p,y)=[ylogp+(1y)log(1p)]L(p,y)=-\left[y\log p+(1-y)\log(1-p)\right]
  • y{0,1}y\in\{0,1\}:真实标签。
  • p(0,1)p\in(0,1):正类预测概率。
  • L[0,+)L\in[0,+\infty):单个样本的标量损失。

因为 yy 只能是 0 或 1,公式每次实际只保留一项:

L(p,1)=logp,L(p,0)=log(1p)L(p,1)=-\log p,\qquad L(p,0)=-\log(1-p)

一个可手算的对比#

有两个正样本,两个模型都把它们判对:

模型p1p_1p2p_2准确率平均 BCE
A:勉强正确0.510.55100%[log0.51log0.55]/20.636[-\log0.51-\log0.55]/2\approx0.636
B:更有把握0.800.90100%[log0.8log0.9]/20.164[-\log0.8-\log0.9]/2\approx0.164

准确率看不出差别,BCE 能推动模型继续改进概率。

再看真实标签 y=1y=1 时的错误预测:

预测 ppBCE含义
0.40.916判断错误,但仍保留不确定性
0.12.303很自信地判断错误
0.0016.908极度自信地判断错误

对数让“自信地犯错”付出巨大代价,同时仍保持平滑可导。

交互手算:负样本被预测成 0.8,损失是多少?

此时 y=0y=0,所以 L=log(10.8)=log0.21.609L=-\log(1-0.8)=-\log0.2\approx1.609。若概率降到 0.2,损失变成 log0.80.223-\log0.8\approx0.223

04 为什么它的梯度特别干净?#

把 Sigmoid 和 BCE 连起来,对 logit zz 求导:

Lz=σ(z)y=py\frac{\partial L}{\partial z}=\sigma(z)-y=p-y

这个结果值得记住,因为它直接说明更新信号是什么:预测概率减真实标签

假设 y=1,z=0y=1,z=0,则 p=0.5p=0.5

Lz=0.51=0.5\frac{\partial L}{\partial z}=0.5-1=-0.5

梯度下降会减去负梯度,使 zz 增大,正类概率随之上升。若 y=0,p=0.8y=0,p=0.8,梯度是 0.80.8,更新会压低 zz

线性模型 z=xw+bz=x^\top w+b 进一步得到:

Lw=x(py),Lb=py\frac{\partial L}{\partial w}=x(p-y),\qquad \frac{\partial L}{\partial b}=p-y

于是数据流完整闭环:

输入 X [B,D]


线性层 Xw+b ──► logits z [B,1]


              BCEWithLogitsLoss ◄── 标签 y [B,1]


              标量 mean loss
                     │ backward

          w.grad [D,1],b.grad [1]
text

05 为什么代码里不要先手动 Sigmoid?#

直接计算 sigmoid(z) 后再取对数,在 z|z| 很大时可能发生数值问题。例如 z=1000z=-1000 的 Sigmoid 在有限精度中接近 0,随后 log(0) 会得到负无穷。

PyTorch 当前官方接口 torch.nn.BCEWithLogitsLoss 把 Sigmoid 与 BCE 合并,并使用 对数和指数技巧(Log-Sum-Exp Trick) 稳定计算。训练时应把原始 logits 直接传给它:

重要 API 的输入输出:

  • torch.nn.Linear(2, 1):最后一维从 2 个特征映射为 1 个 logit。
  • BCEWithLogitsLossinputtarget 形状必须一致;标签是浮点数且通常位于 [0,1][0,1]
  • reduction='mean':先得到每个元素的损失,再取均值,最终输出零维标量。
  • torch.sigmoid(logits):只在需要概率做评价或推理时显式调用。

06 从最大似然看交叉熵#

二分类标签可以看作 伯努利分布(Bernoulli Distribution) 的一次观测:

P(yx)=py(1p)1yP(y\mid x)=p^y(1-p)^{1-y}

NN 个独立样本,最大化全部标签的似然,等价于最小化负对数似然:

1Ni=1NlogP(yixi)=1Ni=1N[yilogpi+(1yi)log(1pi)]-\frac{1}{N}\sum_{i=1}^N\log P(y_i\mid x_i) =-\frac{1}{N}\sum_{i=1}^N \left[y_i\log p_i+(1-y_i)\log(1-p_i)\right]

这正是平均 BCE。它不是随意选出的光滑函数,而是在模型输出被解释为概率时,对观测数据做 最大似然估计(Maximum Likelihood Estimation) 的结果。

不过,“概率输出”不自动等于“概率可信”。模型可能预测 0.9 的样本只有 70% 真为正类,这涉及后续会讲的 概率校准(Calibration)

07 BCE、均方误差和多分类交叉熵不要混用#

任务模型输出常见训练损失标签形状
二分类1 个 logitBCEWithLogitsLoss[B, 1] 浮点 0/1
多标签分类每个标签 1 个独立 logitBCEWithLogitsLoss[B, C] 浮点 0/1
单标签多分类CC 个互斥类别 logitsCrossEntropyLoss[B] 整数类别索引
回归连续实数MSELossL1Loss与预测形状一致

均方误差也能拟合 0/1 标签,但配合 Sigmoid 时,极端错误区域的梯度可能变弱;BCE 的概率解释和梯度结构更适合标准二分类。

多标签任务中的每个类别可以同时为 1,例如一张图片既有“室内”又有“人物”,因此用独立 Sigmoid;单标签多分类只能选一个类别,类别之间竞争,因此用 Softmax 形式的多分类交叉熵。二者不能仅凭输出列数判断。

08 工程中最常见的错误与调试方法#

  1. logits 与概率混淆。 打印最小值和最大值;概率必须在 [0,1][0,1],logits 可以是任意实数。
  2. 标签形状触发广播。 预测为 [B,1] 而标签为 [B] 时,先显式 y = y.float().view(-1, 1),再断言形状相同。
  3. 标签类型错误。 BCE 需要浮点标签;整数类别索引是 CrossEntropyLoss 的接口习惯。
  4. 类别极不平衡。 只看平均损失可能忽视少数类;结合每类召回率,并在训练集上估计合适的 pos_weight
  5. 阈值固定等于 0.5。 0.5 是概率到类别的默认决策,不是普适业务最优点;应在验证集上按误报、漏报成本选择阈值。
  6. 损失突然为 nan 检查输入是否含非有限值、学习率是否过大,并确认没有手动对 0 取对数。

最小调试断言:

assert logits.shape == y.shape
assert y.dtype.is_floating_point
assert torch.isfinite(logits).all()
assert torch.isfinite(loss)
python

09 它什么时候仍会失败?#

BCE 假设训练标签值得信任,并把每个样本对目标的贡献通过平均值汇总。以下情况需要额外设计:

  • 标签含大量错误时,对数损失会重罚模型与错误标签不一致的高置信预测。
  • 正负类成本不对称或比例极端失衡时,需要加权、重采样或专门损失。
  • 数据分布改变时,训练出的概率可能失去校准。
  • 只优化样本平均误差,不能自动满足公平性、延迟或业务约束。
  • 阈值后的准确率仍然不可导,训练损失下降也不保证目标业务指标单调上升。

因此必须同时记录训练损失与验证指标:前者解释优化是否工作,后者判断任务是否真的变好。

10 今天真正需要记住什么?#

  1. 0-1 损失对应分类对错,却几乎处处梯度为 0,无法有效指导参数更新。
  2. Sigmoid 把 logit 映射为正类概率,BCE 同时利用标签和置信程度。
  3. Sigmoid 与 BCE 组合后,对 logit 的梯度是 pyp-y
  4. 训练时把原始 logits 传给 BCEWithLogitsLoss;推理时再显式计算概率和阈值。
  5. 损失负责可优化性,指标负责任务评价,两者要分别监控。

11 思考题与小练习#

练习 1:同样判断正确,谁的损失更小?

对正样本,模型 A 预测 0.6,模型 B 预测 0.9。两者准确率都为 1;BCE 分别约为 0.511 和 0.105,所以 B 的损失更小。

练习 2:写出一个负样本的更新方向

y=0,p=0.7y=0,p=0.7,则 L/z=py=0.7\partial L/\partial z=p-y=0.7。梯度下降会减小 zz,从而减小正类概率。

练习 3:故意制造重复 Sigmoid

把示例中的 loss_fn(logits, y) 改成 loss_fn(torch.sigmoid(logits), y),比较收敛速度和最终概率。解释为什么接口仍接受输入,但语义已经错误。

相关工作#

12 下一篇预告#

损失函数已经把“错得多严重”变成了可导标量,但真实数据不可能每次全部塞进模型。下一篇将追踪一批样本如何经过前向、求平均、反向和更新,解释全批量、随机与小批量梯度下降究竟差在哪里。

为什么分类器不直接优化准确率?从 0-1 损失到二元交叉熵
https://zwjcode.cn/blog/classification-loss-cross-entropy
作者
发布于 2026年8月19日
版权协议 CC BY-NC-SA 4.0
评论加载似乎遇到了问题,请尝试刷新页面。