Softmax 回归简介

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回归可以看作是一个简单的神经网络,其中没有隐藏层,直接将输入与输出层连接。虽然模型相对简单,但在一些多分类问题上表现还是不错的,特别是在类别之间的边界比较清晰的情况下。

../_images/softmaxreg.svg

SoftLabel

SoftLabel 是一种处理标签问题的技术,通常用在机器学习的分类问题中。在传统的分类任务中,每个样本只会被分配一个确定的类别标签,比如说在一个二分类问题中,标签通常是 0 或 1。但在某些情况下,我们可能希望更灵活地表达分类的不确定性或者模糊性。

SoftLabel 就是这样一种技术,允许每个样本有一个属于每个类别的概率分布。例如,在一个二分类问题中,一个样本的 SoftLabel 可能是 (0.6, 0.4),这表示该样本属于第一类的概率是 60%,属于第二类的概率是 40%。

这种方法有以下几个优点:

  1. 灵活性:SoftLabel 能更灵活地表示样本之间的不确定性和模糊性,适用于那些不容易归入单一类别的样本。

  2. 鲁棒性:对于那些具有噪声或不准确标签的数据集,SoftLabel 可以提供一种更鲁棒的方法来表达这种不确定性。

  3. 更好的梯度信息:在训练神经网络时,SoftLabel 可以提供更丰富的梯度信息,有助于网络的训练。

  4. 平滑化决策边界: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

发表评论