深度学习--链式法则
可以链接一个多元函数对其所有变量的偏导数的方式来计算梯度。
偏导计算示例:
设函数z = f(x,y) = 3x^2y + 2xy^2
求z对x和y的偏导数
对x求偏导数
把y看作事常熟,对x求导数
= 3x2xy + 2y^2
对y求偏导数
3x^2 + 2x x 3y^2
2.4.4 链式法则
用上吗的方法可能很难找到梯度,因为在深度学习中,多元函数通常是复合的, 所以难以应用上述任何规则来对这些函数来微分,幸运的是,链式法则可以被用来对复合函数求微分。
我们先考虑单变量函数,假设函数y = f(u) 和 u = g(x) 都是可微的,根据链式法则。
dy/dx = dy/du
现在考虑一个更一般的场景,即函数具有任意数量的变量的情况,假设可微函数y有变量u1, u2,.. un 其中每个可微函数u,都有变量x1,x2,...xn ,注意,y是x1,x2..xn的函数,对于任意i = 1,2,...n 链式法则给出
2.5 自动微分
正如2.4节中所述,求导师几乎所有深度学习优化算法的关键步骤,虽然求导的计算很简单,只需要一些基本的微积分知识,但是对于复杂的模型,手动进行更新时一件很痛苦的事情。
深度学习框架通过自动计算导数,即自动微分来加快求导。实践中,根据设计好的模型,系统会构建一个计算图,来跟踪计算时哪些数据通过哪些操作组合起来产生输出,自动微分使系统能够随后反向传播梯度。这里,反向传播意味着跟踪整个计算图,填充关于每个参数的偏导数。
2.5.1 一个简单的例子
作为一个演示的例子,假设我们想对函数y = 2x^Tx关于列向量x求导,我们先创建变量x并为其分配一个初始值。
import torch
x = torch.arange(4.0)
tensor([0,1,2,3])
在我们计算y关于x的梯度之前,需要一个区域来存储梯度,重要的是,我们不会在每次对一个参数求导时都分配新的内存,因为我们经常会成千上万次的更新相同参数,如果每次都分配新的内存可能很快就回将内存耗尽,注意,一个标量函数关于向量x的梯度时向量,并且与x具有相同的形状。
x.requires_grad_(True)
x.grad
现在计算y
y = 2 * torch.dot(x,y)
tensor(28,grad_fn)
x时一个长度为4的向量,计算x和x的点积,得到了我们赋值给y的标量输出,接下来通过调用反向传播函数来自动计算y关于x的每个分量的梯度,并打印这些梯度。
y.backward()
x.grad
函数y=2x^Tx 关于x的梯度应为4x,我们快速验证这个梯度是否计算正确。
x.grad == 4 * x
现在计算x的另一个函数
在默认情况下,PyTorch会累积梯度,我们需要清除之前的值。
x.grad.zero()
y = x.sum()
y.backward()
x.grad
2.5.2 非标量的反向传播
当y不是标量时,向量y关于向量x的导数的最自然解释是一个矩阵。对于高阶和高纬的y和x,求导的结果可以是一个高阶张量。
然而,虽然这些更奇特的对象确实出现在高级机器学习中,但当调用向量的反向传播函数计算时,我们通常会试图计算第一批训练样本中每个组成部分的损失函数的导数,这里,我们的目的不是计算微分矩阵,而是单独计算批量中每个样本的偏导数之和。
对非标量调用backward函数需要传入一个gradient参数,该参数指定微分函数关于self的梯度,本例只想求偏导数的和,所以传递一个1的梯度是合适的。
x.grad.zero()
y = x*x
等价于y.backward(torch.ones(len(x)))
y.sum().backward()
x.grad()
2.5.3 分离计算
有时,我们希望将某些计算移到记录的计算图之外,例如,假设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
由于记录了y的计算结果,因此我们可以随后在y上调用反向传播函数得到y=x*x关于x的导数,即2*x
x.grad.zero()
y.sum().backward()
x.grad = 2*x
2.5.4 Python控制流的梯度计算
使用自动微分的一个好处是,即使构造函数的计算图需要通过Python控制流,我们也可以计算得到变量的梯度,在下面的代码中,While 循环的迭代次数和if语句的结果都取决于输入a的值。
def f(a)
b = a *2
while b.norm() < 1000;
b = b * 2;
if b.sum() > 0
c = b;
else
c = 100 * b;
return c
我们来计算梯度
a = torch.randn(size=(), requires_grad=True)
d = f(a)
d.backward()
我们现在可以分析上面定义的f函数注意,它在输入a中分段线性的,换言之对于任何a,存在某个常量标量k,使得f(a) = k*a, 其中k的值取决于输入a,因此可以用d/a验证梯度是否正确。