D2L 4.微分和自动求导

Author:baiyucraft

BLog: baiyucraft’s Home

原文:《动手学深度学习》


一、微分和导数

  微分和导数概念相信大家懂得都懂,根据书中的例子一样画个图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import numpy as np
from matplotlib import pyplot as plt


def set_figsize(figsize=(3.5, 2.5)):
"""设置matplotlib的图表大小。"""
plt.rcParams['figure.figsize'] = figsize


def set_axes(axes, xlabel, ylabel, xlim, ylim, xscale, yscale, legend):
"""设置matplotlib的轴。"""
axes.set_xlabel(xlabel)
axes.set_ylabel(ylabel)
axes.set_xscale(xscale)
axes.set_yscale(yscale)
axes.set_xlim(xlim)
axes.set_ylim(ylim)
if legend:
axes.legend(legend)
axes.grid()


def plot(X, Y=None, xlabel=None, ylabel=None, legend=None, xlim=None, ylim=None, xscale='linear', yscale='linear',
fmts=('-', 'm--', 'g-.', 'r:'), axes=None):
"""绘制数据点。"""
if legend is None:
legend = []

# 设置画布大小
set_figsize((4, 2.5))
# 移动坐标轴
axes = axes if axes else plt.gca()

# 如果 `X` 有一个轴,输出True
def has_one_axis(X):
return hasattr(X, "ndim") and X.ndim == 1 or isinstance(X, list) and not hasattr(X[0], "__len__")

if has_one_axis(X):
X = [X]
if not Y:
X, Y = [[]] * len(X), X
elif has_one_axis(Y):
Y = [Y]
if len(X) != len(Y):
X = X * len(Y)
# 清除当前活动轴
axes.cla()
for x, y, fmt in zip(X, Y, fmts):
if len(x):
axes.plot(x, y, fmt)
else:
axes.plot(y, fmt)
set_axes(axes, xlabel, ylabel, xlim, ylim, xscale, yscale, legend)
plt.show()


if __name__ == '__main__':
def f(x):
return 3 * x ** 2 - 4 * x

def g(x):
return 2 * x - 3

x = np.arange(0, 3, 0.1)
plot(x, [f(x), g(x)], 'x', 'f(x)', legend=['f(x)', 'Tangent line (x=1)'])

运行结果:

二、自动求导

  pytorch是可以通过自动求导来计算导数的。实际中,根据我们设计的模型,系统会构建一个计算图,来跟踪计算是哪些数据通过哪些操作组合起来产生输出。自动求导使系统能够随后反向传播梯度。所以求导所运用的反向传播函数

1.标量对向量的求导

我们想对函数 y=2xTxy = 2x^{T}x 关于列向量求 xx 求导:

1
2
3
4
5
6
7
8
9
10
11
12
x = torch.arange(4.0)
# 能存放梯度
x.requires_grad_(True)
print('\n======x======\n', x)
# y = 2x^{T}x
y = 2 * torch.dot(x, x)
print('\n======y======\n', y)
# 调用反向传播函数
# y = 2x1**2 + 2x2**2 + 2x3**2 + 2x4**2 求导后,4*(x1, x2, x3, x4)
y.backward()
print('\n======y关于x的梯度======\n', x.grad)
print('\n======验证======\n', x.grad == 4 * x)

运行结果:

  现在让我们计算 xx 的另一个函数:

1
2
3
4
5
6
7
# 在默认情况下,PyTorch会累积梯度,我们需要清除之前的值
x.grad.zero_()
y = x.sum()
print('\n======x======\n', x)
print('\n======y======\n', y)
y.backward()
print('\n======y关于x的梯度======\n', x.grad)

运行结果:

2.非标量变量的反向传播

  当y不是标量时,向量y关于向量x的导数的最自然解释是一个矩阵。对于高阶和高维的yx,求导的结果可以是一个高阶张量。

  然而,虽然这些更奇特的对象确实出现在高级机器学习中(包括深度学习中),但当我们调用向量的反向计算时,我们通常会试图计算一批训练样本中每个组成部分的损失函数的导数。这里,我们的目的不是计算微分矩阵,而是批量中每个样本单独计算的偏导数之和。

1
2
3
4
5
6
7
8
# 对非标量调用`backward`需要传入一个`gradient`参数,该参数指定微分函数关于`self`的梯度。在我们的例子中,我们只想求偏导数的和,所以传递一个1的梯度是合适的
x.grad.zero_()
y = x * x
print('\n======x======\n', x)
print('\n======y======\n', y)
# 等价于y.backward(torch.ones(len(x)))
y.sum().backward()
print('\n======y关于x的梯度======\n', x.grad)

运行结果:

3.分离计算

  有时,我们希望将某些计算移动到记录的计算图之外。由于某种原因,我们希望将 y 视为一个常数,并且只考虑到 xy被计算后发挥的作用,这里可以用detach()函数将y变为相较于x的常数u

1
2
3
4
5
6
7
8
9
10
11
# 分离计算
x.grad.zero_()
y = x * x
print('\n======x======\n', x)
print('\n======y======\n', y)
u = y.detach()
print('\n======u======\n', u)
z = u * x
print('\n======z======\n', z)
z.sum().backward()
print('\n======验证x.grad == u======\n', x.grad == u)

运行结果:

4.Python控制流的梯度计算

  使用自动求导的一个好处是,即使构建函数的计算图需要通过 Python控制流(例如,条件、循环或任意函数调用),我们仍然可以计算得到的变量的梯度。在下面的代码中,while 循环的迭代次数和 if 语句的结果都取决于输入 a 的值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Python控制流的梯度计算
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()
print('\n======a======\n', a)
print('\n======f(a)======\n', d)
print('\n======f(a)关于a的梯度======\n', a.grad)
print('\n======验证======\n', a.grad == d / a)

运行结果:


D2L 4.微分和自动求导
http://baiyucraft.top/D2lLearning/D2lLearning-4.html
作者
baiyucraft
发布于
2021年6月13日
许可协议