本次讲述softamx回归
softmax回归
回归:
- 单连续数值输出
- 自然区间R
- 跟真实值的区别作为损失
分类
- 通常多个输出
- 输出i是预测为第i类的置信度
无校验比例
对每个类别进行一位有效编码
最大值为预测
需要更置信的识别正确类(大余量)
分类数据的简单方法:独热编码。独热编码是一个向量,它的分量和类别一样多,如一个三维分量,其中(1,0,0)对应猫,(0,1,0)对应鸡,(0,0,1)对应狗。
网络结构:
我们有4个特征和3个可能的输出类别,我们将需要12个标量来表示权重(带下标的w),3个标量表示偏置,3个未规范化的预测:$o_1$、$o_2$和$o_3$。
softmax回归是一个单层神经网络。
softmax函数能将未规范化的预测变换未非负数并且总和为1,同时让模型保持可导的性质。
尽管softmax是一个非线性函数,但softmax回归输出仍然由输入特征的仿射变换决定,因此,softmax回归是一个线性模型。
这里,对于所有的$j$总有$0{\leq}{\hat{y}_j}{\leq}1$。因此,${\hat{y}}$可以视为一个正确的概率分布。softmax运算不会改变未规范化的预测$o$之间的大小次序,只会确定分配给每个类别的概率。因此在预测过程中,我们仍然可以用下式来选择最有可能的类别:
导包:
1 | %matplotlib inline |
读取数据集
1 | #读取数据集 |
1 | def get_fashion_mnist_labels(labels):#@save |
1 | #可视化样本 |
1 | x,y = next(iter(data.DataLoader(mnist_train,batch_size=18)))#从 MNIST 训练集中获得了一个包含18个图像(x)及其对应标签(y)的批次数据 |
1 | #读取小批量 |
1 | #训练数据所需要的时间 |
1 | #整合以上函数 |
1 | #效果展示 |
softmax回归的从零开始实现
初始化模型参数
1 | #softmax回归从零开始实现 |
1 | batch_size = 256 #设置数据迭代器的批量大小 |
1 | #初始化参数模型 |
定义softmax操作
1 | X = torch.tensor([[1.0,2.0,3.0],[4.0,5.0,6.0]])#创建了一个包含两行三列的2D张量 X。 |
实现softmax由以下3个步骤组成:
- 对每个项求幂(使用exp)
- 对每一行求和(小批量中的每个样本是一行),得到每个样本的规范化常数
- 将每一行除以其规范化常数,确保结果的和为1
回顾一下公式:
分母或规范化常数有时也称为配分函数(其对数称为对数-配分函数)。该名称来自统计物理学中一个模拟粒子群分布的方程。
1 | def softmax(X): |
上述代码,对于任何随机输入,我们将每个元素转变成一个非负数。
定义模型
定义输入如何通过网络映射到输出
1 | def net(X): |
定义损失函数
引入交叉熵损失函数,交叉熵采用实际标签的预测概率的负对数似然。这里我们不使用python的for循环迭代预测(这往往是低效的),而是通过一个运算符选择所有元素。下面,我们创建一个数据样本y_hat,其中包含2个样本在3个类别上的预测概率,以及它们对应的标签y。有了y,我们知道在第一个样本中,第一个类别是正确的预测;而在第二个样本中,第三个类别是正确的预测。然后使用y作为y_hat中的概率索引,我们选择第一个样本中第一个类别的概率和第二个样本中第三个类别的概率
1 | y = torch.tensor([0,2]) |
现在完成交叉熵损失函数
1 | def cross_entropy(y_hat,y): |
分类精度
给定预测概率分布y_hat,当我们必须输出硬预测时,我们通常选择预测概率最高的类别。当预测与分类标签y一致时是正确的。分类精度是正确预测数与预测总数之比。
为了计算精度,我们执行以下操作。首先,如果y_hat是矩阵,那么假定第二个维度存储每个类别的预测分数。我们使用argmax获得每行中最大元素的索引来获得预测类别。然后我们将预测类别与真实的y元素进行比较。由于等式运算符“==”对数据类型很敏感,因此我们将y_hat的数据类型转换为与y的数据类型一致。结果是一个包含0(错)和1(对)的张量。最后,我们求和会得到预测正确的数量。
1 | def accuracy(y_hat,y): |
我们将继续使用之前的定义的变量y_hat和y分别作为预测的概率分布和标签。可以看到,第一个样本的预测类别是2(该行的最大元素为0.6,索引为2),这与实际标签0不一致。第二个样本的预测类别是2(该行的最大元素为0.5,索引为2),这与实际标签2一致。因此这两个样本的分类精度为0.5。
同样对于任意数据迭代器data_iter可访问的数据集,我们可以评估在任意模型net上的精度。
1 | def evaluate_accuracy(net,data_iter): #@save |
这里定义一个使用程序类Accumulator,用于对多个变量进行累加。在上面的evaluate_accuracy函数中,我们在Accumulator实例中创建了两个变量,分别存储正确预测数和预测总数。当我们遍历数据集时,两者都将随着时间的推移而累加。
1 | class Accumulator: #@save |
由于我们使用的随机权重初始化,所以模型精度应接近于随机猜测,如若有十个类别,情况下,精度趋向于0.1
1 | evaluate_accuracy(net,test_iter) |
训练
首先注意updater是更新模型参数的常用函数,它接收批量大小作为参数。它可以是d2l.sgd函数,也可以是框架的内置优化函数。
1 | def train_epoch_ch3(net,train_iter,loss,updater): #@save |
定义一个在动画中绘制图表的实用程序类Animator,
1 | class Animator: #@save |
接下来我们实现一个训练函数,它会在train_iter访问的训练数据集上训练一个模型net。该训练函数将会运行多轮(由num_epochs指定)。在每轮结束时,利用test_iter访问的测试数据集对模型进行评估。
1 | def train_ch3(net,train_iter,test_iter,loss,num_epochs,updater):#@save |
1 | lr = 0.1 |
预测
1 | def predict_ch3(net,test_iter,n=6):#@save |
softmax回归简洁实现
1 | from torch import nn |
1 | batch_size = 256 |
1 | net = nn.Sequential(nn.Flatten(),nn.Linear(784,10)) |
1 | loss = nn.CrossEntropyLoss(reduction='none') |
1 | trainer = torch.optim.SGD(net.parameters(),lr=0.1) |
1 | num_epochs = 10 |
课后问题
增加轮数,为什么测试精度会在一段时间后降低?我们怎么解决这个问题?
增加迭代周期的数量可能会导致过拟合,从而导致测试精度下降。具体来说,当我们增加迭代周期的数量时,模型可能会开始学习到一些只能满足训练样本的非共性特征(这些更多是一种偶然性特征,不适用于测试样本),从而导致过拟合。为了解决这个问题,可以使用早停技术或正则化技术。早停技术是指在模型出现过拟合时(测试集表现开始下降)停止训练。正则化技术是指通过向损失函数添加惩罚项来限制模型参数的大小,从而减少过拟合。