Tensorflow指南(1)

变量

我们使用 tf.Variable 类操作变量。tf.Variable 表示可通过对其运行操作来改变其值的张量。与 tf.Tensor 对象不同,tf.Variable 存在于单个 session.run 调用的上下文之外,变量只存在与一个session之处。

在 TensorFlow 内部,tf.Variable 会存储持久性张量。具体op(操作图的节点)允许您读取和修改此张量的值。这些修改在多个 tf.Session 之间是可见的,因此对于一个 tf.Variable,多个工作器可以看到相同的值。

创建变量

tf.get_variable()

要使用 tf.get_variable 创建变量,只需提供名称和形状即可

my_variable = tf.get_variable("my_variable", [1, 2, 3])

这将创建一个名为“my_variable”的变量,该变量是形状为 [1, 2, 3] 的三维张量。默认情况下,此变量将具有 dtypetf.float32,其初始值将通过 tf.glorot_uniform_initializer 随机设置。

您可以选择为 tf.get_variable 指定 dtype 和初始化器。例如:

my_int_variable = tf.get_variable("my_int_variable", [1, 2, 3], dtype=tf.int32,
  initializer=tf.zeros_initializer)

TensorFlow 提供了许多方便的初始化器。或者,您也可以将 tf.Variable 初始化为 tf.Tensor 的值。例如:

other_variable = tf.get_variable("other_variable", dtype=tf.int32,
  initializer=tf.constant([23, 42]))

请注意,当初始化器是 tf.Tensor 时,不需要再指定变量的形状,因为将使用初始化器张量的形状。

变量集合

由于 TensorFlow 程序的未连接部分可能需要创建变量,因此能有一种方式访问所有变量有时十分受用。为此,TensorFlow 提供了集合,它们是张量或其他对象(如 tf.Variable 实例)的命名列表。

默认情况下,每个 tf.Variable 都放置在以下两个集合中:

tf.GraphKeys.GLOBAL_VARIABLES - 可以在多台设备间共享的变量,
tf.GraphKeys.TRAINABLE_VARIABLES - TensorFlow 将计算其梯度的变量。
如果您不希望变量可训练,可以将其添加到tf.GraphKeys.LOCAL_VARIABLES 集合中。例如,以下代码段展示了如何将名为 my_local 的变量添加到此集合中:

my_local = tf.get_variable("my_local",shape=())
collections = [tf.GraphKeys.LOCAL_VARIABLES]

或者,一种更常用的方式,您可以指定 trainable=False(作为tf.get_variable的参数):

my_non_trainable = tf.get_variable("my_non_trainable",shape=(),trainable=False)

您也可以使用自己的集合。集合名称可为任何字符串,且您无需显式创建集合。创建变量(或任何其他对象)后,要将其添加到集合中,请调用 tf.add_to_collection。例如,以下代码将名为 my_local 的现有变量添加到名为 my_collection_name 的集合中:

tf.add_to_collection("my_collection_name",my_local)

要检索您放置在某个集合中的所有变量(或其他对象)的列表,您可以使用:

tf.get_collection("my_collection_name")

变量放置在哪个设备上?

与任何其他 TensorFlow 指令一样,您可以将变量放置在特定设备上。例如,以下代码段创建了名为 v 的变量并将其放置在第二个 GPU 设备上:

with tf.device("/device:GPU:1"):
   v = tf.get_variable("v",1)

在分布式设置中,将变量放置在正确设备上尤为重要。如果不小心将变量放在工作器而不是参数服务器上,可能会严重减慢训练速度,最坏的情况下,可能会让每个工作器不断复制各个变量。为此,我们提供了 tf.train.replica_device_setter,它可以自动将变量放置在参数服务器中。例如:

cluster_spec = {
    "ps": ["ps0:2222", "ps1:2222"],
    "worker": ["worker0:2222", "worker1:2222", "worker2:2222"]}
with tf.device(tf.train.replica_device_setter(cluster=cluster_spec)):
  v = tf.get_variable("v", shape=[20, 20])  # this variable is placed
                                            # in the parameter server
                                            # by the replica_device_setter

初始化变量

变量必须先初始化后才可使用。如果您在低级别 TensorFlow API 中进行编程(即您在显式创建自己的图和会话),则必须明确初始化变量。tf.contrib.slimtf.estimator.EstimatorKeras 等大多数高级框架在训练模型前会自动为您初始化变量。

显式初始化在其他方面很有用。它允许您在从检查点重新加载模型时不用重新运行潜在资源消耗大的初始化器,并允许在分布式设置中共享随机初始化的变量时具有确定性。

要在训练开始前一次性初始化所有可训练变量,请调用 tf.global_variables_initializer()。此函数会返回一个操作,负责初始化 tf.GraphKeys.GLOBAL_VARIABLES 集合中的所有变量. 例如:

session.run(tf.global_variables_initializer())
Now all variables are initialized

如果你需要自行初始化变量,则可以运行变量的初始化操作,例如:

session.run(my_variable.initializer)

您还可以查询哪些变量尚未初始化。例如,以下代码会打印所有的尚未初始化的变量名称:

print(session.run(tf.report_uninitialized_variables))

请注意,默认情况下,tf.global_variables_initializer()不会指定变量的初始化顺序。因此,如果变量的初始值取决于另一个变量的值,那么很可能会出现错误。 任何时候,如果您在并非所有变量都已初始化的上下文中使用某个变量值(例如在初始化某个变量时使用另一变量的值),最好使用 variable.initialized_value(),而非 variable:

v = tf.get_variable("v",shape=(),initializer=tf.zeros_initializer)
w = tf.get_variable("w",shape=(),initializer=v.initialized_value+1)

使用变量:要在Tensoflow 图中使用 tf.Variable的值,只需要将其视为普通的tf.Tensor即可:

v = tf.get_variable("v",shape=(),initializer=tf.zeros_initializer())
w = v+1 # w is a tf.Tensor which is computed based on the value of v.
           # Any time a variable is used in an expression it gets automatically
           # converted to a tf.Tensor representing its value.

要为变量赋值,请使用assign,assign_add方法以及tf.Variable类中的友元。

v = tf.get_variable("v",shape=(),initializer=tf.zeros_initializer())
assignment = v.assign_add(1)
tf.global_variables_initializer().run()
sess.run(assignment) # or assignment.op.run() , or assignment.eval()

由于变量是可变的,因此及时了解任意时间点所使用的变量值版本有时十分有用。要在事件发生后强制重新读取变量的值,可以使用 tf.Variable.read_value。例如:

v = tf.get_variable("v", shape=(), initializer=tf.zeros_initializer())
assignment = v.assign-add(1)
with tf.control_dependencies([assignment]):
   w = v.read_value()  # w is guaranteed to reflect v's value after the
                       # assign_add operation.

共享变量

Tensorflow 支持两种 共享变量的方式:

  • 显示传递 tf.Variable 对象
  • 将tf.Variable对象隐式封装在 tf.variable_scope 对象内。
def conv_relu(input, kernel_shape, bias_shape):
    # Create variable named "weights".
    weights = tf.get_variable("weights", kernel_shape,
        initializer=tf.random_normal_initializer())
    # Create variable named "biases".
    biases = tf.get_variable("biases", bias_shape,
        initializer=tf.constant_initializer(0.0))
    conv = tf.nn.conv2d(input, weights,
        strides=[1, 1, 1, 1], padding='SAME')
    return tf.nn.relu(conv + biases)

此函数使用短名称 weights 和 biases,这有利于清晰区分二者。然而,在真实模型中,我们需要很多此类卷积层,而且重复调用此函数将不起作用

input1 = tf.random_normal([1,10,10,32])
input2 = tf.random_normal([1,20,20,32])
x = conv_relu(input1, kernel_shape=[5, 5, 32, 32], bias_shape=[32])
x = conv_relu(x, kernel_shape=[5, 5, 32, 32], bias_shape = [32])  # This fails.

由于期望的操作不清楚(创建新变量还是重新使用现有变量?),因此 TensorFlow 将会失败。不过,在不同作用域内调用 conv_relu 可表明我们想要创建新变量:

def my_image_filter(input_images):
    with tf.variable_scope("conv1"):
        # Variables created here will be named "conv1/weights", "conv1/biases".
        relu1 = conv_relu(input_images, [5, 5, 32, 32], [32])
    with tf.variable_scope("conv2"):
        # Variables created here will be named "conv2/weights", "conv2/biases".
        return conv_relu(relu1, [5, 5, 32, 32], [32])

如果您想要共享变量,有两种方法可供选择。首先,您可以使用 reuse=True 创建具有相同名称的作用域:


with tf.variable_scope("model"):
  output1 = my_image_filter(input1)
with tf.variable_scope("model", reuse = True):
  output2 = my_image_filter(input2)

您也可以调用scope.reuse_variables 以触发重用:

with tf.variable_scope("model") as scope:
   output1 = my_image_filter(input1)
   scope.reuse_variables()
   output2 = my_image_filter(input2)

由于依赖于作用域的确切字符串可能比较危险,因此也可以根据另一作用域初始化某个变量作用域:

with tf.variable_scope("model") as scope:
   output1 = my_image_filter(input1)
with tf.variable_scope(scope,reuse=True):
   output2 = my_image_filter(input2)

使用多个GPU卡训练模型

现代工作站可能会包含多个用于科学计算的 GPU。TensorFlow 可利用此环境在多个卡上同时运行训练操作。

如果要以并行的分布式方式训练模型,则需要协调训练过程。在接下来的内容中,术语“模型副本”指在数据子集上训练的模型副本。

简单地采用模型参数异步更新方法会导致训练性能无法达到最佳,因为单个模型副本在训练时使用的可能是过时的模型参数。反之,如果采用完全同步的更新后参数,其速度堪比最慢的模型副本

在具有多个 GPU 卡的工作站中,每个 GPU 的速度大致相当,且具有足够的内存来运行整个 CIFAR-10 模型。因此,我们选择按照以下方式设计训练系统:

  • 在每个GPU上放一个模型副本
  • 等待所有的GPU完成一批数据的处理工作,然后同步更新模型参数
    模型示意图如下所示:


请注意,每个 GPU 都会针对一批唯一的数据计算推理和梯度。这种设置可以有效地将一大批数据划分到各个 GPU 上。这种设置要求所有 GPU 都共享模型参数。众所周知,将数据传输到 GPU 或从中向外传输数据的速度非常慢。因此,我们决定在 CPU 上存储和更新所有模型参数(如绿色方框所示)。当所有 GPU 均处理完一批新数据时,系统会将一组全新的模型参数传输给相应 GPU。

GPU会同步运行。GPU的所有梯度将累积并求平均值(如绿色方框所示)。模型参数会更新为所有模型副本的梯度平均值。

将变量和操作放在多个设备上

将操作和变量放到多个设备上需要一些特殊的抽象操作。

第一个抽象操作是计算单个模型副本的推理和梯度的函数。在代码中,我们将此抽象操作称为“tower”。我们必须为每个 tower 设置两个属性:

  • tower 中所有操作的唯一名称。 tf.name_scope 通过添加作用域前缀提供唯一的名称。例如,第一个 tower 中的所有操作都会附带 tower_0 前缀,例如 tower_0/conv1/Conv2D
  • 运行 tower 中操作(op)的首选硬件设备。 tf.device 会指定该属性。例如,第一个 tower 中的所有操作都位于 device('/device:GPU:0') 作用域内,表示它们应在第一个 GPU 上运行
    为了在多 GPU 版本中共享变量,所有变量都固定到 CPU 上且通过 tf.get_variable 访问。了解如何共享变量。

在多个GPU上启动并训练模型

如果计算机上安装了多个 GPU 卡,您可以使用 cifar10_multi_gpu_train.py 脚本借助它们加快模型的训练过程。此版训练脚本可在多个 GPU 卡上并行训练模型。

python cifar10_multi_gpu_train.py --num_gpus=2

请注意,使用的 GPU 卡数量默认为 1。此外,如果计算机上仅有一个 GPU,则所有计算都会在该 GPU 上运行,即使您设置的是多个 GPU。

以下是一个示例代码:

如果您想要在多个 GPU 上运行 TensorFlow,则可以采用多塔式方式构建模型,其中每个塔都会分配给不同 GPU。例如:

Creates a graph.

import tensorflow as tf
# Creates a graph
c = []
for d in ['/device:GPU:0','/device:GPU:1','/device:GPU:2']:
    with tf.device(d):
        a = tf.constant([1.0,2.0,3.0,4.0,5.0,6.0],shape=[2,3])
        b = tf.constant([1.0,2.0,3.0,4.0,5.0,6.0],shape=[3,2])
        c.append(tf.matmul(a,b))
with tf.device("/cpu:0"):
    sum = tf.add_n(c)
init = tf.global_variables_initializer()
config  = tf.ConfigProto(allow_soft_placement=True,
                         log_device_placement=True)

with tf.Session(config=config) as sess:
    sess.run(init)
    # run the op.
    print(sess.run(sum))

您可以看到一下输出内容:

init: (NoOp): /job:localhost/replica:0/task:0/device:GPU:0
2019-09-17 21:45:30.112143: I tensorflow/core/common_runtime/placer.cc:886] init: (NoOp)/job:localhost/replica:0/task:0/device:GPU:0
MatMul_2: (MatMul): /job:localhost/replica:0/task:0/device:GPU:2
2019-09-17 21:45:30.112162: I tensorflow/core/common_runtime/placer.cc:886] MatMul_2: (MatMul)/job:localhost/replica:0/task:0/device:GPU:2
MatMul_1: (MatMul): /job:localhost/replica:0/task:0/device:GPU:1
2019-09-17 21:45:30.112170: I tensorflow/core/common_runtime/placer.cc:886] MatMul_1: (MatMul)/job:localhost/replica:0/task:0/device:GPU:1
MatMul: (MatMul): /job:localhost/replica:0/task:0/device:GPU:0
2019-09-17 21:45:30.112198: I tensorflow/core/common_runtime/placer.cc:886] MatMul: (MatMul)/job:localhost/replica:0/task:0/device:GPU:0
AddN: (AddN): /job:localhost/replica:0/task:0/device:CPU:0
2019-09-17 21:45:30.112223: I tensorflow/core/common_runtime/placer.cc:886] AddN: (AddN)/job:localhost/replica:0/task:0/device:CPU:0
Const_5: (Const): /job:localhost/replica:0/task:0/device:GPU:2
2019-09-17 21:45:30.112232: I tensorflow/core/common_runtime/placer.cc:886] Const_5: (Const)/job:localhost/replica:0/task:0/device:GPU:2
Const_4: (Const): /job:localhost/replica:0/task:0/device:GPU:2
2019-09-17 21:45:30.112240: I tensorflow/core/common_runtime/placer.cc:886] Const_4: (Const)/job:localhost/replica:0/task:0/device:GPU:2
Const_3: (Const): /job:localhost/replica:0/task:0/device:GPU:1
2019-09-17 21:45:30.112247: I tensorflow/core/common_runtime/placer.cc:886] Const_3: (Const)/job:localhost/replica:0/task:0/device:GPU:1
Const_2: (Const): /job:localhost/replica:0/task:0/device:GPU:1
2019-09-17 21:45:30.112254: I tensorflow/core/common_runtime/placer.cc:886] Const_2: (Const)/job:localhost/replica:0/task:0/device:GPU:1
Const_1: (Const): /job:localhost/replica:0/task:0/device:GPU:0
2019-09-17 21:45:30.112266: I tensorflow/core/common_runtime/placer.cc:886] Const_1: (Const)/job:localhost/replica:0/task:0/device:GPU:0
Const: (Const): /job:localhost/replica:0/task:0/device:GPU:0
2019-09-17 21:45:30.112275: I tensorflow/core/common_runtime/placer.cc:886] Const: (Const)/job:localhost/replica:0/task:0/device:GPU:0
[[ 66.  84.]
 [147. 192.]]

更具体的一个例子请参看 cifar教程.


  转载规则: DorMin Tensorflow指南(1)

 上一篇
dataset pipeline dataset pipeline
本教程旨在: 说明 TensorFlow 输入流水线本质上是一个 ETL 流程。 介绍围绕 tf.data API 的常见性能优化。 讨论转换的应用顺序对性能的影响。 总结设计高性能 TensorFlow 输入流水线的最佳做法。 输入流水
2019-09-18
下一篇 
multimodal neural translation series 2 multimodal neural translation series 2
作者: [Thang Luong, Eugene Brevdo, Rui Zhao] (Google Research Blogpost, Github) 译者: 宗道明If make use of this codebase for
2019-09-15
  目录