1.4 TensorFlow 计算模型和运行模型
在TensorFlow中,定义计算操作的过程通过计算图的构建来完成。计算图也叫数据流图,它是用来描述TensorFlow中要进行的数学计算操作的有向图。计算图包含两种基本的要素:“节点”(Node)和“边”(Edge)。其中,节点一般表示对数据施加的数学操作(有时也可以表示数据输入的起点/输出的终点,或者读取/写入持久变量的终点);边则表示在节点间相互联系的多维数据数组,即张量。图1-2展示了通过TensorBoard画的一个计算图的例子。
图1-2 乘法计算操作的计算图示例
在该计算图中,包含a、b和MatMul 三个节点。其中a和b是两个常量,表示数据输入的起点,而MatMul表示对a和b施加的乘法操作;计算图中存在1条从a到MatMul的边和1条从b到MatMul的边,表示MatMul操作依赖读取a和b的值。在TensorFlow中,任何程序都可以通过类似的计算图的形式来表示。
目前,TensorFlow 有三种计算图的构建方式:静态计算图、动态计算图及使用Autograph构建计算图。我们以图1-2中的计算图构建为例,分别对三种计算图的构建方式进行说明。
1.静态计算图
TensorFlow 1.0 的各种版本主要采用静态计算图的构建方式。在这种构建方式中,TensorFlow 程序被组织成一个构建阶段和一个执行阶段。构建阶段用来定义TensorFlow中将要进行的所有计算操作,执行阶段则根据输入的数据执行之前定义的所有计算操作。当计算图构建完成后,所有的运算并不会立即执行,而是在开启会话(Session)后,所有的计算才开始执行。
静态计算图这种构建方式通过 tf.graph 类建立一个新的计算图,并在新的计算图中构建节点和边。利用这种方式进行图1-2中的计算图构建的代码如下:
经过以上代码,我们已经完成了图1-2中的计算图的构建。构建的计算图g中现在有三个节点:两个常量类型的输入节点a和b,以及一个乘法操作类型的节点MatMul。可以通过以下代码查看节点a是否在构建的静态计算图中:
可知,打印的结果为True。
此时,打印y的值:
得到的打印结果:
Tensor("MatMul:0",shape=(1,1),dtype=float32)
可以看到,在上述代码中,尽管我们已经定义了TensorFlow中将要进行的所有计算操作,但具体的操作运算并未开始执行,即当前张量 y 中并没有保存具体的数值,而只是保存了如何得到y的计算操作。要想得到y的具体数值,还需要在执行阶段通过会话来实现。
TensorFlow 中的会话是来执行定义好的运算的。在静态计算图的构建模型中,会话拥有并管理TensorFlow程序运行时的所有资源,可以在一台或多台机器上进行资源的分配并保存中间结果和变量的实际值,所有的实际计算都必须在会话中进行。而当所有计算都完成之后,必须及时关闭会话来进行系统资源的回收,否则就可能出现资源泄露的问题。
在TensorFlow中,一般采用下列两种方式来使用会话:第一种方式,采用会话生成函数和关闭函数来创建及关闭会话;第二种方式,采用Python中的上下文管理器来使用会话。
以图1-2中的计算图为例,通过上述代码,TensorFlow已经完成了矩阵a和b相乘的计算图的构建,但还未进行y的具体数值的计算。为了真正执行a和b的矩阵相乘运算,并得到矩阵乘法的结果,必须在会话里启动定义好的计算图以完成y值的计算。
根据第一种使用会话的方式,计算y值的代码如下:
这种方式通过 tf.Session()和 Session.close()函数来关闭会话,但缺点是,在程序因意外退出时,关闭会话的函数可能不会被执行,从而导致系统资源的外泄。
我们也可以采用第二种使用会话方式来完成y值的计算,代码如下:
这种方式不需要再调用Session.close()函数来关闭会话。当上下文退出时,会话关闭和资源释放也自动完成,从而避免了系统资源的泄露,因此这种方式更为常用。
TensorFlow 中也存在一种默认会话的机制,用户可以手动地将某个会话指定为默认会话。在默认会话中,计算一个张量的取值可以通过tf.Tensor.eval()函数实现。以下给出了通过设定默认会话来计算张量y值的代码:
在交互环境下(如 IPython 或者 Jupyter 的编辑器下),通过设置默认会话的方式来获取张量的值更加方便。因此,TensorFlow 提供了一种在交互环境下直接构建默认会话的函数 tf.InteractiveSession()。通过该函数可以将生成的会话自动地指定为默认会话,从而避免了人为的指定过程。以下给出了交互环境下采用 tf.InteractiveSession()函数计算 y值的代码:
2.动态计算图
不同于静态计算图的构建方式中 TensorFlow 程序被组织成一个构建阶段和一个执行阶段,在动态计算图的构建方式中,每进行一个计算操作后,该操作会动态地加入默认的计算图中,立即执行并得到结果,而无须再开启会话进行操作运算。这种立即执行操作计算的方式被称为Eager模式。Eager模式在TensorFlow 1.0的各版本中已经可以通过以下代码进行初步使用:
TensorFlow 2.0的版本中,为了使用户更方便地使用TensorFlow,将Eager模式作为默认的计算图构建模式。在 Eager 模式下,所有的计算操作会动态地加入默认的计算图中,并立即执行。通过该方式进行计算图的构建和执行的代码如下:
可以通过以下代码查看节点a是否在默认计算图中:
可知,打印的结果为True。
此时,打印y的值:
得到的打印结果:
tf.Tensor([[14.]],shape=(1,1),dtype=float32)
可知,y的值已经计算出来了。
3.使用Autograph构建计算图
与静态计算图的构建方式相比,动态计算图的方式更为方便,目前也更为常用。但由于 TensorFlow 以C++为底层的构建语言,使用动态计算图会有许多次 Python 进程和TensorFlow 的 C++进程之间的通信,而静态计算图则在构建完成后几乎全部在TensorFlow 内核上采用 C++代码执行,因此动态计算图比静态计算图的运行效率稍低。此外,静态计算图也会对计算步骤进行一定的优化,这也是静态计算图的运行效率更高的一个原因。
为了使TensorFlow程序既能够像动态计算图一样被用户较为方便地使用,又具有较高的运行效率,可以用@tf.function装饰器将普通Python函数转换成与TensorFlow 1.0对应的静态计算图构建代码。类似于使用静态计算图,TensorFlow 程序的实现分为定义计算图和在会话中执行计算图两个步骤。在TensorFlow 2.0中,如果采用Autograph的方式使用计算图,则包括定义函数(类似于定义计算图)和调用函数(类似于执行计算图)两个步骤。
通过这种方式进行计算图的构建和执行的代码如下:
此时,打印y的值:
得到的打印结果:
tf.Tensor([[14.]],shape=(1,1),dtype=float32)