自动微分
一、应用torch.autograd模块进行自动微分
我们训练神经网络的时候,最常用的算法就是反向传播算法。在这种算法中,参数(也称为模型权重)是基于利用 与给定参数相关的 损失函数 的梯度进行调整的。损失函数衡量期望输出和NN模块实际输出的差值。为了优化整个模型性能,我们的目标就是尽可能的调整损失函数的输出值,使其接近0.该算法通过神经网络网络向后遍历来调整权重和偏差,以重新训练模型。反向传播因此命名。这种随着时间的推移对模型进行再培训以将损失减少到0的前后过程称为梯度下降法。
为了计算这些梯度,PyTorch 有一个内置的微分模块,称为 torch.autograd。它支持任意计算图像的梯度自动计算。
以一个最简单的单层神经网络为例,它有输入 x,参数 w 和 b,以及一些损失函数。它可以在 PyTorch 中以下面的方式定义:
%matplotlib inline
import torch
x = torch.ones(5) # input tensor
y = torch.zeros(3) # expected output
w = torch.randn(5, 3, requires_grad=True) # requires_grad说明当前量是否需要在计算中保留对应的梯度信息
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 是我们需要优化的参数。因此,我们需要能够计算损失函数关于这些变量的梯度。为了做到这一点,我们设置了这些张量的 require_grad 属性。
注意: require_grad可以在定义张量时传入也可以在定义之后利用x.require _ (True)方法来设置。
我们用来构造计算图的张量函数实际上是类Function的对象。这个对象知道如何向前计算函数,以及如何在向后传播步骤中计算函数的导数。在张量的 grad_fn 属性中可以调用反向传播函数。
print('Gradient function for z =',z.grad_fn)
print('Gradient function for loss =', loss.grad_fn)
Gradient function for z = <AddBackward0 object at 0x000001D5AB4DE340>
Gradient function for loss = <BinaryCrossEntropyWithLogitsBackward0 object at 0x000001D5AB463A00>
三、计算梯度
为了优化神经网络中参数的权重,需要计算损失函数对参数的导数,也就是说,我们需要计算基于最小二乘法的遗传算法。我们需要计算损失函数对权重和偏移量的偏导数: 和 ,当然这些偏导数的计算是要基于给定的x和y值的。PyTorch内置了loss.backward()方法来计算这些偏导数,可以通过w.grad 和 b.grad方法来查看对应的计算结果
loss.backward()
print(w.grad)
print(b.grad)
tensor([[0.1284, 0.3144, 0.2341],
[0.1284, 0.3144, 0.2341],
[0.1284, 0.3144, 0.2341],
[0.1284, 0.3144, 0.2341],
[0.1284, 0.3144, 0.2341]])
tensor([0.1284, 0.3144, 0.2341])
注意:我们只能得到计算图的叶子节点的梯度属性,这些叶子节点需要梯度属性requires_grad设置为 True。也就是图中的其他节点都不能用这种方法计算梯度。此外,出于性能原因,我们只能在给定图上使用向后一次的梯度计算。如果我们需要对同一个图执行多个向后调用,我们需要将 retain_graph = True 传递给向后调用。
四、禁用梯度跟踪
默认情况下,所有带 require_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 在一个由函数对象组成的有向无环图(DAG)中保存数据(张量)和所有执行的操作(以及由此产生的新张量)的记录。在这个 DAG 中,叶子是输入张量,根是输出张量。在从从根到叶的计算过程中,可以使用链式规则自动计算梯度。
在前向传播时,autograd会进行两步操作:
- 调用对应的请求来计算结果张量
- 在 DAG 中留存操作的梯度函数
若想进行后向传递,则需要将backward()方法调用至DAG根目录。
- 计算每个.grad_fn的梯度
- 将计算出的梯度保存在各张量的.grad的属性中
- 使用链式法则,将梯度一致传输到叶子张量(输入层)
另外,在 PyTorch 中,DAGs 是动态的
需要注意的是,这个图是从头开始重新创建的。在每个backward()被调用后,autograd 开始填充一个新图。若要在每次迭代中更改形状、大小和操作,可以使用对应的控制流语句;
六、可选读数: 张量梯度和雅可比乘积
在许多情况下,我们有一个标量损失函数,我们需要计算一些参数的梯度。然而,在有些情况下,输出函数是任意张量。在这种情况下,PyTorch 允许计算所谓的 Jacobian 乘积(梯度计算所得),而不是实际的梯度。
对于每一个向量函数 , 当 并且 , 关于向量的向量的梯度就通过Jacobian matrix给出, 其中的矩阵元素 也就是对应偏导 的值.
当然,我们并不需要计算雅克比矩阵,PyTorch为每一个输入向量 提供了计算雅克比乘积 的方法。这是通过传入 向量 v 调用backward方法来实现的。v的尺寸应该和我们想要输出的乘积个数以及原始张量的尺寸一致。
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.]])
请注意,当我们第二次使用相同的参数向后调用时,梯度的值是不同的。这是因为在执行向后传播时,PyTorch 累积了梯度,即将计算梯度的值添加到计算图的所有叶节点的梯度属性中。如果你想要计算正确的梯度,你需要在之前将 grad 属性归零。在现实生活中,优化器会帮助我们做到这一点。
注意: 以前我们调用的是不带参数的 backward ()函数。这相当于调用反向(torch.tensor (1.0)) ,这是一种在标量值函数情况下计算梯度的有用方法,例如在神经网络训练过程中的损失。