🥇接下来我们进入到Pytorch的自动微分模块torch.autograd~
自动微分模块是PyTorch中用于实现张量自动求导的模块。PyTorch通过torch.autograd
模块提供了自动微分的功能,这对于深度学习和优化问题至关重要,因为它可以自动计算梯度,无需手动编写求导代码。torch.autograd
模块的一些关键组成部分:
- 函数的反向传播:
torch.autograd.function
包含了一系列用于定义自定义操作的函数,这些操作可以在反向传播时自动计算梯度。 - 计算图的反向传播:
torch.autograd.functional
提供了一种构建计算图并自动进行反向传播的方式,这类似于其他框架中的符号式自动微分。 - 数值梯度检查:
torch.autograd.gradcheck
用于检查数值梯度与自动微分得到的梯度是否一致,这是确保正确性的一个有用工具。 - 错误检测模式:
torch.autograd.anomaly_mode
在自动求导时检测错误产生路径,有助于调试。 - 梯度模式设置:
torch.autograd.grad_mode
允许用户设置是否需要梯度,例如在模型评估时通常不需要计算梯度。 - 求导方法:PyTorch提供
backward()
和torch.autograd.grad()
两种求梯度的方法。backward()
会将梯度填充到叶子节点的.grad
字段,而torch.autograd.grad()
直接返回梯度结果。 - requires_grad属性:在创建张量时,可以通过设置
requires_grad=True
来指定该张量是否需要进行梯度计算。这样在执行操作时,PyTorch会自动跟踪这些张量的计算过程,以便后续进行梯度计算。
梯度基本计算
def func1():
x = torch.tensor(10, requires_grad=True, dtype=torch.float64)
f = x ** 2 +10
# 自动微分求导
f.backward() # 反向求导
# backward 函数计算的梯度值会存储在张量的 grad 变量中
print(x.grad)
def func2():
x = torch.tensor([10, 20, 30, 40], requires_grad=True, dtype=torch.float64)
# 变量经过中间计算
f1 = x ** 2 + 10
# f2 = f1.mean() # 平均损失,相当于每个值/4
f2 = f1.sum() # 求和损失,相当于每个值*1
f2.backward()
print(x.grad)
def func3():
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 func4():
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,x2.grad)
func1
和func2
,它们分别处理标量张量和向量张量的梯度计算。
- 在
func1
中,首先创建了一个标量张量x
,并设置requires_grad=True
以启用自动微分。然后计算f = x ** 2 + 10
,接着使用f.backward()
进行反向求导。最后,通过打印x.grad
输出梯度值。- 在
func2
中,首先创建了一个向量张量x
,并设置requires_grad=True
以启用自动微分。然后计算f1 = x ** 2 + 10
,接着使用f1.sum()
对向量张量进行求和操作,得到一个标量张量f2
。最后,使用f2.backward()
进行反向求导。func3
和func4分别求多个标量和向量的情况,与上面相似。
控制梯度计算
我们可以通过一些方法使 requires_grad=True 的张量在某些时候计算时不进行梯度计算。
- 第一种方式是使用torch.no_grad()上下文管理器,在这个上下文中进行的所有操作都不会计算梯度。
- 第二种方式是通过装饰器@torch.no_grad()来装饰一个函数,使得这个函数中的所有操作都不会计算梯度。
- 第三种方式是通过torch.set_grad_enabled(False)来全局关闭梯度计算功能,之后的所有操作都不会计算梯度,直到下一次再次调用此方法torch.set_grad_enabled(True)开启梯度计算功能。
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)
默认张量的 grad 属性会累计历史梯度值,如果需要重复计算每次的梯度,就需要手动清除。
x = torch.tensor([10, 20, 30, 40], requires_grad=True, dtype=torch.float64)
for _ in range(3):
f1 = x ** 2 + 20
f2 = f1.mean()
if x.grad is not None:
x.grad.data.zero_() # 本身来改动
f2.backward()
print(x.grad)
x.grad不是x,因为x是一个tensor张量,而x.grad是x的梯度。在PyTorch中,张量的梯度是通过自动求导机制计算得到的,而不是直接等于张量本身。
梯度下降优化最优解
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)
更新参数相当于通过学习率对当前数值进行迭代。
f.backward()是PyTorch中自动梯度计算的函数,用于计算张量`f`关于其所有可学习参数的梯度。在这个例子中,`f`是一个标量张量,它只有一个可学习参数`x`。当调用f.backward()`时,PyTorch会自动计算`f`关于`x`的梯度,并将结果存储在`x.grad`中。这样,我们就可以使用这个梯度来更新`x`的值,以便最小化损失函数`f`。
梯度计算注意
当对设置 requires_grad=True 的张量使用 numpy 函数进行转换时, 会出现如下报错:
Can't call numpy() on Tensor that requires grad. Use tensor.detach().numpy() instead.
此时, 需要先使用 detach 函数将张量进行分离, 再使用 numpy 函数。detach 之后会产生一个新的张量, 新的张量作为叶子结点,并且该张量和原来的张量共享数据, 但是分离后的张量不需要计算梯度。
import torch
def func1():
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())
def func2():
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)