购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

2.1 理解TensorFlow 1.x

通常来说,学习使用任何计算机语言时,编写的第一个程序都是“Hello World”。在本书中我们也保持惯例!从Hello World程序开始:

让我们细看下这段简单的代码。第一行导入了tensorflow,第二行使用tf.con-stant定义了变量message,第三行使用with定义了Session(),第四行使用run()运行上述会话(session)。注意,这时的运行结果是“字节字符串”(byte string)。为了移除字符串引号和b(对于字节),使用了decode()方法。

2.1.1 TensorFlow 1.x计算图程序结构

TensorFlow 1.x不同于其他编程语言,需要先为要创建的神经网络构建一个蓝图,具体实现是通过将程序分为两个部分:定义计算图和执行计算图。

计算图

计算图是由节点和边构成的神经网络。在本节中,要使用的数据称为张量对象(常量、变量、占位符)。要执行的计算称为操作对象。每个节点可以有零个或多个输入,但只有一个输出。网络中的节点表示对象(张量和操作),边表示不同操作之间传递的张量。计算图定义了神经网络的蓝图,但其中的张量尚未与“值”关联。

占位符只是一个变量,随后会为其分配数据。它让我们不需要数据即可构建计算图。

要构建一个计算图,需要定义所有用到的常量、变量和操作。在后续几节中,我们使用一个简单示例来描述计算图的程序结构,定义并执行一个图来添加两个向量。

计算图执行

计算图的执行由会话对象实现,它封装了张量对象和操作对象求值运算的环境。实际的计算过程和信息的层际传递都在此部分发生。在此之前,所有张量对象的值仅仅是被初始化、访问以及保存在会话对象中,这只是一些抽象定义。直至执行时,它们才开始有了“生命”。

为什么要使用计算图

使用计算图有很多原因。首先,计算图是描述(深度)神经网络最自然的隐喻。其次,可通过移除通用子表达式、融合内核以及剪除多余表达式等方法对计算图进行自动优化。再次,在训练过程中可轻松分发计算图,并将其部署到不同的运行环境(比如CPU、GPU或TPU,以及云、物联网、移动或传统服务器)中。总之,如果你熟悉函数式编程,那么计算图就是一个常用概念,可把它看作简单基本类型的组合(这在函数式编程中很常见)。TensorFlow从计算图中借鉴了很多概念,并做了一些内部优化。

从一个示例开始

对于一个将两个向量相加的简单示例,其计算图如下所示。

定义计算图的相应代码为:

在会话中执行图:

或者:

结束后会打印两个向量的和:

记住,每个会话都需要使用close()显式关闭。

计算图的构建非常简便,只需添加变量和运算并将它们传递(使张量流动)。如此你就可以逐层构建神经网络。另外,TensorFlow还允许使用tf.device()将特定设备(CPU/GPU)与不同的计算图对象一起使用。在示例中,计算图由三个节点组成。其中,v_1和v_2表示两个向量,v_add表示要在v_1和v_2上执行的操作。现在,为了使该图生效,首先需要使用tf.Session()定义一个会话对象。我们将会话对象命名为sess。接下来,使用Session类中定义的run方法运行它:

该方法将评估fetches参数的张量。示例中fetches参数的张量为v_add。run方法将执行图中导入v_add的每个张量和每个操作。假如fetches是v_1而不是v_add,则结果将是向量v_1的值:

fetches可以是单个(或多个)张量对象或者操作对象。例如,如果fetches为[v_1,v_2,v_add],则输出为:

同一程序代码中可以有许多会话对象。在本节中,我们看到了TensorFlow 1.x的计算图程序结构的示例。下面将更详细地介绍TensorFlow 1.x的编程结构。

2.1.2 常量、变量和占位符的使用

简而言之,TensorFlow提供了一个库用来定义和执行带有张量的不同数学运算。张量一般是 n 维数组。所有类型的数据(即标量、向量和矩阵)都是张量的特殊类型:

TensorFlow支持三种类型的张量:

1. 常量 :常量是值不可变的张量。

2. 变量 :当在会话中需要更新值时,应使用变量张量。例如,神经网络在训练期间需要更新权重,这通过将权重声明为变量来实现。变量在使用前需要进行显式初始化。另外要注意,常量存储在计算图定义中,而且每次加载图时都会加载它们,所以会占用大量内存。与之不同,变量是独立存储的,可以存储在参数服务器上。

3. 占位符 :占位符用于将值注入TensorFlow的计算图中,通常与参数feed_dict一起来注入数据。在训练神经网络时,通常用来提供新的训练示例。在会话中运行计算图时,我们将值分配给占位符。它们使我们无须任何数据即可创建操作对象并构建计算图。需要注意的重要细节是,占位符不含任何数据,因此无须初始化。

2.1.3 操作对象示例

让我们看看TensorFlow 1.x中一些不同操作对象的示例。

1. 常量

下面是一些常见的常量。

示例:形状为[1, 3]的常数向量:

示例:zero_t = tf.zeros([2,3],tf.int32) ==>[[0 0 0], [0 0 0]]

示例:print(tf.zeros([2,3],tf.int32).shape) ==> (2, 3)

示例:ones_t = tf.ones([2,3],tf.int32) ==>[[0 0 0], [0 0 0]]

示例:t = tf.Variable([[0., 1., 2.], [3., 4., 5.], [6., 7., 8]])

2. 序列

示例:range_t = tf.linspace(2.0,5.0,5) ==> [ 2. 2.75 3.5 4.25 5. ]

示例:range_t = tf.range(10) ==> [0 1 2 3 4 5 6 7 8 9]

3. 随机张量

TensorFlow允许创建具有不同分布特征的随机张量:

4. 变量

使用类tf.Variable创建变量。定义变量应包括初始化常数或随机值。在下面的代码中,我们创建了两个不同的张量变量t_a和t_b。用形状[50, 50]、minval=0和maxval=10的随机均匀分布对它们进行初始化:

变量常用于表示神经网络的权重和偏差:

此处,我们用可选参数name为计算图中定义的变量命名。在上述所有示例中,变量初始化源自一个常量。我们还可以指定另一个变量来初始化变量。以下语句用上面定义的权重初始化weight2:

初始化变量 :变量的定义需要指定如何初始化,因为必须显式初始化所有声明的变量。在计算图的定义中,通过声明一个操作对象来实现这一点:

运行计算图时,还可以使用tf.Variable.initializer分别初始化每个变量:

保存变量 :可以使用Saver类保存所有变量。为此,定义一个操作对象saver:

占位符 :采用如下语法定义占位符:

dtype用来指定placeholder的数据类型,且须声明时指定。下面,我们将x定义为占位符,并使用feed_dict计算随机4×5矩阵的 y = 2 x (记住,feed_dict用于将值注入TensorFlow占位符中):

所有变量和占位符都在代码的“计算图”部分中确定。如果在定义部分中使用print语句,则将仅获得有关张量类型的信息,而不是张量的值。

要获知其值,我们需要创建会话图,并使用所需的张量值显式运行run命令,代码如下所示:

2.1.4 TensorFlow 2.x中的TensorFlow 1.x示例

我们可以看到TensorFlow 1.x API为创建和操控表示(深度)神经网络和其他类型机器学习程序的计算图提供了灵活的方式。相对而言,TensorFlow 2.x提供了更高级别的API,这些API抽象隐藏了更多的底层实现细节。最后,让我们回到上一章中遇到的TensorFlow 1.x程序示例。此处,我们还添加了一行代码来显示计算图:

注意,语句x*W+b正是上一章中定义的线性感知器。现在,我们启动一个名为“Tensor-Board”的可视化应用程序以显示计算图:

然后打开浏览器访问http://localhost:6006/#graphs&run=。

你应该看到如图2-1所示的内容。

图2-1 计算图的示例

本节概述了TensorFlow 1.x编程范式。现在,让我们将注意力转向TensorFlow 2.x。 W0tWWM66sYH/N+TLXA18iMLkbCtZoxJN57jKYpwP4tcXJlC8XKqNnM9CgQS56Clb

点击中间区域
呼出菜单
上一章
目录
下一章
×