我们通常使用 TensorFlow 来构建神经网络。然而,TensorFlow 的功能不止于此。在后台,TensorFlow 是一个具有自动微分功能的张量库。因此,您可以轻松地使用它通过梯度下降来解决数值优化问题。在这篇文章中,您将学习 TensorFlow 的自动微分引擎 autograd 的工作原理。
完成本教程后,你将学到:
- TensorFlow 中的 autograd 是什么
- 如何利用 autograd 和优化器解决优化问题
让我们开始吧。

在 TensorFlow 中使用 autograd 解决回归问题
照片作者:Lukas Tennie。部分权利保留。
概述
本教程分为三个部分;它们是
- TensorFlow 中的 Autograd
- 使用 Autograd 进行多项式回归
- 使用 Autograd 解决数学谜题
TensorFlow 中的 Autograd
在 TensorFlow 2.x 中,您可以将变量和常量定义为 TensorFlow 对象,并用它们构建表达式。表达式本质上是变量的函数。因此,您可以推导出其导数函数,即微分或梯度。此功能是 TensorFlow 的众多基本功能之一。深度学习模型将在训练循环中利用此功能。
通过示例解释 autograd 会更容易。在 TensorFlow 2.x 中,您可以像这样创建一个常量矩阵:
1 2 3 4 5 6 |
import tensorflow as tf x = tf.constant([1, 2, 3]) print(x) print(x.shape) print(x.dtype) |
上面的输出是:
1 2 3 |
tf.Tensor([1 2 3], shape=(3,), dtype=int32) (3,) <dtype: 'int32'> |
这会创建一个整数向量(以 Tensor 对象的形式)。在大多数情况下,此向量可以像 NumPy 向量一样工作。例如,您可以执行 x+x
或 2*x
,结果将如您所料。TensorFlow 提供了许多与 NumPy 匹配的数组操作函数,例如 tf.transpose
或 tf.concat
。
在 TensorFlow 中创建变量也同样简单,例如:
1 2 3 4 5 6 |
import tensorflow as tf x = tf.Variable([1, 2, 3]) print(x) print(x.shape) print(x.dtype) |
这将打印:
1 2 3 |
<tf.Variable 'Variable:0' shape=(3,) dtype=int32, numpy=array([1, 2, 3], dtype=int32)> (3,) <dtype: 'int32'> |
您可以应用于 Tensor 对象的运算(例如 x+x
和 2*x
)也可以应用于变量。变量和常量之间的唯一区别在于前者允许值更改,而后者是不可变的。当您运行 梯度带 时,这种区别很重要,如下所示:
1 2 3 4 5 6 7 8 9 |
import tensorflow as tf x = tf.Variable(3.6) with tf.GradientTape() as tape: y = x*x dy = tape.gradient(y, x) print(dy) |
输出如下:
1 |
tf.Tensor(7.2, shape=(), dtype=float32) |
它的作用是:定义一个变量 x
(值为 3.6),然后创建一个梯度带。梯度带工作时,它计算 y=x*x
或 $y=x^2$。梯度带会监控变量是如何被操作的。之后,您要求梯度带找出导数 $\dfrac{dy}{dx}$。您知道 $y=x^2$ 意味着 $y’=2x$。因此,输出将为您提供 $3.6\times 2=7.2$ 的值。
使用 Autograd 进行多项式回归
TensorFlow 中的这个功能有什么用?让我们考虑一个情况,您有一个形式为 $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 tensorflow as tf # 假设样本 X 和 Y 已在别处准备好 XX = np.hstack([X*X, X, np.ones_like(X)]) w = tf.Variable(tf.random.normal((3,1))) # 这 3 个系数 x = tf.constant(XX, dtype=tf.float32) # 输入样本 y = tf.constant(Y, dtype=tf.float32) # 输出样本 optimizer = tf.keras.optimizers.Nadam(lr=0.01) print(w) for _ in range(1000): with tf.GradientTape() as tape: y_pred = x @ w mse = tf.reduce_sum(tf.square(y - y_pred)) grad = tape.gradient(mse, w) optimizer.apply_gradients([(grad, w)]) print(w) |
循环之前的 print
语句会给出三个随机数,例如:
1 2 3 4 |
<tf.Variable 'Variable:0' shape=(3, 1) dtype=float32, numpy= array([[-2.1450958 ], [-1.1278448 ], [ 0.31241694]], dtype=float32)> |
但循环后的输出会给出接近多项式系数的系数:
1 2 3 4 |
<tf.Variable 'Variable:0' shape=(3, 1) dtype=float32, numpy= array([[1.0000628], [2.0002015], [2.996219 ]], dtype=float32)> |
上述代码的作用如下:首先,它创建一个包含 3 个值(即系数 $A,B,C$)的变量向量 w
。然后,您创建一个形状为 $(N,3)$ 的数组,其中 $N$ 是数组 X
中的样本数量。该数组有 3 列,分别是 $x^2$、 $x$ 和 1 的值。此类数组使用 np.hstack()
函数从向量 X
构建。类似地,我们从 NumPy 数组 Y
构建 TensorFlow 常量 y
。
之后,您使用 for 循环运行梯度下降 1,000 次迭代。在每次迭代中,您以矩阵形式计算 $x \times w$ 来找到 $Ax^2+Bx+C$,并将其赋值给变量 y_pred
。然后,比较 y
和 y_pred
,找出均方误差。接下来,导出梯度,即均方误差相对于系数 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 tensorflow as tf N = 20 # 样本数量 # 生成大约在 -10 到 +10 之间的随机样本 polynomial = np.poly1d([1, 2, 3]) X = np.random.randn(N,1) * 5 Y = polynomial(X) # 将输入准备为形状为 (N,3) 的数组 XX = np.hstack([X*X, X, np.ones_like(X)]) # 准备 TensorFlow 对象 w = tf.Variable(tf.random.normal((3,1))) # 这 3 个系数 x = tf.constant(XX, dtype=tf.float32) # 输入样本 y = tf.constant(Y, dtype=tf.float32) # 输出样本 optimizer = tf.keras.optimizers.Nadam(lr=0.01) print(w) # 运行优化器 for _ in range(1000): with tf.GradientTape() as tape: y_pred = x @ w mse = tf.reduce_sum(tf.square(y - y_pred)) grad = tape.gradient(mse, w) optimizer.apply_gradients([(grad, w)]) 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 tensorflow as tf import random A = tf.Variable(random.random()) B = tf.Variable(random.random()) C = tf.Variable(random.random()) D = tf.Variable(random.random()) # 梯度下降循环 EPOCHS = 1000 optimizer = tf.keras.optimizers.Nadam(lr=0.1) for _ in range(EPOCHS): with tf.GradientTape() as tape: 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 gradA, gradB, gradC, gradD = tape.gradient(sqerr, [A, B, C, D]) optimizer.apply_gradients([(gradA, A), (gradB, B), (gradC, C), (gradD, D)]) print(A) print(B) print(C) print(D) |
这个问题有多个解。以下是一个解:
1 2 3 4 |
<tf.Variable 'Variable:0' shape=() dtype=float32, numpy=4.6777573> <tf.Variable 'Variable:0' shape=() dtype=float32, numpy=4.3222437> <tf.Variable 'Variable:0' shape=() dtype=float32, numpy=3.3222427> <tf.Variable 'Variable:0' shape=() dtype=float32, numpy=2.3222432> |
这意味着 $A=4.68$,$B=4.32$,$C=3.32$,$D=2.32$。您可以验证此解决方案是否符合问题。
上述代码将四个未知数定义为具有随机初始值的变量。然后,您计算四个方程的结果并将它们与预期答案进行比较。然后,您将平方误差相加,并要求 TensorFlow 最小化它。可能实现的最小平方误差为零,当我们的解决方案完全符合问题时即可达到。
请注意梯度带被要求生成梯度的方式:您要求 sqerr
相对于 A
、B
、C
和 D
的梯度。因此,找到了四个梯度。然后,您在每次迭代中将每个梯度应用于相应的变量。这在 TensorFlow 中是必需的,因为默认情况下,梯度 sqerr
只能被调用一次,而不是通过四个单独的 tape.gradient()
调用来获取。
进一步阅读
如果您想深入了解,本节提供了更多关于该主题的资源。
文章
总结
在这篇文章中,我们演示了 TensorFlow 的自动微分是如何工作的。这是执行深度学习训练的基础。具体来说,您学习了:
- TensorFlow 中的自动微分是什么
- 如何使用梯度带执行自动微分
- 如何使用自动微分解决优化问题
非常感谢,这篇文章太棒了!!
不客气 Rajadurai!我们感谢您的支持!