一些关键问题
-
with tf.Graph().as_default():
之前不明白为什么要这么写呢,有人说是为了隔离多个图,但是实际上并没有需要多图才能完成的任务。这么写只是为了能更好的隔离变量和操作,其实并没有什么卵用。 -
with tf.Graph() as g:
这个代码基本没人写的原因是如果不设置成默认的graph那么以后sess
中要设置tf.Session(graph = g)
比较麻烦,所以写这个语句不如写上面的语句。 -
tf.Graph().as_default()
的含义和tf.Session().as_default()
的实际含义是不一样的,因为Graph的as_default()出了这个上下文管理器就不能用了,只是表示在这个with语句里面讲这个图设为默认图,出了with语句就自动恢复原来默认图了。而Session的as_default()是出了with语句还能继续使用。
# 1. Using Graph.as_default():
g = tf.Graph()
with g.as_default():
c = tf.constant(5.0)
assert c.graph is g
# 2. Constructing and making default:
with tf.Graph().as_default() as g:
c = tf.constant(5.0)
assert c.graph is g
什么是图
- tensorflow中的数据和相应的运算都是定义在图上的。
- Graph(计算图)就是节点与边的集合,领域模型何其简单。计算图是一个DAG图,计算图的执行过程将按照DAG的拓扑排序,依次启动OP的运算。其中,如果存在多个入度为0的节点,TensorFlow运行时可以实现并发,同时执行多个OP的运算,提高执行效率。
- tensorflow中的大多数函数只会将操作和张量添加到默认图中,而不会执行实际计算。
- tf.Graph() 表示实例化了一个类,一个用于tensorflow 计算和表示用的数据流图,通俗来讲就是:在代码中添加的操作(画中的结点)和数据(画中的线条)都是画在纸上的“画”,而图就是呈现这些画的纸,你可以利用很多线程生成很多张图,但是默认图就只有一张。
- tf.Graph().as_default() 表示将这个实例即新生成的图作为整个 tensorflow 运行环境的默认图,如果只有一个主线程不写也没有关系,tensorflow 里面已经存好了一张默认图,可以使用tf.get_default_graph() 来调用(显示这张默认纸),当你有多个线程就可以创造多个tf.Graph(),就是你可以有一个画图本,有很多张图纸,这时候就会有一个默认图的概念了。
self.model = model
self.graph = tf.Graph()
with self.graph.as_default():
self.sess = tf.Session()
with self.sess.as_default():
initializer = tf.contrib.layers.xavier_initializer(uniform = True)
with tf.variable_scope("model", reuse=None, initializer = initializer):
self.trainModel = self.model(config = self)
if self.optimizer != None:
pass
elif self.opt_method == "Adagrad" or self.opt_method == "adagrad":
self.optimizer = tf.train.AdagradOptimizer(learning_rate = self.alpha, initial_accumulator_value=1e-20)
elif self.opt_method == "Adadelta" or self.opt_method == "adadelta":
self.optimizer = tf.train.AdadeltaOptimizer(self.alpha)
elif self.opt_method == "Adam" or self.opt_method == "adam":
self.optimizer = tf.train.AdamOptimizer(self.alpha)
else:
self.optimizer = tf.train.GradientDescentOptimizer(self.alpha)
grads_and_vars = self.optimizer.compute_gradients(self.trainModel.loss)
self.train_op = self.optimizer.apply_gradients(grads_and_vars)
self.saver = tf.train.Saver()
self.sess.run(tf.initialize_all_variables())
空图
计算图的初始状态,并非是一个空图。实现添加了两个特殊的节点:Source与Sink节点,分别表示DAG图的起始节点与终止节点。其中,Source的id为0,Sink的id为1;依次论断,普通OP节点的id将大于1。
另外,Source与Sink之间,通过连接「控制依赖」的边,保证计算图的执行始于Source节点,终于Sink节点。它们之前连接的控制依赖边,其src_output, dst_input
值都为-1。
习惯上,仅包含Source与Sink节点的计算图也常常称为空图。
图的可视化
# Build your graph.
x = tf.constant([[37.0, -23.0], [1.0, 4.0]])
w = tf.Variable(tf.random_uniform([2, 2]))
y = tf.matmul(x, w)
# ...
loss = ...
train_op = tf.train.AdagradOptimizer(0.01).minimize(loss)
with tf.Session() as sess:
# `sess.graph` provides access to the graph used in a <a href="../api_docs/python/tf/Session"><code>tf.Session</code></a>.
writer = tf.summary.FileWriter("/tmp/log/...", sess.graph)
# Perform your computation...
for i in range(1000):
sess.run(train_op)
# ...
writer.close()
创建多个图
- 训练模型时,整理代码的一种常用方法是使用一个图训练模型,然后使用另一个图对训练过的模型进行评估或推理。
- 在许多情况下,推理图与训练图不同。
- 默认图会存储与添加的每个
tf.Operation
和tf.Tensor
有关的信息。如果程序创建了大量未连接的子图,更有效的做法是使用另一个tf.Graph
构建每个子图,以便回收不相关的状态。 - 图是归属于线程的,如果想在不同的线程里面使用默认图,必须使用g.as_default()
The default graph is a property of the current thread. If you create a new thread, and wish to use the default graph in that thread, you must explicitly add a with g.as_default(): in that thread's function.
with g_1.as_default():
# Operations created in this scope will be added to `g_1`.
c = tf.constant("Node in g_1")
# Sessions created in this scope will run operations from `g_1`.
sess_1 = tf.Session()
g_2 = tf.Graph()
with g_2.as_default():
# Operations created in this scope will be added to `g_2`.
d = tf.constant("Node in g_2")
# Alternatively, you can pass a graph when constructing a <a href="../api_docs/python/tf/Session"><code>tf.Session</code></a>:
# `sess_2` will run operations from `g_2`.
sess_2 = tf.Session(graph=g_2)
assert c.graph is g_1
assert sess_1.graph is g_1
assert d.graph is g_2
assert sess_2.graph is g_2
执行过程
- 在分布式的运行时环境中,Client 执行Session.run 时,传递整个计算图给后端的
Master。此时,计算图是完整的,常称为Full Graph。随后,Master 根据Session.run 传递给它的fetches, feeds 参数列表,反向遍历Full Graph,并按照依赖关系,对其实施剪枝,最终计算得到最小的依赖子图,常称为Client Graph。 - 接着,Master 负责将Client Graph 按照任务的名称分裂(SplitByTask) 为多个Graph Partition;其中,每个Worker 对应一个Graph Partition。随后,Master 将Graph Partition分别注册到相应的Worker 上,以便在不同的Worker 上并发执行这些Graph Partition。最后,Master 将通知所有Work 启动相应Graph Partition 的执行过程。其中,Work 之间可能存在数据依赖关系,Master 并不参与两者之间的数据交换,它们两两之间互相通信,独立地完成交换数据,直至完成所有计算。
图实例
- 一般地,OP 注册到一个全局的、唯一的、隐式的、默认的图实例中。特殊地,TensorFlow也可以显式地创建新的图实例g,并调用g.as_default() 使其成为当前线程中唯一默认的图实例,并在该上下文管理器中所创建的OP 都将自动注册到该图实例中。
with tf.Graph().as_default() as g:
c = tf.constant(5.0)
assert c.graph is g
- 事实上,g.as_default 从当前线程的图栈中返回了一个上下文管理器,使得当前的图实例g 覆盖原来默认的图实例;当退出了上下文管理器后,则恢复原来默认的图实例。但是,在任何一个时刻,在当前线程中有且仅有一个图实例成为默认的,可以调用tf.get_default_graph(),返回该默认的图实例。
- 如果没有显式创建多个图实例,则所有OP 都默认地注册到该图实例中。
图上的device方法
- 常常使用上下文管理器device(device_spec) 指定OP 设备规范,在该上下文的作用域
内构造的OP 在运行时将被放置在指定设备上运行。 - 其中,device 是Graph 的一个方法,它设计了一个栈式结构的上下文管理器,实现设
备规范的闭包、合并、覆盖等特性。
with g.device('/gpu:0'):
# All OPs constructed here will be placed on GPU 0.
为什么会需要会话
- 在Session 的生命周期中,将根据计算图的计算需求,按需分配系统资源,包括变量,队列,读取器等。
- 当计算完成后,需要确保Session 被安全地关闭,以便安全释放所管理的系统资源。
- 一个Session 实例,只能运行一个图实例;但是,一个图实例,可以运行在多个Session实例中。如果尝试在同一个Session 运行另外一个图实例,必须先关闭Session(不必销毁),再启动新图的计算过程。
- 虽然一个Session 实例,只能运行一个图实例。但是,因为Session 是一个线程安全
的类,可以并发地执行该图实例上的不同子图。例如,一个典型的机器学习训练模型中,可以使用同一个Session 实例,并发地运行输入子图,训练子图,以及Checkpoint 子图。 - 为了提高效率,避免计算图频繁地创建与销毁,存在一种实现上的优化技术。在图实例中维护一个Session 的引用计数器,当且仅当Session 的数目为零时,才真正地销毁图实例。
默认会话
- 通过调用Session.as_default(),将该Session 置为默认Session,同时它返回了一个
上下文管理器。在默认Session 的上文中,可以直接实施OP 的运算,或者Tensor 的求值。 - 但是,Session.as_default() 并不会自动关闭Session,需要用户显式地调用Session.close 方法
hello = tf.constant('hello, world')
sess = tf.Session()
with sess.as_default():
print(hello.eval())
sess.close()
张量求值
如上例代码,hello.eval() 等价于tf.get_default_session().run(hello)。其中,Tensor.
eval 如下代码实现。
class Tensor(_TensorLike):
def eval(self, feed_dict=None, session=None):
if session is None:
session = get_default_session()
return session.run(tensors, feed_dict)
- 同理,当用户未显式提供Session,Operation.run 将自动获取默认的Session 实例,
并按照当前OP 的依赖关系,以某个特定的拓扑排序执行该计算子图。
会话类型
- 一般地,存在两种基本的会话类型:Session 与InteractiveSession。后者常常用于交
互式环境,它在构造期间将其自身置为默认,简化默认会话的管理过程。 - 此外,两者在运行时的配置也存在差异。例如,InteractiveSession 将GPUOptions.allow_growth
置为True,避免在实验环境中独占整个GPU 的存储资源。