50. TensorFlow 基础概念语法#

50.1. 介绍#

本次实验开始,我们将正式进入到深度学习的内容。深度学习的关键,其实在于深度神经网络的构建,而如果你从 0 开始自己编程构建一个深度神经网络,那么过程将会是十分复杂的。所以,为了更方便地实现深度学习模型,我们需要掌握一些常见的深度学习框架的使用。目前在整个深度学习社区里,比较流行的框架有 TensorFlow 和 PyTorch ,他们都有各自独特的特点。其中,TensorFlow 因为背靠谷歌 Google 这座大山,再加上庞大的开发者群体,更新和发版速度着实非常快。了解并掌握 TensorFlow 的使用,将使你在搭建深度学习模型时更加得心应手。

50.2. 知识点#

  • TensorFlow 介绍

  • 张量的概念

  • Eager Execution 特性

  • TensorFlow API 概览

50.3. TensorFlow 介绍#

https://cdn.huhuhang.com/hands-on-ai/images/document-uid214893labid7506timestamp1553241997327.svg

TensorFlow 是由谷歌在 2015 年 11 月发布的深度学习开源工具,我们可以用它来快速构建深度神经网络,并训练深度学习模型。运用 TensorFlow 及其他开源框架的主要目的,就是为我们提供一个更利于搭建深度学习网络的模块工具箱,使开发时能够简化代码,最终呈现出的模型更加简洁易懂。根据官方的介绍,TensorFlow 主要有以下特点:

  • 高度灵活性:采用数据流图结构,只要计算可以表示为一个数据流图,就可以使用 TensorFlow。

  • 可移植性:在 CPU,GPU,服务器,移动端,云端,Docker 容器上都可以运行。

  • 自动求微分:在 TensorFlow 中,梯度的运算都将基于你输入的模型结构和目标函数自动完成。

  • 多语言支持:提供 Python,C++,Java,Go 接口。

  • 优化计算资源:TensorFlow 允许用户将数据流图上的不同计算元素分配到不同设备,最大化利用硬件资源来进行深度学习运算。

2019 年,TensorFlow 推出了 2.0 版本,也意味着 TensorFlow 从 1.x 正式过度到 2.x 时代。根据 TensorFlow 官方 更新说明,2.0 版本将专注于简洁性和易用性的改善,主要升级方向包括:

  • 使用 Keras 和 Eager Execution 轻松构建模型。

  • 在任意平台上实现稳健的生产环境模型部署。

  • 为研究提供强大的实验工具。

  • 通过清理废弃的 API 和减少重复来简化 API。

看完这些特点,总结来讲就是 TensorFlow 很好、很强大。与此同时,TensorFlow 还在日益发展。所以,如果我们使用 TensorFlow 作为生产平台和科研基础研发,已经非常坚实可靠。接下来,我们将从 TensorFlow 基础概念语法入手,一步一步学习 TensorFlow 的使用。

50.4. 张量#

TensorFlow 中的 Tensor 即为张量,如果你第一次听说,一定会感觉到它是一个很厉害的东西吧。它的确很厉害,但是不难理解。张量的概念贯穿于物理学和数学中,如果你去看它的很多理论描述,可能并不那么浅显易懂。例如,下面有两种关于 张量的定义

  • 通常定义张量的物理学或传统数学方法,是把张量看成一个多维数组,当变换坐标或变换基底时,其分量会按照一定规则进行变换,这些规则有两种:即协变或逆变转换。

  • 通常现代数学中的方法,是把张量定义成某个矢量空间或其对偶空间上的多重线性映射,这矢量空间在需要引入基底之前不固定任何坐标系统。例如协变矢量,可以描述为 1-形式,或者作为逆变矢量的对偶空间的元素。

上面的定义不知道你看懂了没有?估计会有点困难。下面,我们进行通俗易懂的说明:

首先,你应该知道什么是向量和矩阵。先前的介绍中,我们把 \(1\) 维的数组称之为向量,\(2\) 维的数组称之为矩阵。那么,现在告诉你张量其实代表着更大的范围,你也可以把其看作是 \(N\) 维数组。

所以,如果现在重新描述向量和矩阵,就可以是:一阶张量为向量,二阶张量为矩阵。当然,零阶张量也就是标量,而更重要的是 \(N\) 阶张量,也就是 \(N\) 维数组。

数学实例

0

标量(只有大小)

1

矢量(大小和方向)

2

矩阵(数据表)

3

3 阶张量(数据立体)

N

N 阶张量(自行想象)

https://cdn.huhuhang.com/hands-on-ai/images/document-uid214893labid6102timestamp1532052623486.png

所以,张量并不是什么晦涩难懂的概念。如果不严谨的讲,张量就是 \(N\) 维数组。前面提到的向量、矩阵,也是张量。你即将学习到的大多数深度学习框架都会使用张量的概念,这样做的好处是统一对数据的定义。NumPy 中,数据都使用 Ndarray 多维数组进行定义,TensorFlow 中,数据都会用张量进行表述。

50.4.1. 张量类型#

在 TensorFlow 中,每一个张量都具备 3 个基础属性:数据,数据类型和形状。而根据不同的用途,张量本身又分为 2 种类型,分别是:

  • tf.Variable:变量 Tensor,需要指定初始值,常用于定义可变参数,例如神经网络的权重。

  • tf.constant:常量 Tensor,需要指定初始值,定义不变化的张量。

我们可以通过传入数组来新建变量和常量类型的张量:

import tensorflow as tf

v = tf.Variable([[1, 2], [3, 4]])  # 形状为 (2, 2) 的二维变量
v
<tf.Variable 'Variable:0' shape=(2, 2) dtype=int32, numpy=
array([[1, 2],
       [3, 4]], dtype=int32)>
c = tf.constant([[1, 2], [3, 4]])  # 形状为 (2, 2) 的二维常量
c
<tf.Tensor: shape=(2, 2), dtype=int32, numpy=
array([[1, 2],
       [3, 4]], dtype=int32)>

仔细观察,你会发现输出包含了张量的 3 部分属性,分别是形状 shape,数据类型 dtype,以及对应的 NumPy 数组。

我们可以直接通过 .numpy() 输出张量的 NumPy 数组。

c.numpy()
array([[1, 2],
       [3, 4]], dtype=int32)

50.4.2. 常用张量#

上面我们已经介绍了常量张量,这里再列举几个经常会用到的新建常用的常量张量方法:

  • tf.zeros:新建指定形状且全为 0 的常量 Tensor

  • tf.zeros_like:参考某种形状,新建全为 0 的常量 Tensor

  • tf.ones:新建指定形状且全为 1 的常量 Tensor

  • tf.ones_like:参考某种形状,新建全为 1 的常量 Tensor

  • tf.fill:新建一个指定形状且全为某个标量值的常量 Tensor

tf.zeros([3, 3])  # 3x3 全为 0 的常量 Tensor
<tf.Tensor: shape=(3, 3), dtype=float32, numpy=
array([[0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.]], dtype=float32)>
tf.ones_like(c)  # 与 c 形状一致全为 1 的常量 Tensor
<tf.Tensor: shape=(2, 2), dtype=int32, numpy=
array([[1, 1],
       [1, 1]], dtype=int32)>
tf.fill([2, 3], 6)  # 2x3 全为 6 的常量 Tensor
<tf.Tensor: shape=(2, 3), dtype=int32, numpy=
array([[6, 6, 6],
       [6, 6, 6]], dtype=int32)>

除此之外,我们还可以创建一些序列,例如:

tf.linspace(1.0, 10.0, 5)  # 从 1 到 10,共 5 个等间隔数
<tf.Tensor: shape=(5,), dtype=float32, numpy=array([ 1.  ,  3.25,  5.5 ,  7.75, 10.  ], dtype=float32)>
tf.range(start=1, limit=10, delta=2)  # 从 1 到 10 间隔为 2
<tf.Tensor: shape=(5,), dtype=int32, numpy=array([1, 3, 5, 7, 9], dtype=int32)>

如果你熟悉 NumPy 的话,你会发现这与 NumPy 中创建各式各样的多维数组方法大同小异。了解完张量,我们就可以继续学习 TensorFlow 中的张量运算机制了。

Note

一次性记忆 TensorFlow 中的各种张量方法是不可能的,是能根据后续的使用不断熟悉。很多时候,我们会先有需求,比如你需要生成等间隔数。此时,再去官方文档中搜索是否有相应现成函数可供使用。TensorFlow 提供的方法基本满足了常见的各种需求。

50.5. Eager Execution#

TensorFlow 2 带来的最大改变之一是将 1.x 的 Graph Execution(图与会话机制)更改为 Eager Execution(动态图机制)。在 1.x 版本中,低级别 TensorFlow API 首先需要定义数据流图,然后再创建 TensorFlow 会话,这一点在 2.0 中被完全舍弃。TensorFlow 2 中的 Eager Execution 是一种命令式编程环境,可立即评估操作,无需构建图。

所以说,TensorFlow 的张量运算过程可以像 NumPy 一样直观且自然了。接下来,我们以最简单的加法运算为例:

c + c  # 加法计算
<tf.Tensor: shape=(2, 2), dtype=int32, numpy=
array([[2, 4],
       [6, 8]], dtype=int32)>

如果你接触过 1.x 版本的 TensorFlow,你要知道一个加法运算过程十分复杂。我们需要初始化全局变量 → 建立会话 → 执行计算,最终才能打印出张量的运算结果。

# 上述加法运算的 TensorFlow 1.x 实现代码
init_op = tf.global_variables_initializer()  # 初始化全局变量
with tf.Session() as sess:  # 启动会话
    sess.run(init_op)
    print(sess.run(c + c))  # 执行计算

Eager Execution 带来的好处显而易见,其进一步降低了 TensorFlow 的入门门槛。之前的 Graph Execution 模式,实际上让很多新手在入门时都很郁闷,因为完全不符合正常思维习惯。

TensorFlow 中提供的数学计算,包括线性代数计算方面的方法也是应有尽有,十分丰富。下面,我们列举一个示例。

a = tf.constant([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], shape=[2, 3])
b = tf.constant([7.0, 8.0, 9.0, 10.0, 11.0, 12.0], shape=[3, 2])

c = tf.linalg.matmul(a, b)  # 矩阵乘法
c
<tf.Tensor: shape=(2, 2), dtype=float32, numpy=
array([[ 58.,  64.],
       [139., 154.]], dtype=float32)>
tf.linalg.matrix_transpose(c)  # 转置矩阵
<tf.Tensor: shape=(2, 2), dtype=float32, numpy=
array([[ 58., 139.],
       [ 64., 154.]], dtype=float32)>

你应该能够感觉到,这些常用 API 都能在 NumPy 中找到对应的方法,这也就是课程需要你预先熟悉 NumPy 的原因。由于 TensorFlow 和 NumPy 的紧密结合,就算 TensorFlow 中没有提供 NumPy 中现有的计算函数,你都可以先使用 NumPy 完成计算后再转换为 TensorFlow 张量表示。由于函数实在太多。一般来讲,除了自己经常使用到的,都会在需要某种运算的时候,查阅官方文档。

50.6. 自动微分#

在数学中,微分是对函数的局部变化率的一种线性描述。虽然微分和导数是两个不同的概念。但是,对一元函数来说,可微与可导是完全等价的。前面,我们已经熟悉了神经网络的搭建过程,你应该明白梯度的重要性。而对于复杂函数的微分过程是及其麻烦的,为了提高应用效率,大部分深度学习框架都有自动微分机制。

接下来,我们演示了一个自动微分过程,假设一个数学求导过程如下:

\[ loss = w^2 \rightarrow \frac {\partial loss}{\partial w} = 2w \]

所以,当 \(w\) 等于 1 时,计算结果为 2。

TensorFlow 中,你可以使用 tf.GradientTape 跟踪全部运算过程,以便在必要的时候计算梯度。

w = tf.Variable([1.0])  # 新建张量

with tf.GradientTape() as tape:  # 追踪梯度
    loss = w * w  # 计算过程

tape.gradient(loss, w)  # 计算梯度
<tf.Tensor: shape=(1,), dtype=float32, numpy=array([2.], dtype=float32)>

tf.GradientTape 会像磁带一样记录下计算图中的梯度信息,然后使用 .gradient 即可回溯计算出任意梯度,这对于使用 TensorFlow 低阶 API 构建神经网络时更新参数非常重要。

50.7. 常用模块#

上面,我们已经学习了 TensorFlow 核心知识,接下来将对 TensorFlow API 中的常用模块进行简单的功能介绍。对于框架的使用,实际上就是灵活运用各种封装好的类和函数。由于 TensorFlow API 数量太多,迭代太快,所以大家要养成随时 查阅官方文档 的习惯。

  • tf.:包含了张量定义,变换等常用函数和类。

  • tf.data:输入数据处理模块,提供了像 tf.data.Dataset 等类用于封装输入数据,指定批量大小等。

  • tf.image:图像处理模块,提供了像图像裁剪,变换,编码,解码等类。

  • tf.keras:原 Keras 框架高阶 API。包含原 tf.layers 中高阶神经网络层。

  • tf.linalg:线性代数模块,提供了大量线性代数计算方法和类。

  • tf.losses:损失函数模块,用于方便神经网络定义损失函数。

  • tf.math:数学计算模块,提供了大量数学计算函数。

  • tf.saved_model:模型保存模块,可用于模型的保存和恢复。

  • tf.train:提供用于训练的组件,例如优化器,学习率衰减策略等。

  • tf.nn:提供用于构建神经网络的底层函数,以帮助实现深度神经网络各类功能层。

  • tf.estimator:高阶 API,提供了预创建的 Estimator 或自定义组件。

在构建深度神经网络时,TensorFlow 可以说提供了你一切想要的组件,从不同形状的张量、激活函数、神经网络层,到优化器、数据集等,一应俱全。由于 TensorFlow 包含的接口太多,每个都拿出来练习变得不切实际。所以,后续我们会在使用到相应函数时再仔细介绍。

Note

学习时,可以通过上面的超链接快速浏览 TensorFlow 各主要模块下包含的方法。无需记忆,留下一个基础的印象即可,这对于你了解 TensorFlow 全貌很有帮助。

50.8. 总结#

TensorFlow 是一个非常强大的框架,许多人用了很长时间都不敢说能够学的很好。学习这种庞大而又复杂的框架,不要想一口吃个大胖子,而是一口一口慢慢吃才是正确思路。本节实验中,我们了解了 TensorFlow 的 Eager Execution 工作机制,学习了由张量及自动微分方法,同时梳理了 TensorFlow 中常用模块及功能。

相关链接