这里写自定义目录标题
1.线性回归的从0开始实现
首先导入本节实验中所需的包或者模块
%matplotlib inline
import torch
from IPython import display
from matplotlib import pyplot as plt
import numpy as np
import random
1.1生成数据集
我们构造⼀个简单的⼈⼯训练数据集,它可以使我们能够直观⽐较学到的参数和真实的模型参数的区别。设训练数据集样本数1000,输⼊个数(特征数)为2。给定随机⽣成的批量样本特征 ,我们使⽤线性回归模型真实权重
w
=
[
2
,
−
3
,
4
]
T
w=[2,-3,4]^T
w=[2,−3,4]T 和偏差
b
=
4.2
b=4.2
b=4.2 ,以及⼀个随机噪声
ϵ
\epsilon
ϵ来⽣成标签。
y
=
X
w
+
b
+
ϵ
y=Xw+b+\epsilon
y=Xw+b+ϵ其中,
ϵ
\epsilon
ϵ服从均值为0、标准差为0.01的正态分布。
num_inputs = 2
num_examples = 1000
true_w = [2, -3.4]
true_b = 4.2
features = torch.from_numpy(np.random.normal(0, 1, (num_examples,
num_inputs)))#生成随机1000×2样本矩阵
labels = true_w[0] * features[:, 0] + true_w[1] * features[:, 1] +true_b
labels +=torch.from_numpy(np.random.normal(0, 0.01,
size=labels.size()))#根据样本计算标签
查看生成的数据集(只绘制了标签关于第二个特征的散点图)
plt.scatter(features[:, 1].numpy(), labels.numpy(), 1);
结果如下:
1.2 读取数据集
本文使用小批量随机梯度降来优化模型,在训练模型的时候,我们需要遍历数据集并不断读取⼩批量数据样本。
#遍历数据集并不断读取⼩批量数据样本
def data_iter(batch_size, features, labels):
"""batch_size:每个小批量的样本个数
features:数据集特征样本 (N×d维)
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(LongTensor:float64)
yield features.index_select(0, j), labels.index_select(0, j)
让我们读取第⼀个⼩批量数据样本并打印。每个批量的特征形状为(10, 2),分别对应批量⼤⼩和输⼊个
数;标签形状为批量大小。
batch_size = 10
for X, y in data_iter(batch_size, features, labels):
print(X, y)
break
输出:
1.3 初始化模型参数
我们将权重初始化成均值为0、标准差为0.01的正态随机数,偏差则初始化成0。
w = torch.tensor(np.random.normal(0, 0.01, (num_inputs, 1)),dtype=torch.float32)
b = torch.zeros(1, dtype=torch.float32)
之后的模型训练中,需要对这些参数求梯度来迭代参数的值,因此我们要让它们的requires_grad=True
w.requires_grad_(requires_grad=True)
b.requires_grad_(requires_grad=True)
初始化
w
,
b
w,b
w,b 如下:
1.4定义模型
下⾯是线性回归的⽮量计算表达式的实现。我们使⽤ mm 函数做矩阵乘法。
def linreg(X, w, b):
return torch.mm(X, w) + b
1.5定义损失函数
def squared_loss(y_hat, y):
# 注意这⾥返回的是向量, 另外, pytorch⾥的MSELoss并没有除以 2
return (y_hat - y.view(y_hat.size())) ** 2 / 2
1.6定义优化算法
在求数值解的优化算法中,⼩批量随机梯度下降(mini-batch stochastic gradient descent)在深度学习中被⼴泛使⽤。它的算法很简单:先选取⼀组模型参数的初始值,如随机选取;接下来对参数进⾏多次迭代,使每次迭代都可能降低损失函数的值。在每次迭代中,先随机均匀采样⼀个由固定数⽬训练数据样本所组成的⼩批量(mini-batch) ,然后求⼩批量中数据样本的平均损失有关模型参数的导数(梯度),最后⽤此结果与预先设定的⼀个正数的乘积作为模型参数在本次迭代的减⼩量。
在训练讨论的线性回归模型的过程中,模型的每个参数将作如下迭代:
在上式中,
∣
B
∣
| \Beta|
∣B∣ 代表每个⼩批量中的样本个数(批量⼤⼩,batch size),
η
\eta
η 称作学习率(learningrate)并取正数。需要强调的是,这⾥的批量⼤⼩和学习率的值是⼈为设定的,并不是通过模型训练学出的,因此叫作超参数(hyperparameter)。
以下的 sgd 函数实现了⼩批量随机梯度下降算法。它通过不断迭代模型参数来优化损失函数。这⾥⾃动求梯度模块计算得来的梯度是⼀个批量样本的梯度和。我们将它除以批量⼤⼩来得到平均值。
def sgd(params, lr, batch_size):
for param in params:
param.data -= lr * param.grad / batch_size # 注意这⾥更改param时⽤的param.data
1.7 训练模型
在训练中,我们将多次迭代模型参数。在每次迭代中,我们根据当前读取的⼩批量数据样本(特征 X 和标签 y ),通过调⽤反向函数 backward 计算⼩批量随机梯度,并调⽤优化算法 sgd 迭代模型参数。由于我们之前设批量⼤⼩ batch_size 为10,每个⼩批量的损失
l
l
l 的形状为(10, 1)。由于变量
l
l
l 并不是⼀个标量,所以我们可以调⽤ .sum() 将其求和得到⼀个标量,再运⾏
l
l
l.backward() 得到该变量有关模型参数的梯度。注意在每次更新完参数后不要忘了将参数的梯度清零。
在⼀个迭代周期(epoch)中,我们将完整遍历⼀遍 data_iter 函数,并对训练数据集中所有样本都使⽤⼀次(假设样本数能够被批量⼤⼩整除)。这⾥的迭代周期个数 num_epochs 和学习率 lr 都是超参数,分别设10和0.03。
lr = 0.03
num_epochs = 10
net = linreg
loss = squared_loss
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)
输出:
1.8 总结:
可以看出,仅使⽤ Tensor 和 autograd 模块就可以很容易地实现⼀个模型。接下来,我们介绍更简单的方法来实现线性回归模型。
2.线性回归的简洁实现(框架实现)
2.1 生成数据集
与前面相同
#生成数据集
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)
print('第一个样本的特征为{},标签为{}'.format(features[0,:],labels[0]))
2.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)
此处的 data_iter与前文相同,我们打印第一个小批量的数据样本。
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])
2.3定义模型
** 2.3.1 使用nn.Module 定义模型**
⾸先,导⼊ torch.nn 模块。实际上,“nn”是neural networks(神经⽹络)的缩写。顾名思义,该模块定义了⼤量神经⽹络的层。之前我们已经⽤过了 autograd ,⽽ nn 就是利⽤ autograd 来定义模型。 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可以打印出⽹络的结构
输出:
3.3.2 nn.Sequential 搭建网络
事实上我们还可以⽤ nn.Sequential 来更加⽅便地搭建⽹络, Sequential 是⼀个有序的容器,⽹络层将按照在传⼊ Sequential 的顺序依次被添加到计算图中。
#网络搭建方式2:nn.Sequential
net2=nn.Sequential(nn.Linear(num_inputs,1))
print(net2)
print(net2[0])
输出:
可以通过 net.parameters() 来查看模型所有的可学习参数,此函数将返回⼀个⽣成器。
for param in net.parameters():
print(param)
输出:
注意: torch.nn 仅⽀持输⼊⼀个batch的样本不⽀持单个样本输⼊,如果只有单个样本,可使
⽤ input.unsqueeze(0) 来添加⼀维
2.4 初始化模型参数
在使⽤ net 前,我们需要初始化模型参数,如线性回归模型中的权᯿和偏差。PyTorch在 init 模块中提供了多种参数初始化⽅法。这⾥的 init 是 initializer 的缩写形式。我们通过 init.normal_ 将权重参数每个元素初始化为随机采样于均值为0、标准差为0.01的正态分布。偏差会初始化为零。
#初始化模型参数
from torch.nn import init
init.normal_(net2[0].weight,mean=0,std=0.01)
init.constant_(net2[0].bias,val=0)
print(net2[0].weight,net2[0].bias)
2.5 定义损失函数
loss = nn.MSELoss()
2.6 定义优化算法
同样,我们也⽆须⾃⼰实现⼩批量随机梯度下降算法。 torch.optim 模块提供了很多常⽤的优化算法⽐如SGD、Adam和RMSProp等。下⾯我们创建⼀个⽤于优化 net 所有参数的优化器实例,并指定学习率为0.03的⼩批量随机梯度下降(SGD)为优化算法。
import torch.optim as optim
optimizer = optim.SGD(net2.parameters(), lr=0.03)
print(optimizer)
2.7 训练模型
在使⽤Gluon训练模型时,我们通过调⽤ optim 实例的 step 函数来迭代模型参数。按照⼩批量随机梯度下降的定义,我们在 step 函数中指明批量⼤⼩,从⽽对批量中样本梯度求平均。
num_epochs = 10
for epoch in range(1, num_epochs + 1):
for X, y in data_iter:
output = net2(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()))
输出:
下⾯我们分别⽐较学到的模型参数和真实的模型参数。我们从 net 获得需要的层,并访问其权重( weight )和偏差( bias )。学到的参数和真实的参数很接近。
dense = net2[0]
print(true_w, dense.weight)
print(true_b, dense.bias)
输出:
👉github链接
小结
- 使⽤PyTorch可以更简洁地实现模型。
- torch.utils.data 模块提供了有关数据处理的⼯具, torch.nn 模块定义了⼤量神经⽹络的层, torch.nn.init 模块定义了各种初始化⽅法, torch.optim 模块提供了模型参数初始化
的各种⽅法。