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

import numpy as np
import sys
import torch
import torchvision
from torch.utils import data
from torchvision import transforms

获取和读取数据

先获取Fashion-MNIST数据集,并设置批量大小为256。

def get_dataloader_workers():
    if sys.platform.startswith('win'):
        num_workers = 0  # 0表示不用额外的进程来加速读取数据
    else:
        num_workers = 4

def load_data_fashion_mnist(batch_size, resize=None): 
    # 下载Fashion-MNIST数据集,然后将其加载到内存中,返回训练集和测试集的数据迭代器
    mnist_train = torchvision.datasets.FashionMNIST(root="data", train=True, transform=transforms.ToTensor(), download=True)
    mnist_test = torchvision.datasets.FashionMNIST(root="data", train=False, transform=transforms.ToTensor(), 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()))

batch_size = 256
train_iter, test_iter = load_data_fashion_mnist(batch_size)

使用向量表示每个样本。已知每个样本输入是高和宽均为28像素的图像。模型的输入向量的长度是 :该向量的每个元素对应图像中每个像素。由于图像有10个类别,单层神经网络输出层的输出个数为10,因此softmax回归的权重和偏差参数分别为和的矩阵。

初始化模型参数

num_inputs = 784
num_outputs = 10

W = torch.tensor(np.random.normal(0, 0.01, (num_inputs, num_outputs)), dtype=torch.float) 
# np.random.normal 生成正态分布的随机数,均值为0,标准差为0.01,形状为(num_inputs, num_outputs),torch.tensor 将生成的随机数转换为张量
b = torch.zeros(num_outputs, dtype=torch.float) # 偏置b初始化为0

W.requires_grad_(requires_grad=True) # 设置参数W需要求梯度
b.requires_grad_(requires_grad=True) # 设置参数b需要求梯度

实现softmax运算

先描述一下对如何对多维Tensor​按维度操作。在下面的例子中,给定一个Tensor​矩阵X​。我们可以只对其中同一列(dim=0​)或同一行(dim=1​)的元素求和,并在结果中保留行和列这两个维度(keepdim=True​)。

X = torch.tensor([[1, 2, 3], [4, 5, 6]])
print(X.sum(dim=0, keepdim=True))
print(X.sum(dim=1, keepdim=True))

# output:
# tensor([[5, 7, 9]])
# tensor([[ 6],
#         [15]])

在下面的函数中,矩阵X​的行数是样本数,列数是输出个数。为了表达样本预测各个输出的概率,softmax运算会先通过exp​函数对每个元素做指数运算,再对exp​矩阵同行元素求和,最后令矩阵每行各元素与该行元素之和相除。这样一来,最终得到的矩阵每行元素和为1且非负。因此,该矩阵每行都是合法的概率分布。softmax运算的输出矩阵中的任意一行元素代表了一个样本在各个输出类别上的预测概率。

def softmax(X):
    X_exp = X.exp()
    partition = X_exp.sum(dim=1, keepdim=True)
    return X_exp / partition  # 这里应用了广播机制

定义模型

def net(X):
    return softmax(torch.mm(X.view((-1, num_inputs)), W) + b) 
    # X.view((-1, num_inputs))将X变形为(batch_size, num_inputs)的形状,也就是将每个28*28的图像都转换为长度为num_inputs(784)的向量,一行代表一个样本(图像),一共batch_size行
    # torch.mm(X.view((-1, num_inputs)), W)实现X和W的矩阵乘法,然后加上偏置b,最后将结果输入softmax函数,得到预测概率

定义损失函数

使用gather​函数可以实现通过索引获取数据,也就是可以得到标签所对应的预测概率:

# 
y_hat = torch.tensor([[0.1, 0.3, 0.6], [0.3, 0.2, 0.5]])  # 假设有两个样本,每个样本有三个类别的预测概率
y = torch.LongTensor([0, 2])  # 两个样本的标签分别为0和2
y_hat.gather(1, y.view(-1, 1))   
# y.view(-1, 1)将y变形为列向量,然后使用gather函数,第一个参数是维度,第二个参数是索引,表示在第一个维度上,取索引为y的元素
# 维度:0表示按行取值,1表示按列取值
def cross_entropy(y_hat, y):
    return - torch.log(y_hat.gather(1, y.view(-1, 1))) 
    # gather函数取出标签y对应的预测概率,然后取对数

计算分类准确率

给定一个类别的预测概率分布y_hat​,我们把预测概率最大的类别作为输出类别。如果它与真实类别y​一致,说明这次预测是正确的。分类准确率即正确预测数量与总预测数量之比。

定义一个准确率函数accuracy​

def accuracy(y_hat, y):
    return (y_hat.argmax(dim=1) == y).float().mean().item()
    # y_hat.argmax(dim=1)返回矩阵y_hat每行中最大元素的索引,且返回结果与变量y形状相同。
    # 相等条件判断式(y_hat.argmax(dim=1) == y)是一个类型为ByteTensor的Tensor,用float()将其转换为值为0(相等为假)或1(相等为真)的浮点型Tensor。
    # 最后用mean()求得平均值即为正确率。
    # item()将只含一个元素的tensor转换为标量

评价模型net​在数据集data_iter​上的准确率:

def evaluate_accuracy(data_iter, net):
    acc_sum, n = 0.0, 0
    for X, y in data_iter: # X是图像,y是标签,数量为batch_size
        acc_sum += (net(X).argmax(dim=1) == y).float().sum().item() # net(X) 返回预测概率,argmax(dim=1)返回概率最大的类别,与标签y比较
        n += y.shape[0] # y.shape[0]是y的行数,也就是batch_size
    return acc_sum / n # 返回正确率

训练模型

# 3.2.6 定义优化算法
def sgd(params,lr,batch_size):  #定义优化算法,params:待优化参数,lr:学习率,batch_size:批量大小
    for param in params:
        param.data-=lr*param.grad/batch_size  #注意这里更改param时用的param.data


def train_ch3(net, train_iter, test_iter, loss, num_epochs, batch_size, params=None, lr=None, optimizer=None): 
    # net: 网络,即线性回归模型
    # train_iter: 训练数据集,test_iter: 测试数据集
    # loss: 损失函数,num_epochs: 训练的轮数,batch_size: 批量大小
    # params: 模型参数,即W和b,lr: 学习率,optimizer: 优化算法,如SGD
    for epoch in range(num_epochs):  # 训练模型一共需要num_epochs个迭代周期
        train_l_sum, train_acc_sum, n = 0.0, 0.0, 0 # 训练损失总和,训练准确度总和,样本数
        for X, y in train_iter: # X是图像,y是标签,数量为batch_size
            y_hat = net(X) # 预测概率
            l = loss(y_hat, y).sum() # 计算损失,sum()将所有loss值相加得到一个标量

            # 梯度清零
            if optimizer is not None: # 使用PyTorch内置的优化器和损失函数
                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: # 使用PyTorch内置的优化器和损失函数
                sgd(params, lr, batch_size) # 更新模型参数
            else:
                optimizer.step()  # “softmax回归的简洁实现”一节将用到


            train_l_sum += l.item() # 将当前批次loss值相加得到一个总的loss值
            train_acc_sum += (y_hat.argmax(dim=1) == y).sum().item() # 计算总准确率
            n += y.shape[0] # y.shape[0]是y的行数,也就是batch_size,计算总样本数

        test_acc = evaluate_accuracy(test_iter, net) # 计算测试集准确率
        print('周期 %d, 损失 %.4f, 数据集准确率 %.3f, 测试集准确率 %.3f'
              % (epoch + 1, train_l_sum / n, train_acc_sum / n, test_acc))
  
if __name__ == '__main__':
    batch_size = 256
    train_iter, test_iter = load_data_fashion_mnist(batch_size)


    num_inputs = 28*28
    num_outputs = 10

    W = torch.tensor(np.random.normal(0, 0.01, (num_inputs, num_outputs)), dtype=torch.float) 
    # np.random.normal 生成正态分布的随机数,均值为0,标准差为0.01,形状为(num_inputs, num_outputs),torch.tensor 将生成的随机数转换为张量
    b = torch.zeros(num_outputs, dtype=torch.float) # 偏置b初始化为0

    W.requires_grad_(requires_grad=True) # 设置参数W需要求梯度
    b.requires_grad_(requires_grad=True) # 设置参数b需要求梯度
    num_epochs, lr = 5, 0.1

    train_ch3(net, train_iter, test_iter, cross_entropy, num_epochs, batch_size, [W, b], lr)

预测

import matplotlib.pyplot as plt

def show_images_in_new_window(imgs, num_rows, num_cols, titles=None, scale=1.5):
    figsize = (num_cols * scale, num_rows * scale)
    fig, axes = plt.subplots(num_rows, num_cols, figsize=figsize)
    axes = axes.flatten()
    for i, (ax, img) in enumerate(zip(axes, imgs)):
        if torch.is_tensor(img):
            ax.imshow(img.numpy())
        else:
            ax.imshow(img)
        ax.axes.get_xaxis().set_visible(False)
        ax.axes.get_yaxis().set_visible(False)
        if titles:
            ax.set_title(titles[i])
    plt.show()

def get_fashion_mnist_labels(labels):  #@save  
    """返回Fashion-MNIST数据集的文本标签"""
    text_labels = ['t-shirt', 'trouser', 'pullover', 'dress', 'coat', 'sandal', 'shirt', 'sneaker', 'bag', 'ankle boot']
    return [text_labels[int(i)] for i in labels]


for X, y in test_iter:
    y_hat = net(X)
    show_images_in_new_window(X[0:9].reshape(9, 28, 28), 3, 3, titles=get_fashion_mnist_labels(y_hat[0:9].argmax(dim=1)))
    break


循之际,如星夜般的幻想。