1.线性回归的简洁实现
实践中,我们通常可以用比上分段更简洁的代码来实现同样的模型。在本节中,我们将介绍如何使用PyTorch更方便地实现线性回归的训练。
1.1生成数据集
我们生成与上一级中相同的数据集。其中features
是训练数据特征,labels
是标签。
num_inputs = 2
num_examples = 1000
true_w = [2, -3.4]
true_b = 4.2
features = torch.tensor(np.random.normal(0, 1, (num_examples, num_inputs)), dtype=torch.float)
labels = true_w[0] * features[:, 0] + true_w[1] * features[:, 1] + true_b
labels += torch.tensor(np.random.normal(0, 0.01, size=labels.size()), dtype=torch.float)
1.2读取数据
PyTorch提供了data
包来读取数据。由于data
经常使用变量名,我们将导入的data
模块用代替Data
。在每一次重复中,我们将随机读取包含10个数据样本的小批量。
import torch.utils.data as Data
batch_size = 10
# 将训练数据的特征和标签组合
dataset = Data.TensorDataset(features, labels)
# 随机读取小批量
data_iter = Data.DataLoader(dataset, batch_size, shuffle=True)
让我们读取并打印第一个小批量数据样本。
for X, y in data_iter:
print(X, y)
break
输出:
tensor([[-2.7723, -0.6627],
[-1.1058, 0.7688],
[ 0.4901, -1.2260],
[-0.7227, -0.2664],
[-0.3390, 0.1162],
[ 1.6705, -2.7930],
[ 0.2576, -0.2928],
[ 2.0475, -2.7440],
[ 1.0685, 1.1920],
[ 1.0996, 0.5106]])
tensor([ 0.9066, -0.6247, 9.3383, 3.6537, 3.1283, 17.0213, 5.6953, 17.6279,
2.2809, 4.6661])
1.3定义模型
首先,引入torch.nn
模块。实际上,“ nn”是神经网络(神经网络)的缩写。顾名思义,该模块定义了串联神经网络的层。nn
就是利用autograd
来定义模型。nn
的核心数据结构是Module
,它是一个抽象概念,既可以表示神经网络中的某个层(layer),也可以表示一个包含很多层的神经网络。在实际使用中,最常见的做法是继承nn.Module
,编写自己的网络/层。一个nn.Module
实例应该包含一些层以及返回输出的前向传播(forward)方法。下面先来看看如何用nn.Module
实现一个线性回归模型。
class LinearNet(nn.Module):
def __init__(self, n_feature):
super(LinearNet, self).__init__()
self.linear = nn.Linear(n_feature, 1)
# forward 定义前向传播
def forward(self, x):
y = self.linear(x)
return y
net = LinearNet(num_inputs)
print(net) # 使用print可以打印出网络的结构
输出:
LinearNet(
(linear): Linear(in_features=2, out_features=1, bias=True)
)
我们也可以用nn.Sequential
来更加网求方便地搭建网络,Sequential
是一个有序的容器,层网络将按照在传入Sequential
的顺序依次被添加到计算图产品中。
# 写法一
net = nn.Sequential(
nn.Linear(num_inputs, 1)
# 此处还可以传入其他层
)
# 写法二
net = nn.Sequential()
net.add_module('linear', nn.Linear(num_inputs, 1))
# net.add_module ......
# 写法三
from collections import OrderedDict
net = nn.Sequential(OrderedDict([
('linear', nn.Linear(num_inputs, 1))
# ......
]))
print(net)
print(net[0])
输出:
Sequential(
(linear): Linear(in_features=2, out_features=1, bias=True)
)
Linear(in_features=2, out_features=1, bias=True)
可以通过net.parameters()
来查看模型所有的可学习参数,此函数将返回一个生成器。
for param in net.parameters():
print(param)
输出:
Parameter containing:
tensor([[-0.0277, 0.2771]], requires_grad=True)
Parameter containing:
tensor([0.3395], requires_grad=True)
回顾图1.1中线性回归在神经网络图中的表示。作为一个单层神经网络,线性回归输出层中的神经元和输入层中各个输入完全连接。因此,线性回归的输出层又叫全连接层。
注意:仅
torch.nn
支持输入一个批处理的样本不支持样本样本输入,如果只有单个样本,可使用input.unsqueeze(0)
来添加一维。
1..4初始化模型参数
在使用net
前,我们需要初始化模型参数,如线性回归模型中的权重和偏差。PyTorch在init
模块中提供了多种参数初始化方法。这里的init
是initializer
的缩写形式。我们init.normal_
将权重参数每个元素初始化为随机采样于均值0,标准差为0.01的正态分布。偏差会初始化为零。
from torch.nn import init
init.normal_(net[0].weight, mean=0, std=0.01)
init.constant_(net[0].bias, val=0) # 也可以直接修改bias的data: net[0].bias.data.fill_(0)
注:如果这里的
net
是用3.3.3节一开始的代码自定义的,那么上面代码会报错,net[0].weight
应替换net.linear.weight
,bias
亦然。因为net[0]
这样根据下标访问子模块的写法只有当net
是一个ModuleList
或者Sequential
实例时才可以,详见4.1节。
1.5定义损失函数
PyTorch在nn
模块中提供了各种损失函数,这些损失函数可称为是一种特殊的层,PyTorch也将这些损失函数实现为nn.Module
的子类。函数。
loss = nn.MSELoss()
1.6 定义优化算法
同样,我们也无须自己实现小批量随机梯度下降算法。torch.optim
模块提供了很多常用的优化算法比如SGD,亚当和RMSProp等。我们下面创建³³一个用于优化net
所有参数的优化器实例,并指定学习率为0.03的小批量随机梯度下降(SGD)为优化算法。
import torch.optim as optim
optimizer = optim.SGD(net.parameters(), lr=0.03)
print(optimizer)
输出:
SGD (
Parameter Group 0
dampening: 0
lr: 0.03
momentum: 0
nesterov: False
weight_decay: 0
)
我们还可以为不同子网络设置不同的学习率,这在finetune时经常用到。
optimizer =optim.SGD([
# 如果对某个参数不指定学习率,就使用最外层的默认学习率
{'params': net.subnet1.parameters()}, # lr=0.03
{'params': net.subnet2.parameters(), 'lr': 0.01}
], lr=0.03)
有时候我们不想让学习率固定成一个常数,那如何调整学习率呢?主要有两种做法。一种是修改optimizer.param_groups
中对应的学习率,另一种是更简单也是推荐的做法-新建优化器,由于optimizer极其轻量级,体积很小,故而可以构建新的optimizer。震荡等情况。
# 调整学习率
for param_group in optimizer.param_groups:
param_group['lr'] *= 0.1 # 学习率为之前的0.1倍
1.7训练模型
在使用Gluon训练模型时,我们通过调用optim
实例的step
函数来转换模型参数。按照小批量随机递减的定义,我们在step
函数中指定尺寸大小,从而对批量中样本梯度求平均。
num_epochs = 3
for epoch in range(1, num_epochs + 1):
for X, y in data_iter:
output = net(X)
l = loss(output, y.view(-1, 1))
optimizer.zero_grad() # 梯度清零,等价于net.zero_grad()
l.backward()
optimizer.step()
print('epoch %d, loss: %f' % (epoch, l.item()))
输出:
epoch 1, loss: 0.000457
epoch 2, loss: 0.000081
epoch 3, loss: 0.000198
下面我们分别比较比较到到模型参数和真实的模型参数。我们从net
获得需要的层,并访问其权重(weight
)和偏差(bias
)。学到的参数和真实的参数很接近。
dense = net[0]
print(true_w, dense.weight)
print(true_b, dense.bias)
输出:
[2, -3.4] tensor([[ 1.9999, -3.4005]])
4.2 tensor([4.2011])
小结
- 使用PyTorch可以更简洁地实现模型。
-
torch.utils.data
模块提供了有关数据处理的工具,torch.nn
模块定义了神经网络的层,torch.nn.init
模块定义了各种初始化方法,torch.optim
模块提供了很多常用的优化算法。
2 softmax回归的简洁实现
(线性回归的简洁实现)中已经了解了使用Pytorch实现模型的便利。下面,让我们再次使用Pytorch来实现一个softmax回归模型。首先引入所需的包或模块。
import torch
from torch import nn
from torch.nn import init
import numpy as np
import sys
sys.path.append("..")
import d2lzh_pytorch as d2l
2.1获取和读取数据
我们仍然使用Fashion-MNIST数据集和上段中设置的批量大小。
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
2.2定义和初始化模型
softmax回归的输出层是一个全连接层,所以我们用一个线性模块就可以了。因为前面我们数据返回的每个批量样本x
的形状为(batch_size,1, 28,28),所以我们要先用view()
将x
的形状转换成(batch_size,784)才送入全连接层。
num_inputs = 784
num_outputs = 10
class LinearNet(nn.Module):
def __init__(self, num_inputs, num_outputs):
super(LinearNet, self).__init__()
self.linear = nn.Linear(num_inputs, num_outputs)
def forward(self, x): # x shape: (batch, 1, 28, 28)
y = self.linear(x.view(x.shape[0], -1))
return y
net = LinearNet(num_inputs, num_outputs)
我们将对x
的形状转换的这个功能自定义一个FlattenLayer
并记录在d2lzh_pytorch
中方便后面使用。
# 本函数已保存在d2lzh_pytorch包中方便以后使用
class FlattenLayer(nn.Module):
def __init__(self):
super(FlattenLayer, self).__init__()
def forward(self, x): # x shape: (batch, *, *, ...)
return x.view(x.shape[0], -1)
这样我们就可以更方便地定义我们的模型:
from collections import OrderedDict
net = nn.Sequential(
# FlattenLayer(),
# nn.Linear(num_inputs, num_outputs)
OrderedDict([
('flatten', FlattenLayer()),
('linear', nn.Linear(num_inputs, num_outputs))
])
)
然后,我们使用均值0,标准差为0.01的正态分布随机初始化模型的权重参数。
init.normal_(net.linear.weight, mean=0, std=0.01)
init.constant_(net.linear.bias, val=0)
2.3 softmax和交叉熵损失函数
因此,PyTorch提供了一个包括softmax计算和交叉熵计算的函数。它的数值稳定性更好。
loss = nn.CrossEntropyLoss()
2.4定义优化算法
我们使用学习逐步0.1的小批量随机梯度下降作为优化算法。
optimizer = torch.optim.SGD(net.parameters(), lr=0.1)
2.5训练模型
接下来,我们使用上一级中定义的训练函数来训练模型。
num_epochs = 5
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, batch_size, None, None, optimizer)
输出:
epoch 1, loss 0.0031, train acc 0.745, test acc 0.790
epoch 2, loss 0.0022, train acc 0.812, test acc 0.807
epoch 3, loss 0.0021, train acc 0.825, test acc 0.806
epoch 4, loss 0.0020, train acc 0.832, test acc 0.810
epoch 5, loss 0.0019, train acc 0.838, test acc 0.823
小结
- PyTorch提供的函数往往具有更好的数值稳定性。
- 可以使用PyTorch更简洁地实现softmax回归。
3 多层感知机的简洁实现
下面我们使用PyTorch来实现上分段中的多层感知机。首先引入所需的包或模块。
import torch
from torch import nn
from torch.nn import init
import numpy as np
import sys
sys.path.append("..")
import d2lzh_pytorch as d2l
3.1定义模型
和softmax回归唯一的不同在于,我们多加了一个全连接层作为隐藏层。它的隐藏单元个数为256,并使用ReLU函数作为激活函数。
num_inputs, num_outputs, num_hiddens = 784, 10, 256
net = nn.Sequential(
d2l.FlattenLayer(),
nn.Linear(num_inputs, num_hiddens),
nn.ReLU(),
nn.Linear(num_hiddens, num_outputs),
)
for params in net.parameters():
init.normal_(params, mean=0, std=0.01)
3.2读取数据并训练模型
我们使用与3.7节中训练softmax回归几乎相同的步骤来读取数据并训练模型。
注:由于这里使用的是PyTorch的SGD而不是d2lzh_pytorch里面的sgd,所以就不存在3.9节那样学习率看起来很大的问题了。
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
loss = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(net.parameters(), lr=0.5)
num_epochs = 5
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, batch_size, None, None, optimizer)
输出:
epoch 1, loss 0.0030, train acc 0.712, test acc 0.744
epoch 2, loss 0.0019, train acc 0.823, test acc 0.821
epoch 3, loss 0.0017, train acc 0.844, test acc 0.842
epoch 4, loss 0.0015, train acc 0.856, test acc 0.842
epoch 5, loss 0.0014, train acc 0.864, test acc 0.818
小结
- 通过PyTorch可以更简洁地实现多层感知机。