《Dive-into-DL-Pytorch》-线性回归的简单实现(linear regression pytorch)
11月3日开始学习深度学习,很多东西不太理解,对书中的代码尽量给出自己理解的注释,并进行分析
书中有两处直接运行会出现报错,也对其进行简单分析,因为刚学不久,所以很多地方的理解可能不是很正确
0.模型(Model)
设第一个特征 ( f e a t u r e ) 为 x 1 ,第二个特征为 x 2 ,真实标签 ( l a b e l ) 为 y : y ^ = ω 1 x 1 + ω 2 x 2 + b ω 1 和 ω 2 是权重 ( w e i g h t ) , b 是偏差 ( b i a s ) ,它们是线性回归模型的参数 ( p a r a m e t e r ) 。 y ^ 是线性回归对真实标签 y 的预测 设第一个特征(feature)为x_1,第二个特征为x_2,真实标签(label)为y:\\ \hat y = \omega_1x_1 + \omega_2x_2 + b \\ \omega_1和\omega_2是权重(weight),b是偏差(bias),它们是线性回归模型的参数(parameter)。\hat y是线性回归对真实标签y的预测 设第一个特征(feature)为x1,第二个特征为x2,真实标签(label)为y:y^=ω1x1+ω2x2+bω1和ω2是权重(weight),b是偏差(bias),它们是线性回归模型的参数(parameter)。y^是线性回归对真实标签y的预测
1.导入库
import torch
import numpy as np
import random
from torch import nn
2.生成数据集
给定随机生成的批量样本特征 X ∈ R 1000 × 2 ,使用真实权重 ω = [ 2 , − 3.4 ] T 、偏差 b = 4.2 和一个随机噪声项 ϵ 来生成标签 y = X ω + b + ϵ 给定随机生成的批量样本特征\boldsymbol{X}\in\mathbb{R}^{1000\times2},使用真实权重 \boldsymbol{\omega} = [2, -3.4]^T、偏差b = 4.2和一个随机噪声项\epsilon来生成标签\\ \mathbf{y} = \boldsymbol{X}\boldsymbol{\omega} + b + \epsilon 给定随机生成的批量样本特征X∈R1000×2,使用真实权重ω=[2,−3.4]T、偏差b=4.2和一个随机噪声项ϵ来生成标签y=Xω+b+ϵ
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) # 通过上面计算的标签全部为一个常数值,所以此处加入一个随机噪声项代表数据集中无意义的干扰
# 噪声项服从均值为0,标准差为0.01的正态分布
3.读取数据
# 导入pytorch提供的data包来读取数据
import torch.utils.data as Data
# 定义小批量样本的数量
batch_size = 10
# 将训练数据的特征与标签组合
dataset = Data.TensorDataset(features, labels)
# 将dataset导入dataloader
data_iter = Data.DataLoader(dataset, batch_size, shuffle=True)
# 第一个参数为导入的数据集
# 第二个参数为每次读取的样本个数
# 第三个参数为是否打乱数据
# 打印第一个小批量的数据
for X, y in data_iter:
print(X, y)
break
4.定义模型
class LinearNet(nn.Module): # 定义了一个继承自nn.Module的类LinearNet
def __init__(self, n_feature):
super(LinearNet, self).__init__()
self.linear = nn.Linear(n_feature, 1) # nn.Linear定义了一个神经网络的线性层
# n_feature是输入特征的个数
# 1是输出特征的个数
# forward 定义前向传播
def forward(self, x): # 接受一个输入张量x,并通过线性层后返回输出张量y
y = self.linear(x)
return y
net = LinearNet(num_inputs)
print(net) # 使用print可以打印出网络的结构
书中还给了使用nn.Sequential
来搭建网络,Sequential
是一个有序容器,网络层将按照传入Sequential
时的顺序依次添加到计算图中:
# 写法一
net = nn.Sequential(
nn.Linear(num_inputs, 1)
# 此处还可以传入其他层
)
# 写法二
net = nn.Sequential()
net.add_module('linear', nn.Linear(num_inputs, 1)) # linear是该层的名字
# net.add_module ... 此处还可以传入其他层
# 写法三
from collections import OrderedDict
net = nn.Sequential(OrderedDict([
('linear', nn.Linear(num_inputs, 1)) # linear是该层的名字
# ... 此处还可以传入其他层
]))
print(net)
print(net[0]) # 输出第一层网络,上面的三个写法都只创建了一层网络
通过net.patameters()
可以查看模型所有可学习的参数:
for param in net.parameters():
print(param)
5.初始化模型参数
此处是对net[0]
的模型参数进行初始化,即定义模型中传入的第一层网络,事实上,上面的代码也只传入了一层网络
# 我们通过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.0) # 也可以直接修改bias的data:net[0].bias.data.fill_(0)
6.定义损失函数
# 使用nn模块提供的均方差损失作为模型的损失函数
loss = nn.MSEloss
7.定义优化算法
# 使用torch.optim提供的SGD优化算法,并制定学习率为0.03
import torch.optim as optim
optimizer = optim.SGD(net.parameters(), lr=0.03) # net.parameters()是需要学习的参数
print(optimizer)
当然,我们还可以对不同的网络层设定不同的学习率:
optimizer = optim.SGD([
# 如果对某个参数不指定学习率,就是用最外层的默认学习率
{'params': net.subnet1.parameters()}, # lr=0.03
{'params': net.subnet2.parameters(), 'lr': 0.01}
], lr=0.03)
注意: 上面的代码写进notebook中运行会报错,错误原因为:AttributeError: ‘Sequential’ object has no attribute ‘subnet1’(属性错误:Sequential
中没有sbunet1
这个属性)。这里的subnet1
和subnet2
是网络层的名字,在定义模型的代码中的linear
是一样的(那个地方代码我也有注释),因为上面的代码没有定义名字是subnet1
和subnet2
的网络层,所以此处报错。如果你想让代码跑起来,那将这一段代码注释掉就是正确的解决办法了。详细的原因解释在这里:AttributeError: ‘Sequential’ object has no attribute ‘subnet1’
如果不希望学习率是一个固定的常数,我们也可以使用下面的方法来调整学习率:
# 调整学习率
for param_group in optimizer.param_groups:
param_group['lr'] *= 0.1 # 学习率为之前的0.1倍
思考: 这里为什么用了一个for
循环?
当有多层网络的时候,就可以设定多个学习率,于是,可以用上面的方法对这些学习率进行修改了
8.训练模型
num_epochs = 3 # 训练模型的迭代周期次数
for epoch in range(1, num_epochs + 1): # 这里我也不是很理解为什么用range(1, num_epochs+1),而不用 # range(num_epochs)
for X, y in data_iter: # X为特征值,y为标签
output = net(X) # 将特征值X用net模型进行预测的到输出结果
l = loss(output, y.view(-1, 1)) # 计算输出结果和标签的差异程度
optimizer.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)
9.两个错误
当时将所有的代码敲到notebook上之后运行时出现了两个错误:
- RuntimeError: Boolean value of Tensor with more than one value is ambiguous
- AttributeError: ‘Sequential’ object has no attribute ‘subnet1’
第二个错误已经分析过了,现在来分析第一个:
RuntimeError: Boolean value of Tensor with more than one value is ambiguous(运行时出错:Tensor的布尔值具有多个值有歧义),这个错误找了半天,最后是对着源码一行一行的看,发现定义损失函数的时候,忘记加后面的括号了,当时也成了这样loss = nn.MSEloss
😭