softmax回归的从零开始实现
就像我们从零开始实现线性回归一样,我们认为 softmax 回归也是重要的基础,因此应该知道实现 softmax 回归的细节。本节我们将使用刚刚在 上一节 中引入的 Fashion-MNIST 数据集,并设置数据迭代器的批量大小为256。
初始化模型参数
和之前线性回归的例子一样,这里的每个样本都将用固定长度的向量表示。原始数据集中的每个样本都是 28×28 的图像。本节将展平每个图像,把它们看作长度为784的向量。在后面的章节中,我们将讨论能够利用图像空间结构的特征,但现在我们暂时只把每个像素位置看作一个特征。
回想一下,在 softmax 回归中,我们的输出与类别一样多。因为我们的数据集有10个类别,所以网络输出维度为10。因此,权重将构成一个 784×10 的矩阵,偏置将构成一个 1×10 的行向量。与线性回归一样,我们将使用正态分布初始化我们的权重 W
,偏置初始化为0。
定义softmax操作
在实现 softmax 回归模型之前,我们简要回顾一下 sum
运算符如何沿着张量中的特定维度工作。如 1.3.6 节 和 1.3.6.1 节 所述,给定一个矩阵 X
,我们可以对所有元素求和(默认情况下)。也可以只求同一个轴上的元素,即同一列(轴0)或同一行(轴1)。如果 X
是一个形状为 (2, 3)
的张量,我们对列进行求和,则结果将是一个具有形状 (3,)
的向量。当调用 sum
运算符时,我们可以指定保持在原始张量的轴数,而不折叠求和的维度。这将产生一个具有形状 (1, 3)
的二维张量。
回想一下,实现 softmax 由三个步骤组成:
- 对每个项求幂(使用
exp
);
- 对每一行求和(小批量中每个样本是一行),得到每个样本的规范化常数;
- 将每一行除以其规范化常数,确保结果的和为1。
在查看代码之前,我们回顾一下这个表达式:
softmax(X)ij=∑kexp(Xik)exp(Xij).(2.6.1)
分母或规范化常数,有时也称为配分函数(其对数称为对数-配分函数)。
该名称来自统计物理学中一个模拟粒子群分布的方程。
正如上述代码,对于任何随机输入,我们将每个元素变成一个非负数。此外,依据概率原理,每行总和为1。
注意,虽然这在数学上看起来是正确的,但我们在代码实现中有点草率。矩阵中的非常大或非常小的元素可能造成数值上溢或下溢,但我们没有采取措施来防止这点。
定义模型
定义 softmax 操作后,我们可以实现 softmax 回归模型。下面的代码定义了输入如何通过网络映射到输出。注意,将数据传递到模型之前,我们使用 reshape
函数将每张原始图像展平为向量。
定义损失函数
接下来,我们实现 2.4.6 节 中引入的交叉熵损失函数。这可能是深度学习中最常见的损失函数,因为目前分类问题的数量远远超过回归问题的数量。
回顾一下,交叉熵采用真实标签的预测概率的负对数似然。这里我们不使用 Python 的 for 循环迭代预测(这往往是低效的),而是通过一个运算符选择所有元素。下面,我们创建一个数据样本 y_hat
,其中包含2个样本在3个类别的预测概率,以及它们对应的标签 y
。有了 y
,我们知道在第一个样本中,第一类是正确的预测;而在第二个样本中,第三类是正确的预测。然后使用 y
作为 y_hat
中概率的索引,我们选择第一个样本中第一个类的概率和第二个样本中第三个类的概率。
现在我们只需一行代码就可以实现交叉熵损失函数。
分类精度
给定预测概率分布 y_hat
,当我们必须输出硬预测(hard prediction)时,我们通常选择预测概率最高的类。许多应用都要求我们做出选择。如 Gmail 必须将电子邮件分类为“Primary(主要邮件)”、“Social(社交邮件)”“Updates(更新邮件)”或“Forums(论坛邮件)”。Gmail 做分类时可能在内部估计概率,但最终它必须在类中选择一个。
当预测与标签分类 y
一致时,即是正确的。分类精度即正确预测数量与总预测数量之比。虽然直接优化精度可能很困难(因为精度的计算不可导),但精度通常是我们最关心的性能衡量标准,我们在训练分类器时几乎总会关注它。
为了计算精度,我们执行以下操作。首先,如果 y_hat
是矩阵,那么假定第二个维度存储每个类的预测分数。我们使用 argmax
获得每行中最大元素的索引来获得预测类别。然后我们将预测类别与真实 y
元素进行比较。由于等式运算符“==
”对数据类型很敏感,因此我们将 y_hat
的数据类型转换为与 y
的数据类型一致。结果是一个包含0(错)和1(对)的张量。最后,我们求和会得到正确预测的数量。
我们将继续使用之前定义的变量 y_hat
和 y
分别作为预测的概率分布和标签。可以看到,第一个样本的预测类别是2(该行的最大元素为0.6,索引为2),这与实际标签0不一致。第二个样本的预测类别是2(该行的最大元素为0.5,索引为2),这与实际标签2一致。因此,这两个样本的分类精度率为0.5。
同样,对于任意数据迭代器 data_iter
可访问的数据集,我们可以评估在任意模型 net
的精度。
这里定义一个实用程序类 Accumulator
,用于对多个变量进行累加。在上面的 evaluate_accuracy
函数中,我们在 Accumulator
实例中创建了2个变量,分别用于存储正确预测的数量和预测的总数量。当我们遍历数据集时,两者都将随着时间的推移而累加。
由于我们使用随机权重初始化 net
模型,因此该模型的精度应接近于随机猜测。例如在有10个类别情况下的精度为0.1。
训练
在我们看过 2.2 节 中的线性回归实现,softmax 回归的训练过程代码应该看起来非常眼熟。在这里,我们重构训练过程的实现以使其可重复使用。首先,我们定义一个函数来训练一个迭代周期。请注意,updater
是更新模型参数的常用函数,它接受批量大小作为参数。它可以是 d2l.sgd
函数,也可以是框架的内置优化函数。
在展示训练函数的实现之前,我们定义一个在动画中绘制数据的实用程序类 Animator
,它能够简化本书其余部分的代码。
接下来我们实现一个训练函数,它会在 train_iter
访问到的训练数据集上训练一个模型 net
。该训练函数将会运行多个迭代周期(由 num_epochs
指定)。在每个迭代周期结束时,利用 test_iter
访问到的测试数据集对模型进行评估。我们将利用 Animator
类来可视化训练进度。
作为一个从零开始的实现,我们使用 3.2 节 中定义的小批量随机梯度下降来优化模型的损失函数,设置学习率为 0.1 。
现在,我们训练模型10个迭代周期。请注意,迭代周期(num_epochs
)和学习率(lr
)都是可调节的超参数。通过更改它们的值,我们可以提高模型的分类精度。
预测
现在训练已经完成,我们的模型已经准备好对图像进行分类预测。给定一系列图像,我们将比较它们的实际标签(文本输出的第一行)和模型预测(文本输出的第二行)。
小结
- 借助softmax回归,我们可以训练多分类的模型。
- 训练softmax回归循环模型与训练线性回归模型非常相似:先读取数据,再定义模型和损失函数,然后使用优化算法训练模型。大多数常见的深度学习模型都有类似的训练过程。
练习
- 本节直接实现了基于数学定义softmax运算的
softmax
函数。这可能会导致什么问题?提示:尝试计算exp(50)的大小。
- 本节中的函数
cross_entropy
是根据交叉熵损失函数的定义实现的。它可能有什么问题?提示:考虑对数的定义域。
- 请想一个解决方案来解决上述两个问题。
- 返回概率最大的分类标签总是最优解吗?例如,医疗诊断场景下可以这样做吗?
- 假设我们使用softmax回归来预测下一个单词,可选取的单词数目过多可能会带来哪些问题?
Discussions