Softmax函数
社会科学家邓肯·卢斯于1959年在选择模型(choice model)的理论基础上 发明的了softmax函数: softmax函数能够将未规范化的预测变换为非负数并且总和为1,同时让模型保持 可导的性质。
为什么叫Softmax呢?Softmax从字面上来说,可以分成soft和max两个部分。max故名思议就是最大值的意思。Softmax的核心在于soft,而soft有软的含义,与之相对的是hard硬。很多场景中需要我们找出数组所有元素中值最大的元素。
Softmax函数是一个在数学和计算机科学中常用的函数,特别是在机器学习的分类问题中。它可以把一个含任意实数的K维向量“压缩”到另一个K维实向量中,使得每一个元素的范围都在(0,1)
之间,并且所有元素的和为1。这使得Softmax函数的输出可以被解释为概率分布。
Softmax函数的定义如下:
\text{softmax}(x)_i = \frac{e^{x_i}}{\sum_{j=1}^K e^{x_j}}
其中x
是输入向量,K
是向量的维度,i
是指定的元素索引。
在多分类问题中,Softmax函数常用于将模型的原始输出(通常被称为logits)转换为概率分布。这样,每个类别的概率可以直接相比较,从而选择最可能的类别。
例如,如果一个模型用于对数字图像进行分类,并且原始输出是(2.0, 1.0, 0.1)
,则通过Softmax函数,我们可以得到一个新的向量,如(0.7, 0.2, 0.1)
,其中每个值代表对应数字的预测概率。
Softmax回归
Softmax回归可以看作是一个简单的神经网络,其中没有隐藏层,直接将输入与输出层连接。虽然模型相对简单,但在一些多分类问题上表现还是不错的,特别是在类别之间的边界比较清晰的情况下。
SoftLabel
SoftLabel 是一种处理标签问题的技术,通常用在机器学习的分类问题中。在传统的分类任务中,每个样本只会被分配一个确定的类别标签,比如说在一个二分类问题中,标签通常是 0 或 1。但在某些情况下,我们可能希望更灵活地表达分类的不确定性或者模糊性。
SoftLabel 就是这样一种技术,允许每个样本有一个属于每个类别的概率分布。例如,在一个二分类问题中,一个样本的 SoftLabel 可能是 (0.6, 0.4),这表示该样本属于第一类的概率是 60%,属于第二类的概率是 40%。
这种方法有以下几个优点:
-
灵活性:SoftLabel 能更灵活地表示样本之间的不确定性和模糊性,适用于那些不容易归入单一类别的样本。
-
鲁棒性:对于那些具有噪声或不准确标签的数据集,SoftLabel 可以提供一种更鲁棒的方法来表达这种不确定性。
-
更好的梯度信息:在训练神经网络时,SoftLabel 可以提供更丰富的梯度信息,有助于网络的训练。
-
平滑化决策边界:SoftLabel 通过允许样本具有模糊的类别分配,可以使分类器的决策边界更加平滑,从而减少过拟合现象。
SoftLabel 可以与各种损失函数结合使用,如交叉熵损失。它是许多现代机器学习模型中的一个重要组成部分,尤其在处理具有模糊或不确定标签的数据时。
Softmax注意事项
Softmax函数虽然强大和广泛应用,但在某些情况下也存在一些限制和挑战,如:
- 数值稳定性问题:Softmax涉及指数运算,可能导致数值上溢或下溢。一些改进方法,如对输入进行缩放,可以帮助缓解这个问题。
- 计算成本:对于大规模的类别集合,Softmax的计算可能相对昂贵。
- 不适用于非互斥类别:如果数据可以归入多个类别,传统的Softmax可能不适用。
- 可能的局部最优解:在某些情况下,Softmax可能会陷入局部最优解,不一定能找到全局最优解。
虽然Softmax在许多方面表现出色,但在使用过程中需要注意一些问题。其中一个常见的挑战是数值稳定性。由于涉及指数运算,可能会出现数值上溢或下溢的情况。解决这个问题的一种方法是在计算之前对输入进行缩放。此外,Softmax可能不适用于具有大量类别或非互斥类别的场景。
手动实现Softmax
用Softmax实现图像分类问题
import torch
import torchvision
from torch.utils import data
from torchvision import transforms
lr = 0.1
w = torch.normal(0, 0.01, size=(784, 10), requires_grad=True)
b = torch.zeros(10, requires_grad=True)
def sgd(params, lr, batch_size):
"""Minibatch stochastic gradient descent."""
with torch.no_grad():
for param in params:
param -= lr * param.grad / batch_size
param.grad.zero_()
def get_dataloader_workers(): # @save
"""使用4个进程来读取数据"""
return 4
def load_data_fashion_mnist(batch_size, resize=None): # @save
"""下载Fashion-MNIST数据集,然后将其加载到内存中"""
trans = [transforms.ToTensor()]
if resize:
trans.insert(0, transforms.Resize(resize))
trans = transforms.Compose(trans)
mnist_train = torchvision.datasets.FashionMNIST(
root="../data", train=True, transform=trans, download=True
)
mnist_test = torchvision.datasets.FashionMNIST(
root="../data", train=False, transform=trans, download=True
)
return (
data.DataLoader(
mnist_train, batch_size, shuffle=True, num_workers=get_dataloader_workers()
),
data.DataLoader(
mnist_test, batch_size, shuffle=False, num_workers=get_dataloader_workers()
),
)
def softmax(X):
X_exp = torch.exp(X)
partition = X_exp.sum(1, keepdim=True)
return X_exp / partition # 这里应用了广播机制
def cross_entropy(y_hat, y):
return -torch.log(y_hat[range(len(y_hat)), y])
def net(X):
return softmax(torch.matmul(X.reshape((-1, w.shape[0])), w) + b)
def accuracy(y_hat, y):
if len(y_hat.shape) > 1 and y_hat.shape[1] > 1:
y_hat = y_hat.argmax(axis=1)
cmp = y_hat.type(y.dtype) == y
return float(cmp.type(y.dtype).sum())
def evaluate_accuracy(net, data_iter):
if isinstance(net, torch.nn.Module):
net.eval()
metric = Accumulator(2)
for X, y in data_iter:
metric.add(accuracy(net(X), y), y.numel())
return metric[0] / metric[1]
class Accumulator:
"""在`n`个变量上累加。"""
def __init__(self, n):
self.data = [0.0] * n
def add(self, *args):
self.data = [a + float(b) for a, b in zip(self.data, args)]
def reset(self):
self.data = [0.0] * len(self.data)
def __getitem__(self, idx):
return self.data[idx]
def train_epoch_ch3(net, train_iter, loss, updater):
"""训练模型一个迭代周期(定义见第3章)。"""
# 将模型设置为训练模式
if isinstance(net, torch.nn.Module):
net.train()
# 训练损失总和、训练准确率总和、范例数
metric = Accumulator(3)
for X, y in train_iter:
# 计算梯度并更新参数
# 其余代码不变
y_hat = net(X)
l = loss(y_hat, y)
if isinstance(updater, torch.optim.Optimizer):
# 使用PyTorch内置的优化器和损失函数
updater.zero_grad()
l.backward()
updater.step()
metric.add(float(l) * len(y), accuracy(y_hat, y), y.size().numel())
else:
# 使用定制的优化器和损失函数
l.sum().backward()
updater(X.shape[0])
metric.add(float(l.sum()), accuracy(y_hat, y), y.numel())
# 返回训练损失和训练准确率
return metric[0] / metric[2], metric[1] / metric[2]
def train_ch3(net, train_iter, test_iter, loss, num_epochs, updater):
"""训练模型(定义见第3章)。"""
train_metrics = None
test_acc = None
for epoch in range(num_epochs):
train_metrics = train_epoch_ch3(net, train_iter, loss, updater)
test_acc = evaluate_accuracy(net, test_iter)
train_loss, train_acc = train_metrics
print(f"train_loss {train_loss:f} train_acc {train_acc:f} test_acc {test_acc:f}")
def updater(batch_size):
return sgd([w, b], lr, batch_size)
if __name__ == "__main__":
num_epochs = 10
train_iter, test_iter = load_data_fashion_mnist(batch_size=256)
train_ch3(net, train_iter, test_iter, cross_entropy, num_epochs, updater)
参考资料
https://zh.d2l.ai/chapter_linear-networks/softmax-regression.html