一.前言
本章节我们是要学习梯队计算,⾃动微分(Autograd)模块对张量做了进⼀步的封装,具有⾃动求导功能。⾃动微分模块是构成神经⽹络 训练的必要模块,在神经⽹络的反向传播过程中,Autograd 模块基于正向计算的结果对当前的参数进⾏微 分计算,从⽽实现⽹络权重参数的更新。
二.梯度基本计算
我们使⽤ backward ⽅法、grad 属性来实现梯度的计算和访问.
import torch
# 1. 单标量梯度的计算
# y = x**2 + 20
def test01():
# 定义需要求导的张量
# 张量的值类型必须是浮点类型
x = torch.tensor(10, requires_grad=True, dtype=torch.float64)
# 变量经过中间运算
f = x ** 2 + 20
# ⾃动微分
f.backward()
# 打印 x 变量的梯度
# backward 函数计算的梯度值会存储在张量的 grad 变量中
print(x.grad)
# 2. 单向量梯度的计算
# y = x**2 + 20
def test02():
# 定义需要求导张量
x = torch.tensor([10, 20, 30, 40], requires_grad=True, dtype=torch.float64)
# 变量经过中间计算
f1 = x ** 2 + 20
# 注意:
# 由于求导的结果必须是标量
# ⽽ f 的结果是: tensor([120., 420.])
# 所以, 不能直接⾃动微分
# 需要将结果计算为标量才能进⾏计算
f2 = f1.mean() # f2 = 1/2 * x 2x/4
# ⾃动微分
f2.backward()
# 打印 x 变量的梯度
print(x.grad)
if __name__ == '__main__':
test01()
test02()
tensor(20., dtype=torch.float64)
tensor([ 5., 10., 15., 20.], dtype=torch.float64)
三.控制梯度计算
我们可以通过⼀些⽅法使得在 requires_grad=True 的张量在某些时候计算不进⾏梯度计算。
import torch
# 1. 控制不计算梯度
def test01():
x = torch.tensor(10, requires_grad=True, dtype=torch.float64)
print(x.requires_grad)
# 第⼀种⽅式: 对代码进⾏装饰
with torch.no_grad():
y = x ** 2
print(y.requires_grad)
# 第⼆种⽅式: 对函数进⾏装饰
@torch.no_grad()
def my_func(x):
return x ** 2
print(my_func(x).requires_grad)
# 第三种⽅式
torch.set_grad_enabled(False)
y = x ** 2
print(y.requires_grad)
# 2. 注意: 累计梯度
def test02():
# 定义需要求导张量
x = torch.tensor([10, 20, 30, 40], requires_grad=True, dtype=torch.float64)
for _ in range(3):
f1 = x ** 2 + 20
f2 = f1.mean()
# 默认张量的 grad 属性会累计历史梯度值
# 所以, 需要我们每次⼿动清理上次的梯度
# 注意: ⼀开始梯度不存在, 需要做判断
if x.grad is not None:
x.grad.data.zero_()
f2.backward()
print(x.grad)
# 3. 梯度下降优化最优解
def test03():
# y = x**2
x = torch.tensor(10, requires_grad=True, dtype=torch.float64)
for _ in range(5000):
# 正向计算
f = x ** 2
# 梯度清零
if x.grad is not None:
x.grad.data.zero_()
# 反向传播计算梯度
f.backward()
# 更新参数
x.data = x.data - 0.001 * x.grad
print('%.10f' % x.data)
if __name__ == '__main__':
test01()
# print('--------------------')
# test02()
# print('--------------------')
# test03()
这里得分开打印,就不在展示结果了,大家打印一下看看。
四.梯度计算注意
当对设置 requires_grad=True 的张量使⽤ numpy 函数进⾏转换时, 会出现如下报错:
Can't call numpy() on Tensor that requires grad. Use tensor.detach().numpy() instead.
此时, 需要先使⽤ detach 函数将张量进⾏分离, 再使⽤ numpy 函数.
注意: detach 之后会产⽣⼀个新的张量, 新的张量作为叶⼦结点,并且该张量和原来的张量共享数据, 但是分 离后的张量不需要计算梯度。
import torch
# 1. detach 函数⽤法
def test01():
x = torch.tensor([10, 20], requires_grad=True, dtype=torch.float64)
# Can't call numpy() on Tensor that requires grad. Use tensor.detach().numpy() instead.
# print(x.numpy()) # 错误
print(x.detach().numpy()) # 正确
# 2. detach 前后张量共享内存
def test02():
x1 = torch.tensor([10, 20], requires_grad=True, dtype=torch.float64)
# x2 作为叶⼦结点
x2 = x1.detach()
# 两个张量的值⼀样: 140421811165776 140421811165776
print(id(x1.data), id(x2.data))
x2.data = torch.tensor([100, 200])
print(x1)
print(x2)
# x2 不会⾃动计算梯度: False
print(x2.requires_grad)
if __name__ == '__main__':
test01()
test02()
结果展示:
[10. 20.]
1834543349008 1834543349008
tensor([10., 20.], dtype=torch.float64, requires_grad=True)
tensor([100, 200])
False
五.总结
本⼩节主要讲解了 PyTorch 中⾮常重要的⾃动微分模块的使⽤和理解。我们对需要计算梯度的张量需要设 置 requires_grad=True 属性,并且需要注意的是梯度是累计的,在每次计算梯度前需要先进⾏梯度清零。