本章内容较多,但是作为pytorch的基础却又是重中之重,需要巩固学习
线性回归输出主要可以用于解决回归问题,比如预测房屋价格、气温、销售额等连续值问题;
与回归问题不同的分类问题,分类问题的模型最终输出是一个离散值,比如图像分类,垃圾分类、疾病监测,softmax回归用于解决分类问题
线性回归和softmax回归都是单层神经网络,我们首先学习线性回归
我们以一个简单的房屋价格预测做一个例子来解释线性回归的基本要素,假设房屋价格只取决于两个因素(实际肯定是许多因素,之后可以推广),即面积和房龄,我们希望探讨价格和这两个因素的关系
此部分在书中有具体解释,限于篇幅原因,此处不再列出
模型训练主要涉及三个要素:
(1)训练数据(training dataset)
(2)损失函数 (loss function)
(3)优化算法
模型训练完成后,将最终停止的模型参数作为学习出的线性回归模型,可以用来估算训练集以外的任意一栋房屋
线性回归是一个单层的神经网络,并且输出的房价与输入的面积、房龄完全连接,故输出层又叫做全连接层
在模型训练或者预测时,常会处理多个数据样本并用到矢量计算
回顾矢量加法
a = torch.ones(1000)
b= torch.ones(1000)
d =a + b
本节只利用Tensor和autograd来实现线性回归训练
首先,导入所需的包
%matplotlib incline # 把matplotlib做的图设计为嵌入显示,如果报错就删掉这一行,不影响
import torch
from IPython import display
from matplotlib import pyplot as plt
import numpy as np
import random
我们构造一个简单的训练集,设样本数为1000,输入数(特征)为2,给定随机生成的批量样本特征X(X是1000*2的矩阵),使用真实权重w=[2,-3.4]的转置,偏差b = 4.2, 以及一个随机噪声n 来生成标签
def synthetic_data(w, b, num_examples): #@save
"""生成 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)
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)
print(features[0], labels[0])
output:
features: tensor([-0.8680, 0.1655])
label: tensor([1.8967])
通过生成第二个特征fetures[:, 1]和标签labels的散点图,可以更加直接观察两者的线性关系:
def use_svg_display():
#用矢量图表示
display.set_matplotlib_formats('svg')
def set_figsize(figsize=(3.5, 2.5)):
use_svg_display()
#设置图的尺寸
plt.rcParams['figure.figsize'] = figsize
set_figsize()
plt.scatter(features[:, 1].numpy(), labels.numpy(), 1);
plt.show()
得到散点图:
在训练数据模型时,我们需要遍历数据集并不断读取小批量数据样本,这列我们定义一个函数,每次返回batch_size(批量大小)个随机样本的特征和标签
'''返回样本特征和标签的函数'''
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_indices = torch.tensor(indices[i:min(i +
batch_size, num_examples)])
yield features[batch_indices], labels[batch_indices] # yield的用法自行百度即可,主要是return但是可以记录迭代位置和次数
我们现在来读取一个小批量的数据样本并打印,每个批量的特征形状为(10,2),分别对应批量大小和输入个数;标签形状为批量大小
batch_size = 10
for X, y in data_iter(batch_size, features, labels):
print(X, y)
break
output:
tensor([[ 0.2344, 1.1191],
[-1.0720, -0.3506],
[-1.2767, -0.8060],
[-0.4801, -0.7245],
[-2.2940, -0.4040],
[ 1.5641, 0.5698],
[ 1.0803, 0.9746],
[ 0.1612, 0.5084],
[ 1.3447, 0.0783],
[ 1.7455, 0.5449]])
tensor([[0.8852],
[3.2532],
[4.4060],
[5.6942],
[0.9917],
[5.3822],
[3.0675],
[2.8091],
[6.6250],
[5.8541]])
我们将权重初始化成均值为0,标准差为0.01的正态随机数,偏差初始化为0
w = torch.normal(0, 0.01, size=(2, 1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)
之后的训练中,需要对这些参数求梯度来迭代参数的值,因此我们需要让它们的requires_grad=True
下面是线性回归的矢量计算表达式的实现,我们可以使用mm函数做矩阵乘法
def linreg(X, w, b): #@save
"""线性回归模型。"""
return torch.matmul(X, w) + b
我们利用平方损失来定义线性回归的损失函数;在实现中,我们要把真实值y变形为预测值y_hat的形状,以下函数返回的结果也将和y_hat形状相同
def squared_loss(y_hat, y): #@save
"""均方损失。"""
return (y_hat - y.reshape(y_hat.shape))**2 / 2
下述的sgd函数实现了上一节介绍的小批量随机梯度下降算法,它通过不断迭代模型参数来优化损失函数。 这里自动求梯度模块计算得到的梯度是一个批量样本的梯度和,我们用其除以批量大小得到平均值
def sgd(params, lr, batch_size):
for param in params:
param.data -= lr * param.grad / batch_size
param.grad.zero_()# 此处更改data而不会被梯度记录,在第一章中讲过
在训练中,我们多次迭代模型参数,每次迭代中,我们根据当前读取的小批量数据样本(特征x和标签y),通过调用backward计算小批量随机梯度,并调用sgd迭代模型参数。
由于我们之前设置batch_size为10,每个小批量的损失 l 形状为(10,1)。第一章讲过,由于 l 不是一个标量,我们可以调用.sum()将其求和得到一个标量,再运用 l.backward() 得到该变量有关模型参数梯度,注意,每次更新参数后都要把参数梯度清0
在一个迭代周期(epoch)中,我们将完整遍历一遍data_iter函数,并对训练数据集中所有样本使用一次(假设样本数能够被批量大小整除)。这里的迭代周期个数num_epoch和学习率lr都是超参数,分别设为3,0.03.
在实践中,大多数超参数都需要反复试错来不断调节,此方面在后面优化算法会介绍
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`]的梯度
l.sum().backward()
sgd([w, b], lr, batch_size) # 使用参数的梯度更新参数
with torch.no_grad():
train_l = loss(net(features, w, b), labels)
print(f'epoch {epoch + 1}, loss {float(train_l.mean()):f}')
output:
epoch 1, loss 0.040608
epoch 2, loss 0.000160
epoch 3, loss 0.000052
训练之后,我们可以比较学到的参数和用来生成数据集的真实参数,他们应该很接近
print(true_w, '\n', w)
print(true_b, 'n', b)
output:
tensor([ 2.0000, -3.4000])
tensor([[ 2.0000],
[-3.3995]], requires_grad=True)
4.2 n tensor([4.1997], requires_grad=True)
在这一过程中只使用张量和自动微分,不需要定义层或复杂的优化器。在下面的部分中,我们将基于刚刚介绍的概念描述其他模型,并学习如何更简洁地实现其他模型。
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)
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
output:
tensor([[-0.3357, -0.1912],
[-0.2486, -0.7741],
[ 0.4515, 0.3934],
[ 1.3970, -2.0370],
[-0.5546, -1.2764],
[-0.2723, -0.9360],
[ 0.6152, -0.5039],
[ 1.4477, 0.9355],
[-0.1157, 0.1815],
[-1.9694, 0.8973]]) tensor([ 4.1019, 6.0041, 3.9207, 13.1023, 6.9265, 6.4583, 6.9403, 4.2916,
3.4186, -2.4157])
我们明确定义了模型参数变量,并编写了计算的代码,这样通过基本的线性代数运算得到输出。但是,如果模型变得更加复杂,而且当你几乎每天都需要实现模型时,你会想简化这个过程。
Pytoch提供了大量预定义的层,使得我们只需要关注使用那些层。
首先,导入torch.nn模块,‘’nn’是neural networks的缩写,顾名思义,该层定义了大量的神经网络层。nn使用autograd来定义模型;nn的数据结构是Module,它既可以表示神经网络的某个Layer,又可以表示一个包含很多层的神经网络,实际使用中,最常用的是继承nn.Module,撰写自己的Layer.
一个nn.Module实例应该包含一些层以及返回输出的前向传播(forward)方法,下面是用nn.Module来实现一个线性回归模型
import torch.nn as nn
class LinearNet(nn.Module):
def __init__(self, n_feature):
super(LinearNet, self).__init__()
self.linear = nn.Linear(n_feature, 1)
#前向传播
def forward(self, x):
y = self.linear(x)
return y
net = LinearNet(num_inputs)
print(net)
output:
LinearNet(
(linear): Linear(in_features=2, out_features=1, bias=True)
)
事实上,还有更简洁的定义神经层的方法:nn.Sequential, Sequential是一个有序的容器,网络层将按照在传入Sequential的顺序被添加入计算图
具体写法可以自行百度,这里只列出一种:
net = nn.Sequential(
nn.Linear(num_inputs, 1)
#此处还能传入其他层
)
print(net, '\n', net[0])
output:
Sequential(
(0): 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)
output:
Parameter containing:
tensor([[0.0593, 0.3804]], requires_grad=True)
Parameter containing:
tensor([-0.0103], requires_grad=True)
ps: torch.nn仅支持输入一个batch的样本,不支持单个样本输入,如果只有单个样本,可以使用input.unsqueeze(0)来增加一维
在使用net之前,我们要初始化模型参数,Pytorch在init模块中提供了多种参数初始化的方法,这里我们用init.normal_将权重参数每个元素初始化为随机采样于均值为0、标准差为0.01的正态分布;偏差会初始化为0
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)
Pytoch在nn模块提供了各种损失函数,这些函数可以看作一个特殊的层,Pytorch将这些损失函数实现为nn.Moudle的子类,我们这里利用它的MSELoss(均方误差损失)作为模型损失函数
loss = nn.MSELoss()
我们无需自己实现小批量随机梯度下降算法,torch.optim模块提供了许多常用优化算法,SGD,Adam等,这里我们会创建一个用于优化net所有参数的优化器实例
import torch.optim as optim
optimizer = optim.SGD(net.parameters(), lr=0.03)
print(optimizer)
output:
SGD (
Parameter Group 0
dampening: 0
lr: 0.03
momentum: 0
nesterov: False
weight_decay: 0
)
我们还可以为不同子网络设置不同学习率,比如
optimizer = optim.SGD([
# 如果不对某个参数指定学习率,就默认使用最外层的学习率
{'params': net.subnet1.parameters()}, #此处就是用外部学习率0.03
{'params': net.subnet2.parameters(), 'lr' : 0.01}
], lr = 0.03)
有时候我们不想让学习率固定,如何调整学习率呢?
一种方法是修改optimizer.param_groups中对应的学习率
另一种是推荐且简洁的方法,新建优化器,因为optimizer是轻量级的,构建开销很小,但是后者也有缺点,比如对于使用动量的优化器(如Adam),会丢失动量等状态信息,可能会造成损失函数的收敛出现震荡等情况
#调整学习率
for param_group in optimizer.param_groups:
param_group['lr'] *= 0.1 #学习率为前一次的0.1倍
我们调用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()))
output:
epoch 1, loss 0.000305
epoch 2, loss 0.000092
epoch 3, loss 0.000092
torch.utils.data模块提供了有关数据处理的工具, torch.nn模块定义了大量的神经网络层, torch.nn.init模块定义了有关初始化的方法, torch.optim模块提供了模型参数初始化的各种方法