基础知识
TensorFlow是一种“符号式编程框架”,首先要构造一个图(graph),然后在会话(Session)上根据这个图做真实的运算(op)。打个比方,graph就像多条生产线,session就像生产者。生产线具有一系列的加工步骤(加减乘除等运算),生产者把原料投进去,就能得到产品。不同生产者都可以使用这条生产线,只要他们的加工步骤是一样的就行。同样的,一个graph可以供多个session使用,而一个session不一定需要使用graph的全部,可以只使用其中的一部分。
graph即tf.Graph(),session即tf.Session(),是两个完全独立的概念。
- graph定义了计算方式,是一些加减乘除等运算的组合。它本身不会进行任何计算,也不保存任何中间计算结果。
- session用来运行一个graph,或者运行graph的一部分。它类似于一个执行者,给graph灌入输入数据,得到输出,并保存中间的计算结果。同时它也给graph分配计算资源(如内存、显卡等)。
下图是用tensorflow制作大盘鸡和红烧肉的过程,以此为例来说明graph和session的区别:
左图中绿色矩形为数据,黄色圆圈为中间结果,红色圆圈为最终结果,这是一个完整的制作大盘鸡和红烧肉的graph(相当于是一个菜谱),图中每一个独立单元都可以看成是一个op(操作,包括数据)。在tensorflow中只有graph是没法得到结果的,这就像只有菜谱不可能得到红烧肉是一个道理。于是就有了tf.Session(),他根据graph制定的步骤,将graph变成现实。
tf.Session()就相当于一个厨师长,他下面有很多办事的人(Session()下的各种方法),其中有一个非常厉害厨师叫tf.Session.run(),他不仅会烧菜,还会杀猪、酿酒、制作酱料等一系列工作,比如:
我的酱料 = sess.run(酱料):run收到制作“酱料”的命令,于是他看了下graph,需要“酵母”和“大豆”来制作酱料,最终他把酱料制作好了(这里酵母和大豆是graph定义好的,但也可以根据自己的喜好来换)。
我的料酒 = sess.run(料酒,feed_dic={米:泰国籼米}):run又收到要制作“料酒”的命令,而且不用graph规定的“米”来做,需要用“泰国籼米”,没关系,run跑去买了泰国籼米,又把料酒给做了。
我的红烧肉 = sess.run(红烧肉):傍晚,run又收到了做一份完整红烧肉的命令,这下他有的忙了,必须将整个流程走一遍,才能完成个任务。
我的大盘鸡 = sess.run(大盘鸡): 后来,run又收到做大盘鸡的任务,这是一个独立的任务,跟红烧肉没有半点关系,但不影响,他只要按照步骤照做就可以了。
关于Graph
定义一个图:graph
#定义一个graph
g = tf.Graph()
#默认在g中定义下面op
a = tf.constant(2)
b = tf.constant(3)
x = tf.add(a, b)
上面就定义了一个graph。tensorflow会默认给我们建立一个graph,所以g = tf.Graph()这句其实是可以省略的。上面的graph包含3个操作(即op),但凡是op,都需要通过session运行之后,才能得到结果。如果你直接执行print(a),那么输出结果是:Tensor("a:0", shape=(), dtype=int32)。执行print(tf.Session().run(a))或with方法,才能得到2。可见,在tensorflow中,即使是最基本的对象Tensor(张量)也需要在Session中才能得到其值,而不能企图通过python一样的方式直接得到其结果。
定义多个图:多个graph
你可以定义多个graph,例如一个graph实现z = x + y,另一个graph实现u = 2 * v
g1 = tf.Graph() #定义一个graph
g2 = tf.Graph() #定义另一个graph
with g1.as_default(): #在指定graph中定义op
x = tf.constant(2)
y = tf.constant(3)
z = tf.add(x, y)
with g2.as_default(): #在指定graph中定义op
v = tf.constant(4)
u = tf.mul(2, v)
上述代码定义了两个graph并分别在其中定义了不同的op,但通常不建议这么做,原因如下:
- 运行多个graph需要多个session,而每个session会试图耗尽所有的计算资源,开销太大;
- graph之间没有数据通道,要人为通过python/numpy传数据。
事实上,我们可以把所有的op都定义在一个graph中:
# 没有显示定义graph,系统自动提供一个默认的graph
# 并在其中定义下面op
x = tf.constant(2)
y = tf.constant(3)
z = tf.add(x, y)
v = tf.constant(4)
u = tf.mul(2, v)
从上面graph的定义可以看到,x/y/z是一波,u/v是另一波,二者没有任何交集。这相当于在一个graph里有两个独立的subgraph。当你要计算z = x + y时,执行tf.Session().run(z);当你想计算u = 2 * v,就执行tf.Session().run(u),二者完全独立。但更重要的是,二者在同一个session上运行,系统会均衡地给两个subgraph分配合适的计算资源。
- 我们可以把所有的op(op之间不一定相互有联系,如上图中的红烧肉和大盘鸡,两者完全独立)都定义在同一个graph上,Session在执行某个op时,只执行跟该op有关联的其他op,与其不想关的op是不会被执行的。
关于Session(待完善)
所有的节点计算都在session中完成,tf.Session()是一个大类,使用最多的方法是tf.Session.run()(见:https://www.jianshu.com/writer#/notebooks/29416844/notes/50146577)
通常我们会显示地定义一个session来运行graph,通常采用with的方式(推荐),也可用其他方式:
# 以下在一个默认graph中定义了三个op
x = tf.constant(2)
y = tf.constant(3)
z = tf.add(x, y)
# 以下显示启动了一个Session并在其中执行z
with tf.Session() as sess:
result = sess.run(z)
print(result)
输出结果是5。