学习视频:线性回归 + 基础优化算法【动手学深度学习v2】
线性回归可以看成是一个单层神经网络
使用平方损失来衡量预测值和真实值的差异
虽然线性回归有解析解,但是一般的模型都没有解析解,因此在此介绍更为普适的求解最优解的方法。当一个模型没有显示解的时候,可以使用梯度下降法来逼近最优解
梯度下降
小批量梯度下降
批量不能太小,若计算量太小,不适合并行来最大利用计算资源
批量不能太大,否则内存消耗增加,浪费计算
总结:
梯度下降通过不断沿着反梯度方向更新参数求解
小批量随机梯度下降是深度学习默认的求解算法
两个重要的超参数是批量大小和学习率
torch.normal(means, std, size,require_grade)
means (Tensor) – 均值
std (Tensor) – 标准差
size (Tensor) – 可选的输出张量
require_grade :表示是否求梯度值
torch.matmul(input,other)
计算两个张量input和other的矩阵乘积
matmul函数没有强制规定维度和大小,可以用利用广播机制进行不同维度的相乘操作
import matplotlib.pyplot as plt
import random
import torch
def synthetic_data(w,b,num_examples):
'''生成y = Xw + b + 噪声'''
#X中的元素是均值为0,方差为1的随机值,是一个n*len(w)的二维张量
X = torch.normal(0,1,(num_examples,len(w)))
#y是一个一维的张量
y = torch.matmul(X,w) + b
##增加随机噪音,噪音是均值为0,方差为0.01的随机数
y += torch.normal(0,0.01,y.shape)
#y.reshape((-1, 1)) 的意思是将 y 数组重新排列成一个列向量。其中 -1 表示自动计算行数,1 表示列数为 1
return X,y.reshape((-1,1))
X是一个n*len(w)的二维张量,而w是一个一维张量,根据向量相乘的规则,会得到一个n行一列的一维张量y
定义好w和b的值,利用上面的函数生成1000个数量的数据集
true_w = torch.tensor([2,-3.4])
true_b = 4.2
features,labels = synthetic_data(true_w,true_b,10000)
可以通过散点图的方式,看到两个特征关于y的线性关系,这里为了能使用matplotlib绘图,在pytorch中需要把张量通过detach函数,将tensor从计算图剥离,然后才能使用numpy函数转化为矩阵传入plt,它作为叶子节点仍指向之前tensor
torch.detach()函数
返回一个新的tensor,从当前计算图中分离下来。但是仍指向原变量的存放位置,不同之处只是requirse_grad为false.得到的这个tensor永远不需要计算器梯度,不具有grad.
即使之后重新将它的requires_grad置为true,它也不会具有梯度grad.这样我们就会继续使用这个新的tensor进行计算,后面当我们进行反向传播时,到该调用detach()的tensor就会停止,不能再继续向前进行传播.
plt.scatter(features[:, 0].detach().numpy(), labels.detach().numpy(), 1)
plt.scatter(features[:, 1].detach().numpy(), labels.detach().numpy(), 1)
在下面的代码中,我们定义一个data_iter
函数, 该函数接收批量大小、特征矩阵和标签向量作为输入,生成大小为batch_size
的小批量。 每个小批量包含一组特征和标签。
def data_iter(batch_size, features, labels):
#返回X数据量,即样本数量
num_examples = len(features)
#对正常数据的迭代器生成列表
indices = list(range(num_examples))
# 这些样本是随机读取的,没有特定的顺序,打乱顺序
random.shuffle(indices)
#从0到样本总数量进行迭代,步长为batch_size
for i in range(0, num_examples, batch_size):
#这里巧妙的min可以在最终组数据不够的时候返回的是最后一个节点的下标
batch_indices = torch.tensor(
indices[i: min(i + batch_size, num_examples)])
#yield类似return 返回一个值,并且记住这个返回的位置,下次迭代从这个位置开始
yield features[batch_indices], labels[batch_indices]
使用如下代码进行测试,得到批量读取的效果
batch_size = 10
for X, y in data_iter(batch_size, features, labels):
print(X, '\n', y)
break
在下面的代码中,我们通过从均值为0、标准差为0.01的正态分布中采样随机数来初始化权重, 并将偏置初始化为0
w = torch.normal(0, 0.01, size=(2,1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)
要计算线性模型的输出, 我们只需计算输入特征X和模型权重w的矩阵-向量乘法后加上偏置b
def linreg(X, w, b): #@save
"""线性回归模型"""
return torch.matmul(X, w) + b
def squared_loss(y_hat, y): #@save
"""均方损失"""
return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2
在每一步中,使用从数据集中随机抽取的一个小批量,然后根据参数计算损失的梯度。 接下来,朝着减少损失的方向更新我们的参数。 下面的函数实现小批量随机梯度下降更新。 该函数接受模型参数集合、学习速率和批量大小作为输入。每 一步更新的大小由学习速率lr
决定。 因为我们计算的损失是一个批量样本的总和,所以我们用批量大小(batch_size
) 来规范化步长,这样步长大小就不会取决于我们对批量大小的选择。
def sgd(params, lr, batch_size):
"""小批量随机梯度下降"""
with torch.no_grad():
for param in params:
param -= lr * param.grad / batch_size
param.grad.zero_()
整个训练过程主要分为:
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}')
因为我们使用的是自己合成的数据集,所以我们知道真正的参数是什么。 因此,我们可以通过比较真实参数和通过训练学到的参数来评估训练的成功程度。 事实上,真实参数和通过训练学到的参数确实非常接近。
print(f'w的估计误差: {true_w - w.reshape(true_w.shape)}')
print(f'b的估计误差: {true_b - b}')
#使用深度学习框架实现
import numpy as np
import torch
from torch.utils import data
true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = synthetic_data(true_w, true_b, 1000)
具体参考python之TensorDataset和DataLoader
torch,utils.data.TensorDataset(data_tensor,target_tensor)*
TensorDataset 可以用来对 tensor 进行打包,就好像 python 中的 zip 功能。
该类通过每一个 tensor 的第一个维度进行索引。
因此,该类中的 tensor 第一维度必须相等.
另外:TensorDataset 中的参数必须是 tensor
torch.utils.data.DataLoader(dataset,batch_size,shuffle)*
dataset:加载数据的数据集
batch_size:每批加载多少个样本
shuffle:表示是否希望数据迭代器对象在每个迭代周期内打乱数据
def load_array(data_arrays, batch_size, is_train=True):
"""构造一个PyTorch数据迭代器"""
dataset = data.TensorDataset(*data_arrays)
return data.DataLoader(dataset, batch_size, shuffle=is_train)
batch_size = 10
data_iter = load_array((features, labels), batch_size)
在PyTorch中,全连接层在Linear
类中定义。 值得注意的是,我们将两个参数传递到nn.Linear
中。 第一个指定输入特征形状,即2,第二个指定输出特征形状,输出特征形状为单个标量,因此为1。
# nn是神经网络的缩写
from torch import nn
net = nn.Sequential(nn.Linear(2, 1))
我们这里使用到了Sequential函数,它的作用类似于封装一个我们自定义的模型,可以将我们写好的网络一层一层连接实例化 torch.nn.Sequential()讲解
normal和fill加下划线都是将原本data的值进行填充替换的作用,们通过net[0]
选择网络中的第一个图层, 然后使用weight.data
和bias.data
方法访问参数
net[0].weight.data.normal_(0, 0.01)
net[0].bias.data.fill_(0)
计算均方误差使用的是MSELoss
类,也称为平方L2范数
loss = nn.MSELoss()
小批量随机梯度下降算法是一种优化神经网络的标准工具, PyTorch在optim
模块中实现了该算法的许多变种。 当我们实例化一个SGD
实例时,我们要指定优化的参数 (可通过net.parameters()
从我们的模型中获得)以及优化算法所需的超参数字典。 小批量随机梯度下降只需要设置lr
值,这里设置为0.03。
trainer = torch.optim.SGD(net.parameters(), lr=0.03)
对于每一个小批量,我们会进行以下步骤:
net(X)
生成预测并计算损失l
(前向传播)。num_epochs = 3
for epoch in range(num_epochs):
for X, y in data_iter:
l = loss(net(X) ,y)
trainer.zero_grad()
l.backward()
trainer.step()
l = loss(net(features), labels)
print(f'epoch {epoch + 1}, loss {l:f}')
下面我们比较生成数据集的真实参数和通过有限数据训练获得的模型参数
w = net[0].weight.data
print('w的估计误差:', true_w - w.reshape(true_w.shape))
b = net[0].bias.data
print('b的估计误差:', true_b - b)