LLIS寒假学习(5):动手学深度学习(pytorch版):softmax回归从零开始实现逐步解读

一.具体问题

图像分类数据集(Fashion-MNIST)
详细请转原书:图像分类数据集

二.从零开始实现

首先导入本节实现所需的包或模块。

import torch
import torchvision
import numpy as np
import sys
sys.path.append("..") # 为了导入上层目录的d2lzh_pytorch,我直接把这个包放到了代码文件所在的文件夹内,就可以省去这步。
import d2lzh_pytorch as d2l

获取和读取数据

batch_size = 256#设置批量大小为256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)#在原书上一节内容中,将已经下载好的数据集封装在了该函数中,该函数返回train_iter和test_iter(即训练数据集和测试数据集)

初始化模型参数

num_inputs = 784#输入向量长度784,即每个样本是高和宽均为28像素的图像
num_outputs = 10#由于图像有10个类别,所以输出为10

W = torch.tensor(np.random.normal(0, 0.01, (num_inputs, num_outputs)), dtype=torch.float)#构造784*10的权重参数矩阵
b = torch.zeros(num_outputs, dtype=torch.float)#构造1*10的偏差参数矩阵

W.requires_grad_(requires_grad=True)#求梯度的前提,设置为true
b.requires_grad_(requires_grad=True) 

实现softmax运算

def softmax(X):
    X_exp = X.exp()#X的行数是样本数,列数是输出个数,根据softmax运算,先进行指数运算。
    partition = X_exp.sum(dim=1, keepdim=True)#再进行同行(dim=1)元素求和,keepdim=true的意思是保留行和列这两个维度
    return X_exp / partition  #令矩阵每行各元素与该行元素之和相除,这里应用了广播机制

softmax运算的输出矩阵中的任意一行元素代表了一个样本在各个输出类别上的预测概率。(每行元素和为1且非负)

定义模型

def net(X):
    return softmax(torch.mm(X.view((-1, num_inputs)), W) + b)#矩阵相乘,并使用view函数将每张原始图像改成长度为num_inputs的向量

定义损失函数

def cross_entropy(y_hat, y):#分别传入预测概率分布和真实标签类别
    return - torch.log(y_hat.gather(1, y.view(-1, 1)))#gather函数按照横向索引,取出对应类别的预测值,做log运算并取负(交叉熵)

gather函数的理解:转推荐博客
计算分类准确率
其中y_hat.argmax(dim=1)返回矩阵y_hat每行中最大元素的索引

def accuracy(y_hat, y):
    return (y_hat.argmax(dim=1) == y).float().mean().item()#判断预测概率最大的类别是否和真实类别相等

相等条件判断式(y_hat.argmax(dim=1) == y)是一个类型为ByteTensor的Tensor,我们用float()将其转换为值为0(相等为假)或1(相等为真)的浮点型Tensor。
分类准确率即正确预测数量与总预测数量之比。
mean()求预测是否准确的平均值,item()使返回值为一个实数,如果没有item()的话,经过测试发现输出是Tensor(实数)。

类似地,我们可以评价模型net在数据集data_iter上的准确率。

# 本函数已保存在d2lzh_pytorch包中方便以后使用。该函数将被逐步改进:它的完整实现将在“图像增广”一节中描述
def evaluate_accuracy(data_iter, net):
    acc_sum, n = 0.0, 0
    for X, y in data_iter:
        acc_sum += (net(X).argmax(dim=1) == y).float().sum().item()
        n += y.shape[0]#shape[0]读取第一维度(行)的长度,即单位预测数量,循环累加即总预测数量
    return acc_sum / n

训练模型
和线性回归的从零实现类似,我们同样使用小批量随机梯度下降来优化模型的损失函数。

num_epochs, lr = 5, 0.1#迭代周期数,学习率(都可调)

# 本函数已保存在d2lzh包中方便以后使用
def train_ch3(net, train_iter, test_iter, loss, num_epochs, batch_size,
              params=None, lr=None, optimizer=None):
    for epoch in range(num_epochs):
        train_l_sum, train_acc_sum, n = 0.0, 0.0, 0#损失,训练准确率,预测总数量
        for X, y in train_iter:
            y_hat = net(X)#传入模型
            l = loss(y_hat, y).sum()#这里使用的仍然是平方损失函数????不懂了

            # 梯度清零
            if optimizer is not None:
                optimizer.zero_grad()
            elif params is not None and params[0].grad is not None:
                for param in params:
                    param.grad.data.zero_()

            l.backward()#反向传播
            if optimizer is None:
                d2l.sgd(params, lr, batch_size)#使用SGD优化算法
            else:
                optimizer.step()  # “softmax回归的简洁实现”一节将用到,这里到现在都没搞懂


            train_l_sum += l.item()#小批量求损失之后相加
            train_acc_sum += (y_hat.argmax(dim=1) == y).sum().item()#小批量求准确率之后相加
            n += y.shape[0]#小批量求数量之后相加
        test_acc = evaluate_accuracy(test_iter, net)#模型net在测试数据集上的准确率
        print('epoch %d, loss %.4f, train acc %.3f, test acc %.3f'
              % (epoch + 1, train_l_sum / n, train_acc_sum / n, test_acc))

预测
训练完成后,现在就可以演示如何对图像进行分类了。给定一系列图像(第三行图像输出),我们比较一下它们的真实标签(第一行文本输出)和模型预测结果(第二行文本输出)。

X, y = iter(test_iter).next()

true_labels = d2l.get_fashion_mnist_labels(y.numpy())
pred_labels = d2l.get_fashion_mnist_labels(net(X).argmax(dim=1).numpy())
titles = [true + '\n' + pred for true, pred in zip(true_labels, pred_labels)]

d2l.show_fashion_mnist(X[0:9], titles[0:9])

在这里插入图片描述

三.小结

可以使用softmax回归做多类别分类。与训练线性回归相比,你会发现训练softmax回归的步骤和它非常相似:获取并读取数据、定义模型和损失函数并使用优化算法训练模型。事实上,绝大多数深度学习模型的训练都有着类似的步骤。

不是很简单嘛?

猜你喜欢

转载自blog.csdn.net/weixin_45850972/article/details/104950600