天天看点

关于torch.autograd

关于torch.autograd

本文来自于pytorch官网toturials。

背景

Neural networks (NNs) 神经网络是一系列网状的函数组成,这些函数由一些参数定义(这些参数包括weights和biases),而且这些参数在pytorch中以tensor的形式存储。

训练一个神经网络分为两部:

前向传播Forward Propagation:

前向传播中,输入的数据经过网状的函数,得出一个guess的结果。

后向传播Backward Propagation:

后向传播中,神经网络根据猜测结果中的错误,成比例的调整参数。神经网络通过结果往回倒,采集函数参数(W和B)的梯度,根据梯度下降法来优化函数中的参数。

pytorch当中的用法

Let’s take a look at a single training step. For this example, we load a pretrained resnet18 model from torchvision. We create a random data tensor to represent a single image with 3 channels, and height & width of 64, and its corresponding label initialized to some random values.用resnet18模型,随机的输入数据和label。

import torch, torchvision
model = torchvision.models.resnet18(pretrained=True)
data = torch.rand(1, 3, 64, 64)
labels = torch.rand(1, 1000)
           

Next, we run the input data through the model through each of its layers to make a prediction. This is the forward pass.通过模型处理输入数据得到预测,这是前向传播。

We use the model’s prediction and the corresponding label to calculate the error (loss). The next step is to backpropagate this error through the network. Backward propagation is kicked off when we call .backward() on the error tensor. Autograd then calculates and stores the gradients for each model parameter in the parameter’s .grad attribute.用预测和label来计算loss,下一步就是反向传播,当loss这个tensor上的.backward()这个函数被调用的时候,反向传播开始。autograd然后就开始计算模型里每个参数的梯度,并存储在模型参数的.grad属性里。

loss = (prediction - labels).sum()
loss.backward() # backward pass
           

Next, we load an optimizer, in this case SGD with a learning rate of 0.01 and momentum of 0.9. We register all the parameters of the model in the optimizer.然后载入优化器,此处用SGD,我们在优化器中注册模型中的所有参数。

Finally, we call .step() to initiate gradient descent. The optimizer adjusts each parameter by its gradient stored in .grad.最后,调用.step()来开始梯度下降,优化器通过每个参数存储在.grad中的梯度来调整这些参数。

Differentiation in Autograd(Autograd中的微分)

让我们来看看autograd是如何收集梯度的。我们设置两个tensor,并设置requires_grad=True,这意味着告诉了autograd:这些tensor上的每一个操作都会被追踪(track)。

import torch
a = torch.tensor([2., 3.], requires_grad=True)
b = torch.tensor([6., 4.], requires_grad=True)
           

我们通过tensor a和b创建tensor Q:

我们假设a和b是神经网络(NN)的参数,Q是error或者说loss,在神经网络的训练中,我们需要关于参数的error的梯度。we want gradients of the error w.r.t. parameters, i.e.

∂Q/∂a=9a2 ,∂Q/∂b=−2b

然后我们调用Q上的.backward()函数,然后autograd就会计算上面的梯度,并把它们存储在各个tensor的.grad属性中。

我们需要明确的在Q.backward()中传递一个gradient参数,因为梯度是一个向量。gradient参数是一个和Q相同形状的tensor,它代表着Q自己的梯度。

dQ/dQ=1

与之等价的是,我们可以对Q求和成一个标量,并且明确的调用backward,比如:Q.sum().backward()

(我理解是可以求和成为标量以后再调用.backward()实现自动梯度,也可以用下面的方法,即通过传入这个tensor Q对自己的梯度作为参数,也就是[1,1],传入.backward()实现梯度的反向传播,也就是利用雅各比矩阵与向量的乘积实现链式法则,求梯度)

external_grad = torch.tensor([1., 1.])
Q.backward(gradient=external_grad)
           

梯度向在就被存储到a.grad和b.grad中了。

# check if collected gradients are correct
print(9*a**2 == a.grad)
print(-2*b == b.grad)
           

输出:

tensor([True, True])
tensor([True, True])
           

计算图

autograd在一个由函数对象组成的有向无环图(DAG)上保持着数据(tensor)和在数据上进行的操作,在这个DAG中,叶子节点是input tensor,根结点是output tensor,由根到叶子,我们就可以用链式法则计算梯度。

在前向传播中,autograd同时做了两件事:

  • 计算tensor的操作
  • 在DAG中维持各个操作的梯度函数

在DAG根结点调用.backward()的时候,反向传播开始,然后autograd开始:

  • 从各自的.grad_fn属性中计算梯度
  • 在对应tensor的.grad属性中累积梯度
  • 用链式法则反向传播,直到叶子节点

下面是DAG图可视化,箭头正向传播,蓝色时叶子节点,代表tensor a和b。在pytorch中,DAG计算图是动态的,每次.backward()调用,autograd就会从零开始生成DAG

关于torch.autograd

Exclusion from the DAG从计算图中去掉部分参数更新

如果requires_grad设置为true,torch.autograd会追踪每一个操作,不需要梯度的就可以设置为false从而从计算图中去掉它(免除掉excludes it)。

只要输入的tensor中有一个requires_grad=True,那么得出的结果就会需要梯度。

x = torch.rand(5, 5)
y = torch.rand(5, 5)
z = torch.rand((5, 5), requires_grad=True)

a = x + y
print(f"Does `a` require gradients? : {a.requires_grad}")
b = x + z
print(f"Does `b` require gradients?: {b.requires_grad}")
           

结果

Does `a` require gradients? : False
Does `b` require gradients?: True
           

在神经网络中,不需要计算梯度的参数称为frozen parameters,比如在 finetuning a pretrained network中,在微调中,我们冻结大部分参数,只修改在新labels上预测的分类层,下面是一个小例子。In finetuning, we freeze most of the model and typically only modify the classifier layers to make predictions on new labels. Let’s walk through a small example to demonstrate this. As before, we load a pretrained resnet18 model, and freeze all the parameters.

from torch import nn, optim

model = torchvision.models.resnet18(pretrained=True)

# Freeze all the parameters in the network
for param in model.parameters():
    param.requires_grad = False
           

Let’s say we want to finetune the model on a new dataset with 10 labels. In resnet, the classifier is the last linear layer model.fc. We can simply replace it with a new linear layer (unfrozen by default) that acts as our classifier.

加入我们要分为10类,resnet中最后一层的分类器是model.fc,我们那个新的替换掉他,新的默认就是不锁住的。

Now all parameters in the model, except the parameters of model.fc, are frozen. The only parameters that compute gradients are the weights and bias of model.fc.

除了model.fc,模型中其他参数都被冻结了,只有model.fc中的weights和bias会计算梯度。

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

Notice although we register all the parameters in the optimizer, the only parameters that are computing gradients (and hence updated in gradient descent) are the weights and bias of the classifier.

继续阅读