目前市面上已经存在非常成熟的深度学习框架。
这些框架可以自动化基于梯度的学习算法中重复性的工作。 在从零实现线性回归的小节中,我们只运用了: (1)通过张量来进行数据存储和线性代数; (2)通过自动微分来计算梯度。 实际上,由于数据迭代器、损失函数、优化器和神经网络层很常用, 现代深度学习库也为我们实现了这些组件。
在本节中,我们将介绍如何通过使用深度学习框架来简洁地实现线性回归模型。
我们首先生成数据集。
import numpy as np #引入numpy的工具包
import torch #引入torch
from torch.utils import data #从torch的utils工具包中引入data
true_w = torch.tensor([2, -3.4]) #线性模型真实的参数权重向量w,内含两个标量
true_b = 4.2 #偏置量b = 4.2
def synthetic_data(w, b, num_examples):
X = torch.normal(0, 1, (num_examples,len(w))) #生成均差为0, 标准差为1的数据集,形状为(num_examples,len(w))
y = torch.matmul(X,w) + b #生成所有的y值,即为矩阵X与向量w的乘积
y += torch.normal(0, 0.01, y.shape) #所有的y值再加上一些正态分布的数据,其中均差为0,标准差为0.01
return X, y.reshape(-1,1) #返回X,y值
features, labels = synthetic_data(true_w, true_b, 1000) #生成数据集和其对照的标签
我们可以调用框架中现有的API来读取数据。 我们将features和labels作为API的参数传递,并通过数据迭代器指定batch_size。
此外,布尔值is_train表示是否希望数据迭代器对象在每个迭代周期内打乱数据。
def load_array(data_arrays, batch_size, is_train=True):
#构造一个pytorch数据迭代器
dataset = data.TensorDataset(*data_arrays)
return data.DataLoader(dataset, batch_size, shuffle=True) #返回一个数据加载器对象
batch_size = 10
data_iter = load_array((features, labels), batch_size)
使用data_iter的方式与之前使用data_iter函数的方式相同。
为了验证是否正常工作,让我们读取并打印第一个小批量样本。 与上次不同,这里我们使用iter构造Python迭代器,并使用next从迭代器中获取第一项。
next(iter(data_iter))
[tensor([[-2.2629, 0.2673],
[-0.8266, -0.9312],
[-0.5482, 0.6255],
[-0.0944, 0.9127],
[ 0.0969, -0.0164],
[ 0.5676, -0.5284],
[-0.2676, 1.6671],
[-0.6874, 1.5139],
[ 0.4625, -1.1766],
[-0.9312, -2.7785]]),
tensor([[-1.2375],
[ 5.7146],
[ 0.9779],
[ 0.8872],
[ 4.4527],
[ 7.1393],
[-1.9979],
[-2.3251],
[ 9.1272],
[11.7744]])]
当我们上次实现线性回归时, 我们明确定义了模型参数变量,并编写了计算的代码,这样通过基本的线性代数运算得到输出。但是,如果模型变得更加复杂,且当你几乎每天都需要实现模型时,你会想简化这个过程。 这种情况类似于为自己的博客从零开始编写网页。 做一两次是有益的,但如果每个新博客你就花一个月的时间重新开始编写网页,那并不高效。
对于标准深度学习模型,我们可以使用框架的预定义好的层。这使我们只需关注使用哪些层来构造模型,而不必关注层的实现细节。 我们首先定义一个模型变量net,它是一个Sequential类的实例。 Sequential类将多个层串联在一起。 当给定输入数据时,Sequential实例将数据传入到第一层, 然后将第一层的输出作为第二层的输入,以此类推。 在下面的例子中,我们的模型只包含一个层,因此实际上不需要Sequential。 但是由于以后几乎所有的模型都是多层的,在这里使用Sequential会让你熟悉“标准的流水线”。
from torch import nn #导入神经网络模型的包
net = nn.Sequential(nn.Linear(2,1)) #使用流水线的方式定义线性神经网络
在使用net之前,我们需要初始化模型参数。 如在线性回归模型中的权重和偏置。 深度学习框架通常有预定义的方法来初始化参数。 在这里,我们指定每个权重参数应该从均值为0、标准差为0.01的正态分布中随机采样, 偏置参数将初始化为零。
net[0].weight.data.normal_(0, 0.01) #定义符合正态分布的权重w
net[0].bias.data.fill_(0) #定义偏置量为0
tensor([0.])
计算均方误差使用的是MSELoss类,也称为平方范数 L 2 L_2 L2。 默认情况下,它返回所有样本损失的平均值。
loss = nn.MSELoss() #定义损失函数,计算均方误差
小批量随机梯度下降算法是一种优化神经网络的标准工具, PyTorch在optim模块中实现了该算法的许多变种。 当我们实例化一个SGD实例时,我们要指定优化的参数 (可通过net.parameters()从我们的模型中获得)以及优化算法所需的超参数字典。 小批量随机梯度下降只需要设置lr值,这里设置为0.01。
trainer = torch.optim.SGD(net.parameters(), lr=0.01) #定义优化算法,利用梯度下降法不断减少均方误差,提升模型准确度
通过深度学习框架的高级API来实现我们的模型只需要相对较少的代码。 我们不必单独分配参数、不必定义我们的损失函数,也不必手动实现小批量随机梯度下降。 当我们需要更复杂的模型时,高级API的优势将大大增加。 当我们有了所有的基本组件,训练过程代码与我们从零开始实现时所做的非常相似。
回顾一下:在每个迭代周期里,我们将完整遍历一次数据集(train_data), 不停地从中获取一个小批量的输入和相应的标签。 对于每一个小批量,我们会进行以下步骤:
为了更好的衡量训练效果,我们计算每个迭代周期后的损失,并打印它来监控训练过程。
num_epochs = 3 #迭代次数设置为3
for epoch in range(num_epochs):
for X, y in data_iter: #访问所有的数据集
l = loss(net(X), y) #前向传播,计算损失值l
trainer.zero_grad() #将训练器的梯度清0
l.backward() #后向传播,计算w和b的梯度
trainer.step() #对模型进行一次更新操作
l = loss(net(features), labels) #计算迭代一次后的损失值
print(f'epoch{epoch+1}, loss{l:.5f}', net[0].weight.data, net[0].bias.data) #输出不断迭代时的损失下降程度
epoch1, loss0.74523 tensor([[ 1.6743, -2.8425]]) tensor([3.5694])
epoch2, loss0.01856 tensor([[ 1.9480, -3.3096]]) tensor([4.1038])
epoch3, loss0.00056 tensor([[ 1.9917, -3.3855]]) tensor([4.1852])
下面我们比较生成数据集的真实参数和通过有限数据训练获得的模型参数。
要访问参数,我们首先从net访问所需的层,然后读取该层的权重和偏置。 正如在从零开始实现中一样,我们估计得到的参数与生成数据的真实参数非常接近。
w = net[0].weight.data #预测得出的权重w
b = net[0].bias.data #预测得出的偏置量b
print('w的估计误差:', true_w - w.reshape(true_w.shape))
print('b的估计误差:', true_b - b) #分别输出预估w和b的误差
w的估计误差: tensor([ 0.0083, -0.0145])
b的估计误差: tensor([0.0148])
我们可以使用PyTorch的高级API更简洁地实现模型。
在PyTorch中,data模块提供了数据处理工具,nn模块定义了大量的神经网络层和常见损失函数。
我们可以通过_结尾的方法将参数替换,从而初始化参数。