动手学深度学习之神经网络模型总结

本文主要总结了李沐老师《动手学深度学习》课程中的一些经典的神经网络模型。主要包括线性回归模型多层感知机模型简单的卷积神经网络模型经典卷积神经网络LeNet深度卷积神经网络AlexNet使用块的网络VGG网络中的网络NiN含并行连接的网络GoogLeNet残差网络ResNet等等。
动手学深度学习 PyTorch版:课程链接

一、环境配置

  1. 确认是否有Nvidia GPU,有的话去CUDA官网安装[CUDA安装官网](https://developer.nvidia.cn/cuda-downloads)
  2. 安装Python包管理工具Anaconda或Miniconda
  3. 根据CUDA的版本选择对应版本的GPU版Pytorch[Pytorch安装官网](https://pytorch.p2hp.com/#google_vignette)
  4. 安装包d2l和jupyter
  5. 具体操作可参照视频Windows 下安装 CUDA 和 Pytorch 跑深度学习 - 动手学深度学习v2

一、线性神经网络

1.1 线性回归模型

线性回归是利用数理统计中回归分析,来确定两种或两种以上变量间相互依赖的定量关系的一种统计分析方法,运用十分广泛。其表达形式为y=wx+by=wx+b
回归分析中,只包括一个自变量和一个因变量,且二者的关系可用一条直线近似表示,这种回归分析称为一元线性回归分析。如果回归分析中包括两个或两个以上的自变量,且因变量和自变量之间是线性关系,则称为多元线性回归分析。多元线性回归模型可以表示为y=wx+by={\vec w }\cdot{\vec x}+b,其中w\vec wbb为需要训练的参数。
用线性模型拟合数据

1.1.1 从零开始实现线性回归

为了简单起见,我们将根据带有噪声的线性模型构造一个人造数据集。 我们的任务是使用这个有限样本的数据集来恢复这个模型的参数。

1.1.1.1 导包

第一步,导入需要使用的包,导入random包的目的是帮助我们构造人造数据集

1
2
3
import random
import torch
from d2l import torch as d2l

1.1.1.2 构造人造数据集

第二步,构造人造数据集,我们的目标是构造一个包含10000个样本的数据集,其中每个样本包含从标准正态分布中采样的2个特征,所以最终得到的输入xx应该是一个形状为(10000,2)的矩阵。
我们使用参数w=[2,3.4]Tb=4.2w=[2,-3.4]^\mathrm T、b=4.2和符合正态分布的噪声项ϵ\epsilon生成数据集。

1
2
3
4
5
6
7
8
9
10
def synthetic_data(w, b, num_examples): 
"""生成y=Xw+b+噪声"""
X = torch.normal(0, 1, (num_examples, len(w))) # 生成随机输入
y = torch.matmul(X, w) + b
y += torch.normal(0, 0.01, y.shape) # 为数据添加随机噪音,生成标签向量labels
return X, y.reshape((-1, 1))

true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = synthetic_data(true_w, true_b, 1000)

我们可以根据第二个特征来查看我们生成的随机数据集:

1
2
3
d2l.set_figsize()
d2l.plt.scatter(features[:, (1)].detach().numpy(), labels.detach().numpy(), 1)
d2l.plt.show()

得到的输出图:
linear_regression_data

1.1.1.3 读取数据集

在读取数据集时,我们一般使用小批量进行读取,因为小批量数据读取有以下几个优势:

  1. 内存效率:如果数据集非常大,无法一次性加载到内存中,使用小批量可以有效地管理内存。模型在训练时只需处理一小部分数据,而不是一次读取整个数据集。
  2. 训练稳定性:小批量训练可以让模型的权重更新更加平稳。如果使用整个数据集进行梯度更新(称为全量梯度下降),每次更新都基于所有数据,会导致权重更新比较缓慢。而如果每次只使用一个样本(称为随机梯度下降,SGD),梯度更新会很快,但更新过程容易波动。小批量能够在稳定性和效率之间取得平衡。
  3. 计算效率:现代硬件(如GPU和TPU)在处理批量数据时非常高效。使用小批量能够让硬件的计算资源得到充分利用,增加训练速度。
  4. 更好的泛化能力:通过在每个小批量中对不同数据子集进行梯度计算,模型可以在权重更新时学习更多的样本分布变异,帮助提高模型的泛化能力。
  5. 并行处理:小批量训练可以更好地并行化处理,尤其是在深度学习中,利用GPU或分布式系统进行并行运算可以显著提高训练速度。
    因此,使用小批量读取数据既能提高模型训练的效率,又能保持更新的稳定性和泛化能力。
1
2
3
4
5
6
7
8
9
10
11
12
def data_iter(batch_size, features, labels):
num_examples = len(features) # 计算样本总数
indices = list(range(num_examples)) # 创建一个包含所有样本索引的列表
random.shuffle(indices) # 将样本随机打乱
for i in range(0, num_examples, batch_size):
# 每batch_size个样本作为一个批量数据batch_indices
# indices[i: min(i + batch_size, num_examples)]表示当前批次的样本索引。
# min(i + batch_size, num_examples)确保即使在最后一批样本不足batch_size时也不会超出数据集范围。
batch_indices = torch.tensor(
indices[i: min(i + batch_size, num_examples)])
# yield是一个生成器,每次调用data_iter时,会返回下一批数据,而不会一次性返回所有数据,节省内存
yield features[batch_indices], labels[batch_indices]

在上面的代码中,我们定义了一个data_iter函数,该函数接收批量大小、特征矩阵和标签向量作为输入,生成大小为batch_size的小批量。每个小批量包含一组特征和标签。

我们可以设置一个小批量来验证data_iter函数:

1
2
3
4
5
batch_size = 10

for X, y in data_iter(batch_size, features, labels):
print(X, '\n', y)
break

得到输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
tensor([[-0.3809, -0.8246],
[-0.1036, -0.3585],
[ 1.5089, -0.8039],
[ 0.2423, -0.8148],
[-1.7175, -0.0828],
[ 0.9321, 0.4002],
[ 1.5887, -1.1424],
[-0.0875, 0.4256],
[-0.4705, 0.8662],
[ 1.6353, -0.7181]])
tensor([[ 6.2405],
[ 5.2150],
[ 9.9482],
[ 7.4561],
[ 1.0414],
[ 4.6962],
[11.2425],
[ 2.5706],
[ 0.3183],
[ 9.9026]])

可以看到输入XX中有10个样本数据,每个样本包含两个特征,对应标签数量也为10

1.1.1.4 初始化模型参数

在开始训练模型之前,我们需要初始化模型的参数wwbb,这里我们在均值为0、标准差为0.01的正态分布中随机采样来初始化ww,偏置值bb设为0。在后续学习中我们还会学习一些其他方法来初始化参数以使得模型能够更快拟合。

1
2
w = torch.normal(0, 0.01, size=(2,1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)

1.1.1.5 定义模型

我们使用最简单的线性回归y=wx+by={\vec w }\cdot{\vec x}+b来定义模型:

1
2
3
def linreg(X, w, b): 
"""线性回归模型"""
return torch.matmul(X, w) + b

1.1.1.6 定义损失函数

在线性回归模型中我们使用均方误差损失函数作为损失函数:

l(i)(w,b)=12(y^(i)y(i))2l^{(i)}(\vec w,b) = {1 \over 2}{(\hat y^{(i)}-y^{(i)})^2}

1
2
3
def squared_loss(y_hat, y):  
"""均方损失"""
return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2

1.1.1.7 定义优化算法

这里我们使用随机梯度下降作为优化算法。随机梯度下降通过不断地在损失函数递减的方向上更新参数来降低误差。

repeat until convergence:  {  wj=wjαJ(w,b)wj  b=bαJ(w,b)b}\begin{align*} \text{repeat}&\text{ until convergence:} \; \lbrace \newline \; w_j &= w_j - \alpha \frac{\partial J(\vec{w},b)}{\partial w_j} \; \newline b &= b - \alpha \frac{\partial J(\vec{w},b)}{\partial b} \newline \rbrace \end{align*}

1
2
3
4
5
6
7
def sgd(params, lr, batch_size):  
"""小批量随机梯度下降"""
with torch.no_grad():
# params中一共有两个参数,分别是w和b,我们将其分别取出并做梯度下降
for param in params:
param -= lr * param.grad / batch_size
param.grad.zero_() # 将梯度初始化为0

1.1.1.8 训练模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 我们设置学习率为0.03,迭代周期(epoch)为3
lr = 0.03
num_epochs = 3
net = linreg
loss = squared_loss

for epoch in range(num_epochs):
for X, y in data_iter(batch_size, features, labels):
l = loss(net(X, w, b), y) # 计算X和y的小批量损失
# 因为l形状是(batch_size,1),而不是一个标量。l中的所有元素被加到一起
# 并以此计算关于[w,b]的梯度
# backward()是PyTorch中的自动求导函数,用于计算损失对模型参数(w和b)的梯度。
# 这一步是反向传播的关键部分,标志着模型如何更新其参数以减少误差。
l.sum().backward()
sgd([w, b], lr, batch_size) # 使用参数的梯度更新参数
with torch.no_grad(): # torch.no_grad()表示在评估过程中中禁用梯度计算,这样可以节省开销
train_l = loss(net(features, w, b), labels) # 训练误差等于计算得到的误差和真实值labels之间的差值
print(f'epoch {epoch + 1}, loss {float(train_l.mean()):f}')

print(f'w的估计误差: {true_w - w.reshape(true_w.shape)}')
print(f'b的估计误差: {true_b - b}')

训练结果:
epoch 1, loss 0.021807
epoch 2, loss 0.000082
epoch 3, loss 0.000052
w的估计误差: tensor([-0.0003, 0.0001], grad_fn=)
b的估计误差: tensor([-0.0002], grad_fn=)

这样就实现了一个简单的线性神经网络。

1.1.2 线性回归模型的简洁实现

在上节中我们将每个函数都做了内部实现,但实际上在深度学习框架中有很多组件可以直接调用,现在我们来简洁实现一个线性回归模型。

1.1.2.1 导包并生成数据集

这次我们直接通过导包获取数据集。

1
2
3
4
5
6
7
8
import numpy as np
import torch
from torch.utils import data
from d2l import torch as d2l

true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = d2l.synthetic_data(true_w, true_b, 1000)

1.1.2.2 读取数据集

之前我们使用遍历样本来读取数据集,这里我们可以调用框架中现有的API来读取数据。 我们将输入值X(features)和标签值(labels)作为API的参数传递,并通过数据迭代器指定batch_size。 此外,布尔值is_train表示是否希望数据迭代器对象在每个迭代周期内打乱数据。

1
2
3
4
5
6
7
8
9
def load_array(data_arrays, batch_size, is_train=True):  
"""构造一个PyTorch数据迭代器"""
# TensorDataset是PyTorch中的一个数据集封装器,它可以将一组张量变为一个数据集。
# *data_arrays代表将data_arrays中的元素(特征值features和标签labels)作为参数传入TensorDataset。
dataset = data.TensorDataset(*data_arrays)
return data.DataLoader(dataset, batch_size, shuffle=is_train)

batch_size = 10
data_iter = load_array((features, labels), batch_size)

1.1.2.3 定义模型

对于标准深度学习模型,我们可以使用框架的预定义好的层。我们首先定义一个模型变量net,它是一个Sequential类的实例。 Sequential类可以将多个层串联在一起。 当给定输入数据时,Sequential实例将数据传入到第一层, 然后将第一层的输出作为第二层的输入,以此类推。
这里我们使用单层的线性神经网络层Linear,第一个指定特征为输入形状,第二个指定特征为输出形状。

1
2
3
4
# nn是神经网络的缩写
from torch import nn

net = nn.Sequential(nn.Linear(2, 1))

1.1.2.4 初始化模型参数

我们初始化模型参数ww为从均值为0、标准差为0.01的正态分布中随机采样,偏置值bb初始化为0。

1
2
3
# net[0]指的就是第0层神经网络,也就是我们的线性层
net[0].weight.data.normal_(0, 0.01)
net[0].bias.data.fill_(0)

1.1.2.5 定义损失函数

这里我们可以直接使用nn类中定义好的均方误差损失函数,也称L2L_2范数。

1
loss = nn.MSELoss()

1.1.2.6 定义优化算法

torch.optim是一个实现了各种优化算法的库。大部分常用的方法得到支持,并且接口具备足够的通用性,使得未来能够集成更加复杂的方法。
梯度下降法是深度学习中常用的优化算法,算法思想可见:[1.2梯度下降](https://sonderhs.github.io/2024/05/02/吴恩达机器学习/吴恩达机器学习(一):监督学习/)
这里我们使用随机梯度下降作为优化算法,我们需要传入两个参数,第一个参数是我们需要优化的神经网络的参数,我们可以通过net.parameters()net.parameters()得到,第二个参数是学习率lrlr

1
trainer = torch.optim.SGD(net.parameters(), lr=0.03)

1.1.2.7 训练模型

我们设置批量数num_epochs为3,对于每个小批量,我们需要做的就是:通过前向传播计算损失\to将梯度初始化为0后反向传播计算梯度\to执行梯度下降算法更新参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
num_epochs = 3
for epoch in range(num_epochs):
for X, y in data_iter:
l = loss(net(X) ,y) # 前向传播计算损失
trainer.zero_grad() # 将梯度初始化为0
l.backward() # 反向传播计算梯度
trainer.step() # 更新所有参数
l = loss(net(features), labels) # 计算本次epoch的损失
print(f'epoch {epoch + 1}, loss {l:f}')

w = net[0].weight.data
print('w的估计误差:', true_w - w.reshape(true_w.shape))
b = net[0].bias.data
print('b的估计误差:', true_b - b)

这样我们就通过pytorch框架简洁实现了一个线性神经网络。

1.2 softmax回归模型

线性回归主要用于解决预测和趋势分析问题,而对于分类问题我们常用的模型为softmax回归模型
与线性回归一样,softmax回归也是一个全连接神经网络,不同之处在于线性回归只有一个输出单元,但解决分类问题的softmax回归有多个输出单元,每个单元对应一种不同的类别。
softmax网络架构
softmax的公式为:

zj=wjx+bj,其中j=1,2,...,Nz_j=\vec w_j \cdot \vec x + b_j ,其中j=1,2,...,N \\

预测结果为j的概率:

aj=ezjj=1Nezj=P(y=jx)a_j=\frac{e^{z_j}}{\sum\limits_{j = 1}^{N}e^{z_j}}=P(y=j|\vec x)

1.2.1 从零开始实现softmax回归

本节我们不再需要手动生成数据集,而是使用[Fashion-MNIST数据集](https://www.alphaxiv.org/abs/1708.07747)。

1.2.1.1 导包

1
2
3
4
5
6
import torch
from IPython import display
from d2l import torch as d2l

batch_size = 256 # 我们设置batch大小为256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size) # 按照batch_size大小生成训练和测试数据集

1.2.1.2 初始化模型参数

Fasion-MNIST中的图片大小都是28*28的,所以我们将其展开成长度为784的一维向量作为输入,所以输入大小为784;由于我们需要将图片分为10类,所以输出大小为10。
由于输出大小不再是一个单元,所以我们的神经网络参数ww也不再是一个向量了,而是一个矩阵,其形状为[num_inputs, num_outputs],

1
2
3
4
5
num_inputs = 784
num_outputs = 10

W = torch.normal(0, 0.01, size=(num_inputs, num_outputs), requires_grad=True)
b = torch.zeros(num_outputs, requires_grad=True)

1.2.1.3 定义softmax

softmax函数有三个基本步骤:

  1. 对每个项求幂;
  2. 对每一行求和,得到每个样本的规范化常数;
  3. 将每一行除以其规范化常数,确保结果的和为1。
    softmax表达式为:

yi=softmax(xi)=exii=1Nexjy_i = softmax(x_i)=\frac{e^{x_i}}{\sum\limits_{i = 1}^{N}e^{x_j}}

用代码实现softmax函数:

1
2
3
4
def softmax(x):
x_exp = np.exp(x) # 第一步:求幂
partition = x_exp.sum(1, keepdim=True) # 第二步:对每一行求和,参数1代表按轴1求和,也就是按行求和(轴0代表按列求和)
return x_exp /partition # 第三步:规范化

1.2.1.4 定义模型

softmax模型实现非常简单,我们首先将输入的图片转化为我们所需的向量形状,然后输入到第一个线性层,在通过softmax层输出即可。

1
2
3
4
5
6
7
"""
X.reshape((-1, W.shape[0])):将输入的图片转化为一个二维向量
np.dot(X.reshape((-1, W.shape[0])), W) + b:将输入向量与参数w相乘后再加上偏置值b,即得到经过第一个线性层后的输出
softmax(...):经过softmax层后输出
"""
def net(X):
return softmax(np.dot(X.reshape((-1, W.shape[0])), W) + b)

1.2.1.5 定义损失函数

在softmax模型中我们使用交叉熵损失函数作为损失函数。
交叉熵损失函数的表达式为:

l(y,y^)=j=1Nyjlogy^jl(y,\hat y) = -\sum\limits_{j = 1}^{N}y_jlog\hat y_j

其中:yy为真实标签,y^\hat y为预测标签
代码实现如下:

1
2
def cross_entropy(y_hat, y):
return - np.log(y_hat[range(len(y_hat)),y])

1.2.1.6 分类精度

分类精度指的是正确预测数量与总预测数量之比。为了计算精度,我们执行以下操作:

  1. y^\hat y是矩阵,则取出其第二维度(预测标签);
  2. 第二维度代表输入x属于每一类别的概率,每一行的最大值即为我们的预测结果。所以我们使用argmax函数找到每行中最大元素的索引来获取预测类别;
  3. 之后我们将预测标签y^\hat y与真实标签yy进行比较,之后统计预测正确的数量
1
2
3
4
5
6
def accuracy(y_hat, y):  #@save
"""计算预测正确的数量"""
if len(y_hat.shape) > 1 and y_hat.shape[1] > 1: # 判断y_hat是不是一个矩阵
y_hat = y_hat.argmax(axis=1) # 取出y_hat的列中的最大值,代表的就是预测类别
cmp = y_hat.type(y.dtype) == y # 判断预测结果是否正确
return float(cmp.type(y.dtype).sum()) # 返回预测正确的总数

对于任意数据迭代器data_iter可访问的数据集, 我们可以评估在任意模型net的精度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def evaluate_accuracy(net, data_iter):  
"""
计算在指定数据集上模型的精度
isinstance(object,classtype):判断对象object是否是classtype类型
net.eval():将模型设置为评估模式,在评估模型下,网络会
1.停用dropout(不过本模型中没有使用dropout)
2.调整Batch Normalization的行为:在评估模式下,BatchNorm使用在训练过程中计算并存储的全局均值和方差,不再基于输入动态计算
使用net.eval()可以确保模型的表现和你在训练后测试模型时的表现一致,尤其是避免 Dropout 和 BatchNorm 的随机性。
with torch.no_grad():表明当前过程中不需要计算梯度,可以节省内存空间,加快模型评估的速度
"""
if isinstance(net, torch.nn.Module): # 判断模型是否为深度学习模型nn.Module
net.eval() # 将模型设置为评估模式
metric = Accumulator(2) # 正确预测数、预测总数
with torch.no_grad():
for X, y in data_iter:
metric.add(accuracy(net(X), y), y.numel())
return metric[0] / metric[1]

再定义一个实用程序类Accumulator,用于对多个变量进行累加,Accumulator实例中创建了2个变量, 分别用于存储正确预测的数量和预测的总数量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Accumulator: 
"""在n个变量上累加"""
# 初始化,创建n个空间并全部初始化为0.0
def __init__(self, n):
self.data = [0.0] * n

# 遍历self中的data和新传入的args并做a + float(b)操作,然后重新赋给该位置的data,从而实现累加
def add(self, *args):
self.data = [a + float(b) for a, b in zip(self.data, args)]

def reset(self):
self.data = [0.0] * len(self.data)

def __getitem__(self, idx):
return self.data[idx]

1.2.1.7 训练模型

softmax回归的训练与之前线性回归模型的训练类似:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def train_epoch_ch3(net, train_iter, loss, updater):  #@save
"""训练模型一个迭代周期(定义见第3章)"""
# 将模型设置为训练模式
if isinstance(net, torch.nn.Module):
net.train()
# 训练损失总和、训练准确度总和、样本数
metric = Accumulator(3)
for X, y in train_iter:
# 计算梯度并更新参数
y_hat = net(X) # 通过神经网络层得到预测结果y_hat
l = loss(y_hat, y) # 计算损失
if isinstance(updater, torch.optim.Optimizer):
# 使用PyTorch内置的优化器和损失函数
updater.zero_grad() # 初始化梯度为0
l.mean().backward() # 通过反向传播计算损失函数对模型参数的梯度
updater.step() # 更新模型参数
else:
# 使用定制的优化器和损失函数
l.sum().backward()
updater(X.shape[0])
metric.add(float(l.sum()), accuracy(y_hat, y), y.numel())
# 返回训练损失和训练精度
return metric[0] / metric[2], metric[1] / metric[2]

定义一个在动画中绘制数据的实用程序类Animator:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class Animator: 
"""在动画中绘制数据"""
def __init__(self, xlabel=None, ylabel=None, legend=None, xlim=None,
ylim=None, xscale='linear', yscale='linear',
fmts=('-', 'm--', 'g-.', 'r:'), nrows=1, ncols=1,
figsize=(3.5, 2.5)):
# 增量地绘制多条线
if legend is None:
legend = []
d2l.use_svg_display()
self.fig, self.axes = d2l.plt.subplots(nrows, ncols, figsize=figsize)
if nrows * ncols == 1:
self.axes = [self.axes, ]
# 使用lambda函数捕获参数
self.config_axes = lambda: d2l.set_axes(
self.axes[0], xlabel, ylabel, xlim, ylim, xscale, yscale, legend)
self.X, self.Y, self.fmts = None, None, fmts

def add(self, x, y):
# 向图表中添加多个数据点
if not hasattr(y, "__len__"):
y = [y]
n = len(y)
if not hasattr(x, "__len__"):
x = [x] * n
if not self.X:
self.X = [[] for _ in range(n)]
if not self.Y:
self.Y = [[] for _ in range(n)]
for i, (a, b) in enumerate(zip(x, y)):
if a is not None and b is not None:
self.X[i].append(a)
self.Y[i].append(b)
self.axes[0].cla()
for x, y, fmt in zip(self.X, self.Y, self.fmts):
self.axes[0].plot(x, y, fmt)
self.config_axes()
display.display(self.fig)
display.clear_output(wait=True)

训练模型:

1
2
3
4
5
6
7
8
9
10
11
12
def train_ch3(net, train_iter, test_iter, loss, num_epochs, updater):  
"""训练模型(定义见第3章)"""
animator = Animator(xlabel='epoch', xlim=[1, num_epochs], ylim=[0.3, 0.9],
legend=['train loss', 'train acc', 'test acc'])
for epoch in range(num_epochs):
train_metrics = train_epoch_ch3(net, train_iter, loss, updater)
test_acc = evaluate_accuracy(net, test_iter)
animator.add(epoch + 1, train_metrics + (test_acc,))
train_loss, train_acc = train_metrics
assert train_loss < 0.5, train_loss
assert train_acc <= 1 and train_acc > 0.7, train_acc
assert test_acc <= 1 and test_acc > 0.7, test_acc

设置训练参数:

1
2
3
4
5
6
7
8
lr = 0.1  # 学习率设置为0.1

def updater(batch_size): # 自定义优化器
return d2l.sgd([W, b], lr, batch_size)

num_epochs = 10 # 模型迭代周期设置为10
train_ch3(net, train_iter, test_iter, cross_entropy, num_epochs, updater)
d2l.plt.show() # 绘图

训练结果:
softmax训练结果

1.2.1.8 预测

给定一系列图像,我们比较它们的实际标签(文本输出的第一行)和模型预测(文本输出的第二行):

1
2
3
4
5
6
7
8
9
10
11
12
def predict_ch3(net, test_iter, n=6):
"""预测标签(定义见第3章)"""
for X, y in test_iter:
break
trues = d2l.get_fashion_mnist_labels(y)
preds = d2l.get_fashion_mnist_labels(net(X).argmax(axis=1))
titles = [true +'\n' + pred for true, pred in zip(trues, preds)]
d2l.show_images(
X[0:n].reshape((n, 28, 28)), 1, n, titles=titles[0:n])

predict_ch3(net, test_iter)
d2l.plt.show()

预测结果:
softmax预测结果

1.2.2 softmax回归的简洁实现

1.2.2.1 导包

1
2
3
4
5
6
import torch
from torch import nn
from d2l import torch as d2l

batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)

1.2.2.2 初始化模型参数

1
2
3
4
5
6
7
8
9
10
# PyTorch不会隐式地调整输入的形状。因此,
# 我们在线性层前定义了展平层(flatten),来调整网络输入的形状
# flatten():将输入向量转变为一维
net = nn.Sequential(nn.Flatten(), nn.Linear(784, 10))

def init_weights(m):
if type(m) == nn.Linear:
nn.init.normal_(m.weight, std=0.01) # 使用正态分布初始化参数w

net.apply(init_weights) # 将初始化参数的方法应用到net的每一层上

1.2.2.3 设置损失函数

直接使用内置的交叉熵损失函数:

1
2
3
4
5
6
7
"""
参数reduction用于指定应用于输出的归约方式,可选值:'none','mean','sum'
'none':不进行归约
'mean':对所有样本的损失求平均值
'sum':对所有样本的损失求和
"""
loss = nn.CrossEntropyLoss(reduction='none')

1.2.2.4 设置优化算法

优化算法使用SGD(随机梯度下降)算法,学习率设置为0.1:

1
trainer = torch.optim.SGD(net.parameters(), lr=0.1)

1.2.2.5 训练

1
2
3
num_epochs = 10
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)
d2l.plt.show()