【动手学深度学习】线性回归+基础优化算法

参考:

08 线性回归 + 基础优化算法【动手学深度学习v2】_哔哩哔哩_bilibili

【pytorch系列】 with torch.no_grad():用法详解_大黑山修道的博客-CSDN博客_torch.no_grad():

对Pytorch中backward()函数的理解_beebabo的博客-CSDN博客_backward

关于李沐动手学深度学习“loss{float(train_l.mean()):f}”中的":f"的解释 - 知乎 (zhihu.com)

pytorch实现自由的数据读取-torch.utils.data的学习_tang-0203的博客-CSDN博客_torch.utils.data

机器学习9:关于pytorch中的zero_grad()函数_小娜美要努力努力的博客-CSDN博客_pytorch zero_grad

PyTorch学习之 torch.optim 的6种优化器及优化算法介绍_Line_Walker的博客-CSDN博客

torch.optim.SGD参数详解(除nesterov)_不牌不改的博客-CSDN博客_optim.sgd

    • 线性回归

    • 线性模型

线性模型可以看做是单层神经网络

    • 衡量预估质量——平方损失

    • 训练数据

    • 参数学习

线性回归是唯一一个有最优解的情形

    • 总结

  • 线性回归是对n维输入的加权,外加偏差

  • 使用平方损失来衡量预测值和真实值的差异

  • 线性回归有显示解

  • 线性回归可以看作是单层神经网络

    • 基础优化算法

虽然线性回归有解析解,但是一般的模型都没有解析解,因此在此介绍更为普适的求解最优解的方法。当一个模型没有显示解的时候,可以使用梯度下降法来逼近最优解

    • 梯度下降

超参数:人为来指定的值

    • 小批量随机梯度下降

  • 批量不能太小,若计算量太小,不适合并行来最大利用计算资源

  • 批量不能太大,否则内存消耗增加,浪费计算

    • 总结

  • 梯度下降通过不断沿着反梯度方向更新参数求解

  • 小批量随机梯度下降是深度学习默认的求解算法

  • 两个重要的超参数是批量大小和学习率

    • 线性回归的从零开始实现

  • %matplotlib inline

%matplotlib inline 可以在Ipython编译器里直接使用,功能是可以内嵌绘图,并且可以省略掉plt.show()这一步
%matplotlib inline
import random
import torch
from d2l import torch as d2l
    • 生成数据集

  • torch.normal(means, std, size,require_grade)

means (Tensor) – 均值
std (Tensor) – 标准差
size (Tensor) – 可选的输出张量
require_grade :表示是否求梯度值
  • torch.matmul(input,other)

计算两个张量input和other的矩阵乘积
matmul函数没有强制规定维度和大小,可以用利用广播机制进行不同维度的相乘操作

为了简单起见,我们将根据带有噪声的线性模型构造一个人造数据集。 我们的任务是使用这个有限样本的数据集来恢复这个模型的参数。

在下面的代码中,我们生成一个包含1000个样本的数据集, 每个样本包含从标准正态分布中采样的2个特征。

我们使用线性模型参数w=[2,−3.4]⊤b=4.2 和噪声项ϵ生成数据集及其标签:

y=Xw+b+ϵ.

ϵ可以视为模型预测和标签时的潜在观测误差。 在这里我们认为标准假设成立,即ϵ服从均值为0的正态分布。 为了简化问题,我们将标准差设为0.01。 下面的代码生成合成数据集。

def synthetic_data(w, b, num_examples):  #@save
    """生成y=Xw+b+噪声"""
    X = torch.normal(0, 1, (num_examples, len(w)))  #X中的元素是均值为0,方差为1的随机数
    y = torch.matmul(X, w) + b
    y += torch.normal(0, 0.01, y.shape) #增加随机噪音,噪音是均值为0,方差为0.01的随机数
    return X, y.reshape((-1, 1)) # 返回时把y转换成为列向量

true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = synthetic_data(true_w, true_b, 1000)

features中的每一行都包含一个二维数据样本, labels中的每一行都包含一维标签值(一个标量)

print('features:', features[0],'\nlabel:', labels[0])

通过生成第二个特征features[:, 1]和labels的散点图, 可以直观观察到两者之间的线性关系。

d2l.set_figsize()
d2l.plt.scatter(features[:, 1].detach().numpy(), labels.detach().numpy(), 1);

    • 读取数据集

训练模型时要对数据集进行遍历,每次抽取一小批量样本,并使用它们来更新我们的模型。

首先需要定义一个函数, 该函数能打乱数据集中的样本并以小批量方式获取数据。

在下面的代码中,我们定义一个data_iter函数, 该函数接收批量大小、特征矩阵和标签向量作为输入,生成大小为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):
        batch_indices = torch.tensor(
            indices[i: min(i + batch_size, num_examples)])
        yield features[batch_indices], labels[batch_indices]

我们直观感受一下小批量运算:读取第一个小批量数据样本并打印。 每个批量的特征维度显示批量大小和输入特征数。 同样的,批量的标签形状与batch_size相等。

batch_size = 10

for X, y in data_iter(batch_size, features, labels):
    print(X, '\n', y)
    break
    • 初始化模型参数

  • torch.zeros(*size, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False)

返回一个形状为为size,类型为torch.dtype,里面的每一个值都是0的tensor

在我们开始用小批量随机梯度下降优化我们的模型参数之前, 我们需要先有一些参数。 在下面的代码中,我们通过从均值为0、标准差为0.01的正态分布中采样随机数来初始化权重, 并将偏置初始化为0。

w = torch.normal(0, 0.01, size=(2,1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)

在初始化参数之后,我们的任务是更新这些参数,直到这些参数足够拟合我们的数据。

(每次更新都需要计算损失函数关于模型参数的梯度。 有了这个梯度,我们就可以向减小损失的方向更新每个参数。 )

    • 定义模型

定义模型,将模型的输入和参数同模型的输出关联起来

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  #将真实值y的形状转换为和预测值y_hat的形状相同
    • 定义优化算法

  • tensor.requires_grad 参数

tensor的requires_grad的属性默认为False,如果设置为True,则反向传播时,该tensor就会自动求导。
若一个节点requires_grad被设置为True,那么所有依赖它的节点requires_grad都为True(即使其他相依赖的tensor的requires_grad = False)
  • with torch.no_grad()

在该模块下,所有计算得出的tensor的requires_grad都自动设置为False。
即使一个tensor(命名为x)的requires_grad = True,在with torch.no_grad计算,由x得到的新tensor(命名为w-标量)requires_grad也为False,且grad_fn也为None,即不会对w求导。
def sgd(params, lr, batch_size):  #params是给定参数的集合,lr是学习率
    """小批量随机梯度下降"""
    with torch.no_grad():  #更新时不用参与梯度计算
        for param in params:   #params是给定参数的集合
            param -= lr * param.grad / batch_size
            param.grad.zero_() #把梯度设为0

with工作原理:

(1)紧跟with后面的语句被求值后,返回对象的“–enter–()”方法被调用,这个方法的返回值将被赋值给as后面的变量;(with后的条件成立时继续执行代码块)

(2)当with后面的代码块全部被执行完之后,将调用前面返回对象的“–exit–()”方法。(with后的代码块执行完后,退出条件)

with open("1.txt") as file:
    data = file.read()
    • 训练

  • .backward(gradient=None,retain_variables=False)

gradient (Tensor) – 其他函数对于此Variable的导数。仅当Variable不是标量的时候使用,类型和位形状应该和self.data一致。(补充:这里说的是其他函数对Variable的导数!)

retain_variables (bool) – True, 计算梯度所必要的buffer在经历过一次backward过程后不会被释放。如果你想多次计算某个子图的梯度的时候,设置为True。
在某些情况下,使用autograd.backward()效率更高。
  • print(f'{xxxx:f}')

将xxxx输出为6位小数
也可以通过格式化输出规定精确的小数位数,例如:
print(f'{xxxx:3f}') 表示将xxxx输出为3位小数

在每次迭代中,我们读取一小批量训练样本,并通过我们的模型来获得一组预测。

计算完损失后,我们开始反向传播,存储每个参数的梯度。

最后,我们调用优化算法sgd来更新模型参数。

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}')

小结

  • 初步学习了深度网络的实现和优化

  • 在这一过程中只使用张量和自动微分,不需要定义层或复杂的优化器

    • 线性回归的简洁实现

    • 生成数据集

  • torch.utils.data

主要包括以下三个类:
1. class torch.utils.data.Dataset
2. class torch.utils.data.sampler.Sampler
3. class torch.utils.data.DataLoader
import numpy as np
import torch
from torch.utils import data  #导入处理数据的模块
from d2l import torch as d2l

true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = d2l.synthetic_data(true_w, true_b, 1000)
#利用synthetic_data生成人工合成的数据
    • 读取数据集

  • 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)
#shuffle 是否要打乱顺序
batch_size = 10
data_iter = load_array((features, labels), batch_size)

为了验证是否正常工作,让我们读取并打印第一个小批量样本。

3.2节不同,这里我们使用iter构造Python迭代器,并使用next从迭代器中获取第一项。

next(iter(data_iter))

    • 定义模型

  • torch.nn.Linear(in_features,out_features,bias=True)

in_features:输入的二维张量的大小
out_features:输出的二维张量的大小
bias*
  • nn.Sequential()* 后面应该会学到,先不追究

首先定义一个模型变量net,它是一个Sequential类的实例。

Sequential类将多个层串联在一起。

当给定输入数据时,Sequential实例将数据传入到第一层, 然后将第一层的输出作为第二层的输入,以此类推。

在下面的例子中,我们的模型只包含一个层,因此实际上不需要Sequential。 但是由于以后几乎所有的模型都是多层的,在这里使用Sequential会让你熟悉“标准的流水线”。

# nn是神经网络的缩写
from torch import nn

net = nn.Sequential(nn.Linear(2, 1))
#2表示输入的维度,1表示输出的维度
#sequential表示将不同的层排成一个列表
    • 初始化模型参数

指定每个权重参数应该从均值为0、标准差为0.01的正态分布中随机采样, 偏置参数将初始化为零

net[0].weight.data.normal_(0, 0.01)#使用正态分布来替换掉data的值
net[0].bias.data.fill_(0) #将偏差设置为0
    • 定义损失函数

  • nn.MSELoss()

计算均方误差使用的是MSELoss类,也称为平方 L2范数。
默认情况下,它返回所有样本损失的平均值
loss = nn.MSELoss()  #计算均方误差函数
    • 定义优化算法

  • torch.optim

torch.optim中包含了6种优化器算法
这6种方法分为2大类:一大类方法是SGD及其改进(加Momentum);另外一大类是Per-parameter adaptive learning rate methods(逐参数适应学习率方法),包括AdaGrad、RMSProp、Adam等
  • torch.optim.SGD(params,lr,momentum=0, dampening=0, weight_decay=0, nesterov=False)*

params:要训练的参数
lr:学习率,即步长
weight_decay: 权重衰退*

小批量随机梯度下降算法是一种优化神经网络的标准工具, PyTorch在optim模块中实现了该算法的许多变种。

当我们实例化一个SGD实例时,我们要指定优化的参数 (可通过net.parameters()从我们的模型中获得)以及优化算法所需的超参数字典。

小批量随机梯度下降只需要设置lr值,这里设置为0.03。

trainer = torch.optim.SGD(net.parameters(), lr=0.03)
#需要传入:所有network的参数w,b和学习率
    • 训练

  • .zero_grad()

根据pytorch中backward()函数的计算,当网络参量进行反馈时,梯度是累积计算而不是被替换,但在处理每一个batch时并不需要与其他batch的梯度混合起来累积计算,因此需要对每个batch调用一遍zero_grad()将参数梯度置0.
另外,如果不是处理每个batch清除一次梯度,而是两次或多次再清除一次,相当于提高了batch_size,对硬件要求更高,更适用于需要更高batch_size的情况。
应用:
optimizer.zero_grad() # 将模型的参数梯度初始化为0
outputs=model(inputs) # 前向传播计算预测值
loss = cost(outputs, y_train) # 计算当前损失
loss.backward() # 反向传播计算梯度
optimizer.step() # 更新所有参数
  • optimizer.step()

所有的optimizer都实现了 step()方法,这个方法会更新所有的参数。
一旦梯度被如 backward()之类的函数计算好后,我们就可以调用这个函数。

在每个迭代周期里,我们将完整遍历一次数据集(train_data), 不停地从中获取一个小批量的输入和相应的标签。 对于每一个小批量,我们会进行以下步骤:

  • 通过调用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()#有了梯度之后,用step进行模型的更新
    l = loss(net(features), labels)
    print(f'epoch {epoch + 1}, loss {l:f}')

下面我们比较生成数据集的真实参数和通过有限数据训练获得的模型参数。

要访问参数,我们首先从net访问所需的层,然后读取该层的权重和偏置。

w = net[0].weight.data
print('w的估计误差:', true_w - w.reshape(true_w.shape))
b = net[0].bias.data
print('b的估计误差:', true_b - b)

小结

  • 在PyTorch中,data模块提供了数据处理工具,nn模块定义了大量的神经网络层和常见损失函数。

  • 我们可以通过_结尾的方法将参数替换,从而初始化参数

去成为你想成为的人吧

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值