Softmax 是深度学习中最经典的函数之一,几乎所有多分类模型的输出层都少不了它。本文将从原理、数学公式、代码实现到实际应用,带你彻底搞懂 Softmax。
一、什么是 Softmax?#Softmax 函数(归一化指数函数)是一种将任意实数向量转换为概率分布的函数。它是深度学习中多分类任务的"标配"输出函数。
直观理解#1
2
3
输入(logits): [2.0, 1.0, 0.1] ← 任意实数
↓ Softmax
输出(概率): [0.659, 0.242, 0.099] ← 总和 = 1.0
它的作用可以用一句话概括:
把一组"分数"转换成一组"概率",分数越高的类别获得越大的概率。
名字的含义#Soft(软):相对于 argmax(硬性地选最大值),Softmax 给出"软"的概率分布,保留了所有类别的信息max:最大的输入值仍会获得最大的概率(放大效应)二、数学原理#2.1 核心公式#对于一个 $n$ 维实数向量 $\mathbf{x} = [x_1, x_2, …, x_n]$,Softmax 函数定义为:
$$ \text{Softmax}(x_i) = \frac{e^{x_i}}{\sum_{j=1}^{n} e^{x_j}} $$
公式分解:
部分作用分子 $e^{x_i}$对每个元素取指数,保证输出为正,且放大数值差异分母 $\sum_j e^{x_j}$所有指数值之和,作为归一化因子2.2 为什么用指数函数?#保证非负:$e^x > 0$ 恒成立,概率必须非负放大差异:指数函数会让大的更大、小的更小,使"赢家"更突出可微分:处处可导,便于反向传播信息论意义:对应最大熵原理下的解2.3 手动计算示例#以输入 [2.0, 1.0, 0.1] 为例:
Step 1:计算每个 $e^{x_i}$
$$ e^{2.0} \approx 7.389, \quad e^{1.0} \approx 2.718, \quad e^{0.1} \approx 1.105 $$
Step 2:计算总和
$$ \text{sum} = 7.389 + 2.718 + 1.105 = 11.212 $$
Step 3:归一化
$$ p_1 = \frac{7.389}{11.212} \approx 0.659 $$
$$ p_2 = \frac{2.718}{11.212} \approx 0.242 $$
$$ p_3 = \frac{1.105}{11.212} \approx 0.099 $$
验证:$0.659 + 0.242 + 0.099 = 1.000$ ✅
三、Softmax 的三大特性#1️⃣ 输出是概率分布#每个输出值 $p_i \in (0, 1)$所有输出值之和 $\sum_i p_i = 1$2️⃣ 保持单调性#输入大的,输出也大,排序不变:
$$ x_i > x_j \iff p_i > p_j $$
3️⃣ 指数放大效应#数值差距会被指数函数显著放大:
1
2
输入:[1, 2, 3] → Softmax:[0.09, 0.24, 0.67]
输入:[1, 2, 10] → Softmax:[0.0001, 0.0003, 0.9996]
最大值会"吃掉"大部分概率。
四、代码实现#4.1 NumPy 基础实现# 1
2
3
4
5
6
7
8
9
10
import numpy as np
def softmax(x):
exp_x = np.exp(x)
return exp_x / np.sum(exp_x)
logits = np.array([2.0, 1.0, 0.1])
probs = softmax(logits)
print(probs) # [0.659, 0.242, 0.099]
print(probs.sum()) # 1.0
4.2 数值稳定版本(推荐)⭐#直接实现在面对大数值时会溢出:
1
2
3
# ❌ 直接计算会溢出
x = np.array([1000, 1001, 1002])
np.exp(x) # inf,数值溢出!
解决方案:减去最大值
1
2
3
4
def softmax_stable(x):
x_shifted = x - np.max(x) # 防止 exp 溢出
exp_x = np.exp(x_shifted)
return exp_x / np.sum(exp_x)
数学证明(减最大值不改变结果):
$$ \frac{e^{x_i - c}}{\sum_j e^{x_j - c}} = \frac{e^{x_i} \cdot e^{-c}}{e^{-c} \cdot \sum_j e^{x_j}} = \frac{e^{x_i}}{\sum_j e^{x_j}} $$
4.3 PyTorch 实现# 1
2
3
4
5
6
7
8
9
10
11
12
13
import torch
import torch.nn.functional as F
logits = torch.tensor([2.0, 1.0, 0.1])
# 方法 A:函数式
probs = F.softmax(logits, dim=0)
# 方法 B:模块化
softmax = torch.nn.Softmax(dim=0)
probs = softmax(logits)
print(probs) # tensor([0.6590, 0.2424, 0.0986])
4.4 批量处理# 1
2
3
4
5
6
7
8
9
10
11
12
13
# batch_size=2, num_classes=3
logits = torch.tensor([
[2.0, 1.0, 0.1],
[1.0, 3.0, 0.5]
])
# dim=1:对每一行做 softmax(最常见用法)
probs = F.softmax(logits, dim=1)
print(probs)
# tensor([[0.6590, 0.2424, 0.0986],
# [0.1131, 0.8360, 0.0508]])
print(probs.sum(dim=1)) # tensor([1.0, 1.0])
💡 dim 参数记忆法:对哪个维度做 softmax,哪个维度的和就是 1。
五、梯度与反向传播#5.1 Softmax 的梯度#设 $p_i = \text{Softmax}(x_i)$,其雅可比矩阵为:
$$ \frac{\partial p_i}{\partial x_j} = \begin{cases} p_i(1 - p_i), & i = j \ -p_i \cdot p_j, & i \neq j \end{cases} $$
5.2 与交叉熵的完美配合 🎯#当 Softmax 与交叉熵损失(CrossEntropy)组合使用时,梯度变得异常简洁:
$$ \frac{\partial L}{\partial x_i} = p_i - y_i $$
$p_i$:Softmax 的预测概率$y_i$:真实标签(one-hot 编码)例子:
1
2
3
预测:[0.659, 0.242, 0.099]
真实:[1.0, 0.0, 0.0]
梯度:[-0.341, 0.242, 0.099]
这就是为什么分类任务中 Softmax + CrossEntropy 是黄金搭档——梯度形式简洁且不会梯度消失。
六、Softmax vs Sigmoid 🔥#这是面试和实际应用中的高频考点,必须搞清楚!
6.1 对比表格#特性SoftmaxSigmoid输出维度多维向量(n 维)单个标量输出范围每个值 $\in (0, 1)$$\in (0, 1)$概率总和$\sum p_i = 1$独立,不受约束公式$\frac{e^{x_i}}{\sum_j e^{x_j}}$$\frac{1}{1+e^{-x}}$适用场景多分类(互斥)二分类 / 多标签(独立)类别关系竞争关系独立关系常搭配损失CrossEntropyLossBCEWithLogitsLoss6.2 场景对比#场景 A:多分类(互斥)→ 用 Softmax#一张图片识别是猫、狗、鸟中的哪一种,三者只能选一个。
1
2
3
logits = torch.tensor([2.0, 1.0, 0.1]) # [猫, 狗, 鸟]
probs = F.softmax(logits, dim=0)
# [0.659, 0.242, 0.099] → 和为 1,是"猫"的概率最高
场景 B:多标签(独立)→ 用 Sigmoid#一张图片可能同时包含猫和室内两个独立标签。
1
2
3
logits = torch.tensor([2.0, 1.0, 0.1]) # [有猫, 室内, 夜景]
probs = torch.sigmoid(logits)
# [0.881, 0.731, 0.525] → 各自独立,和不为 1
6.3 有趣的数学关系#当 Softmax 用于二分类时,它等价于 Sigmoid:
$$ \text{Softmax}([x_1, x_2])_1 = \frac{e^{x_1}}{e^{x_1}+e^{x_2}} = \frac{1}{1+e^{-(x_1-x_2)}} = \text{Sigmoid}(x_1-x_2) $$
所以 Sigmoid 可以看作 Softmax 的二维特例。
6.4 选择决策树#1
2
3
4
分类任务?
├── 二分类 → Sigmoid(输出1个值)或 Softmax(输出2个值)
├── 多分类,类别互斥 → Softmax ✅
└── 多标签,类别独立 → Sigmoid(对每个类独立输出)✅
七、Softmax 的进阶变体#7.1 带温度的 Softmax(Temperature Scaling)#通过引入"温度"参数 $T$ 调整分布的平滑程度:
$$ \text{Softmax}(x_i / T) = \frac{e^{x_i/T}}{\sum_j e^{x_j/T}} $$
1
2
3
4
5
6
7
8
def softmax_with_temperature(x, T=1.0):
return np.exp(x/T) / np.sum(np.exp(x/T))
x = np.array([2.0, 1.0, 0.1])
print(softmax_with_temperature(x, T=0.5)) # [0.866, 0.117, 0.017] 更尖锐
print(softmax_with_temperature(x, T=1.0)) # [0.659, 0.242, 0.099] 原始
print(softmax_with_temperature(x, T=5.0)) # [0.418, 0.342, 0.240] 更平滑
温度的影响:
温度效果应用场景$T < 1$分布更尖锐,更自信模型蒸馏(学生模型)$T = 1$标准 Softmax常规训练$T > 1$分布更平滑探索性采样$T \to 0$趋近于 argmax(one-hot)确定性输出$T \to \infty$趋近于均匀分布完全随机💡 温度的直觉:温度越高,模型越"犹豫"(概率越平均);温度越低,模型越"自信"(概率越集中)。这与物理中的热力学分布同源。
7.2 LogSoftmax#在实际工程中,常用 LogSoftmax 代替 Softmax,即对 Softmax 的结果取对数:
$$ \text{LogSoftmax}(x_i) = \log\left(\frac{e^{x_i}}{\sum_j e^{x_j}}\right) = x_i - \log\sum_j e^{x_j} $$
1
2
3
4
5
import torch.nn.functional as F
logits = torch.tensor([2.0, 1.0, 0.1])
log_probs = F.log_softmax(logits, dim=0)
print(log_probs) # tensor([-0.4170, -1.4170, -2.3170])
为什么用 LogSoftmax?
数值稳定:避免概率值过小导致的下溢计算高效:交叉熵损失中需要 $\log p$,直接用 LogSoftmax 省去一次对数运算配合 NLLLoss:LogSoftmax + NLLLoss 等价于 CrossEntropyLoss1
2
3
4
5
# 以下两种写法完全等价
loss_a = F.cross_entropy(logits, target)
log_probs = F.log_softmax(logits, dim=0)
loss_b = F.nll_loss(log_probs.unsqueeze(0), target)
八、实际应用场景#8.1 图像分类#多分类模型(ResNet、ViT 等)的输出层几乎都用 Softmax:
1
2
3
4
5
6
7
8
9
10
11
12
import torch.nn as nn
model = nn.Sequential(
nn.Linear(512, 10), # 10 个类别的 logits
# 注意:训练时通常不显式加 Softmax,
# 因为 CrossEntropyLoss 内部已包含
)
# 推理时才显式转为概率
logits = model(features)
probs = F.softmax(logits, dim=1)
pred = probs.argmax(dim=1) # 预测类别
⚠️ 常见错误:训练时在模型里加了 Softmax,又用 CrossEntropyLoss,会导致"双重 Softmax",训练效果变差!
8.2 注意力机制(Attention)#Transformer 的核心——注意力权重就是用 Softmax 归一化的:
$$ \text{Attention}(Q, K, V) = \text{Softmax}\left(\frac{QK^\top}{\sqrt{d_k}}\right) V $$
1
2
3
scores = Q @ K.transpose(-2, -1) / (d_k ** 0.5)
weights = F.softmax(scores, dim=-1) # 每一行的注意力权重和为 1
output = weights @ V
8.3 强化学习中的策略#在策略梯度方法中,Softmax 把动作价值转为动作选择的概率分布:
1
2
3
action_logits = policy_net(state)
action_probs = F.softmax(action_logits, dim=-1)
action = torch.multinomial(action_probs, num_samples=1) # 按概率采样
8.4 知识蒸馏#用带温度的 Softmax 让学生模型学习教师模型的"软标签":
1
2
3
4
T = 3.0
teacher_probs = F.softmax(teacher_logits / T, dim=1) # 软化的教师输出
student_log = F.log_softmax(student_logits / T, dim=1)
distill_loss = F.kl_div(student_log, teacher_probs) * (T * T)
九、常见陷阱与注意事项 ⚠️#9.1 数值溢出#1
2
3
4
5
6
# ❌ 错误:大数值直接 exp 会溢出为 inf
x = np.array([1000, 1001, 1002])
np.exp(x) / np.sum(np.exp(x)) # nan!
# ✅ 正确:减去最大值
softmax_stable(x) # 正常输出
9.2 双重 Softmax#1
2
3
4
5
6
7
# ❌ 错误:模型已输出概率,又传给 CrossEntropyLoss
probs = F.softmax(model(x), dim=1)
loss = F.cross_entropy(probs, target) # 错!cross_entropy 期望 logits
# ✅ 正确:直接传 logits
logits = model(x)
loss = F.cross_entropy(logits, target)
9.3 dim 参数搞错#1
2
3
4
5
# batch_size=2, num_classes=3
logits = torch.randn(2, 3)
F.softmax(logits, dim=0) # ❌ 对 batch 维归一化(错误!)
F.softmax(logits, dim=1) # ✅ 对类别维归一化(正确)
💡 口诀:分类任务中,dim 要指向类别所在的维度。
十、常见问题 FAQ#Q1:Softmax 和 argmax 有什么区别?
argmax 直接返回最大值的索引(硬决策,不可导)Softmax 返回一个概率分布(软决策,可导),训练中必须用 Softmax 才能反向传播Q2:为什么训练时推荐用 CrossEntropyLoss 而不手动写 Softmax?
因为 CrossEntropyLoss 内部融合了 LogSoftmax + NLLLoss,数值更稳定、计算更高效,还能避免双重 Softmax 的坑。
Q3:Softmax 一定要配交叉熵吗?
绝大多数多分类场景是的,因为两者组合后梯度形式最简洁($p_i - y_i$),训练稳定。但在某些场景(如蒸馏、强化学习)也会配 KL 散度等其他损失。
Q4:输入全是负数,Softmax 还能用吗?
可以。$e^x$ 对任意实数都为正,负数输入只是让对应概率变小,不影响归一化。
十一、总结#要点内容本质把实数向量转为概率分布公式$\text{Softmax}(x_i) = \dfrac{e^{x_i}}{\sum_j e^{x_j}}$三大特性输出为概率、保持单调、指数放大数值稳定计算前减去最大值黄金搭档Softmax + CrossEntropy(梯度为 $p_i - y_i$)vs Sigmoid多分类互斥用 Softmax,多标签独立用 Sigmoid工程实践用 CrossEntropyLoss,别手动加 SoftmaxSoftmax 看似简单,却贯穿了分类、注意力、强化学习、知识蒸馏等几乎所有深度学习的核心场景。理解它的原理、数值技巧和使用陷阱,是打好深度学习基础的关键一步。
📌 下一篇预告:交叉熵损失(Cross-Entropy Loss)—— 揭开 Softmax 最佳拍档的神秘面纱。