在训练神经网络时,最常用的算法是反向传播。在该算法中,根据损失函数相对于给定参数的梯度来调整参数(模型权重)。
为了计算这些梯度,PyTorch内置torch.argrad
,它支持梯度的自动计算。
考虑最简单的一层神经网络,输入x,参数w和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 w w 和 b b b是需要优化的参数,因此,需要能够计算损失函数相对于这些变量的梯度requires_grad
我们应用于张量来构造计算图的函数实际上是函数类的对象。这个对象知道如何计算正向传播的函数,以及如何在反向传播步骤中计算其导数。对反向传播函数的引用存储在张量的grad_fn
属性中。
print(f"Gradient function for z = {z.grad_fn}")
print(f"Gradient function for loss = {loss.grad_fn}")
Gradient function for z =
Gradient function for loss =
为了优化神经网络中的参数权重,我们需要计算损失函数相对于参数的导数,即,我们需要 ∂ l o s s ∂ w \frac{\partial loss}{\partial w} ∂w∂loss和 ∂ l o s s ∂ b \frac{\partial loss}{\partial b} ∂b∂loss在固定的 x x x和 y y y下的值。为了计算这些导数,我们调用loss.backward(),然后从w.grad和b.grad中检索值:
loss.backward()
print(w.grad)
print(b.grad)
tensor([[0.2727, 0.3044, 0.2571],
[0.2727, 0.3044, 0.2571],
[0.2727, 0.3044, 0.2571],
[0.2727, 0.3044, 0.2571],
[0.2727, 0.3044, 0.2571]])
tensor([0.2727, 0.3044, 0.2571])
默认情况下,requires_grad=True的所有张量都在跟踪其计算历史并支持梯度计算。但在某些情况下,我们不需要这样做,例如,当我们训练了模型,只想通过网络进行前向计算时,我们可以通过用torch.no_grad()
包围我们的计算代码来停止梯度跟踪:
z = torch.matmul(x, w)+b
print(z.requires_grad)
# True
with torch.no_grad():
z = torch.matmul(x, w)+b
print(z.requires_grad)
# False
另一种可以实现相同效果的方法是在张量上使用detach()
方法
z = torch.matmul(x, w)+b
z_det = z.detach()
print(z_det.requires_grad)
# False
禁用梯度跟踪的两种可能情况:
autograd在由函数对象组成的有向无环图(DAG)中记录数据以及所有执行的操作。在此DAG中叶子节点是输入张量,根节点是输出张量。从根节点到叶子节点进行跟踪就可使用链式法则自动计算梯度。
在前向传播的过程中,autograd会做以下两件事:
在DAG的根节点上调用.backward()时,就会进行反向传播:
.grad
属性中pytorch允许计算雅可比乘积。
对向量函数 y → = f ( x → ) \overrightarrow{y}=f(\overrightarrow{x}) y=f(x),当 x → = ⟨ x 1 , . . . , x n ⟩ \overrightarrow{x}=\langle x_1,...,x_n \rangle x=⟨x1,...,xn⟩、 y → = ⟨ y 1 , . . . , y m ⟩ \overrightarrow{y}=\langle y_1,...,y_m \rangle y=⟨y1,...,ym⟩, y → \overrightarrow{y} y相对于 x → \overrightarrow{x} x的梯度可由雅可比矩阵得出:
J = ( ∂ y 1 ∂ x 1 … ∂ y 1 ∂ x n ⋮ ⋱ ⋮ ∂ y m ∂ x 1 … ∂ y m ∂ x n ) J=\left( \begin{array}{c} \frac{\partial y_1}{\partial x_1} & \dots & \frac{\partial y_1}{\partial x_n} \\ \vdots & \ddots & \vdots \\ \frac{\partial y_m}{\partial x_1} & \dots & \frac{\partial y_m}{\partial x_n} \end{array} \right) J= ∂x1∂y1⋮∂x1∂ym…⋱…∂xn∂y1⋮∂xn∂ym
PyTorch允许计算给定输入向量 v = ( v 1 , . . . , v m ) v=(v_1, ...,v_m) v=(v1,...,vm)的雅可比乘积 v T ⋅ J v^T\cdot J vT⋅J,而不是计算雅可比矩阵本身。这是通过以 v v v为自变量反向传播来实现的。 v v v的大小与原始张量的大小相同:
inp = torch.eye(4, 5, requires_grad=True)
out = (inp+1).pow(2).t()
out.backward(torch.ones_like(out), retain_graph=True)
print(f"First call\n{inp.grad}")
out.backward(torch.ones_like(out), retain_graph=True)
print(f"\nSecond call\n{inp.grad}")
inp.grad.zero_()
out.backward(torch.ones_like(out), retain_graph=True)
print(f"\nCall after zeroing gradients\n{inp.grad}")