Pytorch 自动求导机制

初步了解了Pytorch如何使用自动求导,以及如何编写简单的神经网络,然后通过阅读官方文档Autograd mechanicsAutomatic differentiation package - torch.autograd以及网上各种博客笔记,记录一下对自动求导机制的个人理解。

计算图:Variable和Function

Variable

Variable的 API兼容了几乎所有Tensor API,大多数情况下,都可以将Tensor替换为Variable。
class torch.autograd.Variable中,构造函数接收三个参数:
data (any tensor class)required_grad(bool)volatile(bool)

requires_grad:之前博客中有提到,表示了一个操作中是否有输入设置了该变量。如果任一输入设置requires_grad=True,则输出Variable的该属性也为True,反之,如果所有输入变量都设置requires_grad=False,则输出Variable的该属性也为False
在训练模型中,有时我们想固定一些层,而针对某一特定层单独训练,那么我们就可以设置一些层的变量参数requires_grad=False,这样在训练中,就不会针对这些参数求梯度,从而保证了这些层不会受到训练的影响。如官方文档给出的例子:

model = torchvision.models.resnet18(pretrained=True)
for param in model.parameters():
    param.requires_grad = False
# Replace the last fully-connected layer
# Parameters of newly constructed modules have requires_grad=True by default
model.fc = nn.Linear(512, 100)

# Optimize only the classifier
optimizer = optim.SGD(model.fc.parameters(), lr=1e-2, momentum=0.9)

这里使用了预训练的模型,通过设置模型中的所有参数的属性requires_grad=False,然后替换掉最后一层全连接层,从而使模型训练时只针对最后一层全连接层。

volatile:推断模式,比设置requires_grad更加有效,可以最大程度的节省内存。volatile=True决定了requires_grad=False。当一个输入设置了volatile=True,那么输出变量的该属性也为True(意味着不需要求梯度),反之,如果所有输入变量都设置volatile=False,则输出Variable的该属性也=才为False。在实际修改模型时,只需将输入叶子节点设置属性volatile=True,不必修改任何requires_grad属性

二者的参数值传递方式相同,但是结果相反。

Variable类有以下属性:

  • data:封装的Tensor
  • grad:保存着变量的梯度,并且该变量只能赋值一次
  • requires_grad:指示了是否在创建该Variable的子图中,有指定该属性为True的Variable。该属性只能在叶子节点中被修改。
  • volatile:指示了是否用于推断模式(不保留历史信息)该属性只能在叶子节点中被修改。
  • is_leaf:Variable是否为叶子节点(如用户创建的)
  • grad_fn:创建该Variable的Function(如果该 Variable 是由用户创建的,那么该属性为 None)

在对Variable求梯度时,会通过链式法则自动求出该变量对叶子节点的梯度,同时设置叶子节点的grad属性

import torch
from torch.autograd import Variable


a = Variable(torch.Tensor([1, 2]), requires_grad=True)
b = a * a
c = b + 2
c.backward(gradient=torch.Tensor([1, 1]))
print(b.grad)
print(a.grad)
None
Variable containing:
 2
 4
[torch.FloatTensor of size 2]

.backward(gradient=None, retain_graph=None, create_graph=None, retain_variables=None)方法是用来求当前变量对叶子节点的梯度并且累加。该方法有以下参数

  • gradient:与梯度相关的Variable(Tensor自动转换为Variable),需要与所求梯度的维度匹配。
  • retain_graph:如果设置为False,在计算完梯度后,构造的计算图就会被释放。在大多数情况下,该变量都为True,并且可以提高效率。初始化值与create_graph相同。
  • create_graph:表示计算图是否创建,从而计算高阶梯度。默认为False

在上面代码中,计算c对a的梯度,由于a是二维变量,在调用.backward()时传递了参数gradient=torch.Tensor([1, 1])

当多次调用.backward()方法时,由于没有设置create_graphretain_graph,会出现RuntimeError

a = Variable(torch.Tensor([1, 2]), requires_grad=True)
b = a * a
c = b + 2
c.backward(torch.Tensor([1, 1]))
print(a.grad)

c.backward(torch.Tensor([1, 1]))
print(a.grad)
RuntimeError: Trying to backward through the graph a second time, but the buffers have already been freed. Specify retain_graph=True when calling backward the first time.

这是因为我们想在一个计算图中第二次调用backward()方法,但是由于没有设置retain_graph,计算图已经被释放掉,所以无法第二次调用。
在设置参数retain_graph后,即可正常调用

a = Variable(torch.Tensor([1, 2]), requires_grad=True)
b = a * a
c = b + 2
c.backward(torch.Tensor([1, 1]), retain_graph=True)
print(a.grad)

c.backward(torch.Tensor([1, 1]), retain_graph=True)
print(a.grad)

c.backward(torch.Tensor([1, 1]), retain_graph=True)
print(a.grad)
Variable containing:
 2
 4
[torch.FloatTensor of size 2]

Variable containing:
 4
 8
[torch.FloatTensor of size 2]

Variable containing:
  6
 12
[torch.FloatTensor of size 2]

可以看到,每次调用backward()方法时,梯度都会累加,所以大多数情况下,在调用backward()前,我们都需要先将梯度清零。

Function

在计算图中,节点是由Variable构成,边则是由Function构成,保存了一项操作中的运算方式,如加减乘除,relu等。
在对Variable进行操作时,新获得的Variable中,其.grad_fn属性就是生成该Variable的Function

import torch
from torch.autograd import Variable
import torch.nn.functional as F


a = Variable(torch.ones(2))
b = a * a
c = F.relu(a)
print(a.grad_fn)
print(b.grad_fn)
print(c.grad_fn)
None
<torch.autograd.function.MulBackward object at 0x7f0c72def228>
<torch.autograd.function.ThresholdBackward object at 0x7f0c72def318>

Function类有以下属性:

  • requires_grad :指示是否.backward()方法会被调用

Function类有两个静态方法,在自定义Function时需要重写这两个方法:

  • static backward(ctx, *grad_outputs):定义微分运算,微分顺序为拓扑排序,从根节点到叶子节点。第一个参数为一个上下文对象,之后接收的参数需要与forward()方法的输出相对应。上下文对象用来存放变量,使变量可以在forward()中取回

  • static forward(ctx, *args, **kwargs):定义一个Function的运算。第一个参数为一个上下文对象,之后可以接收任意参数,输入参数需要与backward()的输出相对应。上下文对象用来存放变量,使变量可以在backward()中取回

简言之,上下文对象ctx就是在两个方法中传递变量,两个方法中,任意一个的输入都需要与另一个的输出相对应。

自定义Function

假设我们需要定义一个线性函数 y = w x + b ,那么在forward()中需要定义该操作,在backward()中需要定义微分操作。

import torch
from torch.autograd import Variable
from torch.autograd import Function

class MyFunction(Function):

    @staticmethod
    def forward(ctx, w, x, b):
        ctx.save_for_backward(w,x)
        output = w * x + b
        return output

    @staticmethod
    def backward(ctx, output):
        w,x = ctx.saved_variables
        grad_w = output * x
        grad_x = output * w
        grad_b = output * 1
        return grad_w, grad_x, grad_b

之后尝试使用该Function,通过.apply()传递参数

x = Variable(torch.rand(1), requires_grad = True)
w = Variable(torch.rand(1), requires_grad = True)
b = Variable(torch.rand(1), requires_grad = True)
y = MyFunction.apply(w, x, b)
y.backward()
print(w, x, b)
print(w.grad, x.grad, b.grad)
Variable containing:
 0.2667
[torch.FloatTensor of size 1]
 Variable containing:
 0.2042
[torch.FloatTensor of size 1]
 Variable containing:
 0.2314
[torch.FloatTensor of size 1]

Variable containing:
 0.2042
[torch.FloatTensor of size 1]
 Variable containing:
 0.2667
[torch.FloatTensor of size 1]
 Variable containing:
 1
[torch.FloatTensor of size 1]

猜你喜欢

转载自blog.csdn.net/ljm_9615/article/details/79582900