为什么 VGG/ResNet 最后不接 Softmax,而是直接输出 fc 的结果?

打开 torchvision 里的 ResNet、VGG,你通常会发现:最后是一个全连接层 fc,输出 1000 维向量,然后就结束了。没有 Softmax。

很多人第一次看到会疑惑:分类网络不是应该输出概率吗?没有 Softmax,怎么知道属于哪一类?

答案其实很简洁:

因为推理时根本不需要 Softmax。Softmax 是保序的,取 TopK 只需要排序;训练时虽然需要概率,但 Softmax 已经被合并进损失函数里了。损失函数只训练用,推理不用,所以模型最后只输出 logits 就够了。

下面慢慢解释。


一、模型最后输出的是什么?

VGG、ResNet 最后的 fc 层输出通常叫 logits,也就是未归一化的得分。

比如一个 3 分类模型输出:

logits = [3.2, -1.1, 0.5]

你可以把它理解成模型对三个类别的“原始打分”。分数越高,模型越觉得像那个类别。

如果接上 Softmax:

softmax([3.2, -1.1, 0.5]) ≈ [0.82, 0.01, 0.05]

就变成了概率形式,三个数加起来等于 1。

但问题是:分类任务真的需要概率吗?

如果只是要知道“最可能是哪一类”,其实不需要。

因为:

argmax(logits) = argmax(softmax(logits))

也就是说,[3.2, -1.1, 0.5] 里最大的是第 0 类,Softmax 之后最大仍然是第 0 类。


二、Softmax 是保序的,所以 TopK 不需要它

Softmax 的定义是:

[
p_i = \frac{e^{z_i}}{\sum_j e^{z_j}}
]

其中 (z_i) 是第 (i) 类的 logit。

看任意两个类别 (i) 和 (j):

[
\frac{p_i}{p_j} = \frac{e{z_i}}{e{z_j}} = e^{z_i - z_j}
]

因为指数函数 (e^x) 是单调递增的,所以:

[
z_i > z_j \iff p_i > p_j
]

这意味着:

  • logits 的排序,和 Softmax 之后的概率排序完全一样;
  • argmax 不变;
  • TopK 的类别集合不变;
  • 第几名还是第几名。

所以推理时如果只是取最大类别,或者取 TopK 候选,根本不需要算 Softmax。

直接:

pred = logits.argmax(dim=1)
topk = logits.topk(k=5, dim=1)

就可以了。

Softmax 在这里只是多做了一堆指数、求和、除法,还可能带来数值溢出风险,却没有改变排序结果。


三、训练时为什么需要 Softmax?

训练分类网络通常用交叉熵损失。交叉熵是定义在概率上的:

[
L = -\log p_y
]

其中 (p_y) 是正确类别的预测概率。

所以训练时确实需要把 logits 变成概率,这一步就是 Softmax。

但现代框架不会让你先显式 Softmax,再取 log。因为:

[
p_y = \frac{e^{z_y}}{\sum_j e^{z_j}}
]

所以:

[
L = -\log p_y
= -\log \frac{e^{z_y}}{\sum_j e^{z_j}}
= -z_y + \log \sum_j e^{z_j}
]

也就是:

[
L = \text{logsumexp}(z) - z_y
]

这个式子只依赖 logits。

PyTorch 里的 nn.CrossEntropyLoss 就是干这件事的:它接收 logits,内部做 log_softmax 和负对数似然,而不是让你在模型最后加 Softmax。

所以训练时:

logits = model(x)
loss = nn.CrossEntropyLoss()(logits, target)

这里 CrossEntropyLoss 内部已经包含了 Softmax 的逻辑。

如果你在模型最后加了 Softmax,再送进 CrossEntropyLoss,反而会变成“Softmax 两次”,通常是不对的。


四、既然训练需要、推理不需要,那就放进损失函数

这其实是深度学习框架里一个很常见的设计原则:

模型前向传播只保留推理也需要的计算;训练专用的计算,放进损失函数或训练循环里。

损失函数本身就是训练才需要的东西。推理时你只做前向传播,不会计算 loss。

Softmax 也是类似:

  • 训练时需要它来构造交叉熵;
  • 推理取类别时不需要它;
  • 它没有参数,不改变排序;
  • 它更像“损失函数的一部分”,而不是“模型结构的一部分”。

所以现代实现通常把它合并进损失函数:

模型:backbone → fc → logits
训练:logits → CrossEntropyLoss(内部含 Softmax)
推理:logits → argmax / topk

这样模型导出、部署、推理都更干净。


五、一个代码例子

import torch
import torch.nn as nn
import torch.nn.functional as F

logits = model(x)  # 假设输出 [batch, 1000]

# 推理:只要类别,不需要 Softmax
pred = logits.argmax(dim=1)

# 推理:只要 TopK,也不需要 Softmax
values, indices = logits.topk(k=5, dim=1)

# 训练:CrossEntropyLoss 接收 logits,内部含 Softmax
loss = F.cross_entropy(logits, target)

# 如果确实需要概率,再手动加 Softmax
prob = logits.softmax(dim=1)

注意最后一行:需要概率时才加 Softmax。不需要概率时,它就是多余的。


六、那是不是所有推理都不需要 Softmax?

也不是。

如果你只是做标准分类,取最大类别或 TopK,那么 Softmax 可以省掉。

但如果你需要:

  • 显示“置信度”;
  • 设置概率阈值;
  • 做多模型概率融合;
  • 做温度缩放校准;
  • 做知识蒸馏的软标签;
  • 做不确定性估计;

那么你可能仍然需要 Softmax,或者需要经过校准的 Softmax。

但这是后处理或特定任务的需求,不是 VGG/ResNet 标准分类推理的必需步骤。

另外,多标签分类通常用 Sigmoid,而不是 Softmax,因为每个类别独立,不是互斥的。


七、总结

VGG、ResNet 最后只到 fc,输出 logits,不接 Softmax,原因可以概括成三句话:

  1. 推理不需要概率,只需要排序。
    Softmax 是保序的,argmax 和 TopK 在 logits 和概率上结果一致。

  2. 训练需要概率,所以需要 Softmax。
    但 Softmax 被合并进了交叉熵损失函数,不需要显式放在模型最后。

  3. 损失函数只训练用,推理不用。
    所以模型结构只保留到 logits,部署时更简单、更稳定、更通用。

一句话:

VGG/ResNet 最后输出的是“得分”,不是“概率”。得分用于排序,概率用于解释。训练时交叉熵需要概率,但 Softmax 已经藏进损失函数;推理时只要第一名,Softmax 就可以省掉。

posted @ 2026-10-15 16:26  绝不原创的飞龙  阅读(6)  评论(0)    收藏  举报