从头学pytorch(三) 线性回归
关于什么是线性回归,不多做介绍了.可以参考我以前的博客https://www.cnblogs.com/sdu20112013/p/10186516.html
实现线性回归
分为以下几个部分:
- 生成数据集
- 读取数据
- 初始化模型参数
- 定义模型
- 定义损失函数
- 定义优化算法
- 训练模型
生成数据集
我们构造一个简单的人工训练数据集,它可以使我们能够直观比较学到的参数和真实的模型参数的区别。设训练数据集样本数为1000,输入个数(特征数)为2。给定随机生成的批量样本特征 \(\boldsymbol{X} \in \mathbb{R}^{1000 \times 2}\),我们使用线性回归模型真实权重 \(\boldsymbol{w} = [2, -3.4]^\top\) 和偏差 \(b = 4.2\),以及一个随机噪声项 \(\epsilon\) 来生成标签
\]
其中噪声项 \(\epsilon\) 服从均值为0、标准差为0.01的正态分布。噪声代表了数据集中无意义的干扰。
%matplotlib inline
import torch
from IPython import display
from matplotlib import pyplot as plt
import numpy as np
import random
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)))
print(type(features),features.shape)
labels = true_w[0] * features[:, 0] + true_w[1] * features[:, 1] + true_b
print(type(labels),labels.shape)
labels += torch.from_numpy(np.random.normal(0, 0.01, size=labels.size()))
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);

读取数据
每次读取batch_size个样本.注意乱序读取.以使得每个batch的样本多样性足够丰富.
def data_iter(batch_size, features, labels):
num_examples = len(features)
#print(num_examples)
indices = list(range(num_examples))
random.shuffle(indices) # 样本的读取顺序是随机的
#print(indices)
for i in range(0, num_examples, batch_size):
j = torch.LongTensor(indices[i: min(i + batch_size, num_examples)]) # 最后一次可能不足一个batch
#print(j)
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
pass
关于yiled用法参考:https://www.cnblogs.com/sdu20112013/p/11216584.html中yield部分.
关于torch的index_select用法参考:https://pytorch-cn.readthedocs.io/zh/latest/package_references/torch/#torchindex_select

features是[1000,2]的Tensor。所以features.index_select(0, j)即在第0维度上对索引为j的输入进行切片.也即选取第j(j为一个长度为batch_size的tensor)个样本.
初始化模型参数
权重值有2个.所以我们初始化一个shape为[2,1]的Tensor.我们将其随机初始化为符合均值0,标准差0.01的正态分布随机数,bias初始化为0.
w=torch.from_numpy(np.random.normal(0,0.01,(num_inputs,1)))
b = torch.zeros(1, dtype=torch.float64)
print(w.dtype,b.dtype)
ndarray的类型是float64,所以w的类型是float64,在生成b的时候我们指定dtype=float64.
之后的模型训练中,需要对这些参数求梯度来迭代参数的值,因此我们要让它们的requires_grad=True。
w.requires_grad_(requires_grad=True)
b.requires_grad_(requires_grad=True)
定义模型
下面是线性回归的矢量计算表达式的实现。我们使用mm函数做矩阵乘法。
在我们的例子中,X是[1000,2]的矩阵,w是[2,1]的矩阵,相乘得到[1000,1]的矩阵.
def linreg(X, w, b): # 本函数已保存在d2lzh_pytorch包中方便以后使用
return torch.mm(X, w) + b
定义损失函数
我们使用平方损失来定义线性回归的损失函数。在实现中,我们需要把真实值y变形成预测值y_hat的形状。以下函数返回的结果也将和y_hat的形状相同。
def squared_loss(y_hat, y):
# 注意这里返回的是向量, 另外, pytorch里的MSELoss并没有除以 2
return (y_hat - y.view(y_hat.size())) ** 2 / 2
定义优化算法
以下的sgd函数实现了上一节中介绍的小批量随机梯度下降算法。它通过不断迭代模型参数来优化损失函数。这里自动求梯度模块计算得来的梯度是一个批量样本的梯度和。我们将它除以批量大小来得到平均值。均值反映了平均而言,对单个样本,朝着哪个梯度方向去更新参数可以使得loss最小
def sgd(params, lr, batch_size):
for param in params:
param.data -= lr * param.grad / batch_size # 注意这里更改param时用的param.data
这里的params传入的即w,b
训练模型
我们创建一个循环,每次传入batch_size个样本,计算损失.反向传播,计算w,b的梯度,然后更新w,b.循环往复.注意每次方向传播后清空梯度. 以及l是一个向量. 调用.sum()将其转换为标量,再计算梯度.
一个epoch即所有样本均计算一次损失.
代码如下:
lr = 0.03
num_epochs = 3
net = linreg
loss = squared_loss
batch_size=10
for epoch in range(num_epochs):
for X,y in data_iter(batch_size,features,labels):
l = loss(linreg(X,w,b),y).sum()
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)
输出如下:
epoch 1, loss 0.051109
epoch 2, loss 0.000217
epoch 3, loss 0.000049
[2, -3.4]
tensor([[ 1.9996],
[-3.3993]], dtype=torch.float64, requires_grad=True)
4.2
tensor([4.1995], dtype=torch.float64, requires_grad=True)
可以看到得到的w和b都已经非常接近true_w,true_b了.
之前我们是手写代码构建模型,创建损失函数,定义随机梯度下降等等.用pytorch里提供的类和函数,可以更方便地实现线性回归.
线性回归的简洁实现
生成数据集
与前面没有区别.
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)))
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()))
数据读取
用torch.utils.data模块,主要使用TensorDataset类和DataLoader类
import torch.utils.data as Data
batch_size=10
dataset = Data.TensorDataset(features,labels)
data_iter = Data.DataLoader(dataset,batch_size=batch_size,shuffle=True)
for X,y in data_iter:
print(X,y)
break
创建网络结构
在上一节从零开始的实现中,我们需要定义模型参数,并使用它们一步步描述模型是怎样计算的。当模型结构变得更复杂时,这些步骤将变得更繁琐。其实,PyTorch提供了大量预定义的层,这使我们只需关注使用哪些层来构造模型。下面将介绍如何使用PyTorch更简洁地定义线性回归。
首先,导入torch.nn模块。实际上,“nn”是neural networks(神经网络)的缩写。顾名思义,该模块定义了大量神经网络的层。之前我们已经用过了autograd,而nn就是利用autograd来定义模型。nn的核心数据结构是Module,它是一个抽象概念,既可以表示神经网络中的某个层(layer),也可以表示一个包含很多层的神经网络。在实际使用中,最常见的做法是继承nn.Module,撰写自己的网络/层。一个nn.Module实例应该包含一些层以及返回输出的前向传播(forward)方法。下面先来看看如何用nn.Module实现一个线性回归模型。
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)
)
事实上我们还可以用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)
可以通过net.parameters()来查看模型所有的可学习参数,此函数将返回一个生成器。
for param in net.parameters():
print(param)
输出:
Parameter containing:
tensor([[-0.2956, -0.2817]], requires_grad=True)
Parameter containing:
tensor([-0.1443], requires_grad=True)
作为一个单层神经网络,线性回归输出层中的神经元和输入层中各个输入完全连接。因此,线性回归的输出层又叫全连接层。
注意:
torch.nn仅支持输入一个batch的样本不支持单个样本输入,如果只有单个样本,可使用input.unsqueeze(0)来添加一维。
初始化模型参数
在使用net前,我们需要初始化模型参数,如线性回归模型中的权重和偏差。PyTorch在init模块中提供了多种参数初始化方法。这里的init是initializer的缩写形式。我们通过init.normal_将权重参数每个元素初始化为随机采样于均值为0、标准差为0.01的正态分布。偏差会初始化为零。
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)
定义优化算法
同样,我们也无须自己实现小批量随机梯度下降算法。torch.optim模块提供了很多常用的优化算法比如SGD、Adam和RMSProp等。下面我们创建一个用于优化net所有参数的优化器实例,并指定学习率为0.03的小批量随机梯度下降(SGD)为优化算法。
import torch.optim as optim
optimizer = optim.SGD(net.parameters(), lr=0.03)
print(optimizer)
输出:
SGD (
Parameter Group 0
dampening: 0
lr: 0.03
momentum: 0
nesterov: False
weight_decay: 0
)
我们还可以为不同子网络设置不同的学习率,这在finetune时经常用到。例:
optimizer =optim.SGD([
# 如果对某个参数不指定学习率,就使用最外层的默认学习率
{'params': net.subnet1.parameters()}, # lr=0.03
{'params': net.subnet2.parameters(), 'lr': 0.01}
], lr=0.03)
有时候我们不想让学习率固定成一个常数,那如何调整学习率呢?主要有两种做法。
- 一种是修改
optimizer.param_groups中对应的学习率
# 调整学习率
for param_group in optimizer.param_groups:
param_group['lr'] *= 0.1 # 学习率为之前的0.1倍
- 另一种是更简单也是较为推荐的做法——新建优化器,由于optimizer十分轻量级,构建开销很小,故而可以构建新的optimizer。但是后者对于使用动量的优化器(如Adam),会丢失动量等状态信息,可能会造成损失函数的收敛出现震荡等情况。
训练
所有的optimizer都实现了step()方法,这个方法会更新所有的参数。它能按两种方式来使用:
- optimizer.step()
这是大多数optimizer所支持的简化版本。一旦梯度被如backward()之类的函数计算好后,我们就可以调用这个函数。
for input, target in dataset:
optimizer.zero_grad()
output = model(input)
loss = loss_fn(output, target)
loss.backward()
optimizer.step()
- optimizer.step(closure)
一些优化算法例如Conjugate Gradient和LBFGS需要重复多次计算函数,因此你需要传入一个闭包去允许它们重新计算你的模型。这个闭包应当清空梯度, 计算损失,然后返回。
for input, target in dataset:
def closure():
optimizer.zero_grad()
output = model(input)
loss = loss_fn(output, target)
loss.backward()
return loss
optimizer.step(closure)
具体参考https://pytorch-cn.readthedocs.io/zh/latest/package_references/torch-optim/
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)
输出:
epoch 1, loss: 0.000227
epoch 2, loss: 0.000160
epoch 3, loss: 0.000136
[2, -3.4] Parameter containing:
tensor([[ 2.0007, -3.4010]], requires_grad=True)
4.2 Parameter containing:
tensor([4.1998], requires_grad=True)
总结:
- 使用PyTorch可以更简洁地实现模型。
torch.utils.data模块提供了有关数据处理的工具,torch.nn模块定义了大量神经网络的层,torch.nn.init模块定义了各种初始化方法,torch.optim模块提供了模型参数优化的各种方法。
从头学pytorch(三) 线性回归的更多相关文章
- 从头学pytorch(一):数据操作
跟着Dive-into-DL-PyTorch.pdf从头开始学pytorch,夯实基础. Tensor创建 创建未初始化的tensor import torch x = torch.empty(5,3 ...
- 从头学pytorch(六):权重衰减
深度学习中常常会存在过拟合现象,比如当训练数据过少时,训练得到的模型很可能在训练集上表现非常好,但是在测试集上表现不好. 应对过拟合,可以通过数据增强,增大训练集数量.我们这里先不介绍数据增强,先从模 ...
- 从头学pytorch(二) 自动求梯度
PyTorch提供的autograd包能够根据输⼊和前向传播过程⾃动构建计算图,并执⾏反向传播. Tensor Tensor的几个重要属性或方法 .requires_grad 设为true的话,ten ...
- 从头学pytorch(九):模型构造
模型构造 nn.Module nn.Module是pytorch中提供的一个类,是所有神经网络模块的基类.我们自定义的模块要继承这个基类. import torch from torch import ...
- 从头学pytorch(十二):模型保存和加载
模型读取和存储 总结下来,就是几个函数 torch.load()/torch.save() 通过python的pickle完成序列化与反序列化.完成内存<-->磁盘转换. Module.s ...
- 从头学pytorch(十四):lenet
卷积神经网络 在之前的文章里,对28 X 28的图像,我们是通过把它展开为长度为784的一维向量,然后送进全连接层,训练出一个分类模型.这样做主要有两个问题 图像在同一列邻近的像素在这个向量中可能相距 ...
- 从头学pytorch(十五):AlexNet
AlexNet AlexNet是2012年提出的一个模型,并且赢得了ImageNet图像识别挑战赛的冠军.首次证明了由计算机自动学习到的特征可以超越手工设计的特征,对计算机视觉的研究有着极其重要的意义 ...
- 从头学pytorch(十九):批量归一化batch normalization
批量归一化 论文地址:https://arxiv.org/abs/1502.03167 批量归一化基本上是现在模型的标配了. 说实在的,到今天我也没搞明白batch normalize能够使得模型训练 ...
- 从头学pytorch(二十):残差网络resnet
残差网络ResNet resnet是何凯明大神在2015年提出的.并且获得了当年的ImageNet比赛的冠军. 残差网络具有里程碑的意义,为以后的网络设计提出了一个新的思路. googlenet的思路 ...
随机推荐
- Inventor安装失败怎样卸载重新安装Inventor,解决Inventor安装失败的方法总结
技术帖:Inventor没有按照正确方式卸载,导致Inventor安装失败.楼主也查过网上关于如何解决Inventor安装失败的一些文章,是说删除几个Inventor文件和Inventor软件注册表就 ...
- python 类与类之间的关系. 特殊成员
一.类与类之间的关系 1.依赖关系 在方法的参数位置把另一个类的对象作为参数进行传递 class Person: def play(self, tools): # 通过参数的传递把另一个类的对象传递进 ...
- python内置函数and匿名函数
一.内置函数 什什么是内置函数? 就是python给你提供的. 拿来直接⽤用的函数, 比如print., input等等. 截⽌止 到python版本3.6.2 python⼀一共提供了了68个内置函 ...
- oracle函数 extract(c1 from d1)
[功能]:日期/时间d1中,参数(c1)的值 [参数]:d1日期型(date)/日期时间型(timestamp),c1为字符型(参数) [参数表]:c1对应的参数表详见示例 [返回]:字符 [示例] ...
- SDUT-2054_数据结构实验之链表九:双向链表
数据结构实验之链表九:双向链表 Time Limit: 1000 ms Memory Limit: 65536 KiB Problem Description 学会了单向链表,我们又多了一种解决问题的 ...
- JS判断在哪一端浏览器打开
<script src="js/jquery-2.2.3.min.js"></script> <script> var browser = { ...
- js递归遍历树结构(tree)
如图: 代码: let datas = [] //是一个树结构的数据 setName(datas){ //遍历树 获取id数组 for(var i in datas){ this.expandedKe ...
- laravel5 怎么获取数组形式的数据
当构建 JSON API 时,您可能常常需要把模型和关联对象转换成数组或JSON.所以Eloquent里已经包含了这些方法.要把模型和已载入的关联对象转成数组,可以使用 toArray方法: $use ...
- POJ2752 Seek the Name, Seek the Fame 题解 KMP算法
题目链接:http://poj.org/problem?id=2752 题目大意:给你一个字符串 \(S\) ,如果它的一个前缀同时也是它的后缀,则输出这个前缀(后缀)的长度. 题目分析:next函数 ...
- 使用属性position:fixed的时候如何才能让div居中
css: .aa{ position: fixed; top: 200px; left: 0px; right: 0px; width: 200px; height: 200px; margin-le ...