自动微分 TORCH.AUTOGRAD
文章目录
在训练神经网络时,最常用的算法是 反向传播。在该算法中,根据损失函数相对于给定参数的梯度来调整参数(模型权重)。
为了计算这些梯度,PyTorch具有一个称为的内置微分引擎torch.autograd。它支持任何计算图的梯度自动计算。
考虑最简单的单层神经网络,它具有input x,parametersw和b,并且具有一些损失函数。可以通过以下方式在PyTorch中进行定义:
import torch
x = torch.ones(5) # input tensor
y = torch.zeros(3) # expected output
w = torch.randn(5, 3, requires_grad=True)
b = torch.randn(3, requires_grad=True)
z = torch.matmul(x, w)+b
loss = torch.nn.functional.binary_cross_entropy_with_logits(z, y)
张量,函数和计算图
这段代码定义了以下计算图:

在这个网络中,w和b为参数,这是我们需要优化。因此,我们需要能够针对这些变量计算损失函数的梯度。为此,我们设置requires_grad这些张量的属性。
在创建张量时设置requires_grad的值,也可以稍后使用x.requires_grad_(True)方法设置。
我们应用于张量以构造计算图的函数实际上是class的对象Function。该对象知道如何在正向方向上计算该函数,以及如何在反向传播步骤中计算其导数。向后传播函数的参考存储在grad_fn张量的属性中。您可以在文档中找到Function 的更多信息。
print('Gradient function for z =',z.grad_fn)
print('Gradient function for loss =', loss.grad_fn)
Gradient function for z = <AddBackward0 object at 0x7f4acdaba7b8>
Gradient function for loss = <BinaryCrossEntropyWithLogitsBackward object at 0x7f4acdaba7b8>
计算梯度
为了优化神经网络中参数的权重,我们需要计算损失函数相对于参数的导数,即,我们需要
要计算这些导数,我们调用 loss.backward(),然后从w.grad和b.grad得到这些导数
loss.backward()
print(w.grad)
print(b.grad)
tensor([[0.3230, 0.3116, 0.0060],
[0.3230, 0.3116, 0.0060],
[0.3230, 0.3116, 0.0060],
[0.3230, 0.3116, 0.0060],
[0.3230, 0.3116, 0.0060]])
tensor([0.3230, 0.3116, 0.0060])
notes
- 我们只能获得grad计算图的叶节点的requires_grad属性,这些属性的属性设置为True。对于图中的所有其他节点,gradients将不可用。
- backward由于性能原因,我们只能在给定的图形上执行一次梯度计算 。如果需要backward在同一图形上进行多个调用,则需要传递
retain_graph=True给该backward调用。
禁用梯度跟踪with torch.no_grad():
默认情况下,所有张量requires_grad=True都将跟踪其计算历史并支持梯度计算。但是,在某些情况下,我们不需要这样做,例如,当我们训练模型并只想将其应用于某些输入数据时,即我们只想通过网络进行正向计算。我们可以通过将我们的计算代码括在代码torch.no_grad()块中来停止跟踪计算 :
z = torch.matmul(x, w)+b
print(z.requires_grad)
with torch.no_grad():
z = torch.matmul(x, w)+b
print(z.requires_grad)
True
False
获得相同结果的另一种方法是在张量上使用detach()方法:
z = torch.matmul(x, w)+b
z_det = z.detach()
print(z_det.requires_grad)
False
您可能要禁用梯度跟踪的原因有以下几种:
- 将神经网络中的某些参数标记为冻结参数。这是微调预训练网络的非常常见的情况
- 为了加快运算速度,当你只是做向前传递,因为张量计算不跟踪梯度会更高效。
有关计算图的更多信息
从概念上讲,autograd在由功能 对象组成的
有向无环图directed acyclic graph (DAG)中记录数据(张量)和所有已执行的操作(以及由此产生的新张量) 。在此DAG中,叶子是输入张量,根是输出张量。通过从根到叶跟踪该图,可以使用链规则自动计算梯度。
在前向传递中,autograd同时执行两项操作:
- 运行请求的操作以计算结果张量
- 维护DAG中操作的梯度函数。
The backward pass kicks off when .backward() is called on the DAG root. autograd then:
当.backward()在DAG根目录上调用时,向后传递开始。autograd然后:
- 从每个.grad_fn计算梯度
- 将它们累积在各自的张量.grad属性中
- 使用链规则,一直传播到叶张量。
note
DAG在PyTorch中是动态的。 需要注意的重要一点是,该图是从头开始重新创建的。每次 .backward()调用后,autograd开始填充新图。这正是允许您在模型中使用控制流语句的原因。您可以根据需要在每次迭代中更改形状,大小和操作。
可选阅读:张量梯度和雅可比积
在许多情况下,我们具有标量损失函数,并且需要针对某些参数计算梯度。但是,在某些情况下,输出函数是任意张量。在这种情况下,PyTorch允许您计算所谓的Jacobian乘积,而不是实际的梯度。

inp = torch.eye(5, requires_grad=True)
out = (inp+1).pow(2)
out.backward(torch.ones_like(inp), retain_graph=True)
print("First call\n", inp.grad)
out.backward(torch.ones_like(inp), retain_graph=True)
print("\nSecond call\n", inp.grad)
inp.grad.zero_()
out.backward(torch.ones_like(inp), retain_graph=True)
print("\nCall after zeroing gradients\n", inp.grad)
First call
tensor([[4., 2., 2., 2., 2.],
[2., 4., 2., 2., 2.],
[2., 2., 4., 2., 2.],
[2., 2., 2., 4., 2.],
[2., 2., 2., 2., 4.]])
Second call
tensor([[8., 4., 4., 4., 4.],
[4., 8., 4., 4., 4.],
[4., 4., 8., 4., 4.],
[4., 4., 4., 8., 4.],
[4., 4., 4., 4., 8.]])
Call after zeroing gradients
tensor([[4., 2., 2., 2., 2.],
[2., 4., 2., 2., 2.],
[2., 2., 4., 2., 2.],
[2., 2., 2., 4., 2.],
[2., 2., 2., 2., 4.]])
注意,当我们backward第二次使用相同的参数调用时,gradients的值是不同的。发生这种情况的原因是,在进行backward传播时,PyTorch会累积梯度,即,将计算出的梯度的值添加到grad计算图的所有叶节点的 属性中。如果要计算适当的梯度,则需要先将该grad 属性归零。在现实生活中的培训中,优化程序可以帮助我们做到这一点。
note:
以前我们是在backward()不带参数的情况下调用函数。这本质上等效于call backward(torch.tensor(1.0)),这是在标量值函数(例如神经网络训练期间的损失)的情况下计算梯度的一种有用方法。
总结
torch.eye()函数:这个函数主要是为了生成对角线全1,其余部分全0的二维数组
函数原型:
result = torch.eye(n,m=None,out=None)
参数解释:
n:行数
m:列数
out:输出类型
例:
c = torch.eye(3)
print( c )
print(type( c ) )
output:
tensor([[1., 0., 0.],
[0., 1., 0.],
[0., 0., 1.]])
<class ‘torch.Tensor’>
本文介绍了PyTorch中的自动微分机制torch.autograd,它是训练神经网络的基础。通过示例展示了如何定义计算图,计算梯度以及在不需要梯度跟踪时如何使用torch.no_grad()或detach()。此外,还讨论了计算图的工作原理和Jacobian乘积的概念,强调了在多次调用.backward()时正确管理梯度的重要性。
4093

被折叠的 条评论
为什么被折叠?



