Pytorch教程:Autograd基础

news2024/11/20 14:35:39

PyTorch的Autograd特征可以让PyTorch灵活快速的构建机器学习项目。autograd可以实现快速和容易的多重偏微分(梯度)计算。偏微分计算时反向传播神经网络学习的核心。

autograd的可以在运行时动态追踪计算,这意味着如果模型有决策分支、或者有在运行时之前长度未知的循环,仍然可以正确的追踪计算,得到正确的梯度进而驱动学习。并且,当模型是通过python构建的,在计算梯度时,PyTorch的autograd比那些依赖于对更加严格结构的模型进行静态分析的框架提供了更多的灵活性。

目录

Autograd可以做什么

一个简单例子

训练中的autograd

autograd的开启和关闭

autograd和in-place操作


Autograd可以做什么

PyTorch机器学习模型是一个有输入和输出的函数,将输入看作i维的特征向量\vec{x},元素是x_{i},我们可以将模型M表达为关于输入的向量值的函数\vec{y} = \vec{M}(\vec{x})。将M的输出视作向量是因为一般而言,模型有多个输出。

讨论autograd大部分情况下是在训练的时候,这时输出是模型的loss。损失函数L(\vec{y}) = L(\vec{M}(\vec{x}))是模型输出的一个单值标量函数。这个函数表示模型的预测值和真值距离有多远。Note:从现在开始,当上下文比较清晰时我们省略向量符号,例如使用y代替\vec{y}

在训练模型时,想要最小化损失。在理想的情形下,这意味着调整其学习权重(learning weights)--也就是调整函数的参数,使得损失函数对于所有的输入都是0。在真实的世界,这就是一个迭代过程,不断调整学习权重,直到我们对各种各样的输入都有一个可容忍的损失。

我们如何决定从那个方向以及多远调整权重呢?想最小化损失,这意味着使得损失函数对输入的一阶导数为0:\frac{\partial L}{\partial x} = 0

损失L不是直接从输入导出的,而是模型输出的函数,而模型输出是关于输入的直接函数,通过链式微分法则:

\frac{\partial L}{\partial x} = \frac{\partial L(\vec{y})}{\partial x} = \frac{\partial L}{\partial y}\frac{\partial y}{\partial x}= \frac{\partial L}{\partial y}\frac{\partial M(x)}{\partial x}

\frac{\partial M(x)}{\partial x}使得事情变得复杂。模型输出关于输出的偏微分,如果使用链式法则再次展开表达式,我们将得到许多局部偏微分(模型中的每一个相乘学习权重,每一个激活函数,以及每一个其他的数学变换)。每个这样的偏微分的完整表达式是通过计算图的每个可能路径的局部梯度的乘积之和,该计算图的叶节点就是可测量梯度的变量。

特别的,关于学习权重的梯度是我们关心的,他告诉我们为了使得损失函数为0,对应改我们要改变权重的方向。

局部偏导数的数量和计算的复杂度会随着神经网络的加深成指数级的增长。这就是autograd出现的地方:它追踪每一个历史计算。Pytorch模型中的每一个计算的tensor携带了其输入tensor和创建它所使用的数学函数的历史信息。PyTorch每个数学函数都有一个计算自己导数的内置的实现,这大大加快了学习所需的局部导数的计算。

一个简单例子

import torch

import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import math

a = torch.linspace(0., 2. * math.pi, steps=25, requires_grad=True)
print(a)

输出:

tensor([0.0000, 0.2618, 0.5236, 0.7854, 1.0472, 1.3090, 1.5708, 1.8326, 2.0944,
        2.3562, 2.6180, 2.8798, 3.1416, 3.4034, 3.6652, 3.9270, 4.1888, 4.4506,
        4.7124, 4.9742, 5.2360, 5.4978, 5.7596, 6.0214, 6.2832],
       requires_grad=True)

我们创建了一个输入的tensor,包含[0, 2pi]区间等间距值,并设置requires_grad=True。设置这个标志意味着接下来的每一个计算,autograd都将在该计算的输出tensor中累积计算的历史

b = torch.sin(a)
#plt.plot(a.detach(), b.detach())
print(a.detach())
print(b.detach())
print(b)
tensor([0.0000, 0.2618, 0.5236, 0.7854, 1.0472, 1.3090, 1.5708, 1.8326, 2.0944,
        2.3562, 2.6180, 2.8798, 3.1416, 3.4034, 3.6652, 3.9270, 4.1888, 4.4506,
        4.7124, 4.9742, 5.2360, 5.4978, 5.7596, 6.0214, 6.2832])
tensor([ 0.0000e+00,  2.5882e-01,  5.0000e-01,  7.0711e-01,  8.6603e-01,
         9.6593e-01,  1.0000e+00,  9.6593e-01,  8.6603e-01,  7.0711e-01,
         5.0000e-01,  2.5882e-01, -8.7423e-08, -2.5882e-01, -5.0000e-01,
        -7.0711e-01, -8.6603e-01, -9.6593e-01, -1.0000e+00, -9.6593e-01,
        -8.6603e-01, -7.0711e-01, -5.0000e-01, -2.5882e-01,  1.7485e-07])
tensor([ 0.0000e+00,  2.5882e-01,  5.0000e-01,  7.0711e-01,  8.6603e-01,
         9.6593e-01,  1.0000e+00,  9.6593e-01,  8.6603e-01,  7.0711e-01,
         5.0000e-01,  2.5882e-01, -8.7423e-08, -2.5882e-01, -5.0000e-01,
        -7.0711e-01, -8.6603e-01, -9.6593e-01, -1.0000e+00, -9.6593e-01,
        -8.6603e-01, -7.0711e-01, -5.0000e-01, -2.5882e-01,  1.7485e-07],
       grad_fn=<SinBackward0>)

grad_fn提示我们当执行反向传播步骤并计算梯度时,需要对tensor的输入,计算sin(x)的导数。让我们再执行更多的计算:

c = 2 * b
print(c)

d = c + 1
print(d)

输出:

tensor([ 0.0000e+00,  5.1764e-01,  1.0000e+00,  1.4142e+00,  1.7321e+00,
         1.9319e+00,  2.0000e+00,  1.9319e+00,  1.7321e+00,  1.4142e+00,
         1.0000e+00,  5.1764e-01, -1.7485e-07, -5.1764e-01, -1.0000e+00,
        -1.4142e+00, -1.7321e+00, -1.9319e+00, -2.0000e+00, -1.9319e+00,
        -1.7321e+00, -1.4142e+00, -1.0000e+00, -5.1764e-01,  3.4969e-07],
       grad_fn=<MulBackward0>)
tensor([ 1.0000e+00,  1.5176e+00,  2.0000e+00,  2.4142e+00,  2.7321e+00,
         2.9319e+00,  3.0000e+00,  2.9319e+00,  2.7321e+00,  2.4142e+00,
         2.0000e+00,  1.5176e+00,  1.0000e+00,  4.8236e-01, -3.5763e-07,
        -4.1421e-01, -7.3205e-01, -9.3185e-01, -1.0000e+00, -9.3185e-01,
        -7.3205e-01, -4.1421e-01,  4.7684e-07,  4.8236e-01,  1.0000e+00],
       grad_fn=<AddBackward0>)

最后让我们计算一个单元素输出。当在tensor上不使用任何参数调用.backward()时,它期望被调用的tensor只包含单个元素,这正是计算loss函数的情形。

out = d.sum()
print(out)

输出:

tensor(25., grad_fn=<SumBackward0>)

可以通过使用tensor存储的grad_fn的next_functions属性,沿着计算的所有路径回溯到tensor的输入。下面的代码可以看到在tensor d上不断获取这个属性,将会展示所有前一级的tensor的梯度函数。调用a.grad_fn将会输出None,这说明a没有任何历史信息,是输入。

print('d:')
print(d.grad_fn)
print(d.grad_fn.next_functions)
print(d.grad_fn.next_functions[0][0].next_functions)
print(d.grad_fn.next_functions[0][0].next_functions[0][0].next_functions)
print(d.grad_fn.next_functions[0][0].next_functions[0][0].next_functions[0][0].next_functions)
print('\nc:')
print(c.grad_fn)
print('\nb:')
print(b.grad_fn)
print('\na:')

输出:

d:
<AddBackward0 object at 0x0000024725AF23A0>
((<MulBackward0 object at 0x0000024725AF2610>, 0), (None, 0))
((<SinBackward0 object at 0x0000024725AF23A0>, 0), (None, 0))
((<AccumulateGrad object at 0x0000024725A2CA30>, 0),)
()

c:
<MulBackward0 object at 0x000002471D90F580>

b:
<SinBackward0 object at 0x000002471D90F580>

a:
None

需要注意的是,只有计算图的叶子节点才计算了他们的梯度。如果你尝试打印print(c.grad),你将得到None。只有输入时叶子节点,所以只有输入才有梯度。

训练中的autograd

到目前为止我们知道autograd如何工作的,我们还没有在实际模型中使用autograd。先定义一个小模型,然后检查一下在一个training batch后,梯度和学习权重是如何改变的。首先定义一些常量,模型以及输入输出。

BATCH_SIZE = 16
DIM_IN = 1000
HIDDEN_SIZE = 100
DIM_OUT = 10

class TinyModel(torch.nn.Module):

    def __init__(self):
        super(TinyModel, self).__init__()
        
        self.layer1 = torch.nn.Linear(1000, 100)
        self.relu = torch.nn.ReLU()
        self.layer2 = torch.nn.Linear(100, 10)
    
    def forward(self, x):
        x = self.layer1(x)
        x = self.relu(x)
        x = self.layer2(x)
        return x
    
some_input = torch.randn(BATCH_SIZE, DIM_IN, requires_grad=False)
ideal_output = torch.randn(BATCH_SIZE, DIM_OUT, requires_grad=False)

model = TinyModel()

上面的代码并没有为模型的层指定requires_grad=True,我们定义的TinyModel类是torch.nn.Module的子类,它已经为我们设定了要跟踪层权重上的梯度以进行学习。
如果我们查看模型的层,我们可以检查权重的值,但是它没有计算出梯度。

print(model.layer2.weight[0][0:10]) # just a small slice
print(model.layer2.weight.grad)

让我们看一下当运行一个training batch后,权重和梯度是如何改变的。loss函数使用的是预测和真实输出的欧式距离的平方,优化器是基本的随机梯度下降优化器。

optimizer = torch.optim.SGD(model.parameters(), lr=0.001)

prediction = model(some_input)

loss = (ideal_output - prediction).pow(2).sum()
print(loss)

输出:

tensor(176.4542, grad_fn=<SumBackward0>)
loss.backward()
print(model.layer2.weight[0][0:10])
print(model.layer2.weight.grad[0][0:10])

输出:

tensor([-0.0017, -0.0628, -0.0098, -0.0960, -0.0369, -0.0101, -0.0257, -0.0494,
         0.0435,  0.0878], grad_fn=<SliceBackward0>)
tensor([ 2.8742, -6.6696, -2.7497, -0.2776,  1.1777,  0.4863, -1.2661, -3.1284,
        -5.0247,  2.4285])

执行loss.backward()后,我们可以看到已经计算出了每一个学习权重的梯度,但是权重值未改变,这是因为我们没有执行optimizer。optimizer负责基于计算得到的梯度更新模型的参数。

optimizer.step()
print(model.layer2.weight[0][0:10])
print(model.layer2.weight.grad[0][0:10])

输出:

tensor([-0.0046, -0.0561, -0.0070, -0.0957, -0.0381, -0.0106, -0.0244, -0.0463,
         0.0486,  0.0854], grad_fn=<SliceBackward0>)
tensor([ 2.8742, -6.6696, -2.7497, -0.2776,  1.1777,  0.4863, -1.2661, -3.1284,
        -5.0247,  2.4285])

执行optimizer.step()后,layer2的权重改变了。在调用optimizer.step()后,需要调用optimizer.zero_grad(),否则每次调用loss.backward()后学习权重上的梯度将会累积。

print(model.layer2.weight.grad[0][0:10])

for i in range(0, 5):
    prediction = model(some_input)
    loss = (ideal_output - prediction).pow(2).sum()
    loss.backward()
    
print(model.layer2.weight.grad[0][0:10])

optimizer.zero_grad(set_to_none=False)

print(model.layer2.weight.grad[0][0:10])

输出:

tensor([ 2.8742, -6.6696, -2.7497, -0.2776,  1.1777,  0.4863, -1.2661, -3.1284,
        -5.0247,  2.4285])
tensor([ 23.3073, -15.0802,   2.5358,  -0.3116,   6.0961,   5.9180,  -3.2840,
        -11.6558, -27.8526,   1.1333])
tensor([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])

在运行上面的代码后,你会发现多次运行loss.backward(),梯度的值将会变得非常大。在运行下一个training batch之前,未能将梯度归零将导致梯度以这种方式膨胀,从而导致不正确和不可预测的学习结果

autograd的开启和关闭

有时候希望细粒度控制autogard是否开启,有多种方式去做这件事。 最简单的方式是直接改变tensor的requires_grad标志。

a = torch.ones(2, 3, requires_grad=True)
print(a)

b1 = 2 * a
print(b1)

a.requires_grad = False
b2 = 2 * a
print(b2)

输出:

tensor([[1., 1., 1.],
        [1., 1., 1.]], requires_grad=True)
tensor([[2., 2., 2.],
        [2., 2., 2.]], grad_fn=<MulBackward0>)
tensor([[2., 2., 2.],
        [2., 2., 2.]])

如果只希望autograd临时关闭,更好的方式时使用torch.no_grad();

a = torch.ones(2, 3, requires_grad=True) * 2
b = torch.ones(2, 3, requires_grad=True) * 3

c1 = a + b
print(c1)

with torch.no_grad():
    c2 = a + b

print(c2)

c3 = a * b
print(c3)

输出:

tensor([[5., 5., 5.],
        [5., 5., 5.]], grad_fn=<AddBackward0>)
tensor([[5., 5., 5.],
        [5., 5., 5.]])
tensor([[6., 6., 6.],
        [6., 6., 6.]], grad_fn=<MulBackward0>)

可能有时候我们有一个需要梯度跟踪开启的tensor,但是希望拷贝它的值,但不希望拷贝的tensor开启梯度跟踪,可以在Tensor对象上使用detach()方法。它创建从计算历史中脱离的tensor的拷贝。

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

print(x)
print(y)

输出:

tensor([0.5531, 0.4273, 0.6955, 0.4804, 0.0501], requires_grad=True)
tensor([0.5531, 0.4273, 0.6955, 0.4804, 0.0501])

autograd和in-place操作

上面的每一个例子都是使用变量来获取计算的中间值。autograd需要这些中间值执行梯度计算,正是由于这个原因,我们在使用autograd时必须小心使用in-place操作。使用in-place操作会破坏在调用backward()计算导数所需的信息。PyTorch甚至可能阻止你在需要进行autograd的叶子变量上使用in-place操作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/640890.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

linux 系统服务管理

目录 一、chkconfig 1、列出服务列表 chkconfig --list 2、关闭开启服务 chkconfig 服务名 on/off 3、添加新服务 chkconfig --add 服务文件名 4、删除已有服务 chkconfig --del 服务名 5、系统级别定义&#xff1a; 一、chkconfig chkconfig——centos…

Python学习46:分配学号(python123)

类型&#xff1a;列表元组‪‬‪‬‪‬‪‬‪‬‮‬‪‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‭‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‮‬‪‬‪‬‪‬‪‬‪‬‮‬‭‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‪‬‪‬‪‬‪‬‪‬‪‬‮‬‭‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‪‬ 描述‪‬…

【Java项目】多种方式解决SpringBoot中遇到的控制台中文乱码问题

文章目录 配置JVM参数配置maven参数配置Runner配置Encoding通过配置文件 配置JVM参数 -Dfile.encodingUTF-8配置maven参数 重点就是 <configuration><fork>true</fork><jvmArguments>-Dfile.encodingUTF-8</jvmArguments></configuration>…

Linux常用命令——git命令

在线Linux命令查询工具 git 是目前世界上最先进的分布式版本控制系统 补充说明 git命令很多人都知道&#xff0c;Linus在1991年创建了开源的Linux&#xff0c;从此&#xff0c;Linux系统不断发展&#xff0c;已经成为最大的服务器系统软件了。 Linus虽然创建了Linux&#x…

线程池最佳实践

文章目录 yml参数配置定义参数实体bean配置线程池实战线程池高级理论线程池工作流程概述线程池拒绝策略线程池参数设置原则1&#xff09;如何为线程池设置合适的线程参数&#xff1f;2) 如何获取当前服务器的cpu核数&#xff1f;3) 无界队列问题 yml参数配置 # 定时任务线程池…

Spring高手之路1——深入理解与实现IOC依赖查找与依赖注入

本文从xml开始讲解&#xff0c;注解后面给出 文章目录 1. 一个最基本的 IOC 依赖查找实例2. IOC 的两种实现方式2.1 依赖查找&#xff08;Dependency Lookup&#xff09;2.2 依赖注入&#xff08;Dependency Injection&#xff09; 3. 在三层架构中的 service 层与 dao 层体会依…

Kafka与消息队列的比较

消息队列&#xff08;Message Queues&#xff09;是一种允许分布式系统和应用异步通信的服务。异步通信意味着消息发送者在等待接收者接收消息之前不需要等待&#xff0c;这可以提高性能和可扩展性&#xff0c;使应用能够并行处理消息。消息队列通过将消息存储在队列中来工作。…

【Vue_项目搭建部署】VUE快速入门——部署_安装ele等

检查是否安装 node npm npm -v node -v 全局安装yarn npm install -g yarn 安装完后&#xff0c;可以配置yarn的淘宝镜像 yarn config get registry //查看当前配置的镜像源 //https://registry.yarnpkg.com yarn config set registry http://registry.npm.taobao.or…

【Redis29】Redis进阶:缓存穿透、击穿与雪崩

Redis进阶&#xff1a;缓存穿透、击穿与雪崩 其实啊&#xff0c;这个内容本来不打算写了&#xff0c;网上讲这一块的内容实在是太多了。不过呢&#xff0c;本着学习还是要全面的原则&#xff0c;而且还要让自己多多巩固复习的原则&#xff0c;咱还是来写一道吧。 同样的&#x…

把钢铁侠战衣交给Z世代,没想到联想商用PC可以这么炫酷!

在数字化转型成为全球政企战略性与常态化诉求的今天&#xff0c;没有人会怀疑新一代数字技术和工具的重要性。 千行百业需要拥有全新的工具握力&#xff0c;也带动了商用 PC的市场需求不断被激发&#xff0c;产品升级迭代速度加快&#xff0c;成为PC行业最具发展机遇的市场。 从…

【Python】Python进阶系列教程--Python AI 绘画(二十)

文章目录 前言Windows 环境安装Civitai 介绍 前言 往期回顾&#xff1a; Python进阶系列教程-- Python3 正则表达式&#xff08;一&#xff09;Python进阶系列教程-- Python3 CGI编程&#xff08;二&#xff09;Python进阶系列教程-- Python3 MySQL - mysql-connector 驱动&a…

好几位朋友最近被迫创业!

见字如面&#xff0c;我是军哥&#xff01; 最近好几位朋友被迫创业&#xff0c;有程序员也有之前做业务的朋友&#xff0c;问其原因&#xff0c;都说现在找工作比较难&#xff0c;想想还是自己干吧。 对于这样的回答&#xff0c; 我表示非常的担心&#xff0c;因为风险实在是太…

VMIX如何RTMP推流给灵派编码器

本文链接&#xff1a;https://blog.csdn.net/weixin_45326556/article/details/131181058 第三方设备&#xff08;例如vMix&#xff0c;OBS&#xff09;如何RTMP推流给灵派编码器 1. 灵派编码器内置RTMP-SERVER2. 其他设备RTMP推流给灵派编码器方法3. 如何使用第三方推上来的RT…

Linux---ln命令、date命令

1. 链接命令ln ln&#xff08;link files&#xff09;命令的功能是为某一个文件在另外一个位置建立一个同步的链接。 当需要在不同的目录&#xff0c;用到相同的文件时&#xff0c;不需要在每一个目录下都放一个相同的文件&#xff0c;只需要在 某个固定目录&#xff0c;放上…

以正式员工身份从京东出来,又通过外包回去了,不甘心啊!

人生是一个圈&#xff0c;职场也是一个圈&#xff0c;一位京东员工就以实际行动诠释了这个“圈”&#xff1a; 以正式员工身份从京东出来&#xff0c;又通过外包回去了&#xff0c;不甘心啊&#xff0c;但外面找工作是地狱级难度&#xff01; 网友纷纷表示“笑死”、“有被笑到…

基于最小费用流(MCF)法的相位解包裹理论与实验验证-含Matlab代码

一、引言 最小费用流算法(Minimum cost flow, MCF) 最早是由 Costantini M. A1998 年提出的&#xff0c;该方法是将未解缠相位的相邻梯度差与解缠相位的相邻梯度差间的差异即不连续性最小化&#xff0c;具有极强鲁棒性与准确性&#xff0c;有基于规则与不规则网络之分。2002年…

Python实现ACO蚁群优化算法优化XGBoost分类模型(XGBClassifier算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 蚁群优化算法(Ant Colony Optimization, ACO)是一种源于大自然生物世界的新的仿生进化算法&#xff0c…

Flutter的Stack和Positioned的控件

简介 Flutter中的Stack控件是一种可用于将多个子控件重叠在一起的布局控件。Stack将所有子控件放在同一个位置&#xff0c;它们可以根据需要进行定位、缩放或旋转。Stack中的子控件可以是任何类型的控件&#xff0c;例如文本、图像、按钮等。 主要属性 Stack控件的主要属性包…

Qcom Camera HAL 流程详解

和你一起终身学习&#xff0c;这里是程序员Android 经典好文推荐&#xff0c;通过阅读本文&#xff0c;您将收获以下知识点: 一、概览二、基本组件概念三、组件结构关系四、关键流程详解 一、概览 回顾高通平台Camera HAL历史&#xff0c;之前高通采用的是QCamera & MM-Cam…

chatgpt赋能python:Python并列输出——让你的数据展示更加美观

Python并列输出——让你的数据展示更加美观 在数据分析和机器学习中&#xff0c;输出数据的格式和展示方式都十分重要。而对于Python程序员来说&#xff0c;如何实现并列输出是一个必须掌握的技能。本文将向您介绍Python的并列输出方式&#xff0c;并教您如何将其结合使用&…