购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

2.2 微分基础

微分是数学中的一个重要概念,它是对函数的局部变化率的一种线性描述。本节介绍微分的概念,以及PyTorch中的自动微分技术及其案例。

2.2.1 微分及其公式

1.微分基础

如果函数 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 求导与微分法则表

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 多变量函数的极值

此外,对于最值问题,在闭区间上连续函数一定存在最大值和最小值。连续函数在闭区间上的最大值和最小值只可能在区间内的驻点、不可导点或闭区间的端点处取得。

2.2.2 PyTorch自动微分

几乎所有机器学习算法在训练或预测时都归结为求解最优化问题,如果目标函数可导,那么问题变为训练函数的驻点(即一阶导数等于零的点)。自动微分也称自动求导,算法能够计算可导函数在某点处的导数值,是反向传播算法的一般化。

自动微分技术在深度学习库中处于重要地位,是整个训练算法的核心组件之一。深度学习模型的训练就是不断更新权值,权值的更新需要求解梯度,求解梯度十分烦琐,PyTorch提供自动求导系统,我们只要搭建好前向传播的计算图,就能获得所有张量的梯度。

PyTorch中自动求导模块中的相关函数有torch.autograd.backward()和torch.autograd.grad()。下面逐一进行介绍。

1.torch.autograd.backward()

该函数实现自动求取梯度,函数参数如下所示:

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.])

其中:

2.torch.autograd.grad()

该函数实现求取梯度,函数参数如下:

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.]),)
3.注意事项

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)

输出如下: KI7MDCfmUwD7aG71fS6+S2MHiynwAxQVTbEbGTVVlw5IU92jSc9fIIokrsbJi498

2638883967360 tensor([1])
2638883954112 tensor([2])
2638883954112 tensor([3])
点击中间区域
呼出菜单
上一章
目录
下一章
×