为什么 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,原因可以概括成三句话:
-
推理不需要概率,只需要排序。
Softmax 是保序的,argmax和TopK在 logits 和概率上结果一致。 -
训练需要概率,所以需要 Softmax。
但 Softmax 被合并进了交叉熵损失函数,不需要显式放在模型最后。 -
损失函数只训练用,推理不用。
所以模型结构只保留到 logits,部署时更简单、更稳定、更通用。
一句话:
VGG/ResNet 最后输出的是“得分”,不是“概率”。得分用于排序,概率用于解释。训练时交叉熵需要概率,但 Softmax 已经藏进损失函数;推理时只要第一名,Softmax 就可以省掉。

浙公网安备 33010602011771号