自动求导机制
PyTorch 中所有神经网络的核心是 autograd 包。 我们先简单介绍一下这个包,然后训练第一个简单的神经网络。
autograd包为张量上的所有操作提供了自动求导。 它是一个在运行时定义的框架,这意味着反向传播是根据你的代码来确定如何运行,并且每次迭代可以是不同的。
张量(Tensor)
torch.Tensor是这个包的核心类,如果设置.requires_grad为True, 那么就会追踪所有对于该张量的操作。当完成计算后通过调用.backward(),自动计算所有梯度,这个张量的所有梯度都会自动累计到.grad属性。
要阻止张量跟踪历史记录,可以调用.detach()方法将其与计算历史记录分离,并禁止跟踪它将来的计算记录。
推断时 为了防止跟踪历史记录(和使用内存),可以将代码块包装在with torch.no_grad():中。 在评估模型时特别有用,因为模型可能具有requires_grad = True的可训练参数,但是我们不需要梯度计算。
在自动梯度计算中还有另外一个重要的类Function.
Tensor和Function互相连接并生成一个非循环图,它表示和存储了完整的计算历史,每个张量都由.grad_fn属性,这个属性引用了一个创建了Tensor的Function(除非这个Tensor是由用户手动创建,即,该张量的.grad_fn是None)
如果需要计算导数,你可以再Tensor上调用.backward()。如果Tensor是一个标量(即它包含一个元素数据)则不需要为backward()指定任何参数,但是如果有更多元素,你需要指定一个gradient参数来匹配张量形状。
import torch
x = torch.ones(2,2,requires_grad=True)
print(x)
# tensor([[1., 1.],
# [1., 1.]], requires_grad=True)
y = x+2
print(y)
#tensor([[3., 3.],
# [3., 3.]], grad_fn=<AddBackward0>)
x.requires_grad_(True)
# 可以用于改变requires_grad属性
梯度
反向传播因为out是一个纯量(scalar), out.backward()等于out.backward(torch.tensor(1))
out.backward()
# 打印 d(out)/dx 梯度
print(x.grad)
如果.requires_grad = True但是又不希望进行autograd计算,可以将变量包裹在with torch.no_grad()中
总结流程:
- 当我们执行z.backward()的时候,这个操作将调用z里面的grad_fn属性,执行求导的操作
- 这个操作将遍历grad_fn的next_functions, 然后分别取出里面的Function(AccumulateGrad), 执行求导操作,这个部分是一个递归的过程直到最后类型为叶子节点。
- 计算出结果以后,将结果保存在他们对应的variable这个变量所引用的对象(x和y)的grad这个属性里面
- 求导结束。所有叶节点的grad变量都得到相应的更新
最终当我们执行完c.backward()之后,a和b里面的grad值就得到了更新。
扩展Autograd
如果需要自定义autograd扩展新的功能,需要扩展Function类,因为Function使用autograd来计算结果和梯度,并对操作历史进行编码。在Function类中最主要的方法是forward()和backward()他们分别代表前向传播和后向传播。
一个自定义的Function需要以下三个方法:
init(optional):如果该操作需要额外的参数,则需要定义该function的构造函数,不需要可以省略
forward(): 执行前向传播的计算代码
backward():执行后向传播的计算代码
# 引入Function便于扩展
from torch.autograd.function import Function
# 定义一个乘以常数的操作(输入参数是张量)
# 方法必须是静态方法,所以要加上@staticmethod
class MulConstant(Function):
@staticmethod
def forward(ctx, tensor, constant):
# ctx 用来保存信息这里类似self,并且ctx的属性可以在backward中调用
ctx.constant=constant
return tensor *constant
@staticmethod
def backward(ctx, grad_output):
# 返回的参数要与输入的参数一样.
# 第一个输入为3x3的张量,第二个为一个常数
# 常数的梯度必须是 None.
return grad_output, None
# 测试定义的Function
a=torch.rand(3,3,requires_grad=True)
b=MulConstant.apply(a,5)
print("a:"+str(a))
print("b:"+str(b)) # b为a的元素乘以5
#a:tensor([[0.0118, 0.1434, 0.8669],
# [0.1817, 0.8904, 0.5852],
# [0.7364, 0.5234, 0.9677]], #requires_grad=True)
#b:tensor([[0.0588, 0.7169, 4.3347],
# [0.9084, 4.4520, 2.9259],
# [3.6820, 2.6171, 4.8386]], grad_fn=<MulConstantBackward>)
# 反向传播,返回值不是标量,所以backward要参数
b.backward(torch.ones_like(a))
a.grad
#tensor([[1., 1., 1.],
# [1., 1., 1.],
# [1., 1., 1.]])