借助TensorFlow API用代码描述的数据流图是每个TensorFlow程序的核心。毫不意外,数据流图这种特殊类型的有向图正是用于定义计算结构的。在 TensorFlow中,数据流图本质上是一组链接在一起的函数,每个函数都会将其输出传递给0个、1个或更多位于这个级联链上的其他函数。按照这种方式,用户可 利用一些很小的、为人们所充分理解的数学函数构造数据的复杂变换。下面来看一个比较简单的例子。

上图展示了可完成基本加法运算的数据流图。在该图中,加法运算是用圆圈表示的,它可接收两个输入(以指向该函数的箭头表示),并将12之和3输出 (对应从该函数引出的箭头)。该函数的运算结果可传递给其他函数,也可直接返回给客户。

  该数据流图可用如下简单公式表示:
 

上面的例子解释了在构建数据流图时,两个基础构件——节点和边是如何使用的。下面回顾节点和边的基本性质:

·节点(node) :在数据流图的语境中,节点通常以圆圈、椭圆和方框表示,代表了对数据所做的运算或某种操作。在上例中,“add”对应于一个孤立节点。

·边(edge) :对应于向Operation传入和从Operation传出的实际数值,通常以箭头表示。在“add”这个例子中,输入12均为指向运算节点的边,而输出3则为 从运算节点引出的边。可从概念上将边视为不同Operation之间的连接,因为它们将信息从一个节点传输到另一个节点。

  下面来看一个更有趣的例子。
 

相比之前的例子,上图所示的数据流图略复杂。由于数据是从左侧流向右侧的(如箭头方向所示),因此可从最左端开始对这个数据流图进行分析:

1)最开始时,可看到两个值53流入该数据流图。它们可能来自另一个数据流图,也可能读取自某个文件,或是由客户直接输入。

2)这些初始值被分别传入两个明确的“input”节点(图中分别以ab标识)。这些“input”节点的作用仅仅是传递它们的输入值——节点a接收到输入值5后,将 同样的数值输出给节点c和节点d,节点b对其输入值3也完成同样的动作。

3)节点c代表乘法运算。它分别从节点ab接收输入值53,并将运算结果15输出到节点e。与此同时,节点d对相同的两个输入执行加法运算,并将计算结 果8传递给节点e

4)最后,该数据流图的终点——节点e是另一个“add”节点。它接收输入值158,将两者相加,然后输出该数据流图的最终结果23

下面说明为何上述图形表示看起来像是一组公式:

a=5b=3时,若要求解e,只需依次代入上述公式。

经过上述步骤,便完成了计算,这里有一些概念值得重点说明:

·上述使用“input”节点的模式十分有用,因为这使得我们能够将单个输入值传递给大量后继节点。如果不这样做,客户(或传入这些初值的其他数据源)便 不得不将输入值显式传递给数据流图中的多个节点。按照这种模式,客户只需保证一次性传入恰当的输入值,而如何对这些输入重复使用的细节便被隐藏起 来。稍后,我们将对数据流图的抽象做更深入的探讨。

·突击小测验。哪一个节点将首先执行运算?是乘法节点c还是加法节点d?答案是:无从知晓。仅凭上述数据流图,无法推知cd中的哪一个节点将率先执 行。有的读者可能会按照从左到右、自上而下的顺序阅读该数据流图,从而做出节点c先运行的假设。但我们需要指出,在该数据流图中,将节点d绘制在c的上 方也未尝不可。也可能有些读者认为这些节点会并发执行,但考虑到各种实现细节或硬件的限制,实际情况往往并非总是如此。实际上,最好的方式是将它们 的执行视为相互独立。由于节点c并不依赖于来自节点d的任何信息,所以节点c在完成自身的运算时无需关心节点d的状态如何。反之亦然,节点d也不需要任何 来自节点c的信息。在本章稍后,还将对节点依赖关系进行更深入的介绍。

  接下来,对上述数据流图稍做修改。
 

主要的变化有两点:
1)来自节点b“input”3现在也传递给了节点e2)节点e中的函数“add”被替换为“sum”,表明它可完成两个以上的数的加法运算。

你已经注意到,上图在看起来被其他节点隔离的两个节点之间添加了一条边。一般而言,任何节点都可将其输出传递给数据流图中的任意后继节点,而无 论这两者之间发生了多少计算。数据流图甚至可以拥有下图所示的结构,它仍然是完全合法的。

通过这两个数据流图,想必你已能够初步感受到对数据流图的输入进行抽象所带来的好处。我们能够对数据流图中内部运算的精确细节进行操控,但客户 只需了解将何种信息传递给那两个输入节点则可。我们甚至可以进一步抽象,将上述数据流图表示为如下的黑箱。

这样,我们便可将整个节点序列视为拥有一组输入和输出的离散构件。这种抽象方式使得对级联在一起的若干个运算组进行可视化更加容易,而无需关心 每个部件的具体细节。