Pytorch自动求导技术详解(附带实例)
自动求导技术在深度学习库中处于重要地位,是整个训练算法的核心组件之一。深度学习模型的训练,就是不断更新权值,权值的更新需要求解梯度,求解梯度十分烦琐,PyTorch提供自动求导系统,我们只要搭建好前向传播的计算图,就能获得所有张量的梯度。
PyTorch 中自动求导模块中的相关函数有 torch.autograd.backward() 和 torch.autograd.grad(),下面逐一进行介绍。
PyTorch torch.autograd.backward()
该函数实现自动求取梯度,函数参数如下:torch.autograd.backward (tensors, grad_tensors=None, retain_graph=None, create_graph=False)代码说明:
- tensors:用于求导的张量,如 loss;
- grad_tensors:多梯度权重,当有多个 loss 需要计算梯度时,需要设置每个 loss 的权值;
- retain_graph:保存计算图,由于 PyTorch 采用动态图机制,在每次反向传播之后计算图都会释放掉,如果还想继续使用,就要设置此参数为 True;
- create_graph:创建导数计算图,用于高阶求导。
例如,线性的一阶导数的代码如下:
import torch # 创建一个需要梯度的张量w w = torch.tensor([1.], requires_grad=True) # 创建一个需要梯度的张量x x = torch.tensor([2.], requires_grad=True) # 将x和w相加,结果存储在a中 a = torch.add(x, w) # 将w和1相加,结果存储在b中 b = torch.add(w, 1) # 将a和b相乘,结果存储在y中 y = torch.mul(a, b) # 对y进行反向传播计算梯度 y.backward() # 打印w的梯度 print(w.grad)输出如下:
tensor([5.])
下面通过案例介绍 grad_tensors 参数的用法:
import torch # 创建一个需要梯度的张量w w = torch.tensor([1.], requires_grad=True) # 创建一个需要梯度的张量x x = torch.tensor([2.], requires_grad=True) # 将x和w相加,结果存储在a中 a = torch.add(x, w) # 将w和1相加,结果存储在b中 b = torch.add(w, 1) # 将a和b相乘,结果存储在y0中 y0 = torch.mul(a, b) # 将a和b相加,结果存储在y1中 y1 = torch.add(a, b) # 将y0和y1拼接起来,形成一个新的张量loss loss = torch.cat([y0, y1], dim=0) # 定义一个梯度张量grad_t grad_t = torch.tensor([1., 2.]) # 对loss进行反向传播计算梯度 loss.backward(gradient=grad_t) # 打印w的梯度 print(w.grad)输出如下:
tensor([9.])
其中:
y0=(x+w)x(w+1), ∂y0/∂w = 5
y1=(x+w)x(w+1), ∂y0/∂w = 2
w.grad=y0×1+y1×2=5+2× 2=9
PyTorch torch.autograd.grad()
该函数实现求取梯度,函数参数如下:
torch.autograd.grad(outputs,
inputs,
grad_outputs=None,
retain_graph=None,
create_graph=False)
- outputs:用于求导的张量,如上例中的 loss;
- inputs:需要梯度的张量,如上例中的 w;
- retain_graph:保存计算图;
- grad_outputs:多梯度权重;
- create_graph:创建导数计算图,用于高阶求导。
例如,计算 y=x^2 的二阶导数的代码如下:
import torch # 创建一个张量x,值为3.0,并设置requires_grad=True以计算梯度 x = torch.tensor([3.], requires_grad=True) # 计算x的平方,得到y y = torch.pow(x, 2) # 计算y关于x的梯度,create_graph=True表示同时计算二阶导数 grad1 = torch.autograd.grad(y, x, create_graph=True) print(grad1) # 输出一阶导数:[6.] # 计算一阶导数关于x的梯度 grad2 = torch.autograd.grad(grad1[0], x) print(grad2) # 输出二阶导数:[2.]输出如下:
(tensor([6.], grad_fn=<MulBackward0>),)
(tensor([2.]),)
注意事项
1) 梯度不能自动清零,在每次反向传播中会叠加,代码如下:import torch # 创建一个需要梯度的张量w w = torch.tensor([1.], requires_grad=True) # 创建一个需要梯度的张量x x = torch.tensor([2.], requires_grad=True) # 循环3次 for i in range(3): # 将x和w相加,结果存储在a中 a = torch.add(x, w) # 将w和1相加,结果存储在b中 b = torch.add(w, 1) # 将a和b相乘,结果存储在y中 y = torch.mul(a, b) # 计算梯度 y.backward() # 打印w的梯度 print(w.grad)输出如下:
tensor([5.])
tensor([10.])
tensor([15.])
# 创建一个需要梯度的张量w w = torch.tensor([1.], requires_grad=True) # 创建一个需要梯度的张量x x = torch.tensor([2.], requires_grad=True) # 循环3次 for i in range(3): # 将x和w相加,结果存储在a中 a = torch.add(x, w) # 将w和1相加,结果存储在b中 b = torch.add(w, 1) # 将a和b相乘,结果存储在y中 y = torch.mul(a, b) # 计算梯度 y.backward() # 打印w的梯度 print(w.grad) # 梯度清零 w.grad.zero_()输出如下:
tensor([5.])
tensor([5.])
tensor([5.])
2) 依赖于叶子节点的节点,requires_grad 默认为 True,代码如下:
# 创建一个需要梯度的张量w w = torch.tensor([1.], requires_grad=True) # 创建一个需要梯度的张量x x = torch.tensor([2.], requires_grad=True) # 将x和w相加,结果存储在a中 a = torch.add(x, w) # 将w和1相加,结果存储在b中 b = torch.add(w, 1) # 将a和b相乘,结果存储在y中 y = torch.mul(a, b) # 打印a、b、y是否需要梯度 print(a.requires_grad, b.requires_grad, y.requires_grad)输出如下:
True True True
3) 叶子节点不可以执行 in-place,因为前向传播记录了叶子节点的地址,反向传播需要用到叶子节点的数据时,要根据地址寻找数据,执行 in-place 操作改变了地址中的数据,梯度求解也会发生错误,代码如下:
# 创建一个需要梯度的张量w w = torch.tensor([1.], requires_grad=True) # 创建一个需要梯度的张量x x = torch.tensor([2.], requires_grad=True) # 将x和w相加,结果存储在a中 a = torch.add(x, w) # 将w和1相加,结果存储在b中 b = torch.add(w, 1) # 将a和b相乘,结果存储在y中 y = torch.mul(a, b) # 对w进行原地加法操作,即w = w + 1 w.add_(1)输出如下:
RuntimeError: a leaf Variable that requires grad has been used in an in-place
operation.
in-place 操作即原位操作,在原始内存中改变这个数据,代码如下:
a = torch.tensor([1]) print(id(a), a) #开辟新的内存地址 a = a + torch.tensor([1]) print(id(a), a) #in-place操作,地址不变 a += torch.tensor([1]) print(id(a), a)输出如下:
2638883967360 tensor([1])
2638883954112 tensor([2])
2638883954112 tensor([3])