Pytorch自动求导

使用Pytorch自动求导

autograd 是Pytorch框架中一个重要的包,它为张量上的所有操作提供自动求导功能。其运算机制稍后给出。

要弄清楚Pytorch自动求导,首先需要理解的是Pytorch自动求导中反向传播和计算图的概念。

计算图

给定一个表达式z = wx + b,可将其分解两个表达式,分别是z = y + b和y = wx,则其正向传播计算图如下所示:
计算图
在这个计算图中,可以看出变量w,x,b均为用户创建的,它们不依赖与其他的变量,我们称之为叶子节点,而y和z是计算得到的变量,即非叶子节点,其中z为根节点,MUL和ADD称为算子(操作或函数)。

我们的目标是更新各个叶子节点的梯度,即w,x和b,由链式法则可以推出,各叶子节点的梯度计算如下:

w偏导
x偏导
b偏导
Pytorch调用backward()方法,自动计算各节点的梯度,这是一个反向传播的过程,即从根节点z开始,反向溯源计算叶子节点w,x,b的梯度。

自动求导的要点

Pytorch中实现自动求导,需要了解一下几点:

  • 对于叶子节点的Tensor,使用requires_grad参数指定是否记录对其的操作,以便后续调用backward()方法求梯度。requires_grad默认为false,若要对其求导,则设置为True,这样与之有依赖关系的节点也会自动变为True
  • 通过requires_grad()方法来修改Tensor的requires_grad属性,也可以调用.detach()将某些张量排除在外,例如上述z = y + b和y = wx,若张量y调用了y.detach(),则计算梯度时将 y 视为一个常数,并且只考虑到 x 在y被计算后发挥的作用
  • 使用with torch.no_grad():包裹代码块的形式阻止autograd跟踪张量的历史记录
  • 对于非叶子节点,会自动被赋予grad_fn属性,用于存储记录相关的计算操作,叶子节点的grad_fn值为None
  • Tensor执行backward函数计算各变量的梯度值,结果会累加至grad属性中,计算完成后,非叶子节点的梯度值自动释放
  • 对非标量求导时,backward()需接收gradient参数,该参数应与调用backward的Tensor维度相同,实际上对于标量调用backward函数,该参数是隐含的,例如标量out自动求导,out.backward() 等同于out.backward(torch.tensor(1))
  • 若需要多次反向传播,需要指定backward中的参数retain_graph=True,在多次反向传播时,梯度是累加的

需要特别注意的是,在默认情况下,PyTorch会累积梯度,我们需要清除之前的值,可以调用grad.zero_()方法清除记录。

自动求导案例

对于上述理论,给出几个示例
x为一个向量,计算y = y=2x x的导数

x = torch.arange(4.0, requires_grad=True)
print(x)
y = 2*torch.dot(x,x)
print(y)
y.backward()
x.grad

# outputs
# x的requires_grad为True
tensor([0., 1., 2., 3.], requires_grad=True)
# y为非叶子节点,会自动赋予grad_fn属性
tensor(28., grad_fn=<MulBackward0>)
# 最终结果,y = 4x,符合推导
tensor([ 0.,  4.,  8., 12.])

此时如果继续对向量x的另一个函数计算,则需要先清除之前计算的梯度值,否则Pytorch会累加计算

print(x.grad)
x.grad.zero_()
print(x.grad)
y = x.sum()
print(y)
y.backward()
x.grad

# outputs
# 上次计算的梯度
tensor([ 0.,  4.,  8., 12.])
# 梯度清零后的x.grad
tensor([0., 0., 0., 0.])
# 新函数y = x.sum()
tensor(6., grad_fn=<SumBackward0>)
# 计算y关于x的梯度
tensor([1., 1., 1., 1.])

在计算过程中,若希望将某些计算移动到记录的计算图之外,可以使用detach函数。假设y是作为x的函数计算的,而z则是作为y和x的函数计算的。 现在我们需要计算 z 关于 x 的梯度,但由于某种原因,希望将 y 视为一个常数,我们可以分离 y 来返回一个新变量 u,该变量与 y 具有相同的值,但丢弃计算图中如何计算 y 的任何信息。

x.grad.zero_()
y = x * x
u = y.detach()
z = u * x

z.sum().backward()
# z = u*x,关于x求导,理论结果为u
x.grad == u

# output
tensor([True, True, True, True])

非标量反向传播的处理

当 y 不是标量时,向量y关于向量x的导数的最自然解释是一个矩阵。对于高阶和高维的 y 和 x,求导的结果可以是一个高阶张量。Pytorch不允许张量对张量求导,只允许标量对张量求导,所以当目标张量对一个非标量调用backward时,需要传入我们在上面提到的gradient参数,这是为了把张量对张量的求导转换为标量对张量的求导。

例如,目标值l = (y1,y2,…,ym),传入的参数为v = (v1,v2,…,vm),就可以把对l的求导,转换成为对loss*vT 的求导,即把原来偏微分得到的雅可比矩阵乘以向量vT ,所谓的"标量对向量求导"本质上是函数对各个自变量求导,即y.backward(gradient)实质上求解的是y的各分量对自变量的导数,然后乘上y对自己分量的梯度gradient。

这里我们就需要对backward()函数做一个介绍,backward函数的格式为:
backward(gradient=None, retain_graph=None, create_graph=False)
其中第一个参数就是我们在处理非标量反向传播时要传入的参数,必须与当前的调用它的变量的维度相同,例如

x = torch.tensor([0.0, 2.0, 8.0], requires_grad = True)
y = torch.tensor([5.0, 1.0, 7.0], requires_grad = True)
z = x * y
z.backward(torch.Tensor([1, 1, 1]))

x.grad, y.grad

# outputs
(tensor([5., 1., 7.]), tensor([0., 2., 8.]))

以x的梯度为例来看,z关于x求导的Jacobian应为
x的Jacobian
z调用backward传入的参数为一个1*3的张量,v = (1, 1, 1)
所以J与v相乘,就得到了x.grad
J*v

  • 7
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值