微分是数学中的一个重要概念,它是对函数的局部变化率的一种线性描述。本节介绍微分的概念,以及PyTorch中的自动微分技术及其案例。
如果函数 y=f ( x )在点 x 处的改变量 Δy=f ( x+Δx )- f ( x ),可以表示为:
Δy = AΔx + o ( Δx )
其中, o ( Δx )是比 Δx ( Δx → 0 )高阶的无穷小,则称函数 y = f ( x )在点 x 处可微,称 AΔx 为 Δy 的线性主部,又称 AΔx 为函数 y = f ( x )在点 x 处的微分,记为 dy 或 df ( x ),即 dy = AΔx 。
基本初等函数的求导公式及微分公式如表2-1所示。
表2-1 求导与微分公式
对于一般形式的函数,求导与微分法则如表2-2所示。
表2-2 求导与微分法则表
设函数 f ( x )在点 x 0 的某邻域内有定义,如果对于该邻域内任一点 x ( x ≠ x 0 ),都有 f ( x )< f ( x 0 ),则称 f ( x 0 )是函数 f ( x )的极大值;如果对于该邻域内任一点 x ( x ≠ x 0 ),都有 f ( x )> f ( x 0 ),则称 f ( x 0 )是函数 f ( x )的极小值。函数的极大值与极小值统称为函数的极值,使函数取得极值的点 x 0 称为函数 f ( x )的极值点。
单变量函数的极值问题较为简单,那么它的极值可能是函数的边界点或驻点。使 f ' ( x )= 0 (即一阶导数为0)的点 x 称为函数 f ( x )的驻点。
对于多变量函数的极值,与单变量函数类似,极值点只能在函数不可导的点或导数为零的点上取得,如图2-1所示。
图2-1 多变量函数的极值
此外,对于最值问题,在闭区间上连续函数一定存在最大值和最小值。连续函数在闭区间上的最大值和最小值只可能在区间内的驻点、不可导点或闭区间的端点处取得。
几乎所有机器学习算法在训练或预测时都归结为求解最优化问题,如果目标函数可导,那么问题变为训练函数的驻点(即一阶导数等于零的点)。自动微分也称自动求导,算法能够计算可导函数在某点处的导数值,是反向传播算法的一般化。
自动微分技术在深度学习库中处于重要地位,是整个训练算法的核心组件之一。深度学习模型的训练就是不断更新权值,权值的更新需要求解梯度,求解梯度十分烦琐,PyTorch提供自动求导系统,我们只要搭建好前向传播的计算图,就能获得所有张量的梯度。
PyTorch中自动求导模块中的相关函数有torch.autograd.backward()和torch.autograd.grad()。下面逐一进行介绍。
该函数实现自动求取梯度,函数参数如下所示:
torch.autograd.backward(tensors, grad_tensors=None, retain_graph=None, create_graph=False)
参数说明:
● Tensors:用于求导的张量,如loss。
● retain_graph:保存计算图,由于PyTorch采用动态图机制,在每次反向传播之后计算图都会释放掉,如果还想继续使用,就要设置此参数为True。
● create_graph:创建导数计算图,用于高阶求导。
● grad_tensors:多梯度权重,当有多个loss需要计算梯度时,需要设置每个loss的权值。
例如,线性的一阶导数的代码如下:
import torch w = torch.tensor([1.], requires_grad=True) x = torch.tensor([2.], requires_grad=True) a = torch.add(x, w) b = torch.add(w, 1) y = torch.mul(a, b) y.backward() print(w.grad)
输出如下:
tensor([5.])
下面通过案例介绍grad_tensors参数的用法。
import torch w = torch.tensor([1.], requires_grad=True) x = torch.tensor([2.], requires_grad=True) a = torch.add(x, w) b = torch.add(w, 1) y0 = torch.mul(a, b) y1 = torch.add(a, b) loss = torch.cat([y0, y1], dim=0) grad_t = torch.tensor([1., 2.]) loss.backward(gradient=grad_t) print(w.grad)
输出如下:
tensor([9.])
其中:
该函数实现求取梯度,函数参数如下:
torch.autograd.grad(outputs, inputs, grad_outputs=None, retain_graph=None, create_graph=False)
参数说明:
● outputs:用于求导的张量,如上例中的loss。
● inputs:需要梯度的张量,如上例中的w。
● create_graph:创建导数计算图,用于高阶求导。
● retain_graph:保存计算图。
● grad_outputs:多梯度权重。
例如,计算 y = x 2 的二阶导数的代码如下:
import torch x = torch.tensor([3.], requires_grad=True) y = torch.pow(x, 2) grad1 = torch.autograd.grad(y, x, create_graph=True) print(grad1) grad2 = torch.autograd.grad(grad1[0], x) print(grad2)
输出如下:
(tensor([6.], grad_fn=<MulBackward0>),) (tensor([2.]),)
1)梯度不能自动清零,在每次反向传播中会叠加,代码如下:
w = torch.tensor([1.], requires_grad=True) x = torch.tensor([2.], requires_grad=True) for i in range(3): a = torch.add(x, w) b = torch.add(w, 1) y = torch.mul(a, b) y.backward() print(w.grad)
输出如下:
tensor([5.]) tensor([10.]) tensor([15.])
这会导致我们得不到正确的结果,所以需要手动清零,代码如下:
w = torch.tensor([1.], requires_grad=True) x = torch.tensor([2.], requires_grad=True) for i in range(3): a = torch.add(x, w) b = torch.add(w, 1) y = torch.mul(a, b) y.backward() print(w.grad) w.grad.zero_() # 梯度清零
输出如下:
tensor([5.]) tensor([5.]) tensor([5.])
2)依赖于叶子节点的节点,requires_grad默认为True,代码如下:
w = torch.tensor([1.], requires_grad=True) x = torch.tensor([2.], requires_grad=True) a = torch.add(x, w) b = torch.add(w, 1) y = torch.mul(a, b) print(a.requires_grad, b.requires_grad, y.requires_grad)
输出如下:
True True True
3)叶子节点不可以执行in-place,因为前向传播记录了叶子节点的地址,反向传播需要用到叶子节点的数据时,要根据地址寻找数据,执行in-place操作改变了地址中的数据,梯度求解也会发生错误,代码如下:
w = torch.tensor([1.], requires_grad=True) x = torch.tensor([2.], requires_grad=True) a = torch.add(x, w) b = torch.add(w, 1) y = torch.mul(a, b) w.add_(1)
输出如下:
RuntimeError: a leaf Variable that requires grad has been used in an in-place operation.
in-place操作即原位操作,在原始内存中改变这个数据,代码如下:
a = torch.tensor([1]) print(id(a), a) #开辟新的内存地址 a = a + torch.tensor([1]) print(id(a), a) #in-place操作,地址不变 a += torch.tensor([1]) print(id(a), a)
输出如下:
2638883967360 tensor([1]) 2638883954112 tensor([2]) 2638883954112 tensor([3])