动手学人工智能-线性神经网络7-Softmax回归的简洁实现

344 阅读5分钟

1. Softmax回归简介

在机器学习中,分类问题常常要求我们将输入数据分配到不同的类别。例如,假设我们有一组图像数据,每个图像可能是一个数字(0到9中的一个)。我们希望创建一个模型,使得它可以根据输入的图像预测它所代表的数字。

Softmax回归就是 用来解决这类多类别分类问题 的一种模型。它通过将每个类别的得分转化为概率,帮助我们确定输入属于哪个类别。

在之前的文章中,我们已经学习了线性回归的实现。在本文中,我们将使用类似的方法来实现Softmax回归,但是输出的结果会有一些不同,因为我们需要处理多个类别的情况。

2. 初始化模型参数

在实现Softmax回归时,我们的模型包含一个输出层,它是一个全连接层(fully connected layer)。这个层将每个输入的特征映射到多个输出类别。

在PyTorch中,我们可以通过 nn.Linear 来定义全连接层。对于Softmax回归模型,我们将输入特征的维度(例如28x28像素的图像,我们的输入特征维度为784)映射到10个输出类别。这里我们还需要初始化模型的权重。

from torch import nn
import d2l

# 设置批量大小
batch_size = 256
# 加载训练和测试数据
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)

in_features = 28 * 28  # 输入特征数(展平成一维)
out_features = 10  # 输出特征数
# 使用框架定义模型
net = nn.Sequential(nn.Flatten(), nn.Linear(in_features, out_features))


# 初始化模型权重参数
def init_weights(m):
    if isinstance(m, nn.Linear):
        nn.init.normal_(m.weight, std=0.01)


# 模型应用参数
net.apply(init_weights)

在这段代码中,我们定义了一个简单的神经网络,其中包含了一个Flatten层,将28x28的图像展平为784个输入特征,然后通过一个线性层输出10个类别的得分。

nn.Flatten是PyTorch中的一个层,用于将多维输入张量展平为一维张量。在处理图像数据时,图像通常是二维的(例如28x28像素),而全连接层(如nn.Linear)的输入需要是一维的。因此,我们使用nn.Flatten

将图像的二维数据展平为一维数据,以便将其输入到全连接层。

3. 重新审视Softmax的实现

Softmax回归的核心在于 使用Softmax函数将每个类别的得分转化为概率。Softmax函数的公式如下:

Softmax(z)i=ezijezj\text{Softmax}(\mathbf{z})_i = \frac{e^{z_i}}{\sum_{j} e^{z_j}}

其中,z\mathbf{z}是模型的输出向量,ziz_i是输出向量中的第ii个元素,jezj\sum_{j} e^{z_j}是所有元素的指数和。

然而,计算Softmax时可能遇到数值稳定性的问题。具体来说,计算指数时,某些元素的值可能非常大,导致数值溢出(overflow)或下溢(underflow)。为了解决这个问题,我们通常会在计算Softmax之前,从所有输出中减去最大值,这样做不会影响Softmax的结果,但可以避免溢出问题。

数学上,改进后的Softmax公式为:

Softmax(z)i=ezimax(z)jezjmax(z)\text{Softmax}(\mathbf{z})_i = \frac{e^{z_i - \max(\mathbf{z})}}{\sum_{j} e^{z_j - \max(\mathbf{z})}}

这样做的好处是,我们避免了数值溢出的风险,同时保持了Softmax的准确性。

为了进一步提高计算的稳定性,我们将Softmax和交叉熵损失结合在一起,直接计算未规范化的得分和它们的对数。这样就避免了在反向传播中可能出现的数值不稳定问题。

在PyTorch中,我们可以使用nn.CrossEntropyLoss来实现这一点:

loss = nn.CrossEntropyLoss(reduction='none')

这个函数内部已经对Softmax和交叉熵损失进行了优化,并且能够处理数值稳定性的问题。

nn.CrossEntropyLoss中的reduction参数用于指定如何对损失值进行处理,具体有三个选项:

  • none:不进行任何处理,返回每个样本的损失值。返回的结果是一个包含每个样本损失的张量。

  • mean(默认值):对所有样本的损失值求平均,返回一个标量,表示所有样本的平均损失。

  • sum:对所有样本的损失值求和,返回一个标量,表示所有样本的总损失。

4. 优化算法

为了训练我们的模型,我们需要使用优化算法来更新模型的参数。我们使用小批量随机梯度下降(SGD)作为优化算法。其主要思想是:每次计算模型的梯度后,沿着梯度的反方向调整模型的参数

在这里,我们设置学习率为0.1,使用PyTorch的torch.optim.SGD来创建优化器。

lr = 0.1  # 学习率
# 使用小批量随机梯度下降(SGD)作为优化算法
trainer = torch.optim.SGD(net.parameters(), lr=lr)

5. 训练模型

现在我们已经完成了模型的定义、损失函数和优化算法的选择,接下来就是训练模型。我们可以调用之前定义的训练函数来进行训练。

if __name__ == '__main__':
    num_epochs = 10
    metric = d2l.Accumulator(3)
    animator = d2l.Animator(xlabel='轮数', xlim=[1, num_epochs], ylim=[0, 1],
                            legend=['train loss', 'train acc', 'test acc'])
    # 训练模型
    d2l.train_softmax(net, train_iter, test_iter, loss,
                      num_epochs, trainer, batch_size, animator)
    # 可视化优化过程
    animator.show()

在这段代码中,我们设置了训练的轮数为10,并开始训练模型。训练过程中,模型会通过多次迭代逐步优化参数,从而提高预测的准确性。

myplot.png

6. 总结

本文我们学习了如何用PyTorch实现Softmax回归。Softmax回归的核心思想是将每个类别的得分转化为概率,并通过交叉熵损失函数进行优化。我们还学习了如何处理Softmax中的数值稳定性问题,以及如何使用PyTorch来简洁地实现Softmax回归。