打开任何一本深度学习教材,或者浏览机器学习面试题库,几乎都会遇到这个问题:多分类任务应该用Sigmoid还是Softmax?答案似乎很简单——多分类用Softmax,二分类用Sigmoid。但当面试官追问"为什么"时,很多人就卡住了。更麻烦的是多标签分类场景:一个样本同时属于多个类别,这时候该用什么?

这个看似基础的问题,实际上涉及概率论基础、信息论原理、优化理论,以及神经网络训练的底层机制。理解清楚这个问题,不仅能帮助你在面试中从容应对,更能让你在实际项目中做出正确的架构决策。

从一个具体问题说起

假设你在构建一个新闻分类系统,需要判断一篇新闻属于哪个类别。如果类别是"体育"、“财经”、“科技"互斥的几个选项,这是一个典型的多分类问题。但如果类别变成了"体育”、“足球”、“国际"这样可能重叠的标签,一篇关于国际足球赛事的新闻可能同时属于这三个类别,这就变成了多标签问题。

这两种场景在技术实现上有本质区别,而区别的根源在于对概率分布的不同假设。

graph TD
    A[分类任务] --> B{类别关系}
    B -->|互斥| C[多分类]
    B -->|独立共存| D[多标签分类]
    C --> E[Softmax + 交叉熵]
    D --> F[Sigmoid + 二元交叉熵]
    E --> G[输出和为1]
    F --> H[各输出独立]

Sigmoid:独立的二元决策

Sigmoid函数的定义是:

$$\sigma(z) = \frac{1}{1 + e^{-z}}$$

这个看似简单的公式背后有深刻的统计学基础。从逻辑回归的角度看,Sigmoid函数是对数几率的逆变换。假设一个事件发生的概率为$p$,那么对数几率定义为:

$$\text{logit}(p) = \log\left(\frac{p}{1-p}\right)$$

Sigmoid函数正是这个变换的逆函数,将任意实数映射到$(0, 1)$区间。

graph LR
    subgraph Sigmoid函数特性
        A[输入: 任意实数 z] --> B["σ(z) = 1/(1+e^-z)"]
        B --> C[输出: 0到1之间的概率]
    end
    
    subgraph 关键性质
        D["σ(0) = 0.5"]
        E["σ(∞) → 1"]
        F["σ(-∞) → 0"]
    end
    
    C --> D
    C --> E
    C --> F

为什么Sigmoid适合二分类?

在二分类问题中,我们只需要判断一个样本是否属于某个类别。神经网络的输出层产生一个标量值$z$(logit),通过Sigmoid函数转换为概率:

$$P(y=1|x) = \sigma(z)$$

对应的类别为0的概率自然是:

$$P(y=0|x) = 1 - \sigma(z) = \sigma(-z)$$

这天然符合概率公理:两个互斥事件的概率之和为1。

Sigmoid的梯度特性

Sigmoid函数的导数有一个优雅的性质:

$$\sigma'(z) = \sigma(z)(1 - \sigma(z))$$

这意味着梯度在函数值接近0.5时最大(约为0.25),而在接近0或1时趋于0。这既是优点也是缺点:优点是模型对不确定的样本给予更大的调整力度,缺点是当输出接近饱和区时会出现梯度消失问题。

Softmax:竞争性的概率分布

Softmax函数的定义是:

$$\text{softmax}(z_i) = \frac{e^{z_i}}{\sum_{j=1}^{K} e^{z_j}}$$

其中$K$是类别总数,$z_i$是第$i$个类别的logit值。

graph TD
    subgraph Softmax计算过程
        A["Logits向量 z = [z₁, z₂, z₃]"] --> B["指数化: e^z = [e^z₁, e^z₂, e^z₃]"]
        B --> C["求和: S = e^z₁ + e^z₂ + e^z₃"]
        C --> D["归一化: pᵢ = e^zᵢ/S"]
    end
    
    subgraph 输出性质
        D --> E["所有pᵢ ∈ (0,1)"]
        D --> F["Σpᵢ = 1"]
        D --> G["各类别竞争关系"]
    end

Softmax的概率解释

Softmax函数将一个$K$维向量转换为概率分布,满足两个关键性质:

  1. 非负性:每个输出值都在$(0, 1)$区间内
  2. 归一性:所有输出值之和为1

但更重要的是,Softmax隐含了一个假设:各类别之间是互斥的。当一个类别的概率增大时,其他类别的概率必然减小——这是一种竞争关系。

从统计力学的角度看,Softmax实际上是Boltzmann分布(也称Gibbs分布)在离散状态空间的具体形式。给定能量状态$E_i$,系统处于状态$i$的概率为:

$$P(i) = \frac{e^{-E_i/T}}{\sum_j e^{-E_j/T}}$$

其中$T$是温度参数。如果将logit视为能量的负值($z_i = -E_i$),并令$T=1$,就得到了Softmax公式。

Softmax与温度参数

在大语言模型中,温度参数$T$被广泛使用:

$$\text{softmax}(z_i, T) = \frac{e^{z_i/T}}{\sum_{j=1}^{K} e^{z_j/T}}$$

温度参数控制概率分布的"锐利度”:

  • 低温度($T < 1$):放大差异,使最大值更突出,分布更"尖锐"
  • 高温度($T > 1$):平滑差异,使分布更均匀

当$T \to 0$时,Softmax趋近于argmax(取最大值);当$T \to \infty$时,趋近于均匀分布。

多分类与多标签的本质区别

现在回到核心问题:什么情况下用Softmax,什么情况下用Sigmoid?

多分类(Multi-class Classification)

定义:每个样本恰好属于一个类别,类别之间互斥。

例子

  • 手写数字识别(0-9,只能是一个数字)
  • 新闻单标签分类(体育/财经/科技,只能选一个)
  • 图像分类(猫/狗/鸟,只能是一种动物)

技术方案:输出层使用Softmax激活函数,损失函数使用交叉熵损失(Cross-Entropy Loss)。

# PyTorch实现
import torch.nn as nn

# 多分类模型
model = nn.Sequential(
    nn.Linear(input_dim, hidden_dim),
    nn.ReLU(),
    nn.Linear(hidden_dim, num_classes)  # 输出K个logits
)

# 损失函数(内部已包含Softmax)
criterion = nn.CrossEntropyLoss()

多标签分类(Multi-label Classification)

定义:每个样本可以属于零个、一个或多个类别,类别之间不互斥。

例子

  • 电影类型标注(一部电影可以同时是"动作片"和"喜剧片")
  • 新闻标签(一篇文章可以有多个标签)
  • 疾病诊断(一个病人可能同时患多种疾病)

技术方案:输出层使用Sigmoid激活函数(每个类别独立),损失函数使用二元交叉熵损失(Binary Cross-Entropy Loss)。

# PyTorch实现
import torch.nn as nn

# 多标签分类模型
model = nn.Sequential(
    nn.Linear(input_dim, hidden_dim),
    nn.ReLU(),
    nn.Linear(hidden_dim, num_labels)  # 输出K个独立的logits
)

# 损失函数(内部已包含Sigmoid)
criterion = nn.BCEWithLogitsLoss()
graph TD
    subgraph 多分类示例
        A1[一张图片] --> B1{单选}
        B1 --> C1[猫]
        B1 --> D1[狗]
        B1 --> E1[鸟]
        F1["结果: 只能选一个"]
    end
    
    subgraph 多标签示例
        A2[一部电影] --> B2{多选}
        B2 --> C2[动作]
        B2 --> D2[喜剧]
        B2 --> E2[科幻]
        F2["结果: 可选多个"]
    end

为什么不能混用?

这是一个关键问题。假设在多标签分类中错误地使用了Softmax会发生什么?

考虑一个电影分类任务,一部电影的真实标签是"动作片"和"喜剧片"(两个标签都为1)。如果使用Softmax,由于归一化约束,模型会被迫输出一个概率分布,比如[0.6, 0.4, 0.0, …]。这传达了一个错误信息:模型认为这部电影最可能是动作片,其次是喜剧片,而不是同时属于两者。

更严重的是梯度计算。Softmax的梯度会涉及所有类别,修改一个类别的权重会影响所有其他类别。这违背了多标签分类的基本假设:各个标签应该是独立的。

从损失函数看本质差异

损失函数的选择与激活函数紧密相关,理解损失函数的计算过程能更深入地揭示两种方案的区别。

graph LR
    subgraph 交叉熵损失 CE
        A1[Logits z] --> B1[Softmax]
        B1 --> C1["概率 p = softmax(z)"]
        C1 --> D1["L = -Σyᵢlog(pᵢ)"]
        D1 --> E1["单标签场景"]
    end
    
    subgraph 二元交叉熵损失 BCE
        A2[Logits z] --> B2[Sigmoid]
        B2 --> C2["概率 p = sigmoid(z)"]
        C2 --> D2["L = -Σ[yᵢlog(pᵢ)+(1-yᵢ)log(1-pᵢ)]"]
        D2 --> E2["多标签场景"]
    end

交叉熵损失(用于Softmax)

对于多分类问题,交叉熵损失定义为:

$$L = -\sum_{i=1}^{K} y_i \log(p_i)$$

其中$y_i$是真实标签的one-hot编码(只有一个位置为1),$p_i$是Softmax输出的概率。

当$y = [0, 0, 1, 0, ...]$(第三个类别为真)时:

$$L = -\log(p_3)$$

最小化这个损失意味着最大化$p_3$,即模型预测第三个类别的概率。

二元交叉熵损失(用于Sigmoid)

对于多标签问题,二元交叉熵损失定义为:

$$L = -\sum_{i=1}^{K} [y_i \log(p_i) + (1-y_i)\log(1-p_i)]$$

注意这里对每个类别$y_i$可以是0或1,与真实标签完全对应。当$y_i=1$时,损失促使$p_i \to 1$;当$y_i=0$时,损失促使$p_i \to 0$。每个类别的损失独立计算,互不影响。

数值稳定性考虑

在实际实现中,数值稳定性是一个重要问题。

Softmax的数值稳定性

Softmax计算涉及指数函数,当logit值过大时会导致数值溢出。标准解决方案是减去最大值:

$$\text{softmax}(z_i) = \frac{e^{z_i - \max(z)}}{\sum_{j=1}^{K} e^{z_j - \max(z)}}$$

这个技巧不会改变Softmax的输出值(分子分母同时乘以$e^{-\max(z)}$),但能有效避免溢出。

现代深度学习框架(如PyTorch、TensorFlow)的交叉熵损失函数都内置了这个处理,用户无需手动实现。

Sigmoid的数值稳定性

Sigmoid同样涉及指数计算,当输入值绝对值很大时,可能遇到数值问题。稳定的实现如下:

def stable_sigmoid(x):
    # 对于正数,使用标准公式
    # 对于负数,使用等价形式避免溢出
    positive_mask = x >= 0
    negative_mask = ~positive_mask
    
    result = torch.zeros_like(x)
    result[positive_mask] = 1 / (1 + torch.exp(-x[positive_mask]))
    
    exp_x = torch.exp(x[negative_mask])
    result[negative_mask] = exp_x / (1 + exp_x)
    
    return result

同样,PyTorch的BCEWithLogitsLoss已经处理了这些数值问题。

梯度计算与反向传播

理解梯度流动有助于深入把握两种方案的区别。

graph TD
    subgraph Softmax梯度流
        A1["输入 z = [z₁, z₂, z₃]"] --> B1["Softmax"]
        B1 --> C1["输出 p = [p₁, p₂, p₃]"]
        C1 --> D1["交叉熵损失 L"]
        D1 --> E1["∂L/∂zᵢ = pᵢ - yᵢ"]
        E1 --> F1["梯度涉及所有类别"]
    end
    
    subgraph Sigmoid梯度流
        A2["输入 z = [z₁, z₂, z₃]"] --> B2["Sigmoid各元素独立"]
        B2 --> C2["输出 p = [p₁, p₂, p₃]"]
        C2 --> D2["BCE损失 L"]
        D2 --> E2["∂L/∂zᵢ = pᵢ - yᵢ"]
        E2 --> F2["各类别梯度独立"]
    end

Softmax的雅可比矩阵

Softmax是一个向量到向量的函数,其梯度是一个雅可比矩阵。设$\mathbf{s} = \text{softmax}(\mathbf{z})$,则:

$$\frac{\partial s_i}{\partial z_j} = \begin{cases} s_i(1-s_i) & \text{if } i=j \\ -s_i s_j & \text{if } i \neq j \end{cases}$$

这个矩阵的对角线元素($i=j$)和非对角线元素($i \neq j$)都有非零值,说明改变任何一个logit都会影响所有输出概率。

交叉熵损失下的简化

当Softmax与交叉熵损失结合时,梯度有一个简洁的形式:

$$\frac{\partial L}{\partial z_i} = p_i - y_i$$

这个结果非常优雅:梯度等于预测概率与真实标签的差值。这使得反向传播的计算非常高效。

Sigmoid的梯度

Sigmoid的梯度已经提到过:

$$\frac{\partial \sigma(z)}{\partial z} = \sigma(z)(1 - \sigma(z))$$

在二元交叉熵损失下,梯度同样简化为:

$$\frac{\partial L}{\partial z} = \sigma(z) - y$$

形式与Softmax+交叉熵一致。

实际案例对比

案例1:新闻分类系统

假设构建一个新闻分类系统,类别为:体育、财经、科技、娱乐、政治。

场景分析:一篇新闻通常只属于一个主要类别。虽然可能涉及多个领域,但编辑通常会给一个主要分类。这是一个典型的多分类问题。

graph LR
    subgraph 新闻分类模型架构
        A[新闻文本] --> B[Embedding层]
        B --> C[平均池化]
        C --> D[全连接层]
        D --> E[5个Logits]
        E --> F[Softmax]
        F --> G[概率分布]
        G --> H["预测类别<br/>argmax"]
    end

方案选择

  • 输出层:5个神经元(对应5个类别)
  • 激活函数:Softmax
  • 损失函数:CrossEntropyLoss
  • 预测:取最大概率对应的类别
import torch
import torch.nn as nn

class NewsClassifier(nn.Module):
    def __init__(self, vocab_size, embed_dim, hidden_dim, num_classes=5):
        super().__init__()
        self.embedding = nn.Embedding(vocab_size, embed_dim)
        self.fc = nn.Sequential(
            nn.Linear(embed_dim, hidden_dim),
            nn.ReLU(),
            nn.Dropout(0.3),
            nn.Linear(hidden_dim, num_classes)
        )
    
    def forward(self, x):
        # x: [batch_size, seq_len]
        embedded = self.embedding(x).mean(dim=1)  # 平均池化
        return self.fc(embedded)  # 返回logits

# 训练
model = NewsClassifier(vocab_size=10000, embed_dim=128, hidden_dim=256)
criterion = nn.CrossEntropyLoss()

# 推理时
logits = model(input_ids)
probs = torch.softmax(logits, dim=-1)
predicted_class = torch.argmax(probs, dim=-1)

案例2:电影标签系统

假设构建一个电影内容标签系统,标签包括:动作、喜剧、爱情、科幻、恐怖、悬疑、动画、纪录片。

场景分析:一部电影可以同时拥有多个标签。《死侍》同时是动作片和喜剧片,《银翼杀手2049》同时是科幻片和悬疑片。这是典型的多标签问题。

graph LR
    subgraph 电影标签模型架构
        A[电影特征] --> B[全连接层]
        B --> C[ReLU激活]
        C --> D[Dropout]
        D --> E[8个Logits]
        E --> F["Sigmoid<br/>(各独立)"]
        F --> G[概率向量]
        G --> H["预测标签<br/>阈值判断"]
    end

方案选择

  • 输出层:8个神经元(对应8个标签)
  • 激活函数:Sigmoid(每个独立)
  • 损失函数:BCEWithLogitsLoss
  • 预测:对每个标签独立判断,概率>阈值则为正
import torch
import torch.nn as nn

class MovieTagger(nn.Module):
    def __init__(self, input_dim, hidden_dim, num_labels=8):
        super().__init__()
        self.fc = nn.Sequential(
            nn.Linear(input_dim, hidden_dim),
            nn.ReLU(),
            nn.Dropout(0.3),
            nn.Linear(hidden_dim, num_labels)
        )
    
    def forward(self, x):
        return self.fc(x)  # 返回logits

# 训练
model = MovieTagger(input_dim=768, hidden_dim=256)
criterion = nn.BCEWithLogitsLoss()

# 推理时
logits = model(movie_features)
probs = torch.sigmoid(logits)
predicted_tags = (probs > 0.5).float()  # 阈值0.5

特殊情况与边界问题

二分类的等价性

一个有趣的数学事实是:当类别数$K=2$时,Softmax退化为与Sigmoid等价的形式。

设两类的logits为$z_1$和$z_2$,Softmax输出为:

$$p_1 = \frac{e^{z_1}}{e^{z_1} + e^{z_2}} = \frac{1}{1 + e^{z_2 - z_1}} = \sigma(z_1 - z_2)$$

如果我们令$z = z_1 - z_2$为单一logit,那么Softmax的输出就是Sigmoid$(z)$。

这也解释了为什么在二分类问题中,可以使用单个输出神经元+Sigmoid,或者两个输出神经元+Softmax。两者在数学上是等价的,但前者更节省参数。

不平衡数据问题

在多标签分类中,类别不平衡是一个常见问题。如果正负样本比例悬殊,模型可能倾向于预测多数类。

解决方案包括:

  1. 加权损失:给少数类更高的权重
pos_weight = torch.tensor([neg_count / pos_count])  # 正类权重
criterion = nn.BCEWithLogitsLoss(pos_weight=pos_weight)
  1. Focal Loss:自动降低简单样本的权重
class FocalLoss(nn.Module):
    def __init__(self, alpha=0.25, gamma=2):
        super().__init__()
        self.alpha = alpha
        self.gamma = gamma
    
    def forward(self, logits, targets):
        probs = torch.sigmoid(logits)
        pt = torch.where(targets == 1, probs, 1 - probs)
        focal_weight = (1 - pt) ** self.gamma
        bce = nn.functional.binary_cross_entropy_with_logits(
            logits, targets, reduction='none'
        )
        return (self.alpha * focal_weight * bce).mean()

阈值选择

在多标签分类中,选择合适的阈值很重要。默认使用0.5,但可以根据任务调整:

  • 高召回率:降低阈值(如0.3),捕捉更多正样本
  • 高精确率:提高阈值(如0.7),减少假阳性
  • 自适应阈值:根据验证集选择每个标签的最优阈值
from sklearn.metrics import f1_score

def find_best_threshold(probs, labels):
    """寻找最优阈值"""
    best_thresholds = []
    for i in range(probs.shape[1]):
        best_f1, best_t = 0, 0.5
        for t in np.arange(0.1, 0.9, 0.05):
            preds = (probs[:, i] > t).astype(int)
            f1 = f1_score(labels[:, i], preds)
            if f1 > best_f1:
                best_f1, best_t = f1, t
        best_thresholds.append(best_t)
    return best_thresholds

大语言模型中的特殊情况

在大语言模型的文本生成任务中,情况变得更加复杂。

自回归生成的Softmax

语言模型在每个时间步预测下一个token,这本质上是一个多分类问题(词汇表中选一个词)。因此,语言模型的输出层使用Softmax:

$$P(w_t|w_{其中$h_t$是当前隐状态,$W_o$是输出词嵌入矩阵。

Logit Bias的应用

在大语言模型的API中,经常提供logit bias功能,允许用户调整特定token的生成概率。这是通过直接修改logits实现的:

# 假设我们想禁止生成token 1234
logits[1234] = float('-inf')
probs = torch.softmax(logits / temperature, dim=-1)
# 现在token 1234的概率为0

这在结构化输出、安全过滤等场景非常有用。

决策流程图

flowchart TD
    A[开始: 分类任务] --> B{类别是否互斥?}
    B -->|是| C[多分类问题]
    B -->|否| D{样本可属于多个类别?}
    D -->|是| E[多标签问题]
    D -->|否| F[二分类问题]
    
    C --> G[输出层: K个神经元]
    G --> H[激活函数: Softmax]
    H --> I[损失函数: CrossEntropyLoss]
    
    E --> J[输出层: K个神经元]
    J --> K[激活函数: Sigmoid]
    K --> L[损失函数: BCEWithLogitsLoss]
    
    F --> M{选择方案}
    M -->|简洁| N[1个神经元 + Sigmoid]
    M -->|对称| O[2个神经元 + Softmax]
    N --> P[BCEWithLogitsLoss]
    O --> Q[CrossEntropyLoss]

面试常见问题

Q1: 为什么多标签分类不能用Softmax?

核心原因:Softmax隐含了类别互斥的假设。在多标签场景,一个样本同时属于多个类别,这些类别之间应该相互独立。Softmax的归一化约束会强制各输出竞争,这与多标签的独立性假设矛盾。

Q2: 二分类应该用Sigmoid还是Softmax?

两者等价。单输出Sigmoid更简洁,节省一个神经元的参数;双输出Softmax更对称,在某些框架中可能更易处理。实际选择看个人偏好和工程便利性。

Q3: 多标签分类的输出概率之和大于1是正常的吗?

完全正常。多标签分类中,每个标签独立判断,各个概率没有约束关系。一篇新闻有0.8的概率是"科技"类,同时有0.7的概率是"财经"类,概率和可以是1.5。

Q4: 损失函数选择错误会有什么后果?

多分类用了BCE:模型仍然可以训练,但效果可能不佳。因为BCE对每个类别独立处理,没有利用类别互斥的信息。

多标签用了CE:更严重的问题。模型会被强制学习一个归一化分布,导致当有多个标签时,概率被分散,无法正确表达"同时属于多个类别"。

Q5: 类别数量很多时(如几万类),Softmax效率如何?

这是一个实际问题。标准Softmax需要计算所有类别的概率,计算量为$O(K)$。当$K$很大时,可以采用:

  1. 分层Softmax:将类别组织成树结构,计算复杂度降为$O(\log K)$
  2. 负采样:训练时只采样部分负类,如Word2Vec的做法
  3. 候选采样:推理时先快速筛选候选集,再精细计算

总结对比表

特性 Sigmoid Softmax
输出约束 各输出独立,范围(0,1) 输出和为1
类别关系 独立,可共存 互斥,竞争
适用场景 多标签分类、二分类 多分类
损失函数 BCE Loss CE Loss
概率解释 每个类别的独立概率 类别上的概率分布
梯度特性 各类梯度独立 梯度涉及所有类别
典型应用 标签预测、疾病诊断 图像分类、语言模型

理解这个选择背后的逻辑,不仅能帮助你正确构建模型架构,更能在面试中展现出对机器学习基础的扎实掌握。当你下次被问到"为什么多分类用Softmax"时,不再只是背诵答案,而是能够从概率论、信息论、优化理论等多个角度娓娓道来。