PyTorch 学习 -2- 自动求导

2023-07-20 20:47:33 浏览数 (1)

本文介绍张量自动求导的基本知识 。

参考 深入浅出PyTorch ,系统补齐基础知识。

本节目录

  • autograd的求导机制
  • 梯度的反向传播

前言

PyTorch 中,所有神经网络的核心是 autograd 包。autograd 包为张量上的所有操作提供了自动求导机制。它是一个在运行时定义 ( define-by-run )的框架,这意味着反向传播是根据代码如何运行来决定的,并且每次迭代可以是不同的。

Autograd

torch.Tensor 是这个包的核心类。如果设置它的属性 .requires_gradTrue,那么它将会追踪对于该张量的所有操作。当完成计算后可以通过调用 .backward(),来自动计算所有的梯度。这个张量的所有梯度将会自动累加到.grad属性。

注意:在 y.backward() 时,如果 y 是标量,则不需要为 backward() 传入任何参数;否则,需要传入一个与 y 同形的 Tensor。

阻止梯度

要阻止一个张量被跟踪历史,可以调用.detach()方法将其与计算历史分离,并阻止它未来的计算记录被跟踪。为了防止跟踪历史记录(和使用内存),可以将代码块包装在 with torch.no_grad(): 中。在评估模型时特别有用,因为模型可能具有 requires_grad = True 的可训练的参数,但是我们不需要在此过程中对他们进行梯度计算。

Function

还有一个类对于autograd的实现非常重要:FunctionTensorFunction 互相连接生成了一个无环图 (acyclic graph),它编码了完整的计算历史。每个张量都有一个.grad_fn属性,该属性引用了创建 Tensor 自身的Function(除非这个张量是用户手动创建的,即这个张量的grad_fnNone )。下面给出的例子中,张量由用户手动创建,因此grad_fn返回结果是None。

12345

from __future__ import print_functionimport torchx = torch.randn(3,3,requires_grad=True)print(x.grad_fn)None

计算导数

如果需要计算导数,可以在 Tensor 上调用 .backward()。如果 Tensor 是一个标量(即它包含一个元素的数据),则不需要为 backward() 指定任何参数,但是如果它有更多的元素,则需要指定一个gradient参数,该参数是形状匹配的张量。

创建一个张量并设置requires_grad=True用来追踪其计算历史

1234

x = torch.ones(2, 2, requires_grad=True)print(x)tensor([1., 1., 1., 1.], requires_grad=True)

对这个张量做一次运算:

1234

y = x**2print(y)tensor([1., 1., 1., 1.], grad_fn=<PowBackward0>)

y是计算的结果,所以它有grad_fn属性。

12

print(y.grad_fn)<PowBackward0 object at 0x000001CB45988C70>

对 y 进行更多操作

123456

z = y * y * 3out = z.mean()print(z, out)tensor([3., 3., 3., 3.], grad_fn=<MulBackward0>) tensor(3., grad_fn=<MeanBackward0>)

requires_grad_

.requires_grad_(...) 原地改变了现有张量的requires_grad标志。如果没有指定的话,默认输入的这个标志是 False

12345678910

a = torch.randn(2, 2) # 缺失情况下默认 requires_grad = Falsea = ((a * 3) / (a - 1))print(a.requires_grad)a.requires_grad_(True)print(a.requires_grad)b = (a * a).sum()print(b.grad_fn)FalseTrue<SumBackward0 object at 0x000001CB4A19FB50>

梯度

现在开始进行反向传播,因为 out 是一个标量,因此out.backward()out.backward(torch.tensor(1.)) 等价。

1

out.backward()

输出导数 d(out)/dx

123

print(x.grad)tensor([3., 3., 3., 3.])

数学上,若有向量函数 vec{y}=f(vec{x}) ,那么 vec{y} 关于 vec{x} 的梯度就是一个雅可比矩阵 : J=left(begin{array}{ccc}frac{partial y_{1}}{partial x_{1}} & cdots & frac{partial y_{1}}{partial x_{n}} vdots & ddots & vdots frac{partial y_{m}}{partial x_{1}} & cdots & frac{partial y_{m}}{partial x_{n}}end{array}right)

v 是一个标量函数 l=g(vec{y}) 的梯度: v=left(begin{array}{lll}frac{partial l}{partial y_{1}} & cdots & frac{partial l}{partial y_{m}}end{array}right)

注意:grad 在反向传播过程中是累加的(accumulated),这意味着每一次运行反向传播,梯度都会累加之前的梯度,所以一般在反向传播之前需把梯度清零。

12345678910111213

再来反向传播⼀一次,注意grad是累加的out2 = x.sum()out2.backward()print(x.grad)out3 = x.sum()x.grad.data.zero_()out3.backward()print(x.grad)tensor([4., 4., 4., 4.])tensor([1., 1., 1., 1.])

现在我们来看一个雅可比向量积的例子:

12345678910111213

x = torch.randn(3, requires_grad=True)print(x)y = x * 2i = 0while y.data.norm() < 1000: y = y * 2 i = i 1print(y)print(i)tensor(-0.9332, 1.9616, 0.1739, requires_grad=True)tensor(-477.7843, 1004.3264, 89.0424, grad_fn=<MulBackward0>)8

在这种情况下,y 不再是标量。torch.autograd 不能直接计算完整的雅可比矩阵,但是如果我们只想要雅可比向量积,只需将这个向量作为参数传给 backward:

12345

v = torch.tensor(0.1, 1.0, 0.0001, dtype=torch.float)y.backward(v)print(x.grad)tensor(5.1200e 01, 5.1200e 02, 5.1200e-02)

也可以通过将代码块包装在 with torch.no_grad(): 中,来阻止 autograd 跟踪设置了.requires_grad=True的张量的历史记录。

12345678

print(x.requires_grad)print((x ** 2).requires_grad)with torch.no_grad(): print((x ** 2).requires_grad)TrueTrueFalse

如果我们想要修改 tensor 的数值,但是又不希望被 autograd 记录(即不会影响反向传播), 那么我们可以对 tensor.data 进行操作。

123456789101112131415

x = torch.ones(1,requires_grad=True)print(x.data) # 还是一个tensorprint(x.data.requires_grad) # 但是已经是独立于计算图之外y = 2 * xx.data *= 100 # 只改变了值,不会记录在计算图,所以不会影响梯度传播y.backward()print(x) # 更改data的值也会影响tensor的值 print(x.grad)tensor(1.)Falsetensor(100., requires_grad=True)tensor(2.)

雅克比矩阵

向量对向量的求导结果是雅克比矩阵,比如如下代码:

12345678910111213141516171819

import torchif __name__ == '__main__': x = torch.tensor(1,2,3, dtype=torch.float64, requires_grad=True) x1 = x ** 2 y = 2 * x1 print(x.grad) y.backward(torch.ones(3), retain_graph=True) print(x.grad) y.backward(torch.ones(3), retain_graph=False) print(x.grad) pass-->Nonetensor( 4., 8., 12., dtype=torch.float64)tensor( 8., 16., 24., dtype=torch.float64)

vec{Y} '= 4 vec{X}

因此雅克比矩阵为:

frac{partial vec{Y}}{partial vec{X}}=left(begin{array}{ccc}frac{partial y_{1}}{partial x_{1}} & frac{partial y_{1}}{partial x_{2}} & frac{partial y_{1}}{partial x_{3}} frac{partial y_{2}}{partial x_{1}} & frac{partial y_{2}}{partial x_{2}} & frac{partial y_{2}}{partial x_{3}} frac{partial y_{3}}{partial x_{1}} & frac{partial y_{3}}{partial x_{2}} & frac{partial y_{3}}{partial x_{3}}end{array}right)= left(begin{array}{ccc} 4&0&0 &8&0&0&12 end{array}right)

维度相同的权重向量,左乘到雅可比矩阵上。

left{begin{array}{l}frac{partial o u t}{partial a_{1}}=k_{1} * frac{partial text { out }{1}}{partial a{1}} k_{2} * frac{partial o u t_{2}}{partial a_{1}} k_{3} * frac{partial o u t_{3}}{partial a_{1}} ldots k_{n} * frac{partial o u t_{n}}{partial a_{1}} frac{partial o u t}{partial a_{2}}=k_{1} * frac{partial o u_{1}}{partial a_{2}} k_{2} * frac{partial o u t_{2}}{partial a_{2}} k_{3} * frac{partial o u t_{3}}{partial a_{2}} ldots k_{n} * frac{partial o u t_{n}}{partial a_{2}} cdots frac{partial o u t}{partial a_{n}}=k_{1} * frac{partial text { out }{1}}{partial a{n}} k_{2} * frac{partial o u t_{2}}{partial a_{n}} k_{3} * frac{partial o u t_{3}}{partial a_{n}} ldots k_{n} * frac{partial o u t_{n}}{partial a_{n}}end{array}right.

因此对

默认情况下求导后导数信息会被清空,其中 retain_graph 参数则可以保持梯度信息不丢失。

参考资料

  • https://datawhalechina.github.io/thorough-pytorch/第二章/2.2 自动求导.html
  • https://blog.csdn.net/sen873591769/article/details/89819762

文章链接: https://cloud.tencent.com/developer/article/2303814

0 人点赞