TensorFlow 2.0 基础:张量、自动求导与优化器

在上一篇文章里,我们为大家详细介绍了 TensorFlow 2.0 的安装方式。在本篇文章,我们将带大家初窥 TensorFlow 2.0。

TensorFlow 2.0 最激动人心的特性之一,在于其引入的 Eager Execution 模式。这使得我们无需构建计算图后再执行计算,而可以做到直观的 “一经调用,立即执行”,从而让我们能够更为方便的构建和调试模型。在本文中,我们即使用 Eager Execution 模式为大家介绍 TensorFlow 的基础操作,包括张量的定义和操作、自动求导机制以及优化器的使用。

TensorFlow 1+1

我们可以先简单地将 TensorFlow 视为一个科学计算库(类似于 Python 下的 NumPy)。

首先,我们导入 TensorFlow:

1  import tensorflow as tf

警告
在 TensorFlow 1.X 版本中, 必须在导入 TensorFlow 库后调用tf.enable_eager_execution()函数以启用 Eager Execution 模式。
在 TensorFlow 2.0 版本中,Eager Execution 模式为默认模式,无需额外调用tf.enable_eager_execution()函数
(不过若要关闭 Eager Execution,则需调用tf.compat.v1.disable_eager_execution()函数)。

TensorFlow 使用 张量(Tensor)作为数据的基本单位。TensorFlow 的张量在概念上类似于多维数组,我们可以使用它来描述数学中的标量(0 维数组)、向量(1 维数组)、矩阵(2 维数组)等各种量,示例如下:

1 # 定义一个随机数(标量)
2 random_float = tf.random.uniform(shape=())
3
4 # 定义一个有2个元素的零向量
5 zero_vector = tf.zeros(shape=(2))
6
7 # 定义两个2×2的常量矩阵
8 A = tf.constant([[1., 2.], [3., 4.]])
9 B = tf.constant([[5., 6.], [7., 8.]])

张量的重要属性是其形状、类型和值。可以通过张量的shape、dtype属性和numpy()方法获得。例如:

	1 # 查看矩阵A的形状、类型和值
	2 print(A.shape)      # 输出(2, 2),即矩阵的长和宽均为2
	3 print(A.dtype)      # 输出<dtype: 'float32'>
	4 print(A.numpy())    # 输出[[1. 2.]
	5                     #      [3. 4.]]

小技巧

TensorFlow 的大多数 API 函数会根据输入的值自动推断张量中元素的类型(一般默认为tf.float32)。不过你也可以通过加入dtype参数来自行指定类型,例如zero_vector = tf.zeros(shape=(2), dtype=tf.int32)将使得张量中的元素类型均为整数。张量的numpy()方法是将张量的值转换为一个 NumPy 数组。

TensorFlow 里有大量的 操作 (Operation),使得我们可以将已有的张量进行运算后得到新的张量。示例如下:

	1 C = tf.add(A, B)    # 计算矩阵A和B的和
	2 D = tf.matmul(A, B) # 计算矩阵A和B的乘积

操作完成后,C和D的值分别为:

1 tf.Tensor(
2 [[ 6.  8.]
3  [10. 12.]], shape=(2, 2), dtype=float32)
4 tf.Tensor(
5 [[19. 22.]
6  [43. 50.]], shape=(2, 2), dtype=float32)

自动求导机制

在机器学习中,我们经常需要计算函数的导数。TensorFlow 提供了强大的 自动求导机制 来计算导数。以下代码展示了如何使用tf.GradientTape()计算函数y=x^2在x=3时的导数:

1 import tensorflow as tf
2 
3 x = tf.Variable(initial_value=3.)
4 with tf.GradientTape() as tape:     # 在 tf.GradientTape() 的上下文内,所有计算步骤都会被记录以用于求导
5     y = tf.square(x)
6 y_grad = tape.gradient(y, x)        # 计算y关于x的导数
7 print([y, y_grad])

输出:

1[array([9.], dtype=float32), array([6.], dtype=float32)]

这里x是一个初始化为 3 的 变量 (Variable),使用tf.Variable()声明。与普通张量一样,变量同样具有形状、类型和值三种属性。使用变量需要有一个初始化过程,可以通过在tf.Variable()中指定initial_value参数来指定初始值。这里将变量x初始化为3.。变量与普通张量的一个重要区别是其默认能够被 TensorFlow 的自动求导机制所求导,因此往往被用于定义机器学习模型的参数。

tf.GradientTape()是一个自动求导的记录器,在其中的变量和计算步骤都会被自动记录。在上面的示例中,变量x和计算步骤y = tf.square(x)被自动记录,因此可以通过y_grad = tape.gradient(y, x)求张量y对变量x的导数。

在机器学习中,更加常见的是对多元函数求偏导数,以及对向量或矩阵的求导。这些对于 TensorFlow 也不在话下。以下代码展示了如何使用tf.GradientTape()计算函数    在  时分别对  的偏导数。其中:

1 X = tf.constant([[1., 2.], [3., 4.]])
2 y = tf.constant([[1.], [2.]])
3 w = tf.Variable(initial_value=[[1.], [2.]])
4 b = tf.Variable(initial_value=1.)
5 with tf.GradientTape() as tape:
6    L = 0.5 * tf.reduce_sum(tf.square(tf.matmul(X, w) + b - y))
7 w_grad, b_grad = tape.gradient(L, [w, b])        # 计算L(w, b)关于w, b的偏导数
8 print([L.numpy(), w_grad.numpy(), b_grad.numpy()])

输出:

1  [62.5, array([[35.],
2   [50.]], dtype=float32), array([15.], dtype=float32)]

这里,tf.square()操作代表对输入张量的每一个元素求平方,不改变张量形状。tf.reduce_sum()操作代表对输入张量的所有元素求和,输出一个形状为空的纯量张量(可以通过 axis 参数来指定求和的维度,不指定则默认对所有元素求和)。TensorFlow 中有大量的张量操作 API,包括数学运算、张量形状操作(如tf.reshape())、切片和连接(如tf.concat())等多种类型,可以通过查阅 TensorFlow 的官方 API 文档 [2] 来进一步了解。

从输出可见,TensorFlow 帮助我们计算出了:

在这里插入图片描述

基础示例:线性回归

考虑一个实际问题,某城市在 2013 年 - 2017 年的房价如下表所示:
在这里插入图片描述

现在,我们希望通过对该数据进行线性回归,即使用线性模型 来拟合上述数据,此处a和b是待求的参数。

首先,我们定义数据,进行基本的归一化操作。

1 import numpy as np
2
3 X_raw = np.array([2013, 2014, 2015, 2016, 2017], dtype=np.float32)
4 y_raw = np.array([12000, 14000, 15000, 16500, 17500], dtype=np.float32)
5
6 X = (X_raw - X_raw.min()) / (X_raw.max() - X_raw.min())
7 y = (y_raw - y_raw.min()) / (y_raw.max() - y_raw.min())

接下来,我们使用梯度下降方法来求线性模型中两个参数a和b的值。

在这里插入图片描述NumPy 下的线性回归

机器学习模型的实现并不是 TensorFlow 的专利。事实上,对于简单的模型,即使使用常规的科学计算库或者工具也可以求解。在这里,我们使用 NumPy 这一通用的科学计算库来实现梯度下降方法。NumPy 提供了多维数组支持,可以表示向量、矩阵以及更高维的张量。同时,也提供了大量支持在多维数组上进行操作的函数(比如下面的np.dot()是求内积,np.sum()是求和)。在这方面,NumPy 和 MATLAB 比较类似。在以下代码中,我们手工求损失函数关于参数a和b的偏导数 [4],并使用梯度下降法反复迭代,最终获得a和b的值。

 1	a, b = 0, 0
 2
 3	num_epoch = 10000
 4	learning_rate = 1e-3
 5	for e in range(num_epoch):
 6	    # 手动计算损失函数关于自变量(模型参数)的梯度
 7 	   y_pred = a * X + b
 8		    grad_a, grad_b = (y_pred - y).dot(X), (y_pred - y).sum()
 9
10	    # 更新参数
11 	   a, b = a - learning_rate * grad_a, b - learning_rate * grad_b
12	
13	print(a, b)

然而,你或许已经可以注意到,使用常规的科学计算库实现机器学习模型有两个痛点:

经常需要手工求函数关于参数的偏导数。如果是简单的函数或许还好,但一旦函数的形式变得复杂(尤其是深度学习模型),手工求导的过程将变得非常痛苦,甚至不可行。

经常需要手工根据求导的结果更新参数。这里使用了最基础的梯度下降方法,因此参数的更新还较为容易。但如果使用更加复杂的参数更新方法(例如 Adam 或者 Adagrad),这个更新过程的编写同样会非常繁杂。

而 TensorFlow 等深度学习框架的出现很大程度上解决了这些痛点,为机器学习模型的实现带来了很大的便利。

TensorFlow 下的线性回归

TensorFlow 的 Eager Execution(即时运行)模式与上述 NumPy 的运行方式十分类似,然而提供了更快速的运算(GPU 支持)、自动求导、优化器等一系列对深度学习非常重要的功能。以下展示了如何使用 TensorFlow 计算线性回归。可以注意到,程序的结构和前述 NumPy 的实现非常类似。这里,TensorFlow 帮助我们做了两件重要的工作:

使用tape.gradient(ys, xs)自动计算梯度;

使用optimizer.apply_gradients(grads_and_vars)自动更新模型参数。

 1 X = tf.constant(X)
 2 y = tf.constant(y)
 3 
 4 a = tf.Variable(initial_value=0.)
 5 b = tf.Variable(initial_value=0.)
 6 variables = [a, b]
 7 
 8 num_epoch = 10000
 9 optimizer = tf.keras.optimizers.SGD(learning_rate=1e-3)
10 for e in range(num_epoch):
11    # 使用tf.GradientTape()记录损失函数的梯度信息
12    with tf.GradientTape() as tape:
13        y_pred = a * X + b
14        loss = 0.5 * tf.reduce_sum(tf.square(y_pred - y))
15    # TensorFlow自动计算损失函数关于自变量(模型参数)的梯度
16    grads = tape.gradient(loss, variables)
17    # TensorFlow自动根据梯度更新参数
18    optimizer.apply_gradients(grads_and_vars=zip(grads, variables))
19
20 print(a, b)

在这里,我们使用了前文的方式计算了损失函数关于参数的偏导数。同时,使用tf.keras.optimizers.SGD(learning_rate=1e-3)声明了一个梯度下降 优化器(Optimizer),其学习率为 1e-3 。优化器可以帮助我们根据计算出的求导结果更新模型参数,从而最小化某个特定的损失函数,具体使用方式是调用其apply_gradients()方法。

注意到这里,更新模型参数的方法optimizer.apply_gradients()需要提供参数grads_and_vars,即待更新的变量(如上述代码中的variables)及损失函数关于这些变量的偏导数(如上述代码中的grads)。具体而言,这里需要传入一个 Python 列表(List),列表中的每个元素是一个(变量的偏导数,变量)对。比如这里是[(grad_a, a), (grad_b, b)]。我们通过grads = tape.gradient(loss, variables)求出 tape 中记录的loss关于variables = [a, b]中每个变量的偏导数,也就是grads = [grad_a, grad_b],再使用 Python 的zip()函数将grads = [grad_a, grad_b]和variables = [a, b]拼装在一起,就可以组合出所需的参数了。

Python 的 zip() 函数

zip()函数是 Python 的内置函数。用自然语言描述这个函数的功能很绕口,但如果举个例子就很容易理解了:如果a = [1, 3, 5],b = [2, 4, 6],那么zip(a, b) = [(1, 2), (3, 4), …, (5, 6)]。即 “将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表”。在 Python 3 中,zip()函数返回的是一个对象,需要调用list()来将对象转换成列表。

在这里插入图片描述
Python 的zip()函数图示

在实际应用中,我们编写的模型往往比这里一行就能写完的线性模型y_pred = a * X + b(模型参数为variables = [a, b])要复杂得多。所以,我们往往会编写并实例化一个模型类model = Model(),然后使用y_pred = model(X)调用模型,使用model.variables获取模型参数。

注:TensorFlow Python API 概览 链接

https://tensorflow.google.cn/versions/r2.0/api_docs/python/tf

Math 链接

https://tensorflow.google.cn/versions/r2.0/api_docs/python/tf/math

本文转载于微信公众号

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值