Eager Execution

TensorFlow 的 eager execution 是一种可以立即评估操作,无需构建图的命令式编程环境;操作会返回具体值,而不是一个供之后运行的计算图。这降低了 TensorFlow 入门以及调试模型入门的门槛,同时也减少了文档范例。为了遵循本指南,请在 python 的交互式解释器中运行下述示例代码。

Eager execution 为实验和研究提供了一个灵活的机器学习平台:

  • 直观的界面 — 为你合理地构建代码并使用 Python 数据结构。在小型模型和小型数据中可快速迭代。
  • 更简单的调试 — 直接调用 ops 来检测运行模型已经测试更改。使用标准化 Python 调试工具进行即时错误报告。
  • 合理的控制流 — 使用 Python 控制流来取代图控制流,简化了动态模型的规范。

Eager execution 支持大多数 TensorFlow 操作和 GPU 加速功能。如果需要 eager execution 运行的示例集合,请参阅: tensorflow/contrib/eager/python/examples

注意:一些模型在开启 eager execution 后,可能会增加开销。虽然已经在进行性能优化,但如果你发现了问题,请向我们提交错误文件报告并分享你的基准测试。

安装和基本用法

升级到最新版本的 TensorFlow:

  1. $ pip install --upgrade tensorflow

启用 eager execution 时,请在程序或者控制台会话的开头添加 tf.enable_eager_execution()。不要将此操作添加到程序将要调用的其他模块中。

  1. from __future__ import absolute_import, division, print_function
  2. import tensorflow as tf
  3. tf.enable_eager_execution()

现在你可以运行 TensorFlow 操作了,结果会被立即返回:

  1. tf.executing_eagerly() # => True
  2. x = [[2.]]
  3. m = tf.matmul(x, x)
  4. print("hello, {}".format(m)) # => "hello, [[4.]]"

启用 eager execution 会改变 TensorFlow 操作的行为——现在它们会立即计算值并将结果返回给 Python。tf.Tensor 对象会为节点引用计算图中的实际值而不是符号句柄。由于在 session 中没有要构建和运行的计算图,因此使用 print() 或调试器来检查结果会很容易。计算、打印以及检查张量值不会中断计算梯度的流程。

Eager execution 可以和 NumPy 完美结合。NumPy 运算可以接受来自 tf.Tensor 的参数。TensorFlow 数学操作将 Python 对象和 NumPy 数组转换成 tf.Tensor 对象。tf.Tensor.numpy 方法将对象值作为 NumPy ndarray 返回。

  1. a = tf.constant([[1, 2],
  2. [3, 4]])
  3. print(a)
  4. # => tf.Tensor([[1 2]
  5. # [3 4]], shape=(2, 2), dtype=int32)
  6. # Broadcasting support
  7. b = tf.add(a, 1)
  8. print(b)
  9. # => tf.Tensor([[2 3]
  10. # [4 5]], shape=(2, 2), dtype=int32)
  11. # Operator overloading is supported
  12. print(a * b)
  13. # => tf.Tensor([[ 2 6]
  14. # [12 20]], shape=(2, 2), dtype=int32)
  15. # Use NumPy values
  16. import numpy as np
  17. c = np.multiply(a, b)
  18. print(c)
  19. # => [[ 2 6]
  20. # [12 20]]
  21. # Obtain numpy value from a tensor:
  22. print(a.numpy())
  23. # => [[1 2]
  24. # [3 4]]

tf.contrib.eager 模块拥有在 eager 和 graph 执行环境中都可以使用的符号,并且对于使用图的代码编写非常有用:

  1. tfe = tf.contrib.eager

动态控制流

eager execution 的一个主要优势是,在运行模型时,主机语言的所有功能都是可用的。因此,例如,编写 fizzbuzz 是轻而易举的事情:

  1. def fizzbuzz(max_num):
  2. counter = tf.constant(0)
  3. max_num = tf.convert_to_tensor(max_num)
  4. for num in range(max_num.numpy()):
  5. num = tf.constant(num)
  6. if int(num % 3) == 0 and int(num % 5) == 0:
  7. print('FizzBuzz')
  8. elif int(num % 3) == 0:
  9. print('Fizz')
  10. elif int(num % 5) == 0:
  11. print('Buzz')
  12. else:
  13. print(num)
  14. counter += 1
  15. return counter

这是有条件的,即依赖于张量值并且可以在运行时打印这些值。

构建模型

许多机器学习模型都是由组合网络层构成。在使用具有 eager execution 的 TensorFlow 时,你可以编写自己的网络层或者使用 tf.keras.layers 包提供的网络层。

尽管你可以使用任意的 Python 对象来表示网络层,但 TensorFlow 仍然有 tf.keras.layers.Layer 来作为便捷的基类。你可以通过继承它来实现自己的网络层:

  1. class MySimpleLayer(tf.keras.layers.Layer):
  2. def __init__(self, output_units):
  3. super(MySimpleLayer, self).__init__()
  4. self.output_units = output_units
  5. def build(self, input_shape):
  6. # 第一次使用 layer 时,会调用 `build` 方法。
  7. # 在 build() 上创建变量使其形状依赖于输入形状,从而消除了用户指定完整形状的需要,如果你已经知道变量的全部形状,则可以在 _init_() 期间创建变量。
  8. self.kernel = self.add_variable(
  9. "kernel", [input_shape[-1], self.output_units])
  10. def call(self, input):
  11. # 重载 call() 而不是 __call__ ,这样我们就可以执行一些 bookkeeping 操作。
  12. return tf.matmul(input, self.kernel)

使用 tf.keras.layers.Dense 层来替换上述的 MySimpleLayer,因为它有其功能的超集(它还可以添加偏差)。

将层合并为模型时,你可以使用 tf.keras.Sequential 来表示模型,这些模型是层的线性堆栈。这对于基础模型来说,很容易使用:

  1. model = tf.keras.Sequential([
  2. tf.keras.layers.Dense(10, input_shape=(784,)), # must declare input shape
  3. tf.keras.layers.Dense(10)
  4. ])

它是一个网络层的容器,但它自己也是一个网络层,tf.keras.Model 对象可以包含其他的 tf.keras.Model 对象。

  1. class MNISTModel(tf.keras.Model):
  2. def __init__(self):
  3. super(MNISTModel, self).__init__()
  4. self.dense1 = tf.keras.layers.Dense(units=10)
  5. self.dense2 = tf.keras.layers.Dense(units=10)
  6. def call(self, input):
  7. """Run the model."""
  8. result = self.dense1(input)
  9. result = self.dense2(result)
  10. result = self.dense2(result) # reuse variables from dense2 layer
  11. return result
  12. model = MNISTModel()

无需为 tf.keras.Model 类设置 input shape,因为参数是 input 第一次传递给层时设置的。

tf.keras.layers 类创建并包含与其对象层生命周期相关联的模型变量。为了共享变量层,请共享它们的对象。

Eager 训练

计算梯度

自动微分法在训练神经网络方面中,对于实现像向后传播这样的机器学习算法是非常有用的。在 eager execution 中,使用 tf.GradientTape 来跟踪之后的梯度计算操作。

在不进行跟踪时,tf.GradientTape 是一个提供最佳性能的可选特性。因为每次调用都会发生不同的操作,因此所有向前传播操作都会被记录在一个 “tape” 中。为了计算梯度,需要向后播放 tape,然后丢弃。一个特定的 tf.GradientTape 只能计算一个梯度,之后的调用会导致运行时错误。

  1. w = tf.Variable([[1.0]])
  2. with tf.GradientTape() as tape:
  3. loss = w * w
  4. grad = tape.gradient(loss, w)
  5. print(grad) # => tf.Tensor([[ 2.]], shape=(1, 1), dtype=float32)

这是 tf.GradientTape 在训练简单模型时记录向前传播操作的一个示例:

  1. # A toy dataset of points around 3 * x + 2
  2. NUM_EXAMPLES = 1000
  3. training_inputs = tf.random_normal([NUM_EXAMPLES])
  4. noise = tf.random_normal([NUM_EXAMPLES])
  5. training_outputs = training_inputs * 3 + 2 + noise
  6. def prediction(input, weight, bias):
  7. return input * weight + bias
  8. # A loss function using mean-squared error
  9. def loss(weights, biases):
  10. error = prediction(training_inputs, weights, biases) - training_outputs
  11. return tf.reduce_mean(tf.square(error))
  12. # Return the derivative of loss with respect to weight and bias
  13. def grad(weights, biases):
  14. with tf.GradientTape() as tape:
  15. loss_value = loss(weights, biases)
  16. return tape.gradient(loss_value, [weights, biases])
  17. train_steps = 200
  18. learning_rate = 0.01
  19. # Start with arbitrary values for W and B on the same batch of data
  20. W = tf.Variable(5.)
  21. B = tf.Variable(10.)
  22. print("Initial loss: {:.3f}".format(loss(W, B)))
  23. for i in range(train_steps):
  24. dW, dB = grad(W, B)
  25. W.assign_sub(dW * learning_rate)
  26. B.assign_sub(dB * learning_rate)
  27. if i % 20 == 0:
  28. print("Loss at step {:03d}: {:.3f}".format(i, loss(W, B)))
  29. print("Final loss: {:.3f}".format(loss(W, B)))
  30. print("W = {}, B = {}".format(W.numpy(), B.numpy()))

Output (exact numbers may vary):

  1. Initial loss: 71.204
  2. Loss at step 000: 68.333
  3. Loss at step 020: 30.222
  4. Loss at step 040: 13.691
  5. Loss at step 060: 6.508
  6. Loss at step 080: 3.382
  7. Loss at step 100: 2.018
  8. Loss at step 120: 1.422
  9. Loss at step 140: 1.161
  10. Loss at step 160: 1.046
  11. Loss at step 180: 0.996
  12. Final loss: 0.974
  13. W = 3.01582956314, B = 2.1191945076

重放 tf.GradientTape 来计算梯度并应用在循环训练中。这在 mnist_eager.py 中有演示示例:

  1. dataset = tf.data.Dataset.from_tensor_slices((data.train.images,
  2. data.train.labels))
  3. ...
  4. for (batch, (images, labels)) in enumerate(dataset):
  5. ...
  6. with tf.GradientTape() as tape:
  7. logits = model(images, training=True)
  8. loss_value = loss(logits, labels)
  9. ...
  10. grads = tape.gradient(loss_value, model.variables)
  11. optimizer.apply_gradients(zip(grads, model.variables),
  12. global_step=tf.train.get_or_create_global_step())

下述示例创建了一个对标准 MNIST 手写体数字进行了分类的多层模型。它演示了在 eager execution 环境中如何利用优化器和网络层 API 来构建可训练图。

训练模型

即使没有进行训练,也可以调用模型,并在 eager execution 中检查输出:

  1. # Create a tensor representing a blank image
  2. batch = tf.zeros([1, 1, 784])
  3. print(batch.shape) # => (1, 1, 784)
  4. result = model(batch)
  5. # => tf.Tensor([[[ 0. 0., ..., 0.]]], shape=(1, 1, 10), dtype=float32)

本示例使用 TensorFlow MNIST example 中的 dataset.py module;将本文件下载到你的本地目录。运行以下内容将 MNIST 数据文件下载到你的工作目录,并为训练准备一个 tf.data.Dataset

  1. import dataset # download dataset.py file
  2. dataset_train = dataset.train('./datasets').shuffle(60000).repeat(4).batch(32)

为了训练模型,为优化定义一个损失函数并计算梯度。使用优化器进行更新变量:

  1. def loss(model, x, y):
  2. prediction = model(x)
  3. return tf.losses.sparse_softmax_cross_entropy(labels=y, logits=prediction)
  4. def grad(model, inputs, targets):
  5. with tf.GradientTape() as tape:
  6. loss_value = loss(model, inputs, targets)
  7. return tape.gradient(loss_value, model.variables)
  8. optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.001)
  9. x, y = iter(dataset_train).next()
  10. print("Initial loss: {:.3f}".format(loss(model, x, y)))
  11. # 循环训练
  12. for (i, (x, y)) in enumerate(dataset_train):
  13. # 根据参数计算输入函数的导数。
  14. grads = grad(model, x, y)
  15. # 对模型应用梯度
  16. optimizer.apply_gradients(zip(grads, model.variables),
  17. global_step=tf.train.get_or_create_global_step())
  18. if i % 200 == 0:
  19. print("Loss at step {:04d}: {:.3f}".format(i, loss(model, x, y)))
  20. print("Final loss: {:.3f}".format(loss(model, x, y)))

Output(确切的数字可能会发生偏差):

  1. Initial loss: 2.674
  2. Loss at step 0000: 2.593
  3. Loss at step 0200: 2.143
  4. Loss at step 0400: 2.009
  5. Loss at step 0600: 2.103
  6. Loss at step 0800: 1.621
  7. Loss at step 1000: 1.695
  8. ...
  9. Loss at step 6600: 0.602
  10. Loss at step 6800: 0.557
  11. Loss at step 7000: 0.499
  12. Loss at step 7200: 0.744
  13. Loss at step 7400: 0.681
  14. Final loss: 0.670

为了进行更快速的训练,将计算转移到 GPU 中:

  1. with tf.device("/gpu:0"):
  2. for (i, (x, y)) in enumerate(dataset_train):
  3. # minimize() is equivalent to the grad() and apply_gradients() calls.
  4. optimizer.minimize(lambda: loss(model, x, y),
  5. global_step=tf.train.get_or_create_global_step())

变量和优化器

tf.Variable 对象存储在训练时可以访问的可变 tf.Tensor 值来让自动微分更加简单。模型参数可以作为变量封装在类中。

使用结合 tf.GradientTapetf.Variable 可以更好的封装模型参数。例如,可以重写上述的自动微分示例:

  1. class Model(tf.keras.Model):
  2. def __init__(self):
  3. super(Model, self).__init__()
  4. self.W = tf.Variable(5., name='weight')
  5. self.B = tf.Variable(10., name='bias')
  6. def call(self, inputs):
  7. return inputs * self.W + self.B
  8. # A toy dataset of points around 3 * x + 2
  9. NUM_EXAMPLES = 2000
  10. training_inputs = tf.random_normal([NUM_EXAMPLES])
  11. noise = tf.random_normal([NUM_EXAMPLES])
  12. training_outputs = training_inputs * 3 + 2 + noise
  13. # The loss function to be optimized
  14. def loss(model, inputs, targets):
  15. error = model(inputs) - targets
  16. return tf.reduce_mean(tf.square(error))
  17. def grad(model, inputs, targets):
  18. with tf.GradientTape() as tape:
  19. loss_value = loss(model, inputs, targets)
  20. return tape.gradient(loss_value, [model.W, model.B])
  21. # Define:
  22. # 1. A model.
  23. # 2. Derivatives of a loss function with respect to model parameters.
  24. # 3. A strategy for updating the variables based on the derivatives.
  25. model = Model()
  26. optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.01)
  27. print("Initial loss: {:.3f}".format(loss(model, training_inputs, training_outputs)))
  28. # Training loop
  29. for i in range(300):
  30. grads = grad(model, training_inputs, training_outputs)
  31. optimizer.apply_gradients(zip(grads, [model.W, model.B]),
  32. global_step=tf.train.get_or_create_global_step())
  33. if i % 20 == 0:
  34. print("Loss at step {:03d}: {:.3f}".format(i, loss(model, training_inputs, training_outputs)))
  35. print("Final loss: {:.3f}".format(loss(model, training_inputs, training_outputs)))
  36. print("W = {}, B = {}".format(model.W.numpy(), model.B.numpy()))

Output(确切的数字可能会有所不同):

  1. Initial loss: 69.066
  2. Loss at step 000: 66.368
  3. Loss at step 020: 30.107
  4. Loss at step 040: 13.959
  5. Loss at step 060: 6.769
  6. Loss at step 080: 3.567
  7. Loss at step 100: 2.141
  8. Loss at step 120: 1.506
  9. Loss at step 140: 1.223
  10. Loss at step 160: 1.097
  11. Loss at step 180: 1.041
  12. Loss at step 200: 1.016
  13. Loss at step 220: 1.005
  14. Loss at step 240: 1.000
  15. Loss at step 260: 0.998
  16. Loss at step 280: 0.997
  17. Final loss: 0.996
  18. W = 2.99431324005, B = 2.02129220963

在使用 eager execution 环境时将对象应用于状态中

在 graph execution 中,程序状态(例如变量)被存储在全局集合中,它们的生命周期由 tf.Session 对象管理。与之相反的是,在 eager execution 中,状态对象的生命周期则是由与它们对应的 Python 对象的生命周期决定的。

变量即对象

在 eager execution 环境中,变量会持续存在,直到对象的最后一个引用被删除,然后变量才会被删除。

  1. with tf.device("gpu:0"):
  2. v = tf.Variable(tf.random_normal([1000, 1000]))
  3. v = None # v no longer takes up GPU memory

基于对象的保存方式

tf.Checkpoint 可以对检查点进行保存并恢复 tf.Variable

  1. x = tf.Variable(10.)
  2. checkpoint = tf.Checkpoint(x=x) # save as "x"
  3. x.assign(2.) # Assign a new value to the variables and save.
  4. save_path = checkpoint.save('./ckpt/')
  5. x.assign(11.) # Change the variable after saving.
  6. # Restore values from the checkpoint
  7. checkpoint.restore(save_path)
  8. print(x) # => 2.0

在保存并加载模型时,无需请求隐藏变量,tf.Checkpoint 便可以存储对象的内部状态。想要记录 modeloptimizer 的状态和全局步骤,只需将它们传递给 tf.Checkpoint

  1. model = MyModel()
  2. optimizer = tf.train.AdamOptimizer(learning_rate=0.001)
  3. checkpoint_dir = ‘/path/to/model_dir
  4. checkpoint_prefix = os.path.join(checkpoint_dir, "ckpt")
  5. root = tf.train.Checkpoint(optimizer=optimizer,
  6. model=model,
  7. optimizer_step=tf.train.get_or_create_global_step())
  8. root.save(file_prefix=checkpoint_prefix)
  9. # or
  10. root.restore(tf.train.latest_checkpoint(checkpoint_dir))

面向对象度量

tfe.metrics 作为对象被存储。通过将新数据传递给可调用对象来更新度量标准,并使用 tfe.metrics.result 方法来检索结果,例如:

  1. m = tfe.metrics.Mean("loss")
  2. m(0)
  3. m(5)
  4. m.result() # => 2.5
  5. m([8, 9])
  6. m.result() # => 5.5

Summary 以及 TensorBoard

TensorBoard 是一个用于帮助理解、调试和优化模型训练过程的可视化工具。它使用在执行程序时生成的 summary event 进行可视化。

tf.contrib.summary 同时兼容 eager 和 graph 执行环境。Summary 操作,例如 tf.contrib.summary.scalar,是在模型构建时插入的。例如,每执行 100 个全局的 step,就记录一次 summary:

  1. global_step = tf.train.get_or_create_global_step()
  2. writer = tf.contrib.summary.create_file_writer(logdir)
  3. writer.set_as_default()
  4. for _ in range(iterations):
  5. global_step.assign_add(1)
  6. # Must include a record_summaries method
  7. with tf.contrib.summary.record_summaries_every_n_global_steps(100):
  8. # your model code goes here
  9. tf.contrib.summary.scalar('loss', loss)
  10. ...

高级自动微分主题

动态模型

tf.GradientTape 也可以用于动态模型。这个用于回溯行搜索算法的示例看起来像普通的 NumPy 代码,尽管有着复杂的控制流,但它确实存在梯度并且是可微分的。

  1. def line_search_step(fn, init_x, rate=1.0):
  2. with tf.GradientTape() as tape:
  3. # Variables are automatically recorded, but manually watch a tensor
  4. tape.watch(init_x)
  5. value = fn(init_x)
  6. grad = tape.gradient(value, init_x)
  7. grad_norm = tf.reduce_sum(grad * grad)
  8. init_value = value
  9. while value > init_value - rate * grad_norm:
  10. x = init_x - rate * grad
  11. value = fn(x)
  12. rate /= 2.0
  13. return x, value

计算梯度的附加函数

tf.GradientTape 是一个用于计算梯度的功能强大的接口,但其实在自动微分方面,还有另一个 Autograd-风格的 API。在只使用张量和梯度函数编写数学代码,而且不使用 tf.Variables 的时候,这些函数是有用的:

  • tfe.gradients_function — 返回一个计算其输入函数参数导数的函数。输入函数参数必须返回标量值。返回函数被调用时,它会返回一个 tf.Tensor 对象列表:输入函数的每个参数都有一个元素。因为任何感兴趣的东西都必须作为函数参数传递,如果依赖于许多可训练的参数,这就变得很困难。
  • tfe.value_and_gradients_function — 类似于 tfe.gradients_function,但是当调用返回函数时,除了输入函数的导数列表和参数之外,它还会从输入函数返回值。

在下述示例中,tfe.gradients_functionsquare 函数作为参数,并返回一个用于计算其输入 square 的偏导数的函数。为了计算 3square 的导数,grad(3.0) 返回了 6

  1. def square(x):
  2. return tf.multiply(x, x)
  3. grad = tfe.gradients_function(square)
  4. square(3.) # => 9.0
  5. grad(3.) # => [6.0]
  6. # The second-order derivative of square:
  7. gradgrad = tfe.gradients_function(lambda x: grad(x)[0])
  8. gradgrad(3.) # => [2.0]
  9. # The third-order derivative is None:
  10. gradgradgrad = tfe.gradients_function(lambda x: gradgrad(x)[0])
  11. gradgradgrad(3.) # => [None]
  12. # 通过流进行控制:
  13. def abs(x):
  14. return x if x > 0. else -x
  15. grad = tfe.gradients_function(abs)
  16. grad(3.) # => [1.0]
  17. grad(-3.) # => [-1.0]

自定义梯度

在 eager 和 graph 执行环境中,自定义梯度是重载梯度的一种简单方法。在向前函数中,定义相对于输入、输出或中间结果的梯度。例如,下面是在向后传参时用于裁剪梯度的标准:

  1. @tf.custom_gradient
  2. def clip_gradient_by_norm(x, norm):
  3. y = tf.identity(x)
  4. def grad_fn(dresult):
  5. return [tf.clip_by_norm(dresult, norm), None]
  6. return y, grad_fn

自定义梯度通常用于为一系列操作提供数值稳定的梯度:

  1. def log1pexp(x):
  2. return tf.log(1 + tf.exp(x))
  3. grad_log1pexp = tfe.gradients_function(log1pexp)
  4. # The gradient computation works fine at x = 0.
  5. grad_log1pexp(0.) # => [0.5]
  6. # However, x = 100 fails because of numerical instability.
  7. grad_log1pexp(100.) # => [nan]

这里的 log1pexp 函数可以用自定义函数解析简化。以下实现重用了 tf.exp(x) 在正向传递过程中计算出的值——通过消除冗计算来提高效率:

  1. @tf.custom_gradient
  2. def log1pexp(x):
  3. e = tf.exp(x)
  4. def grad(dy):
  5. return dy * (1 - 1 / (1 + e))
  6. return tf.log(1 + e), grad
  7. grad_log1pexp = tfe.gradients_function(log1pexp)
  8. # As before, the gradient computation works fine at x = 0.
  9. grad_log1pexp(0.) # => [0.5]
  10. # And the gradient computation also works at x = 100.
  11. grad_log1pexp(100.) # => [1.0]

性能

在 eager execution 期间,计算会自动加载到 GPU。如果你希望控制计算运行的位置,可以将其封装在 tf.device('/gpu:0') 块(或与 CPU 等效的块中):

  1. import time
  2. def measure(x, steps):
  3. # TensorFlow initializes a GPU the first time it's used, exclude from timing.
  4. tf.matmul(x, x)
  5. start = time.time()
  6. for i in range(steps):
  7. x = tf.matmul(x, x)
  8. # tf.matmul 可以在完成矩阵乘法运算之前返回(例如,可以在对 CUDA 流进行操作之后返回)。
  9. # 下面的 x.numpy() 调用将确保所有排队的操作都已完成(并且还将会结果复制到主机内存中,所以我们要包括的不只是 matmul 操作时间)。
  10. _ = x.numpy()
  11. end = time.time()
  12. return end - start
  13. shape = (1000, 1000)
  14. steps = 200
  15. print("Time to multiply a {} matrix by itself {} times:".format(shape, steps))
  16. # Run on CPU:
  17. with tf.device("/cpu:0"):
  18. print("CPU: {} secs".format(measure(tf.random_normal(shape), steps)))
  19. # Run on GPU, if available:
  20. if tfe.num_gpus() > 0:
  21. with tf.device("/gpu:0"):
  22. print("GPU: {} secs".format(measure(tf.random_normal(shape), steps)))
  23. else:
  24. print("GPU: not found")

Output (exact numbers depend on hardware):

  1. Time to multiply a (1000, 1000) matrix by itself 200 times:
  2. CPU: 1.46628093719 secs
  3. GPU: 0.0593810081482 secs

A tf.Tensor object can be copied to a different device to execute its operations:

  1. x = tf.random_normal([10, 10])
  2. x_gpu0 = x.gpu()
  3. x_cpu = x.cpu()
  4. _ = tf.matmul(x_cpu, x_cpu) # Runs on CPU
  5. _ = tf.matmul(x_gpu0, x_gpu0) # Runs on GPU:0
  6. if tfe.num_gpus() > 1:
  7. x_gpu1 = x.gpu(1)
  8. _ = tf.matmul(x_gpu1, x_gpu1) # Runs on GPU:1

基准

对于计算量很大的模型,例如 ResNet50 在 GPU 上的训练,eager execution 性能可以与 graph execution 像媲美。但是对于计算量较小的模型来说,这种差距会越来越大,而且对于具有大量小计算的模型来说,优化热代码路径仍然有许多工作需要完成。

使用图

尽管 eager execution 使开发和调试更具交互性,但 TensorFlow 的 graph execution 在分布式训练、性能优化以及产品部署上仍旧具有优势。然而,编写 graph 代码不同于编写常规 Python 代码,而且更难进行调试。

为了构建和训练图形构造模型,Python 程序首先构建一个用于表示计算的图,然后调用 Session.run 来发送此图,以便在基于 C++ 运行时执行。这提供了:

  • 使用静态自动微分法来自动微分。
  • 简单部署到独立于平台的服务器。
  • 基于图形的优化(常见的子表达式消除,常量折叠等)。
  • 编译和内核融合。
  • 自动分发和复制(在分布式系统上放置节点)。

为 eager execution 编写部署代码更加困难:要么从模型生成图形,要么在服务器上直接运行 Python 运行时代码。

编写兼容性代码

为 eager execution 编写的相同代码也会在 graph execution 期间构建图形。只需在未启用 eager execution 的新 Python session 中运行相同的代码即可。

大多数 TensorFlow 操作都在 eager execution 期间都是可以运行的,但有些事需要记住:

  • 使用 tf.data 而不是队列,来进行输入处理。这会更快,更简单。
  • 使用面向对象的层 API——例如 tf.keras.layerstf.keras.Model——因为它们对变量进行显示存储。
  • 大多数模型在 eager execution 和 graph execution 中的表现是一样的,但也有特列。(例如,动态模型使用 Python 控制流来改变基于输入的计算。)
  • 一旦通过 tf.enable_eager_execution 启用 eager execution,它就不会被关闭。启动一个新的 Python session 来返回到 graph execution。

最好是同时为 eager execution graph execution 编写代码。这将为你提供 eager 的交互式体验和可调式性,以及 graph execution 的分布式性能优势。

在 eager execution 中编写,调试和迭代,然后为生产部署导入模型图。使用 tf.train.Checkpoint 来保存和存储模型变量,这允许在 eager 和 graph execution 环境之间移动。请参阅以下示例:tensorflow/contrib/eager/python/examples

在图环境中使用 eager execution 执行

使用 tfe.py_func 选择性地启用 TensorFlow 图形化环境中的 eager execution 执行。当 tf.enable_eager_execution() 尚未被调用时使用。

  1. def my_py_func(x):
  2. x = tf.matmul(x, x) # 你可以使用 tf ops
  3. print(x) # 但这是 eager!
  4. return x
  5. with tf.Session() as sess:
  6. x = tf.placeholder(dtype=tf.float32)
  7. # 在图形中调用 eager 函数
  8. pf = tfe.py_func(my_py_func, [x], tf.float32)
  9. sess.run(pf, feed_dict={x: [[2.0]]}) # [[4.0]]