流图与会话
TensorFlow 使用数据流图(Dataflow graph,后文以“流图”简称。)表示计算过程,它是依据各个操作之间的依赖关系生成的。这是一个底层的编程模型,你需要先定义数据流图,然后创建一个 TensorFlow 会话以在多个本地或远程的设备上运行流图的各个部分。
为什么要用数据流图?
TensorFlow 利用数据流模型来执行程序,有如下几个优点:
并行编程 使用显式边来表示操作间的依赖关系,系统就能很容易的识别出可以并行的操作。
分布执行 用显式边来表示操作间传递的值,TensorFlow 可以将你的程序分布到不同机器的不同设备(CPU, GPU 和 TPU)上,并完成必要的设备间通信和协调工作。
tf.Graph
流图结构 图的节点和边表明了各个独立操作组合在一起的方式,但并没有说明他们的用法。流图结构很像汇编代码:查看代码能够得到一些有用的信息,但它并不能传达给你所有源代码中的有效信息。
tf.Graph
操作的命名
-
c_0 = tf.constant(0, name="c") # => operation named "c" # 已经被使用过的名称将会变为 "uniquified"。 c_1 = tf.constant(2, name="c") # => operation named "c_1" # 命名空间为在同一上下文环境中创建的所有操作添加一个前缀。 with tf.name_scope("outer"): c_2 = tf.constant(2, name="c") # => 操作被命名为"outer/c" # 命名空间像分层文件系统中嵌套的路径。 with tf.name_scope("inner"): c_3 = tf.constant(3, name="c") # => 操作被命名为 "outer/inner/c" # 退出命名空间,将返回到前一个前缀名称所表示的命名空间。 c_4 = tf.constant(4, name="c") # => 操作被命名为 "outer/c_1" # 已经被使用过的名称将会变为 "uniquified"。 with tf.name_scope("inner"): c_5 = tf.constant(5, name="c") # => 操作被命名为 "outer/inner_1/c"
图形可视化工具使用命名空间来将操作分组,并减少图形的视觉复杂性。请参阅将流图可视化来获取更多信息。
"<OP_NAME>"
是生成 tensor 的操作名称。"<i>"
是一个整数,表示操作的输出中 tensor 的索引。
在不同设备部署操作
/job:<JOB_NAME>/task:<TASK_INDEX>/device:<DEVICE_TYPE>:<DEVICE_INDEX>
分别表示:
<JOB_NAME>
是一个可以包含字母与数字的字符串,但不能以数字开头。<DEVICE_TYPE>
是一个已注册的设备类型(例如GPU
或者CPU
)。tf.train.ClusterSpec
<DEVICE_INDEX>
是一个非负整数,表示设备的索引,例如,用来区分在同一进程中使用的不同 GPU 设备。
# 创建时没有指定任何设备的操作将在“尽可能最好”的设备上运行。
# 例如,如果你有一个 GPU 和一个 CPU 可用,并且该操作具有 GPU
# 实现,则 TensorFlow 将选择该 GPU。
weights = tf.random_normal(...)
with tf.device("/device:CPU:0"):
# 此上下文中创建的操作会被分配到 CPU 上。
img = tf.decode_jpeg(tf.read_file("img.jpg"))
with tf.device("/device:GPU:0"):
# 当前上下文环境中创建的操作会被分配到 GPU 上。
result = tf.matmul(weights, img)
with tf.device("/job:ps/task:0"):
weights_1 = tf.Variable(tf.truncated_normal([784, 100]))
biases_1 = tf.Variable(tf.zeroes([100]))
with tf.device("/job:ps/task:1"):
weights_2 = tf.Variable(tf.truncated_normal([100, 10]))
biases_2 = tf.Variable(tf.zeroes([10]))
with tf.device("/job:worker"):
layer_1 = tf.matmul(train_batch, weights_1) + biases_1
layer_2 = tf.matmul(train_batch, weights_2) + biases_2
with tf.device(tf.train.replica_device_setter(ps_tasks=3)):
# tf.Variable 默认情况下以轮询调度的方式部署在 "/job:ps" 的任务列表中
w_0 = tf.Variable(...) # placed on "/job:ps/task:0"
b_0 = tf.Variable(...) # placed on "/job:ps/task:1"
w_1 = tf.Variable(...) # placed on "/job:ps/task:2"
b_1 = tf.Variable(...) # placed on "/job:ps/task:0"
input_data = tf.placeholder(tf.float32) # 部署 "/job:worker"
layer_0 = tf.matmul(input_data, w_0) + b_0 # 部署 "/job:worker"
layer_1 = tf.matmul(layer_0, w_1) + b_1 # 部署 "/job:worker"
可用作 Tensor 的对象
tf.Tensor
tf.Variable
numpy.ndarray
list
(和可用做 tensor 对象的列表)- Python 标量类型:
bool
,float
,int
,str
tf.register_tensor_conversion_function
tf.Session
tf.Session
# 创建一个默认的进程内的会话。
with tf.Session() as sess:
# ...
# 创建一个远程会话。
with tf.Session("grpc://example.org:2222"):
# ...
-
graph_options.optimizer_options
提供对 TensorFlow 在执行流图前对其优化的控制。gpu_options.allow_growth
将其设置为True
来更改 GPU 内存分配器,以便逐渐增加分配的内存量,而不是在启动时分配大部分内存。
tf.Session.run
x = tf.constant([[37.0, -23.0], [1.0, 4.0]])
w = tf.Variable(tf.random_uniform([2, 2]))
y = tf.matmul(x, w)
output = tf.nn.softmax(y)
init_op = w.initializer
with tf.Session() as sess:
# 运行 `w` 的初始化操作。
sess.run(init_op)
# 对 `output` 求值。`sess.run(output)` 将返回一个包含计算结果的 NumPy 数组。
print(sess.run(output))
# 对 `y` 和 `output` 求值。请注意, `y` 只会计算一次,计算结果既作为 `y_val` 的值返回,
# 又会作为 `tf.nn.softmax()` 操作的输入。`y_val` 和 `output_val` 都是 NumPy 数组。
y_val, output_val = sess.run([y, output])
# 定义一个三个浮点值向量的占位符和一个依赖于它的计算。
x = tf.placeholder(tf.float32, shape=[3])
y = tf.square(x)
with tf.Session() as sess:
# 提供不同的参数值会求得不同的 `y` 值。
print(sess.run(y, {x: [1.0, 2.0, 3.0]})) # => "[1.0, 4.0, 9.0]"
print(sess.run(y, {x: [0.0, 0.0, 5.0]})) # => "[0.0, 0.0, 25.0]"
# 抛出 `tf.errors.InvalidArgumentError`,因为你必须先给 `tf.placeholder()` 赋值,然后才能计算器依赖关系之上的 tensor 。
sess.run(y)
# 抛出 `ValueError`,因为 `37.0` 与 `x` 形状不匹配。
sess.run(y, {x: 37.0})
y = tf.matmul([[37.0, -23.0], [1.0, 4.0]], tf.random_uniform([2, 2]))
with tf.Session() as sess:
# 定义调用 `sess.run()` 的选项。
options = tf.RunOptions()
options.output_partition_graphs = True
options.trace_level = tf.RunOptions.FULL_TRACE
# 定义一个用于接受返回元数据的容器。
metadata = tf.RunMetadata()
sess.run(y, options=options, run_metadata=metadata)
# 打印出在每个设备上执行的子流图。
print(metadata.partition_graphs)
# 打印出每次执行操作的执行时间。
print(metadata.step_stats)
将流图可视化
# 构建你的流图
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` 提供了对 `tf.Session` 中流图的访问。
writer = tf.summary.FileWriter("/tmp/log/...", sess.graph)
# 执行你的计算...
for i in range(1000):
sess.run(train_op)
# ...
writer.close()
然后,你可以在 tensorboard
中打开日志,在 “Graph” 页面查看高度可视化的计算图结构。请注意, 一个典型的 TensorFlow 计算图——尤其是自动计算梯度的训练计算图——会由于节点太多而不能立刻全部可视化。计算图使用命名空间来将相关操作归纳到父节点。你可以点解父节点的橙色 "+" 按钮来展开其内部的子图。
要了解更多关于如何使用 TensorBoard 来可视化你的 TensorFlow 应用,请参阅 TensorBoard 指南。
使用多个流图编程
如上所述, TensorFlow 提供了一个“默认流图”,隐式传递给同一上下文中的所有 API 函数。对于许多个应用程序来说,一张流图就足够了。但是,TensorFlow 也提供了操作默认流图的方法,这在更高级的用例中是有用处的。例如:
g_1 = tf.Graph()
with g_1.as_default():
# 这作用域中创建的操作会加到 `g_1` 中。
c = tf.constant("Node in g_1")
# 这个作用域中创建的会话会运行 `g_1` 中的操作。
sess_1 = tf.Session()
g_2 = tf.Graph()
with g_2.as_default():
# 这作用域中创建的操作会加到 `g_2` 中。
d = tf.constant("Node in g_2")
# 或者,你可以在构建 `tf.Session` 时传递一个流图:
# `sess_2`会运行 `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
# 打印默认流图中所有的操作。
g = tf.get_default_graph()
print(g.get_operations())