自动微分

我们知道在pytorch是支持自动微分的,也就是自动求导数,在深度学习框架中,我们一般会求loss函数关于可学习参数的偏导数。

import torch
x = torch.arange(4.0)
# x=tensor([0., 1., 2., 3.])
x.requires_grad_(True) # 等价于x=torch.arange(4.0,requires_grad=True)
x.grad # 默认值是None

如果我们将来需要计算关于某个 变量 的偏导数,例如:y=f(x)y=f(x)xx,则我们使用

x.requires_grad_(True)

or

x=torch.arange(4.0,requires_grad=True)

两种方式来给xx打上变量标记,这里变量的意思表示,未来我们希望修改xx的值。

深度学习框架中的变量

我们知道,深度学习中我们需要更新参数,这里参数θ\theta其实就是变量,而样本xx却不是变量。

很多深度学习任务可以表示为下面的数学形式:

y^=fθ(x)loss=(x,y)StrainD(fθ(x),y)nθ=argminθloss\hat{y}=f_{\theta}(x)\\\\ loss=\frac{\sum_{(x,y)\in S_{train}} D(f_{\theta}(x),y)}{n}\\\\ \theta^*=\arg\min_{\theta} loss

这里我们的loss函数是关于StrainS_{train}θ\theta的函数,但是这里StrainS_{train}并不是变量,或者说我们不希望更新StrainS_{train}

θ\theta才是变量,我们希望迭代更新θ\theta,使得其逼近θ\theta^*,这里我们可以采用很多优化方法,最常见的就是梯度下降法,这时我们自然要求,loss函数关于θ\theta的偏导数:lossθ\frac{\partial loss}{\partial \theta}

反向传播和梯度清零

我们定义了变量后,我们只要是对变量进行的计算,都会自动求偏导数

import torch
x=torch.arange(4.0,requires_grad=True)
y = x.sum()

这里y是使用x计算的,那么在计算y的同时,就会在y中存储一个梯度计算方法。

若我们希望查看此时y关于x的偏导数,则需要

y.backward()
x.grad

即先反向传播计算出偏导数,并将偏导数累加在x中,这时我们采用x.grad就能查看x中累加的梯度。

而在实际应用中,我们有时候不希望梯度累加,这时候我们就需要梯度清零

x.grad.zero_()

注意:

  1. 当x刚被初始化时,无法使用x.grad.zero_(),因为此时其梯度为none
  2. 梯度清零清除的是x中的累积梯度,而不会清除y中的梯度计算方法
import torch
x=torch.arange(4.0,requires_grad=True)
y = x.sum()
y.backward()
x.grad # tensor([1., 1., 1., 1.])
z=x.sum()
x.grad.zero_()
z.backward()
x.grad # tensor([1., 1., 1., 1.])

上面若不使用x.grad.zero_()则第二个输出为tensor([2.,2.,2.,2.]),并且在使用x.grad.zero_()后,z仍然可以使用反向传播,来计算梯度累加在x上。

深度学习框架下的梯度清零

在深度学习任务中,我们常常会分批次进行梯度下降(stochastic gradient descent),这里就要求我们在计算每一批次后梯度清零。

num_epochs = 3
for epoch in range(num_epochs):
for X, y in data_iter:
l = loss(net(X) ,y)
trainer.zero_grad()
l.backward()
trainer.step()

例如上面中X,y就是训练集中的一个批次,我们在进行梯度下降前是需要使用梯度清零的。

with torch.no_grad() 和 x.detach()

import torch
x=torch.arange(4.0,requires_grad=True)
y = x.sum()
y.backward()
x_nograd=x.detach()

有时我们需要获得,tensor的值而不需要其梯度,我们可以采用x.detach()

在不需要计算梯度时,我们可以使用with torch.no_grad():临时禁用梯度,在深度学习框架中,这点非常常见,因为只有在训练模型中的推理过程中我们才需要计算梯度,在其它任何时候,只要是关于变量的计算,我们都不希望其发生自动微分,例如:模型验证阶段,模型部署后

# 假设 model 是训练好的神经网络,X 是输入数据
with torch.no_grad():
output = model(X) # 这里不会计算梯度