文章目录
0.速记
torch.utils.data
模块提供了有关数据处理的工具torch.nn
模块定义了大量神经网络的层torch.nn.init
模块定义了各种初始化方法torch.optim
模块提供了很多常用的优化算法。
1.引入包
import torch
from IPython import display
from matplotlib import pyplot as plt
import numpy as np
import random #random模块用于生成随机数
%matplotlib inline
%matplotlib inline
:在使用jupyter notebook 或者 jupyter qtconsole的时候,才会经常用到%matplotlib。%matplotlib具体作用是当你调用matplotlib.pyplot的绘图函数plot()进行绘图的时候,或者生成一个figure画布的时候,可以直接在你的python console里面生成图像。
2.生成数据集
y = X w + b + ϵ y=Xw+b+ϵ y=Xw+b+ϵ
num_inputs = 2 #有两个特征
num_examples = 1000 #有1000个样本
true_w = [2, -3.4] #w的真实值
true_b = 4.2 #b的真实值
#随机生成1000个有2个特征的样本(tensor)
features = torch.randn(num_examples, num_inputs,type=torch.float32)
#根据方程生成样本的标签
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.float32)
torch.randn()
函数的第一个参数是*size,也就是说这个参数可以是一串整数,也可以是一个list或tuple,代表要创建tensor的size。在上面代码中,size就是1000x2。此函数返回的tensor中的数据符合标准正态分布,即均值为0,方差为1。np.random.normal()
有三个参数:loc,scale,size。loc代表mean,scale代表标准差,size是一个整数或一个tuple。返回值从从一个正态分布上采样的数据。
3.画出一个变量和y的图,观察它们的关系
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
# # 在../d2lzh_pytorch(包)里面添加上面两个函数后就可以这样导入
# import sys
# sys.path.append("..")
# from d2lzh_pytorch import *
set_figsize()
plt.scatter(features[:, 1].numpy(), labels.numpy(), 1);
display.set_matplotlib_formats()
:plt.show() 默认都是输出.png文件,图片只要稍微放大一点,就糊的不行。IPython 是 Python 的一个增强版本。它在下列方面有所增强:命名输入输出、使用系统命令(shell commands)、排错(debug)能力。用IPython模块的display类的set_matplotlib_formats函数,设置显示的图像类型为svg。plt.rcParams[ ]
:pylot使用rc配置文件来自定义图形的各种默认属性,称之为rc配置或rc参数。通过rc参数可以修改默认的属性,包括窗体大小、每英寸的点数、线条宽度、颜色、样式、坐标轴、坐标和网络属性、文本、字体等。rc参数存储在字典变量中,通过字典的方式进行访问。figure.figsize
:画板大小。plt.scatter()
的前几个参数分别是x、y、size、color、marker。
4.读取数据
(1)Python方法
在训练模型的时候,我们需要遍历数据集并不断读取小批量数据样本。这里我们定义一个函数:batch_size()
它每次返回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):
j = torch.LongTensor(indices[i: min(i + batch_size, num_examples)])
# 最后一次可能不足一个batch
yield features.index_select(0, j), labels.index_select(0, j)
batch_size = 10
for X, y in data_iter(batch_size, features, labels):
print(X, y)
break
range()
:参数有start,end,step。计数从 start 开始。默认是从 0 开始。 计数到 stop 结束,但不包括 stop。步长,默认为1。
在上面代码的for循环中,range(0, num_examples, batch_size)返回的是[0,10,20…990]range(10) # 从 0 开始到 10 #[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
torch.LongTensor()
:Torch定义了七种CPU tensor类型和八种GPU tensor类型。LongTensor指的是long int类型的Tensor。indices[i:min(i+batch_size,1000)
:这里是一个切片操作,不包含最后一个索引。当i=0时,这里的选取范围是indices[0]~indices[9]tensor.index_select()
:前三个参数为input,dim,index。返回输入Tensor指定维度上的index(index要求是LongTensor类型)对应的值。在上述代码中指选取 features 和 labels 中 j 对应的行(dim=0)。yield
:- 通常的for…in…循环中,in后面是一个数组,这个数组就是一个可迭代对象。它的缺陷是所有数据都在内存中,如果有海量数据的话将会非常耗内存。生成器是可以迭代的,但只可以读取它一次。因为用的时候才生成。
- 简单地讲,yield 的作用就是把一个函数变成一个 generator,带有 yield 的函数不再是一个普通函数,Python 解释器会将其视为一个 generator,调用 data_iter(batch_size, features, labels) 不会执行 data_iter函数,而是返回一个 iterable 对象!
- 在 for 循环执行时,每次循环都会执行 data_iter 函数内部的代码,执行到 yield 时,data_iter 函数就返回一个迭代值,下次迭代时,代码从 yield 的下一条语句继续执行( i 变为下一个数),而函数的本地变量看起来和上次中断执行前是完全一样的,于是函数继续执行,直到再次遇到 yield。
- 这样不断执行下去,就会按照批次,一批一批输出X,y。
(2)简洁:Pytorch方法
PyTorch提供了torch.utils.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
Data.TensorDataset()
:每个样本都将通过索引第一维的张量来检索。Data.DataLoader()
:参数很多,可以只记前三个。API Reference
5.初始化模型参数并定义模型
(1)Python方法:
我们将权重初始化成均值为0、标准差为0.01的正态随机数,偏差则初始化成0。之后的模型训练中,需要对这些参数求梯度来迭代参数的值,因此我们要让它们的requires_grad=True。
w = torch.tensor(np.random.normal(0, 0.01, (num_inputs, 1)), dtype=torch.float32)
b = torch.zeros(1, dtype=torch.float32)
w.requires_grad_(requires_grad=True)
b.requires_grad_(requires_grad=True)
def linreg(X, w, b):
return torch.mm(X, w) + b
net = linreg #模型函数的别名
torch.mm()
:做矩阵乘法。
(2)简洁:Pytorch方法:
- 在上面的实现中,我们需要定义模型参数,并使用它们一步步描述模型是怎样计算的。当模型结构变得更复杂时,这些步骤将变得更繁琐。其实,PyTorch提供了大量预定义的层,这使我们只需关注使用哪些层来构造模型。下面将介绍如何使用PyTorch更简洁地定义线性回归。
- 首先,导入
torch.nn
模块。实际上,“nn”是neural networks(神经网络)的缩写。顾名思义,该模块定义了大量神经网络的层。之前我们已经用过了autograd,而nn就是利用autograd来定义模型。 - torch.nn仅支持输入一个batch的样本不支持单个样本输入,如果只有单个样本,可使用input.unsqueeze(0)来添加一维。
定义模型方法一: nn.Module
- nn的核心数据结构是Module,它是一个抽象概念,既可以表示神经网络中的某个层(layer),也可以表示一个包含很多层的神经网络。在实际使用中,最常见的做法是继承
nn.Module
,撰写自己的网络/层。一个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)
# 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)
)
super()
:super() 函数是用于调用父类(超类)的一个方法。super 是用来解决多重继承问题的,直接用类名调用父类方法在使用单继承的时候没问题,但是如果使用多继承,会涉及到查找顺序(MRO)、重复调用(钻石继承)等种种问题。第一个参数是类(本身),第二个参数一般是self。点后面调用的是父类的方法。nn.Linear()
有三个参数:in_features,每个输入样本的大小。out_features,每个输出样本的大小。bias,如果设置为False,则图层不会学习附加偏差。默认值: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)
查看模型所有的可学习参数:
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)
net.parameters()
:此函数将返回一个生成器
初始化模型参数:
在使用net前,我们需要初始化模型参数,如线性回归模型中的权重和偏差。PyTorch在init模块
中提供了多种参数初始化方法。这里的init是initializer的缩写形式。
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)
init.normal_()
:将权重参数每个元素初始化为随机采样于均值为0、标准差为0.01的正态分布。init.constant_()
:偏差会初始化为零。net[0].bias.data.fill_()
:net[0]这样根据下标访问子模块的写法只有当net是个ModuleList或者Sequential实例时才可以。
6.定义损失函数
(1)Python方法:
def squared_loss(y_hat, y):
# 注意这里返回的是向量, 另外, pytorch里的 MSELoss并没有除以 2
return (y_hat - y.view(y_hat.size())) ** 2 / 2
#我们需要把真实值y变形成预测值y_hat的形状。
loss = squared_loss #损失函数的别名
(2)简洁:Pytorch方法:
PyTorch在nn模块中提供了各种损失函数,这些损失函数可看作是一种特殊的层,PyTorch也将这些损失函数实现为nn.Module的子类。我们现在使用它提供的均方误差损失作为模型的损失函数。
loss = nn.MSELoss()
7.定义优化算法
小批量随机梯度下降(mini-batch stochastic gradient descent)在深度学习中被广泛使用。它的算法很简单:
- 先选取一组模型参数的初始值,如随机选取;
- 接下来对参数进行多次迭代,使每次迭代都可能降低损失函数的值。
- 在每次迭代中,先随机均匀采样一个由固定数目训练数据样本所组成的小批量(mini-batch)B。
- 然后求小批量中数据样本的平均损失有关模型参数的导数(梯度)。
- 最后用此结果与预先设定的一个正数(学习率(learning rate))的乘积作为模型参数在本次迭代的减小量。
(1)Python方法:
以下的sgd函数实现了小批量随机梯度下降算法。它通过不断迭代模型参数来优化损失函数。这里自动求梯度模块计算得来的梯度是一个批量样本的梯度和。我们将它除以批量大小来得到平均值。
def sgd(params, lr, batch_size): #lr是学习率
for param in params:
param.data -= lr * param.grad / batch_size
# 注意这里更改param时用的param.data
(2)简洁:Pytorch方法:
torch.optim模块
提供了很多常用的优化算法比如SGD
、Adam
和RMSProp
等。
- 下面我们创建一个用于优化net所有参数的优化器实例,并指定学习率为0.03的小批量随机梯度下降(SGD)为优化算法。
import torch.optim as optim
optimizer = optim.SGD(net.parameters(), lr=0.03)
print(optimizer)
net.parameters()
来自torch.nn.Module类。optim.SGD()
可以只记前两个参数。- 为不同子网络设置不同的学习率,这在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。但是后者对于使用动量的优化器(如Adam),会丢失动量等状态信息,可能会造成损失函数的收敛出现震荡等情况。
# 调整学习率
for param_group in optimizer.param_groups:
param_group['lr'] *= 0.1 # 学习率为之前的0.1倍
8.训练模型
(1)Python方法:
lr = 0.03
num_epochs = 3
for epoch in range(num_epochs): # 训练模型一共需要num_epochs个迭代周期
# 在每一个迭代周期中,会使用训练数据集中所有样本一次(假设样本数能够被批量大小整除)。
# X和y分别是小批量样本的特征和标签
for X, y in data_iter(batch_size, features, labels):
l = loss(net(X, w, b), y).sum() # l是有关小批量X和y的损失
l.backward() # 小批量的损失对模型参数求梯度
sgd([w, b], lr, batch_size) # 使用小批量随机梯度下降迭代模型参数
# 不要忘了梯度清零
w.grad.data.zero_()
b.grad.data.zero_()
#测试整个训练集,输出每一个周期的误差
train_l = loss(net(features, w, b), labels)
print('epoch %d, loss %f' % (epoch + 1, train_l.mean().item()))
#训练完成后,我们可以比较学到的参数和用来生成训练集的真实参数
print(true_w, '\n', w)
print(true_b, '\n', b)
(2)简洁:Pytorch方法:
在使用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()))
#训练完成后,我们可以比较学到的参数和用来生成训练集的真实参数
dense = net[0]
print(true_w, dense.weight)
print(true_b, dense.bias)