07 自动求导【动手学深度学习v2】
P1 自动求导
(1)向量链式法则
注意标量对向量求导、向量对向量求导(前一个向量大小为m,后一个向量大小为n,则结果矩阵为m*n大小)
(2)计算图的定义
- 将代码分解成操作子
- 将计算表示成一个无环图
(3)自动求导的两种模式
依照链式法则,有两种方向:从前往后计算,从后往前计算。分别称作正向累积和反向累积(反向 传递)。主要是因为求导过程中存在多个中间变量。
(4)反向传递
自动求导中的反向传递(Backpropagation),简称BP。是深度学习中一个关键的优化算法,其主要目的是通过链式法则计算神经网络各层参数的梯度,并用于更新这些参数以优化模型性能。
计算反向时,要利用到正向中的结果,所以计算正向时要将结果保存下来。
而且看反向这个树,下一层的结点的求导必须要依赖上一层的求导结果,例如对a求导时,要知道z对b求导的结果。
P2 求导实现
(1)首先需要一个地方存储梯度,下面代码中的grad就是存储的位置。
x.requires_grad(True) #等价于‘x=torch.arange(4.0,requires_grad=True)’
x.grad #默认值时None
(2)计算y
y = 2 * torch.dot(x, x)
y
(3)y.backward()就是利用反向传递求梯度,x.grad打印出y关于x每一个分量的梯度。
y.backward()
x.grad
注意:求下一个函数导数时,注意x.grad.zero_()清理存储的梯度信息。
(4)一般不用向量对向量求导(结果为矩阵),利用sum()函数将y求和得到一个标量,然后再对x向量求导。这里,我们的目的不是计算微分矩阵,而是单独计算批量中每个样本的偏导数之和。
# 对非标量调用backward需要传入一个gradient参数,该参数指定微分函数关于self的梯度。
# 本例只想求偏导数的和,所以传递一个1的梯度是合适的
x.grad.zero_()
y = x * x
# 等价于y.backward(torch.ones(len(x)))
y.sum().backward()
x.grad
结果:
tensor([0., 2., 4., 6.])
ps:帮助理解y的求和:y.sum()=x(0,0)^2+x(0,1)^2+...
将某些计算移动到记录的计算图之外
u是记录了y的常数,此时它不是关于x的变量,用法为u=y.detach()。通俗点说就是用了detach()也就是说y=x*x不再是对x的函数,理解成y=t*t就行。
书中的话:有时,我们希望将某些计算移动到记录的计算图之外。 例如,假设y
是作为x
的函数计算的,而z
则是作为y
和x
的函数计算的。 想象一下,我们想计算z
关于x
的梯度,但由于某种原因,希望将y
视为一个常数, 并且只考虑到x
在y
被计算后发挥的作用。
这里可以分离y
来返回一个新变量u
,该变量与y
具有相同的值, 但丢弃计算图中如何计算y
的任何信息。 换句话说,梯度不会向后流经u
到x
。 因此,下面的反向传播函数计算z=u*x
关于x
的偏导数,同时将u
作为常数处理, 而不是z=x*x*x
关于x
的偏导数。
x.grad.zero_()
y = x * x
u = y.detach()
z = u * x
z.sum().backward()
x.grad == u
(6) 即使构建函数的计算图需要通过Python控制流(例如,条件、循环或任意函数调用),我们仍然可以计算得到的变量的梯度。 pytorch隐式构造,每次都会保存计算图。