import numpy as np
defsoftmax(x):"""
Compute softmax values for each set of scores in x.
Args:
x: Input array of shape (batch_size, num_classes) or (num_classes,)
Returns:
Softmax probabilities of same shape as input
"""# For numerical stability, subtract the maximum value from each input vector# This prevents overflow when calculating exp(x)
shifted_x = x - np.max(x, axis=-1, keepdims=True)# Calculate exp(x) for each element
exp_x = np.exp(shifted_x)# Calculate the sum of exp(x) for normalization
sum_exp_x = np.sum(exp_x, axis=-1, keepdims=True)# Normalize to get probabilities
probabilities = exp_x / sum_exp_x
return probabilities技术支持
# Sample logits from a neural network (batch of 2 examples, 3 classes each)
logits = np.array([[2.0,1.0,0.5],# First example[3.0,2.0,1.0]# Second example])
probabilities = softmax(logits)print("Logits:\n", logits)print("\nSoftmax probabilities:\n", probabilities)print("\nSum of probabilities (should be 1 for each example):", np.sum(probabilities, axis=1))技术支持
TensorFlow 和 Keras 让您可以轻松在神经网络中使用 softmax 激活函数。让我们一步步为 MNIST 数据集构建一个简单分类器。
首先,导入必要的库并加载数据集:
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Flatten, Softmax
from tensorflow.keras.datasets import mnist
import matplotlib.pyplot as plt
import numpy as np
# Load and preprocess the MNIST dataset(x_train, y_train),(x_test, y_test)= mnist.load_data()# Normalize pixel values to be between 0 and 1
x_train, x_test = x_train /255.0, x_test /255.0# One-hot encode the labels
y_train_one_hot = tf.keras.utils.to_categorical(y_train,10)
y_test_one_hot = tf.keras.utils.to_categorical(y_test,10)技术支持
# Method 1: Using softmax as the activation function in the final layer
model1 = Sequential([
Flatten(input_shape=(28,28)),# Convert 28x28 images to 784-length vectors
Dense(128, activation='relu'),# Hidden layer with ReLU activation
Dense(10, activation='softmax')# Output layer with softmax activation])# Method 2: Using a separate Softmax layer
model2 = Sequential([
Flatten(input_shape=(28,28)),
Dense(128, activation='relu'),
Dense(10),# Linear output (logits)
Softmax()# Separate softmax layer])技术支持
这里展示了在神经网络中引入 softmax 激活函数的两种等价方式:
方法一:在最终的 Dense 层中直接指定 'softmax' 作为激活函数
方法二:在输出原始 logits 的 Dense 层之后添加一个单独的 Softmax 层
两种方式产生相同结果,但第二种方法更明确地区分了 logits 与概率,在某些场景中会更有用。
接下来,编译并训练模型:
# Compile the model
model1.compile(
optimizer='adam',
loss='categorical_crossentropy',# This loss works well with softmax
metrics=['accuracy'])# Print model summaryprint("Model with softmax activation:")
model1.summary()# Train the modelprint("\nTraining the model...")
history = model1.fit(
x_train, y_train_one_hot,
epochs=5,
batch_size=128,
validation_split=0.1,
verbose=1)技术支持
输出:
我们使用类别交叉熵损失函数来编译模型,它与 softmax 输出配合良好。该损失函数衡量预测的概率分布与真实的 one-hot 标签之间的差异。我们使用 Adam 优化器,在训练过程中自适应调整学习率。
训练完成后,我们可以用模型进行预测并可视化结果:
# Let's check predictions on a sample
sample_idx =42
sample_image = x_test[sample_idx]
true_label = y_test[sample_idx]# Get model predictions (probabilities across all classes)
predictions = model1.predict(sample_image[np.newaxis,...])# Visualize the results
plt.figure(figsize=(10,4))# Plot the image
plt.subplot(1,2,1)
plt.imshow(sample_image, cmap='gray')
plt.title(f"True label: {true_label}")
plt.axis('off')# Plot the probability distribution
plt.subplot(1,2,2)
plt.bar(range(10), predictions[0])
plt.xticks(range(10))
plt.xlabel('Digit')
plt.ylabel('Probability')
plt.title('Softmax Probabilities')技术支持
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
import matplotlib.pyplot as plt
import numpy as np
# Set the device
device = torch.device("cuda:0"if torch.cuda.is_available()else"cpu")print(f"Using device: {device}")# Load and preprocess the CIFAR-10 dataset
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5))# Normalize RGB channels])# Load a small subset of the CIFAR-10 dataset for demonstration
trainset = torchvision.datasets.CIFAR10(root='./data', train=True,
download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=64,
shuffle=True, num_workers=2)
testset = torchvision.datasets.CIFAR10(root='./data', train=False,
download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=64,
shuffle=False, num_workers=2)# Define the class names for CIFAR-10
classes =('plane','car','bird','cat','deer','dog','frog','horse','ship','truck')技术支持
# Define a simple convolutional neural networkclassSimpleCNN(nn.Module):def__init__(self):super(SimpleCNN, self).__init__()# Convolutional layers
self.conv1 = nn.Conv2d(3,16,3, padding=1)
self.conv2 = nn.Conv2d(16,32,3, padding=1)
self.pool = nn.MaxPool2d(2,2)# Fully connected layers
self.fc1 = nn.Linear(32*8*8,128)
self.fc2 = nn.Linear(128,10)# 10 classes in CIFAR-10# Activation functions
self.relu = nn.ReLU()# Note: We don't define softmax here as it will be applied with the loss functiondefforward(self, x):# Convolutional layers with ReLU and pooling
x = self.pool(self.relu(self.conv1(x)))# -> 16x16x16
x = self.pool(self.relu(self.conv2(x)))# -> 8x8x32# Flatten the output
x = x.view(-1,32*8*8)# Fully connected layers
x = self.relu(self.fc1(x))
x = self.fc2(x)# Raw logits output# Note: No softmax here, as PyTorch's CrossEntropyLoss applies it internallyreturn x技术支持
# Create the model and move it to the device
model = SimpleCNN().to(device)# Define loss function and optimizer
criterion = nn.CrossEntropyLoss()# Combines LogSoftmax and NLLLoss
optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.9)# Train the model (only a few epochs for demonstration)deftrain_model(epochs=2):for epoch inrange(epochs):
running_loss =0.0for i, data inenumerate(trainloader,0):
inputs, labels = data[0].to(device), data[1].to(device)# Zero the parameter gradients
optimizer.zero_grad()# Forward + backward + optimize
outputs = model(inputs)# These are logits (pre-softmax)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
running_loss += loss.item()if i %100==99:print(f'[{epoch +1}, {i +1}] loss: {running_loss /100:.3f}')
running_loss =0.0print('Finished Training')技术支持
最后,创建一个可视化模型预测的函数:
# Function to display predictions with softmax probabilitiesdefshow_prediction_example():# Get a batch of test images
dataiter =iter(testloader)
images, labels =next(dataiter)# Select a single image
img = images[0].to(device)
label = labels[0]# Get the model's prediction (logits)with torch.no_grad():
logits = model(img.unsqueeze(0))# Apply softmax to get probabilities
probabilities = torch.nn.functional.softmax(logits, dim=1)# Plot the image and probabilities
fig,(ax1, ax2)= plt.subplots(1,2, figsize=(10,4))# Show the image
img_np = img.cpu().numpy()
img_np = np.transpose(img_np,(1,2,0))# Denormalize the image for display
img_np = img_np *0.5+0.5
ax1.imshow(img_np)
ax1.set_title(f"True label: {classes[label]}")
ax1.axis('off')# Show the probabilities
probs = probabilities[0].cpu().numpy()
ax2.bar(range(10), probs)
ax2.set_xticks(range(10))
ax2.set_xticklabels(classes, rotation=45)
ax2.set_xlabel('Class')
ax2.set_ylabel('Probability')
ax2.set_title('Softmax Probabilities')
plt.tight_layout()技术支持
激活函数是神经网络的基石。它们引入非线性,使网络能够学习复杂模式。softmax 激活函数尤为重要,尤其是在处理多分类问题时。
虽然 Sigmoid 和 ReLU 等替代方案各有用武之地,但在需要将输出解释为互斥类别上的概率时,softmax 更胜一筹。
softmax 激活函数会将一个数值向量整体转换为一个概率分布。这个独特特性使其在需要将输入归类到多个可能类别之一的任务中不可或缺。
从识别成千上万物体类别的图像识别系统,到预测句子下一个词的自然语言处理模型,softmax 为在多种可能性中做出决策提供了数学基础。
本文将介绍 softmax 激活函数是什么、其数学原理以及在您的神经网络架构中何时使用它。我们还将介绍在 Python 中的实用实现。
什么是 Softmax 激活函数?
Softmax 激活函数是一种将模型的原始输出向量(称为 logits)转换为概率分布的数学函数。简单来说,它把一组数转换为相加为 1 的概率。
与对单个数值独立作用的一些激活函数不同,softmax 作用于整个数值向量,将其整体变换为元素之和恰为 1 的概率分布。
在神经网络中,softmax 通常用于为多分类任务设计的网络的最后一层。当存在多个可能类别并需要模型给出各类别的概率时,softmax 激活函数是标准选择。
softmax 函数如何工作?
神经网络最后一层的原始输出常称为“logits”。这些值可以从负无穷到正无穷,不能直接解释为概率。softmax 激活函数通过以下步骤将这些 logits 转换为更可解释的形式:
这种变换至关重要,因为它让我们可以将网络输出解释为概率分布。例如,如果一个神经网络将图像分为三类(猫、狗、鸟),softmax 输出可能为 [0.7, 0.2, 0.1],表示猫的概率为 70%,狗为 20%,鸟为 10%。
softmax 激活函数在构造有效概率分布方面起着关键作用,因为:
Softmax 激活函数
上图展示了 softmax 激活函数如何将原始 logits(输入值 2.0、1.0 和 0.5)转换为概率分布(0.7、0.2 和 0.1)。softmax 框中的公式表示:每个输出概率通过对某个输入值取指数,并除以所有指数之和来计算。
右侧突出显示的关键属性说明了为什么 softmax 对多分类问题有用。下一节我们将看看数学公式及其工作原理。
Softmax 激活函数公式及其工作原理
现在我们已经了解了 softmax 激活函数是什么,接下来看看它的数学表述,以及如何将输入转换为概率分布。
softmax 激活函数的公式可以表示为:
其中:
softmax 激活函数按以下步骤将输入向量转换为概率分布:
让我们用一个简单示例应用 softmax 激活函数的公式,看看其实际效果。
假设我们有一个三分类问题(例如识别图像中是猫、狗还是鸟),神经网络的输出层有三个神经元。最终计算后,网络输出如下 logits:z = [2.0, 1.0, 0.5]。
要用 softmax 将这些 logits 转换为概率:
得到的概率分布 [0.628, 0.231, 0.140] 之和为 1,且最高概率对应于最高的 logit 值。本示例说明了 softmax 激活函数如何在将输入转为有效概率分布的同时保留输入值的排序。
请注意,原始输入值(2.0、1.0、0.5)在输出概率中保持了相对排序,但它们之间的差异被放大了。这一特性使 softmax 在希望有把握地识别最可能类别的分类任务中尤为有用。
下一节将介绍在 Python 中实现 softmax 激活函数的不同方式。
在 Python 中实现 Softmax 激活函数
在理解了 softmax 激活函数背后的理论后,接下来看看如何在 Python 中实现。我们将先用 NumPy 从零编写一个 softmax 函数,然后看看如何在 TensorFlow/Keras 和 PyTorch 等流行深度学习框架中使用它。
用 NumPy 从零实现 softmax
在使用框架之前,理解如何从零实现 softmax 激活函数很重要。这有助于建立对底层机制的直观认识。
该实现严格遵循 softmax 激活函数的公式,但有一个重要补充:在指数运算前,我们从每个输入向量中减去其最大值。
这种“平移”操作不会改变数学结果,但能防止在对大数取指数时发生数值溢出。
让我们用一个简单示例测试实现:
输出:
运行上述代码后,您会看到每个样本的概率之和均为 1,说明我们的 softmax 实现生成了有效的概率分布。对于第一个样本,最大概率对应最大的 logit(2.0);第二个样本同理。
在 TensorFlow/Keras 中使用 softmax
TensorFlow 和 Keras 让您可以轻松在神经网络中使用 softmax 激活函数。让我们一步步为 MNIST 数据集构建一个简单分类器。
首先,导入必要的库并加载数据集:
MNIST 数据集包含 28x28 像素的手写数字(0-9)灰度图像。我们将像素值归一化到 0 到 1 之间,以获得更好的训练动态,并将类别标签转换为 one-hot 编码,这是 softmax 输出偏好的标签格式。
现在,创建带有 softmax 输出层的神经网络模型:
这里展示了在神经网络中引入 softmax 激活函数的两种等价方式:
两种方式产生相同结果,但第二种方法更明确地区分了 logits 与概率,在某些场景中会更有用。
接下来,编译并训练模型:
输出:
训练完成后,我们可以用模型进行预测并可视化结果:
这段代码选取一个测试图像,将其传入已训练模型,并同时可视化图像及 softmax 输出的概率分布。概率图中最高的柱表示模型的预测。
输出:
TensorFlow 也提供了直接应用 softmax 的内置函数:
输出:
此示例比较了自定义 softmax 与 TensorFlow 内置
tf.nn.softmax()函数的结果。两者应产生相同输出。自定义实现同样包含了在指数运算前减去最大值的数值稳定性技巧。在 PyTorch 中使用 Softmax
现在,我们用 PyTorch 和 CIFAR-10 数据集实现一个类似的模型。首先导入库并进行数据准备,如下所示:
上述代码设置了如可用则使用 GPU 的 PyTorch 环境,加载 CIFAR-10 数据集(包含 10 个类别的 32x32 彩色图像),并准备训练与测试的数据加载器。
接下来,定义一个简单的卷积神经网络如下:
该 CNN 将 32x32 的 RGB 图像经过卷积层、ReLU 激活与池化处理,最后接全连接层。值得注意的是,模型在前向传播中不包含 softmax 层,而是输出原始 logits。
这是 PyTorch 中的常见模式,因为训练时使用的
CrossEntropyLoss会在计算损失前内部应用 softmax,这样数值更稳定。如有需要,我们也可以显式加入 softmax:
我们定义了两个模型变体:
SimpleCNN输出原始 logits,而ModelWithSoftmax显式应用 softmax 以生成概率。在训练时,我们配合CrossEntropyLoss使用SimpleCNN,这是 PyTorch 的标准做法。接着,定义一个训练函数:
最后,创建一个可视化模型预测的函数:
该函数演示了在推理阶段如何从已训练模型获得 softmax 概率。它将图像传过模型得到 logits,使用
torch.nn.functional.softmax()手动应用 softmax,然后同时可视化图像与概率分布。现在,让我们训练模型:
输出:
接着预测,看看测试图像的概率分布:
输出:
下一节我们将看看 softmax 的最佳使用场景,讨论何时应使用 softmax 激活函数,并介绍在各种深度学习模型中的实际应用。
何时使用 Softmax 激活函数?
了解何时使用 softmax 激活函数对设计高效神经网络至关重要。softmax 激活函数的主要应用场景是用于分类任务的神经网络输出层。
Softmax 用于多分类
当模型需要将输入归入几个互斥类别之一时,softmax 是标准选择。常见示例包括:
概率解释与置信度分数
当我们需要模型输出概率而不仅仅是类别预测时,softmax 为所有可能类别的置信度提供了概率化的解释。
强化学习与注意力机制中的 Softmax
在强化学习与序列决策问题中,softmax 可用于将动作价值转换为用于动作选择的概率分布。
现代基于 Transformer 的架构使用 softmax 在输入序列的不同部分之间分配注意力。
坚实的数学基础与直观的概率解释,使 softmax 激活函数尤为适合类别互斥的分类问题。
深度学习模型
多种深度学习架构都会使用 softmax 激活函数。
图像分类网络如 ResNet、VGG 和 Inception 都在其最终层使用 softmax 将图像分类到成千上万个类别。例如,在 ImageNet 上训练的模型通常拥有一个 1000 维的 softmax 层,对应 1000 个对象类别。
在语言建模中,基于 RNN 的模型使用 softmax 从可能包含数万词的词表中预测序列中的下一个词。
现代 NLP 架构如 BERT、GPT 和 T5 在多个组件中使用 softmax:
变分自编码器(VAE)以及某些生成对抗网络(GAN)会在判别器组件或类别潜在变量建模中使用 softmax。
下一节我们将比较 softmax 激活函数与 sigmoid 激活函数的差异。
Softmax 与 Sigmoid 激活函数对比
Sigmoid 函数定义为 σ(x) = 1/(1+e^(-x)),将单个实数映射到 0 到 1 之间。相比之下,softmax 激活函数作用于一个数值向量,将其转换为概率分布。
以下是这两种激活函数的根本区别:
输入与输出维度
概率解释
输出的相互独立性
数学关系
有趣的是,当类别数为 2 时,sigmoid 实际上是 softmax 激活函数的一个特例。这也是 sigmoid 常被称为“二元 softmax”的原因。
何时用 softmax,何时用 sigmoid
选择合适的激活函数对于确保神经网络输出有意义的预测至关重要,在 softmax 与 sigmoid 之间的决策取决于分类任务的性质。
在以下情况下使用 sigmoid:
在以下情况下使用 softmax:
例如,在将手写数字(0-9)进行分类的神经网络中,输出层应使用 softmax 激活函数,因为每张图像只对应一个数字。另一方面,在检测图像中多个对象(如“包含人”、“包含车”、“包含树”)的网络中,更适合使用 sigmoid 激活,因为多个对象可以同时存在。
下面是一张对照表,快速比较 softmax 与 sigmoid 激活函数:
特性
Sigmoid
Softmax
输入
单个标量
向量
输出范围
介于 0 和 1 之间
介于 0 和 1 之间,且总和为 1
用例
二分类
多分类
输出解释
独立概率
概率分布
多个输出
可同时很高或同时很低
受限于总和为 1
梯度
可能遭遇梯度消失
较不易出现梯度消失问题
训练效率
可配合二元交叉熵损失训练
配合类别交叉熵损失训练
理解这些激活函数的区别对设计有效的神经网络至关重要。softmax 激活函数是多分类问题的不二之选,而 sigmoid 在二分类及需要独立概率的任务中仍然非常有价值。
下一节我们将探讨使用 softmax 激活函数时常见的问题与注意事项。
常见问题与注意事项
在深度学习模型中使用 softmax 激活函数时,若干挑战会影响模型性能与可靠性。了解这些问题并掌握应对方法,将有助于构建更健壮的模型。
数值稳定性挑战
数值稳定性是使用 softmax 激活函数时的关键考量。softmax 激活函数公式中的指数运算若处理不当,可能导致数值溢出或下溢。
例如,当输入值非常大(如 1000 或更高)时,指数函数会产生极大的数,可能超过系统可表示的浮点数上限。这会导致溢出错误,产生“无穷大”值,从而使最终的除法运算得到未定义结果(NaN)。
如我们在实现部分所述,标准解决方案是在应用指数函数前,将所有输入减去其最大值。该平移不会改变归一化后的相对比例,但能让数值保持在可控范围内。
现代深度学习框架会在内部处理这一问题,但当您实现自定义运算或调试异常行为时,理解该问题仍十分重要。
Softmax 与过度自信预测
softmax 的另一个常见问题是神经网络容易对其预测过于自信,即便预测是错的。导致过度自信的原因包括:
在实践中,这意味着模型可能会为实际上错误的预测赋予 99% 的概率。在需要可靠不确定性估计的关键应用中,这会带来问题。例如,若一个医疗诊断系统对错误预测过于自信,可能导致不当治疗决策。
模型置信度(来自 softmax 概率)与其实际准确率之间的差异称为校准误差。校准良好的模型会输出与其准确率相匹配的置信分数——如果模型以 80% 的置信度进行预测,那么这些事件应大约有 80% 的概率发生。
提升性能的正则化技术
幸运的是,有多种技术可帮助应对 softmax 激活函数相关的问题:
温度缩放
温度缩放引入一个参数 T 来控制概率分布的“平滑度”。在应用 softmax 之前用该温度参数去除 logits,可以调整结果分布的尖锐或均匀程度。
较高温度(T > 1)会产生更平滑、极值更小的概率分布,有助于降低过度自信。较低温度(T < 1)使分布在最大值附近更尖锐。温度缩放常作为训练后的后处理技术,用于校准模型置信度,而不改变类别排序。
标签平滑
标签平滑用稍微平滑的分布替代严格的 one-hot 目标向量。与其训练模型输出精确的 0 与 1,不如令其稍微不那么自信,例如在四分类问题中将正确类目标设为 0.9,错误类各为 0.025。
通过使用平滑标签训练,模型会学得不那么自信,从而提升泛化能力并增强对标签噪声的鲁棒性。该技术已成为许多最先进图像分类模型中的标准做法。
Dropout 与集成方法
训练中使用 dropout,并在推理时保持启用(称为蒙特卡洛 dropout),可以对同一输入采样多次预测并估计不确定性。若模型在不同 dropout 模式下对同一输入给出一致预测,则更可能对该预测有把握。
类似地,集成方法结合多个模型的预测以提升性能,并提供更好的不确定性估计。集成中模型之间的分歧可用作不确定性的度量。
校准技术
Platt 缩放等校准方法可在训练后应用,以确保 softmax 输出的置信分数与真实概率相符。这些方法通常使用验证集来学习将原始模型输出映射为良好校准概率的参数。
例如,前述简单的温度缩放方法可在验证数据上优化以最小化校准误差。更复杂的方法包括各向同性回归与贝叶斯分箱。
Softmax 瓶颈
近来的研究发现了 softmax 的一个根本限制,称为“softmax 瓶颈”。这指的是基于 softmax 的语言模型的表达能力受制于最终层权重矩阵的秩。在具有复杂依赖的自然语言环境中,这会阻碍模型充分刻画潜在条件分布。
为缓解该限制,提出了如 Mixture of Softmaxes(MoS)等高级架构,通过对多个 softmax 分布进行加权组合来提升表达力。
了解这些常见问题并实施适当方案,您就能提升使用 softmax 激活函数进行分类任务的模型的可靠性与性能。
结论
softmax 激活函数是多分类神经网络中的关键组件,它将原始 logits 转换为可解释的概率分布。本文探讨了其数学基础、在 Python 中的实现、与 sigmoid 的比较、实际用例,以及应对数值不稳定与过度自信等常见挑战的技术。
准备好进一步深入理解神经网络了吗?