我们通常使用 PyTorch 来构建神经网络。然而,PyTorch 的功能不止于此。由于 PyTorch 也是一个具有自动微分能力的张量库,您可以轻松地使用它通过梯度下降来解决数值优化问题。在本博文中,您将了解 PyTorch 的自动微分引擎 autograd 的工作原理。
完成本教程后,你将学到:
- PyTorch 中的 autograd 是什么
- 如何使用 autograd 和优化器来解决优化问题
通过我的《用PyTorch进行深度学习》一书来启动你的项目。它提供了包含可用代码的自学教程。
让我们开始吧。

使用 PyTorch 的 autograd 解决回归问题。
照片来源:Billy Kwok。保留部分权利。
概述
本教程分为三个部分;它们是
- PyTorch 中的 Autograd
- 使用 Autograd 进行多项式回归
- 使用 Autograd 解决数学谜题
PyTorch 中的 Autograd
在 PyTorch 中,您可以创建张量作为变量或常量,并用它们构建表达式。表达式本质上是变量张量的函数。因此,您可以推导出其导函数,即微分或梯度。这是深度学习模型中训练循环的基础。PyTorch 在其核心就提供了这项功能。
用一个例子来解释 autograd 会更容易。在 PyTorch 中,您可以按如下方式创建一个常数矩阵
1 2 3 4 5 6 |
import torch x = torch.tensor([1, 2, 3]) print(x) print(x.shape) print(x.dtype) |
上面的输出是:
1 2 3 |
tensor([1, 2, 3]) torch.Size([3]) torch.int64 |
这会创建一个整数向量(以 PyTorch 张量的形式)。在大多数情况下,此向量可以像 NumPy 向量一样工作。例如,您可以执行 x+x
或 2*x
,结果正如您所料。PyTorch 提供了许多与 NumPy 匹配的数组操作函数,例如 torch.transpose
或 torch.concatenate
。
但是,此张量不被假定为函数中的变量,这意味着不支持对其进行微分。您可以创建像变量一样的张量,并加上一个额外的选项
1 2 3 4 5 6 |
import torch x = torch.tensor([1., 2., 3.], requires_grad=True) print(x) print(x.shape) print(x.dtype) |
这将打印:
1 2 3 |
tensor([1., 2., 3.], requires_grad=True) torch.Size([3]) torch.float32 |
请注意,上面创建了一个浮点数值张量。这是必需的,因为微分需要浮点数,而不是整数。
操作(例如 x+x
和 2*x
)仍然可以应用,但在这种情况下,张量会记住它是如何获得其值的。您可以在下面的示例中演示此功能
1 2 3 4 5 6 |
import torch x = torch.tensor(3.6, requires_grad=True) y = x * x y.backward() print(x.grad) |
输出如下:
1 |
tensor(7.2000) |
它的作用如下:定义了一个变量 x
(值为 3.6),然后计算 y=x*x
或 $y=x^2$。然后要求 $y$ 的微分。由于 $y$ 是从 $x$ 获得的,您可以在运行 y.backward()
后立即在 x.grad
中找到导数 $\dfrac{dy}{dx}$,形式为张量。您知道 $y=x^2$ 意味着 $y’=2x$。因此,输出将给出 $3.6 \times 2 = 7.2$ 的值。
想开始使用PyTorch进行深度学习吗?
立即参加我的免费电子邮件速成课程(附示例代码)。
点击注册,同时获得该课程的免费PDF电子书版本。
使用 Autograd 进行多项式回归
PyTorch 中的这项功能有何帮助?让我们考虑一个您有一个多项式 $y=f(x)$,并且您有几个 $(x,y)$ 样本的情况。您如何恢复该多项式 $f(x)$?一种方法是假设一个随机系数的多项式,然后输入样本 $(x,y)$。如果找到了多项式,您应该会看到 $y$ 的值与 $f(x)$ 匹配。它们越接近,您的估计就越接近正确的多项式。
这确实是一个数值优化问题,您希望最小化 $y$ 和 $f(x)$ 之间的差异。您可以使用梯度下降来解决它。
让我们以一个例子来说明。您可以使用 NumPy 按如下方式构建一个多项式 $f(x)=x^2 + 2x + 3$
1 2 3 4 |
import numpy as np polynomial = np.poly1d([1, 2, 3]) print(polynomial) |
输出如下:
1 2 |
2 1 x + 2 x + 3 |
您可以将该多项式用作函数,例如
1 |
print(polynomial(1.5)) |
这会打印出 8.25
,因为 $(1.5)^2+2\times(1.5)+3 = 8.25$。
现在您可以使用 NumPy 从该函数生成一些样本
1 2 3 4 5 |
N = 20 # 样本数量 # 生成大致在 -10 到 +10 之间的随机样本 X = np.random.randn(N,1) * 5 Y = polynomial(X) |
在上面,X
和 Y
都是形状为 (20,1)
的 NumPy 数组,它们的关系是 $y=f(x)$,其中 $f(x)$ 是多项式。
现在,假设您不知道多项式是什么,只知道它是二次的。您想恢复系数。由于二次多项式的形式为 $Ax^2+Bx+C$,因此您有三个未知数需要求解。您可以使用实现的梯度下降算法或现有的梯度下降优化器来找到它们。以下演示了它的工作原理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import torch # 假设样本 X 和 Y 已在别处准备好 XX = np.hstack([X*X, X, np.ones_like(X)]) w = torch.randn(3, 1, requires_grad=True) # 这 3 个系数 x = torch.tensor(XX, dtype=torch.float32) # 输入样本 y = torch.tensor(Y, dtype=torch.float32) # 输出样本 optimizer = torch.optim.NAdam([w], lr=0.01) print(w) for _ in range(1000): optimizer.zero_grad() y_pred = x @ w mse = torch.mean(torch.square(y - y_pred)) mse.backward() optimizer.step() print(w) |
for 循环之前的 print 语句给出三个随机数,例如
1 2 3 |
tensor([[1.3827], [0.8629], [0.2357]], requires_grad=True) |
但 for 循环之后的打印结果会给出非常接近多项式系数的值
1 2 3 |
tensor([[1.0004], [1.9924], [2.9159]], requires_grad=True) |
上面的代码的作用是:首先,它创建了一个包含 3 个值的变量向量 w
,即系数 $A,B,C$。然后,您创建一个形状为 $(N,3)$ 的数组,其中 $N$ 是数组 X
中的样本数。此数组有 3 列:分别是 $x^2$、 $x$ 和 1 的值。使用 np.hstack()
函数从向量 X
构建这样的数组。类似地,您从 NumPy 数组 Y
构建 TensorFlow 常量 y
。
之后,您使用 for 循环在 1000 次迭代中运行梯度下降。在每次迭代中,您以矩阵形式计算 $x \times w$ 以找到 $Ax^2+Bx+C$,并将其分配给变量 y_pred
。然后,比较 y
和 y_pred
并计算均方误差。接下来,使用 backward()
函数导出梯度,即均方误差相对于系数 w
的变化率。并根据此梯度,使用梯度下降通过优化器更新 w
。
本质上,上面的代码将找到最小化均方误差的系数 w
。
把所有东西放在一起,下面是完整的代码。
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 |
import numpy as np import torch polynomial = np.poly1d([1, 2, 3]) N = 20 # 样本数量 # 生成大致在 -10 到 +10 之间的随机样本 X = np.random.randn(N,1) * 5 Y = polynomial(X) # 将输入准备为形状为 (N,3) 的数组 XX = np.hstack([X*X, X, np.ones_like(X)]) # 准备张量 w = torch.randn(3, 1, requires_grad=True) # 这 3 个系数 x = torch.tensor(XX, dtype=torch.float32) # 输入样本 y = torch.tensor(Y, dtype=torch.float32) # 输出样本 optimizer = torch.optim.NAdam([w], lr=0.01) print(w) # 运行优化器 for _ in range(1000): optimizer.zero_grad() y_pred = x @ w mse = torch.mean(torch.square(y - y_pred)) mse.backward() optimizer.step() print(w) |
使用 Autograd 解决数学谜题
在上面,使用了 20 个样本,这足以拟合二次方程。您也可以使用梯度下降来解决一些数学谜题。例如,下面的问题
1 2 3 4 5 |
[ A ] + [ B ] = 9 + - [ C ] - [ D ] = 1 = = 8 2 |
换句话说,找到 $A,B,C,D$ 的值,使得
$$\begin{aligned}
A + B &= 9 \\
C – D &= 1 \\
A + C &= 8 \\
B – D &= 2
\end{aligned}$$
这也可以使用 autograd 来解决,如下所示
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 |
import random import torch A = torch.tensor(random.random(), requires_grad=True) B = torch.tensor(random.random(), requires_grad=True) C = torch.tensor(random.random(), requires_grad=True) D = torch.tensor(random.random(), requires_grad=True) # 梯度下降循环 EPOCHS = 2000 optimizer = torch.optim.NAdam([A, B, C, D], lr=0.01) for _ in range(EPOCHS): y1 = A + B - 9 y2 = C - D - 1 y3 = A + C - 8 y4 = B - D - 2 sqerr = y1*y1 + y2*y2 + y3*y3 + y4*y4 optimizer.zero_grad() sqerr.backward() optimizer.step() print(A) print(B) print(C) print(D) |
这个问题可以有多种解决方案。一种解决方案如下
1 2 3 4 |
tensor(4.7191, requires_grad=True) tensor(4.2808, requires_grad=True) tensor(3.2808, requires_grad=True) tensor(2.2808, requires_grad=True) |
这意味着 $A=4.72$,$B=4.28$,$C=3.28$, $D=2.28$。您可以验证此解决方案是否符合问题。
上面的代码将四个未知数定义为具有随机初始值的变量。然后,您计算四个方程的结果并将其与预期答案进行比较。然后,您将平方误差相加,并要求 PyTorch 的优化器最小化它。最小可能的平方误差为零,当我们的解决方案完全符合问题时达到。
请注意 PyTorch 生成梯度的方式:您要求 sqerr
的梯度,它注意到 A
、B
、C
和 D
是唯一具有 requires_grad=True
的依赖项。因此,找到了四个梯度。然后,您通过优化器在每次迭代中将每个梯度应用于相应的变量。
进一步阅读
如果您想深入了解,本节提供了更多关于该主题的资源。
文章
总结
在本博文中,我们演示了 PyTorch 的自动微分是如何工作的。这是执行深度学习训练的基础。具体来说,您学习了
- PyTorch 中的自动微分是什么
- 如何使用梯度带执行自动微分
- 如何使用自动微分来解决优化问题
暂无评论。