自动微分 (Autograd) 模块对张量做了进一步的封装,具有自动求导功能。自动微分模块是构成神经网络训练的必要模块,在神经网络的反向传播过程中,Autograd 模块基于正向计算的结果对当前的参数进行微分计算,从而实现网络权重参数的更新。
梯度基本计算
使用 backward 方法,grad 属性来实现梯度的计算和访问。
import torch
import numpy as np
# 标量的梯度计算
def test01():
# 对于需要求导的张量,需设置 requires_grad = True
x = torch.tensor(10, requires_grad=True, dtype=torch.float64)
# 对 x 的中间计算
f = x ** 2 + 20 # 求导获得 2x
# 自动微分
f.backward()
# 访问梯度
print(x.grad)
# 向量的梯度计算
def test02():
x = torch.tensor([10, 20, 30, 40], requires_grad=True, dtype=torch.float64)
# 定义变量的计算过程
y1 = x ** 2 + 20
# 注意:自动微分的时候,必须是一个标量
y2 = y1.mean() # 对 y1 / 4 的操作
# 自动微分,求导
y2.backward()
print(x.grad)
# 多标量梯度计算
def test03():
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.backward()
# 打印梯度值
print(x1.grad)
print(x2.grad)
# 多向量的梯度计算
def test04():
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
# 将输出结果变为标量
y = y.sum()
# 自动微分
y.backward()
# 打印张量的梯度值
print(x1.grad)
print(x2.grad)
if __name__ == "__main__":
test04()
控制梯度计算
当 requires_grad = True 时,张量在某些时候计算不进行梯度计算。
import torch
import numpy as np
# 控制梯度计算
# 训练时才用到梯度计算
def test01():
x = torch.tensor(10, requires_grad=True, dtype=torch.float64)
print(x.requires_grad)
# 1. 第一钟方法
with torch.no_grad():
y = x**2
print(y.requires_grad)
#2. 针对函数
# 第二种方式
@torch.no_grad()
def my_func(x):
return x ** 2
y = my_func(x)
print(y.requires_grad)
#3. 第三种方式: 全局的方式
torch.set_grad_enabled(False)
y = x ** 2
print(y.requires_grad)
# 梯度累加和梯度清零
def test02():
x = torch.tensor([10, 20, 30, 40], requires_grad=True, dtype=torch.float64)
# 当我们重复对x进行梯度计算的时候,是会将历史的梯度值累加到 x.grad 属性中
# 不要取累加历史梯度
for _ in range(3):
# 对输入x的计算过程
f1 = x ** 2 + 20
# 将向量转换为标量
f2 = f1.mean()
# 梯度清零
if x.grad is not None:
x.grad.data.zero_()
# 自动微分
f2.backward()
print(x.grad)
# 梯度下降优化函数
def test03():
x = torch.tensor(10, requires_grad=True, dtype=torch.float64)
for _ in range(100):
# 正向计算
y = x ** 2
# 梯度清零
if x.grad is not None:
x.grad.data.zero_()
# 自动微分
y.backward()
# 更新参数
x.data = x.data - 0.001 * x.grad
# 打印 x 的值
print('%.10f' % x.data)
if __name__ == "__main__":
test03()
梯度计算注意点
当对设置 requires_grad = True 的张量使用 numpy 函数进行转换时,会出现如下错误:
Can't call numpy() on Tensor that requires grad. Use tensor.detach().numpy() instead.
此时,需要先使用 detach 函数将张量进行分离,再使用 numpy 函数。
注意:detach 之后会产生一个新的张量,新的张量做为叶子节点并且该张量和原来的张量共享数据,但是分离后的张量不需要计算梯度。
import torch
import numpy as np
# 错误方式
def test01():
x = torch.tensor([10, 20], requires_grad=True, dtype=torch.float64)
# RuntimeError: Can't call numpy() on Tensor that requires grad. Use tensor.detach().numpy() instead.
# print(x.numpy())
# 正确的做法
print(x.detach().numpy())
# 共享数据
def test02():
# x 是叶子节点
x1 = torch.tensor([10, 20], requires_grad=True, dtype=torch.float64)
# 使用 detach 函数来分离出一个新的张量
x2 = x1.detach()
print(id(x1.data), id(x2.data))
# 修改分离后产生的新的张量
x2[0] = 100
print(x1)
print(x2)
# 通过结果我么发现,x2 张量不存在 requires_grad = True
# 表示:对 x1 的任何计算都会影响到对 x1 的梯度计算
# 但是,对 x2 的任何计算不会影响到 x1 的梯度计算
print(x1.requires_grad)
print(x2.requires_grad)
if __name__ == "__main__":
test02()