零基础深度学习Pytorch教程,一个小时快速入门(二)

本文详细介绍了PyTorch的autograd模块如何协助神经网络训练,包括前向传播、反向传播过程,以及如何利用autograd自动计算梯度。通过实例演示了创建张量、追踪梯度和优化器的应用。关键概念如计算图、梯度聚合和冻结参数也逐一解析。
摘要由CSDN通过智能技术生成

为了大家观看方便,我在这里直接做了一个四个部分内容的跳转,大家可以自行选择观看。

第一部分第二部分第三部分第四部分

Pytorch官网有非常优秀的教程,其中有几篇小短文属于名为DEEP LEARNING WITH PYTORCH: A 60 MINUTE BLITZ这个小专栏的内容,考虑到大家阅读英文文献有点困难,笔者打算花两天时间做一下翻译,同时结合自己的理解做一些内容调整,原文链接贴在这里点此跳转。承接之前的内容,点此跳转到第一部分。好的我们直接开始第二部分。

这部分叫做一个平缓的对于pytorch自动求梯度的介绍。

这里引入了一个函数torch.autograd,这是一个Pytorch用于增强神经网络训练的自动化区分引擎。在这个部分,你会在概念上完全理解autograd如何帮助一个神经网络进行训练。

背景
神经网络是一个嵌套的函数集合(就理解为大的函数,里面嵌套着一堆函数)。可以用来执行某些被输入到神经网络的数据。这些函数通过一系列参数定义,参数主要包括weights和biases即权重和偏置。这些在pytorch中都被存储在tensors上。

训练一个神经网络需要以下两个步骤:

       1.前向传播:在前向传播中,卷积神经网络能够对正确的输出做出最精确的预测。神经网络通过它的函数运行输入数据给出它的猜测。

        2.反向传播:在反向传播中,神经网络成比例的调整它的猜测于正确结果之间的误差。收集函数对函数参数中导数的误差,同时使用梯度下降优化参数

使用Pytorch

让我们来看一下一个单步训练的过程,举个栗子,我们从torchvision中导入一个预先训练好的resnet18的模型。我们创建了一个随机数构成的tensor去代替一张图片的三个通道(当然就是RGB三个通道了),宽度取64,用随机值初始化对应的标签。

import torch, torchvision
model = torchvision.models.resnet18(pretrained=True)
data = torch.rand(1, 3, 64, 64)
labels = torch.rand(1, 1000)

下一步,我们将输入数据通过模型的每一层做出预测,这是前向传播。

prediction = model(data) # 前向传播

我们使用模型的预测值和对应的标签值去计算误差(也就是loss值,即误差值)。接下来是通过网络反向传播这个误差。在反向传播中自动求梯度然后在参数梯度属性中为每一个模型参数计算梯度,并且存储下来。

loss = (prediction - labels).sum()
loss.backward() # backward pass

下一步,我们引入一个优化器,在这个例子中SGD(随机梯度下降法)使用0.01的学习率和0.9的动量。我们在优化器中注册所有的模型参数。 

最后我们使用.step去初始化梯度下降。这个优化器适应每一个梯度存储在.grad中的情况。

optim.step() #梯度下降

在这一部分中,你需要去训练你的神经网络的每一个部分,下面一些部分的关于自动求梯度的一些细节,你可以直接跳过。 

拆分autograd函数(自动求梯度)

让我们看一下autograd如何收集梯度。我们创建了两个tensor a和b,标记为requires_grad=True,即可以求梯度。这个信号表示autograd的每一步操作都应该被跟踪。

import torch

a = torch.tensor([2., 3.], requires_grad=True)
b = torch.tensor([6., 4.], requires_grad=True)

我们从a和b中创建另一个tensor Q。

Q = 3a^3 - b^2

Q = 3*a**3 - b**2

让我们假设a和b是神经网络的参数,Q是loss值即真实值和神经网络预测值之间的误差值。在神经网络的训练中,我们想要知道loss值参数的梯度信息。

  

当我们在Q上使用.backward函数的时候,autograd会计算这些梯度然后存储在各自的tensor的.grad属性中。

因为它是一个向量.gradient是一个和Q一样形状的tensor,并且代表着Q本身的梯度,因此我们需要去清晰的传递一个在Q.backward()上的梯度参数。 

同样的,我们也可以将Q聚合成一个标量并隐式地向后调用,就像Q.sum().backward().

external_grad = torch.tensor([1., 1.])
Q.backward(gradient=external_grad)

梯度信息现在被放置在a.grad和b.grad中。

# 输出一下梯度看看是不是正确的
print(9*a**2 == a.grad)
print(-2*b == b.grad)

输出结果:

tensor([True, True])
tensor([True, True])

可选读的部分 -使用autograd计算向量微积分

在数学上,如果你有一个数值化的向量函数 \vec{y}=f(\vec{x}),然后梯度\vec{y}\vec{x}求梯度变为Jacobin矩阵J:

通常情况下torch.autograd是一个计算机雅可比矩阵参数的一个引擎。给出任何一个向量\vec{v},计算J^{T}\cdot \vec{v}

如果\vec{v}是标量函数l = g(\vec{y})的一个梯度:

然后通过导数的链式求解规则,雅可比矩阵的元素将会是l\vec{x}的导数:

雅可比矩阵的参数是就是我们显示在上图中的样子,external_grad代表着\vec{v}

计算图 

从概念上说,autograd保持数据(tensor)的记录,以及其他可执行的操作(和新tensor的结果一起)在一个由函数组成的有向无环图(简称DAG)中。在这个DAG中,叶子是输入的tensor,根结点是输出的tensor。通过从图的根节点跟踪到叶结点,你能使用链式法则自动地计算出梯度。

在一个前向传播中,autograd同时做了两件事情:

(1)运行请求的操作去计算一个tensor的结果

(2)在DAG中获取操作函数的梯度

当DAG的根节点调用.backward()的时候反向传播开始:

(1)计算每一个.gard_fn的梯度

(2)聚集他们在各自的tensor的.grad的属性

(3)使用链式法则,传播路径到叶子节点

下图是一个可视化的DAG图的样子。在这个图中,各个方向的箭头代表前向传播的方向。节点代表这每一个操作的反向传播函数。叶子节点(蓝色的)代表叶子tensor a和b。

需要注意的一件重要的事情是,图是从头开始重新创建的。每次调用.backward()后,autograd开始填充一个新图。这正是允许你在模型中使用控制流语句的原因,你可以在每次迭代时更改形状、大小和操作。 

DAG图之外的细节

torch.autograd跟踪在所有的tensor上的操作,需要有一个require_grad的表示设置为True。对于不需要梯度的tensor,设置为require_grad=False,可以将其排除在DAG图之外。

尽管仅有一个输入tensor有require_grad = True那么这个操作输出的tensor将需要梯度。

x = torch.rand(5, 5)
y = torch.rand(5, 5)
z = torch.rand((5, 5), requires_grad=True)

a = x + y
print(f"Does `a` require gradients? : {a.requires_grad}")
b = x + z
print(f"Does `b` require gradients?: {b.requires_grad}")

输出如下:

Does `a` require gradients? : False
Does `b` require gradients?: True

 在神经网络中,不需要计算梯度的元素通常是叫做冻结参数。如果在你的模型中部分使用“greeze”那么你将不需要在这些参数上计算梯度(由于少计算了梯度所以客观上提升了性能)。

其他的一个使用情况就是在微调一个预先设计好的神经网络的时候冻结参数是很有必要的。

在整合过程中,我们冻结模型的大部分参数,仅仅调整分类曾去预测新的标签。让我们用一个小的例子去表示这个情况。和以前一样我们导入了一个resnet18(残差神经网络的一种结构),然后冻结所有参数。

from torch import nn, optim

model = torchvision.models.resnet18(pretrained=True)

# Freeze all the parameters in the network
for param in model.parameters():
    param.requires_grad = False

假设我们想在一个有10个标签的新数据集上微调模型。在resnet中,分类器是最后的线性层model.fc。我们能简单地使用一个新的线性层来替代它,然后作为我们的新分类器。

model.fc = nn.Linear(512, 10)

如今在模型中的所有参数除了model.fc都是被冻结的状态。仅有的参数需要计算梯度的是model.fc的权重和偏置。

# Optimize only the classifier
optimizer = optim.SGD(model.parameters(), lr=1e-2, momentum=0.9)

注意,尽管我们在优化器中注册了所有参数,但计算梯度(并因此在梯度下降中更新)的唯一参数是分类器的权重和偏差。

在torch.no_grad()中,也可以作为上下文管理器使用相同的排除功能。

 好的烧脑的第二部分就更新完毕了,接下来还有两个部分,我会尽快更新完毕。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值