首页 > 编程笔记 > Python笔记 阅读:4

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)
代码说明:
例如,线性的一阶导数的代码如下:
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)

代码说明:
例如,计算 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])

相关文章