在 Pytorch 中,自动微分(Autograd)模块对张量做了进一步的封装,具有自动求导功能。自动微分模块是构成神经网络训练的必要模块,在神经网络的反向传播过程中,Autograd 模块基于正向计算的结果对当前的参数进行微分计算,从而实现网络权重参数的更新。
1. 梯度基本计算
使用 backward 方法、grad 属性来实现梯度的计算和访问。注意:需要求导的张量,张量的值类型必须是浮点类型。
import torch
def test01(): # 1. 单标量梯度的计算
x = torch.tensor(10, requires_grad=True, dtype=torch.float64) # 定义需要求导的张量
f = x ** 2 + 20 # 变量经过中间运算
f.backward() # 自动微分
print(x.grad) # 打印 x 变量的梯度,backward 函数计算的梯度值存储在张量的 grad 属性中
def test02(): # 2. 单向量梯度的计算
x = torch.tensor([10, 20, 30, 40], requires_grad=True, dtype=torch.float64)
f1 = x ** 2 + 20 # 变量经过中间计算
f2 = f1.mean() # 注意:由于求导的结果必须是标量,而 f1 的结果是张量,所以, 不能直接自动微分,需要将结果计算为标量才能进行自动微分!
f2.backward() # 自动微分
print(x.grad) # 打印 x 变量的梯度
def test03(): # 3. 多标量梯度计算
x1 = torch.tensor(10, requires_grad=True, dtype=torch.float64)
x2 = torch.tensor(20, requires_grad=True, dtype=torch.float64)
y = x1 ** 2 + x2 ** 2 + x1 * x2 # 变量经过中间计算
y = y.sum() # 将输出结果变为标量
y.backward() # 自动微分
print(x1.grad, x2.grad) # 打印两个变量的梯度
def test04(): # 4. 多向量梯度计算
x1 = torch.tensor([10, 20], requires_grad=True, dtype=torch.float64) # 定义需要计算梯度的张量
x2 = torch.tensor([30, 40], requires_grad=True, dtype=torch.float64)
y = x1 ** 2 + x2 ** 2 + x1 * x2 # 经过中间的计算
print(y) # tensor([1300., 2800.], dtype=torch.float64, grad_fn=<AddBackward0>)
y = y.sum() # 将输出结果变为标量
y.backward() # 自动微分
print(x1.grad, x2.grad) # 打印两个变量的梯度
test01() # tensor(20., dtype=torch.float64)
test02() # tensor([ 5., 10., 15., 20.], dtype=torch.float64)
test03() # tensor(40., dtype=torch.float64) tensor(50., dtype=torch.float64)
test04() # tensor([50., 80.], dtype=torch.float64) tensor([ 70., 100.], dtype=torch.float64)
2. 控制梯度计算
在模型进行评估或者预测时一般不再进行求导,因此可以通过一些方法使得在 requires_grad 为 True 的张量不进行梯度计算,示例如下:
import torch
def test01(): # 1. 控制不计算梯度
x = torch.tensor(10, requires_grad=True, dtype=torch.float64)
print(x.requires_grad) # True
# 第一种方式: 对代码进行装饰
with torch.no_grad():
y = x ** 2
print(y.requires_grad) # False
# 第二种方式: 对函数进行装饰
@torch.no_grad()
def my_func(x):
return x ** 2
print(my_func(x).requires_grad) # False
# 第三种方式
torch.set_grad_enabled(False)
y = x ** 2
print(y.requires_grad) # False
def test02(): # 2. 注意: 累计梯度
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) # tensor([ 5., 10., 15., 20.], dtype=torch.float64)
def test03(): # 3. 梯度下降优化最优解
x = torch.tensor(10, requires_grad=True, dtype=torch.float64)
for _ in range(100):
f = x ** 2 # 正向计算
if x.grad is not None:
x.grad.data.zero_() # 梯度清零
f.backward() # 反向传播计算梯度
x.data = x.data - 0.05 * x.grad # 更新参数
print('%.10f' % x.data)
test01()
test02()
test03()
3. 梯度计算注意
当对设置 requires_grad 为 True 的张量使用 numpy 函数进行转换时,会出现如下报错:Can’t call numpy() on Tensor that requires grad. Use tensor.detach().numpy() instead.
此时,需要先使用 detach 函数将张量进行分离,再使用 numpy 函数。
注意:detach 之后会产生一个新的张量,新的张量作为叶子结点,并且该张量和原来的张量共享数据,但是分离后的张量不需要计算梯度。
import torch
def test01(): # 1. detach 函数用法
x = torch.tensor([10, 20], requires_grad=True, dtype=torch.float64)
print(x.numpy()) # RuntimeError: Can't call numpy() on Tensor that requires grad. Use tensor.detach().numpy() instead.
print(x.detach().numpy()) # [10. 20.]
def test02(): # 2. detach 前后张量共享内存
x1 = torch.tensor([10, 20], requires_grad=True, dtype=torch.float64)
x2 = x1.detach() # x2 作为叶子结点
print(id(x1.data), id(x2.data)) # 一样: 51365424 51365424
x2.data = torch.tensor([100, 200])
print(x1, x2) # tensor([10., 20.], dtype=torch.float64, requires_grad=True) tensor([100, 200])
print(x2.requires_grad) # False(x2 不会自动计算梯度)
test01()
test02()
以上。