1. 概述
Softmax回归(也称多项逻辑回归或多类逻辑回归)是二分类逻辑回归在多分类问题上的自然推广。与二分类逻辑回归输出一个概率值不同,Softmax回归输出一个概率分布,表示样本属于每个类别的概率。
1.1 为什么需要Softmax?
在多分类问题中,我们需要将样本分到 个类别之一。直接使用多个二分类器的输出不能保证概率之和为1,也无法有效建模类别之间的互斥关系。
Softmax函数正是解决这个问题的完美工具:
其中 是第 类的线性得分。
1.2 与逻辑回归的关系
| 特征 | 逻辑回归 | Softmax回归 |
|---|---|---|
| 分类数 | 2 | |
| 输出 | 概率 | 维概率分布 |
| 链接函数 | Sigmoid | Softmax |
| 损失函数 | 二元交叉熵 | 多元交叉熵 |
详见 逻辑回归。
2. 数学推导
2.1 Softmax函数
给定 个实数得分 ,Softmax函数定义为:
性质:
- 概率归一化:
- 单调性:如果 ,则
- 可微性:Softmax函数处处可微
2.2 数值稳定性
直接计算指数函数可能导致溢出(overflow)。使用以下技巧保证数值稳定:
def stable_softmax(z):
"""数值稳定的Softmax实现"""
z = np.array(z)
z_max = np.max(z, axis=-1, keepdims=True)
exp_z = np.exp(z - z_max) # 减去最大值防止溢出
return exp_z / np.sum(exp_z, axis=-1, keepdims=True)2.3 模型定义
对于输入 ,Softmax回归输出 维概率向量:
其中:
- 是权重矩阵
- 是偏置向量
展开形式:
3. 损失函数
3.1 多元交叉熵
给定训练集 ,使用负对数似然损失(即多元交叉熵):
其中 是第 个样本的真实类别 对应的预测概率。
3.2 One-Hot编码表示
将标签 用 one-hot 编码 表示,则损失函数可写成:
3.3 梯度推导
对第 类的权重 求偏导:
推导过程:
设 ,则 。
对 关于 求偏导:
因此:
再对 求偏导即可得到上述结果。
4. 与神经网络的关系
4.1 作为输出层
在神经网络中,Softmax通常与交叉熵损失配对使用,作为多分类任务的输出层:
全连接层 Softmax层
┌────────────┐ ┌────────────┐
│ │ │ │
│ z₁ ──────┼────►│ p₁ │
│ z₂ ──────┼────►│ p₂ │───► 交叉熵损失
│ ... │ │ ... │
│ zₖ ──────┼────►│ pₖ │
│ │ │ │
└────────────┘ └────────────┘
4.2 Softmax与Sigmoid的关系
当 时,Softmax退化为Sigmoid:
设 ,则:
4.3 梯度计算中的”广播效应”
在神经网络中,Softmax与交叉熵损失的梯度有一个简洁形式:
这意味着Softmax层的梯度就是预测概率与真实标签的差,与 反向传播 中的链式法则完美结合。
5. 代码实现
5.1 NumPy实现
import numpy as np
class SoftmaxRegression:
"""Softmax回归分类器"""
def __init__(self, n_classes: int, lr: float = 0.1, n_iters: int = 1000,
lambda_reg: float = 0.01):
self.n_classes = n_classes
self.lr = lr
self.n_iters = n_iters
self.lambda_reg = lambda_reg
self.W = None
self.b = None
def softmax(self, z: np.ndarray) -> np.ndarray:
"""数值稳定的Softmax实现"""
z = np.array(z)
z_max = np.max(z, axis=1, keepdims=True)
exp_z = np.exp(z - z_max)
return exp_z / np.sum(exp_z, axis=1, keepdims=True)
def cross_entropy(self, y_pred: np.ndarray, y_true: np.ndarray) -> float:
"""计算交叉熵损失"""
n_samples = y_true.shape[0]
# 添加小值防止log(0)
y_pred = np.clip(y_pred, 1e-15, 1 - 1e-15)
return -np.sum(y_true * np.log(y_pred)) / n_samples
def one_hot(self, y: np.ndarray) -> np.ndarray:
"""One-hot编码"""
n_samples = y.shape[0]
one_hot = np.zeros((n_samples, self.n_classes))
one_hot[np.arange(n_samples), y.astype(int)] = 1
return one_hot
def fit(self, X: np.ndarray, y: np.ndarray):
"""训练模型"""
n_samples, n_features = X.shape
# 初始化参数
self.W = np.zeros((n_classes, n_features))
self.b = np.zeros(n_classes)
# One-hot编码
y_one_hot = self.one_hot(y)
for _ in range(self.n_iters):
# 前向传播
z = X @ self.W.T + self.b # (n_samples, n_classes)
y_pred = self.softmax(z)
# 计算梯度
dz = y_pred - y_one_hot # (n_samples, n_classes)
dW = (dz.T @ X) / n_samples + self.lambda_reg * self.W
db = np.mean(dz, axis=0)
# 更新参数
self.W -= self.lr * dW
self.b -= self.lr * db
def predict_proba(self, X: np.ndarray) -> np.ndarray:
"""预测概率"""
z = X @ self.W.T + self.b
return self.softmax(z)
def predict(self, X: np.ndarray) -> np.ndarray:
"""预测类别"""
return np.argmax(self.predict_proba(X), axis=1)5.2 使用sklearn
from sklearn.linear_model import LogisticRegression
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
# 加载数据(3类分类问题)
iris = load_iris()
X, y = iris.data, iris.target
# 划分数据集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 标准化
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# 使用sklearn的逻辑回归(multi_class='multinomial'即Softmax)
model = LogisticRegression(
multi_class='multinomial', # 使用Softmax回归
solver='lbfgs', # 优化器
C=1.0, # 正则化参数(越小越强)
max_iter=1000
)
# 训练
model.fit(X_train_scaled, y_train)
# 预测
y_pred = model.predict(X_test_scaled)
y_prob = model.predict_proba(X_test_scaled)
print(f"Test Accuracy: {(y_pred == y_test).mean():.4f}")
print(f"\nPredicted probabilities for first 3 samples:")
print(y_prob[:3].round(3))6. 与One-vs-Rest的关系
6.1 One-vs-Rest的局限性
使用多个二分类器的 One-vs-Rest 策略存在以下问题:
- 概率不一致:各分类器的概率阈值独立设定,不保证和为1
- 分类不平衡:某些类别样本少,分类器性能差
- 效率低:需要训练 个分类器
6.2 Softmax的优势
| 方面 | OvR策略 | Softmax回归 |
|---|---|---|
| 概率归一化 | 不保证 | 严格保证 |
| 类别互斥建模 | 较弱 | 强 |
| 训练效率 | 分类器 | 模型 |
| 参数共享 | 无 | 共享底层特征 |
6.3 等价性证明
在特定条件下,Softmax回归与OvR策略等价。考虑第 类 vs 其他类:
对于第 类的二分类器,其概率为:
可以证明,当使用适当的参数约束时,两者可以相互转换。
7. 常见变体
7.1 标签平滑(Label Smoothing)
为防止模型对训练数据过度自信,可以在标签上引入平滑:
def label_smoothing(y_one_hot, epsilon=0.1):
"""标签平滑"""
K = y_one_hot.shape[1]
return (1 - epsilon) * y_one_hot + epsilon / K7.2 温度Softmax(Temperature Softmax)
在Softmax前引入温度参数 :
- :分布更平滑(更不确定)
- :分布更尖锐(更确定)
这在RLHF和知识蒸馏中有重要应用。
7.3 稀疏Softmax(Sparse Softmax)
对于超大规模分类(如词表),使用稀疏Softmax避免计算所有 :
- 采样近似:只计算部分类别的概率
- 层次Softmax:构建二叉树结构
8. 应用场景
8.1 图像分类
在深度学习时代之前,Softmax回归常用于手写数字识别(MNIST)等任务:
# 使用Softmax回归进行MNIST分类
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
digits = load_digits()
X, y = digits.data, digits.target
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
model = LogisticRegression(multi_class='multinomial', solver='lbfgs', max_iter=1000)
model.fit(X_train, y_train)
print(f"Accuracy: {model.score(X_test, y_test):.4f}")8.2 自然语言处理
- 文本分类:情感分析、主题分类
- 词性标注:每个词标注一个词性
- 命名实体识别:识别实体类别
8.3 神经网络输出层
几乎所有多分类神经网络的输出层都使用Softmax:
图像分类(ImageNet) 1000类
NLP分类(情感分析) 2类(正面/负面)
目标检测(RetinaNet) K类 + 背景
9. 优缺点分析
优点
| 优点 | 说明 |
|---|---|
| 概率解释 | 输出归一化的概率分布 |
| 可解释性 | 权重反映特征对各类别的贡献 |
| 计算效率 | 训练和推理的时间复杂度较低 |
| 与深度学习兼容 | 作为神经网络的标准输出层 |
| 端到端学习 | 可以与其他层联合优化 |
缺点
| 缺点 | 说明 |
|---|---|
| 线性决策边界 | 无法建模特征间的非线性关系 |
| 类别数限制 | 类别数过多时计算量大 |
| 互斥假设 | 假设类别互斥,不适合多标签问题 |
| 特征工程依赖 | 需要手工特征工程 |
10. 与相关算法对比
10.1 Softmax vs 多个Sigmoid
| 场景 | 推荐使用 |
|---|---|
| 类别互斥 | Softmax |
| 类别独立(多标签) | 多个Sigmoid |
| 类别不平衡严重 | 多个Sigmoid + 阈值调整 |
10.2 Softmax vs SVM
| 特征 | Softmax回归 | SVM |
|---|---|---|
| 输出 | 概率分布 | 类别标签 |
| 决策边界 | 基于概率 | 基于间隔 |
| 损失函数 | 交叉熵 | 合页损失 |
| 扩展性 | 易扩展到神经网络 | 需要核方法 |