TensorFlow Eager 官方教程 * * * * *

TensorFlow 版本:1.10.0 > Guide > Eager Execution

Eager 模式介绍
TensorFlow 的 eager execution 是一个即时运行环境。在该模式下,op 在定义时即时运行(eager 模式下,op 返回具体值,而不是图节点)。Eager 模式使得 TensorFlow 的使用和调试变得简单。为了真实地感受 Eager 模式的魅力,推荐大家在交互式 python 解释器中运行本文的代码。

Eager execution 是进行机器学习研究、实验的一个灵活的平台。Eager 模式有以下优点:

  • 更好的交互 ---------- 可以更自然地组织你的代码,可以直接使用 Python 数据结构。
  • 更容易的调试 ---------- 可以直接调用 op 去检查模型的运行过程,测试更改后的模型。 使用标准的 Python 调试工具来检测模型的错误。
  • 自然的控制流 ---------- 可以使用 Python 控制流,从而简化动态模型的搭建。

Eager 模式支持绝大多数 TensorFlow op 和 GPU 加速。Eager 模式的使用示例,详见tensorflow/contrib/eager/python/examples

注意:虽然一些 model 可能会因 Eager 模式的开启变慢,但性能的提升持续在进行。

1. Eager 模式的开启、基本使用

请大家首先将 TensorFlow 更到最新:

$ pip install --upgrade tensorflow

要开启 Eager 模式,只需要在 “程序” 或 “控制台会话” 的开始添加 tf.enable_eager_execution()不要 将开启 Eager 模式的语句放在程序的其它地方。

import tensorflow as tf

tf.enable_eager_execution()

现在你运行 TensorFlow 的 op 后,op 的运行结果会立即返回:

tf.executing_eagerly()        # => True

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

Eager 模式的开启会改变 TensorFlow op 的运行 ---------- 开启 eager 后 op 会立即会将运行结果返回 Python。tf.Tensor 对象引用(reference)具体的值,而不是计算图中节点的句柄。因为 Eager 模式不构建计算图,因此可以很方便地使用 print() 或 python调试器 来检查结果。评估、print、检查 tensor 的值不影响梯度的计算。

Eager 模式和 NumPy 的兼容性很好。NumPy op 可以以 tf.Tensor 为参数。TensorFlow math operations 将 Python 对象和 NumPy 数组转换为 tf.Tensor 对象。tf.Tensor.numpy 方法将 tf.Tensor 转换为 NumPy 数组。

a = tf.constant([[1, 2],
                 [3, 4]])
print(a)
# => tf.Tensor([[1 2]
#               [3 4]], shape=(2, 2), dtype=int32)

# Broadcasting support
b = tf.add(a, 1)
print(b)
# => tf.Tensor([[2 3]
#               [4 5]], shape=(2, 2), dtype=int32)

# Operator overloading is supported
print(a * b)
# => tf.Tensor([[ 2  6]
#               [12 20]], shape=(2, 2), dtype=int32)

# Use NumPy values
import numpy as np

c = np.multiply(a, b)
print(c)
# => [[ 2  6]
#     [12 20]]

# Obtain numpy value from a tensor:
print(a.numpy())
# => [[1 2]
#     [3 4]]

tf.contrib.eager 模块的 op 可以同时适用于 eager 和 graph 环境。这在编写兼容 graph 模式的的代码时非常有用。

tfe = tf.contrib.eager

2. Eager 模式下 model 的建立

很多机器学习模型通常由很多层组合而成。开启 eager 模式后,你可以自定义层 或 使用 tf.keras.layers 里的层。

在使用 Python 对象来表示一个层时,需要从 tf.keras.layers.Layer 继承来编写自定义层:

class MySimpleLayer(tf.keras.layers.Layer):
  def __init__(self, output_units):
    super(MySimpleLayer, self).__init__()
    self.output_units = output_units

  def build(self, input_shape):
    # The build method gets called the first time your layer is used.
    # Creating variables on build() allows you to make their shape depend
    # on the input shape and hence removes the need for the user to specify
    # full shapes. It is possible to create variables during __init__() if
    # you already know their full shapes.
    self.kernel = self.add_variable(
      "kernel", [input_shape[-1], self.output_units])

  def call(self, input):
    # Override call() instead of __call__ so we can perform some bookkeeping.
    return tf.matmul(input, self.kernel)

使用 tf.keras.layers.Dense 来取代 MySimpleLayer 非常有必要(因为 Dense 的功能包含了 MySimpleLayer 的功能)。

将层组合成模型时,你可以使用 tf.keras.Sequential 来表示模型,其可以线性地多个层堆叠起来。Sequential 在搭建简单模型时非常有用。

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

除了使用 Sequential 来构建模型,也可以从 tf.keras.Model 继承来编写一个模型类表示模型。构建的模型包含了很多层,允许 tf.keras.Model 包含其它 tf.keras.Model 对象。

class MNISTModel(tf.keras.Model):
  def __init__(self):
    super(MNISTModel, self).__init__()
    self.dense1 = tf.keras.layers.Dense(units=10)
    self.dense2 = tf.keras.layers.Dense(units=10)

  def call(self, input):
    """Run the model."""
    result = self.dense1(input)
    result = self.dense2(result)
    result = self.dense2(result)  # reuse variables from dense2 layer
    return result

model = MNISTModel()

tf.keras.Model 类中不需要设置输入的 shape。

tf.keras.layers 类 创建、包含了该层的模型参数,这些参数 与 实例化出的对象 共存亡。当需要共享层的参数时,共享 实例化出的对象 即可。

3. Eager 模式下 model 的训练

3.1 计算梯度

自动微分对于机器学习算法的训练至关重要。在 Eager 模式,为了计算梯度,需要使用 tf.GradientTape 来追踪 op。

tf.GradientTape 是一个

3.2 训练一个模型

即使不进行训练,也可以在 eager 模式调用模型并检查模型的输出。

# Create a tensor representing a blank image
batch = tf.zeros([1, 1, 784])
print(batch.shape)  # => (1, 1, 784)

result = model(batch)
# => tf.Tensor([[[ 0.  0., ..., 0.]]], shape=(1, 1, 10), dtype=float32)

下面的例子使用 TensorFlow MNIST example 中的 dataset.py module 来导入数据,请将该模块下载到你的机器。运行下面的代码去下载 MNIST 数据集到你的机器,并以 tf.data.Dataset 的形式将数据集用于训练:

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

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

def loss(model, x, y):
  prediction = model(x)
  return tf.losses.sparse_softmax_cross_entropy(labels=y, logits=prediction)

def grad(model, inputs, targets):
  with tf.GradientTape() as tape:
    loss_value = loss(model, inputs, targets)
  return tape.gradient(loss_value, model.variables)

optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.001)

x, y = iter(dataset_train).next()
print("Initial loss: {:.3f}".format(loss(model, x, y)))

# Training loop
for (i, (x, y)) in enumerate(dataset_train):
  # Calculate derivatives of the input function with respect to its parameters.
  grads = grad(model, x, y)
  # Apply the gradient to the model
  optimizer.apply_gradients(zip(grads, model.variables),
                            global_step=tf.train.get_or_create_global_step())
  if i % 200 == 0:
    print("Loss at step {:04d}: {:.3f}".format(i, loss(model, x, y)))

print("Final loss: {:.3f}".format(loss(model, x, y)))

输出(具体的数字可能不同):

Initial loss: 2.674
Loss at step 0000: 2.593
Loss at step 0200: 2.143
Loss at step 0400: 2.009
Loss at step 0600: 2.103
Loss at step 0800: 1.621
Loss at step 1000: 1.695
...
Loss at step 6600: 0.602
Loss at step 6800: 0.557
Loss at step 7000: 0.499
Loss at step 7200: 0.744
Loss at step 7400: 0.681
Final loss: 0.670

并且为了更快地训练,将上面的计算使用 GPU 进行加速:

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

3.3 变量和优化器

tfe.Variable 对象存储的 tf.Tensor 的值可变,这使得自动微分过程变得简单。模型的变量可以被封装到类中。

通过使用 tfe.Variabletf.GradientTape 可以更好地封装模型参数。例如,上面的自动微分例子可以重写为:

class Model(tf.keras.Model):
  def __init__(self):
    super(Model, self).__init__()
    self.W = tfe.Variable(5., name='weight')
    self.B = tfe.Variable(10., name='bias')
  def call(self, inputs):
    return inputs * self.W + self.B

# A toy dataset of points around 3 * x + 2
NUM_EXAMPLES = 2000
training_inputs = tf.random_normal([NUM_EXAMPLES])
noise = tf.random_normal([NUM_EXAMPLES])
training_outputs = training_inputs * 3 + 2 + noise

# The loss function to be optimized
def loss(model, inputs, targets):
  error = model(inputs) - targets
  return tf.reduce_mean(tf.square(error))

def grad(model, inputs, targets):
  with tf.GradientTape() as tape:
    loss_value = loss(model, inputs, targets)
  return tape.gradient(loss_value, [model.W, model.B])

# Define:
# 1. A model.
# 2. Derivatives of a loss function with respect to model parameters.
# 3. A strategy for updating the variables based on the derivatives.
model = Model()
optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.01)

print("Initial loss: {:.3f}".format(loss(model, training_inputs, training_outputs)))

# Training loop
for i in range(300):
  grads = grad(model, training_inputs, training_outputs)
  optimizer.apply_gradients(zip(grads, [model.W, model.B]),
                            global_step=tf.train.get_or_create_global_step())
  if i % 20 == 0:
    print("Loss at step {:03d}: {:.3f}".format(i, loss(model, training_inputs, training_outputs)))

print("Final loss: {:.3f}".format(loss(model, training_inputs, training_outputs)))
print("W = {}, B = {}".format(model.W.numpy(), model.B.numpy()))

输出(具体值可能会有变化):

Initial loss: 69.066
Loss at step 000: 66.368
Loss at step 020: 30.107
Loss at step 040: 13.959
Loss at step 060: 6.769
Loss at step 080: 3.567
Loss at step 100: 2.141
Loss at step 120: 1.506
Loss at step 140: 1.223
Loss at step 160: 1.097
Loss at step 180: 1.041
Loss at step 200: 1.016
Loss at step 220: 1.005
Loss at step 240: 1.000
Loss at step 260: 0.998
Loss at step 280: 0.997
Final loss: 0.996
W = 2.99431324005, B = 2.02129220963

4. Eager 模式下,模型状态(变量的状态)及存活时间由与之对应的 Python 对象决定

使用 graph 模式时,程序的状态(例如变量)储存在 “全局容器” 中,并且它们的存活时间由 tf.Session 对象来管理。与 graph 模式不同,在 eager 模式,程序的状态由其对应的 Python 对象决定,并且状态的存活时间由对应 Python 对象决定。

4.1 Variables 是对象

在 Eager 模式下,变量的存活时间 由 与之对应的Python对象的存活时间决定。

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

4.2 基于对象的保存(Object-based saving)

tfe.Checkpoint 能够将 tfe.Variables 储存为 checkpoints,能够从 checkpoints 中恢复 tfe.Variable

x = tfe.Variable(10.)

checkpoint = tfe.Checkpoint(x=x)  # save as "x"

x.assign(2.)   # Assign a new value to the variables and save.
save_path = checkpoint.save('./ckpt/')

x.assign(11.)  # Change the variable after saving.

# Restore values from the checkpoint
checkpoint.restore(save_path)

print(x)  # => 2.0

为了保存和加载模型,tfe.Checkpoint 存储对象的内部状态,

为了记录 [模型、优化器、global step],只需要将它们传给一个 tfe.Checkpoint

model = MyModel()
optimizer = tf.train.AdamOptimizer(learning_rate=0.001)
checkpoint_dir =/path/to/model_dir’
checkpoint_prefix = os.path.join(checkpoint_dir, "ckpt")
root = tfe.Checkpoint(optimizer=optimizer,
                      model=model,
                      optimizer_step=tf.train.get_or_create_global_step())

root.save(file_prefix=checkpoint_prefix)
# or
root.restore(tf.train.latest_checkpoint(checkpoint_dir))

4.3 面对对象的指标

tfe.metrics 被当做对象来存储。通过给指标传递新值来更新指标的值,取回指标的值使用 tfe.metrics.result 方法。例如:

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

Summaries and TensorBoard

TensorBoard 是一个用来理解、调试、优化模型训练过程的可视化工具。

tf.contrib.summary 同时兼容 eager 和 graph 模式。

writer = tf.contrib.summary.create_file_writer(logdir)
global_step=tf.train.get_or_create_global_step()  # return global step var

writer.set_as_default()

for _ in range(iterations):
  global_step.assign_add(1)
  # Must include a record_summaries method
  with tf.contrib.summary.record_summaries_every_n_global_steps(100): # record summary every 100 steps
    # your model code goes here
    tf.contrib.summary.scalar('loss', loss)
     ...

5. Eager 自动微分的高级应用

5.1 动态模型

tf.GradientTape 可以用于动态模型。下面的例子以类 NumPy 的代码实现了回溯搜索算法

def line_search_step(fn, init_x, rate=1.0):
  with tf.GradientTape() as tape:
    # Variables are automatically recorded, but manually watch a tensor
    tape.watch(init_x)
    value = fn(init_x)
  grad = tape.gradient(value, init_x)
  grad_norm = tf.reduce_sum(grad * grad)
  init_value = value
  while value > init_value - rate * grad_norm:
    x = init_x - rate * grad
    value = fn(x)
    rate /= 2.0
  return x, value

5.2 计算梯度的其它函数

虽然 tf.GradientTape 是计算梯度的一个强有力 API,但有另一种 Autograd 风格的 API 来进行自动微分。如果运算的代码只与 tensor 及 梯度函数 相关,并且没有 tfe.Variables,这些函数是非常有用的。

  • tfe.gradients_function ---------- 返回一个函数,其计算输出相对于输入的梯度。
  • tfe.value_and_gradients_function ---------- 与 tfe.gradients_function 类似,但是当调用返回的函数时,其函数的输出值 和 梯度。

在下面的例子中,tfe.gradients_functionsquare 函数为输入,返回一个计算 square 偏微分的函数。

def square(x):
  return tf.multiply(x, x)

grad = tfe.gradients_function(square)

square(3.)  # => 9.0
grad(3.)    # => [6.0]

# The second-order derivative of square:
gradgrad = tfe.gradients_function(lambda x: grad(x)[0])
gradgrad(3.)  # => [2.0]

# The third-order derivative is None:
gradgradgrad = tfe.gradients_function(lambda x: gradgrad(x)[0])
gradgradgrad(3.)  # => [None]


# With flow control:
def abs(x):
  return x if x > 0. else -x

grad = tfe.gradients_function(abs)

grad(3.)   # => [1.0]
grad(-3.)  # => [-1.0]

5.3 自定义梯度

tf.custom_gradient 是一种覆盖梯度的简单方法。

@tf.custom_gradient
def clip_gradient_by_norm(x, norm):
  y = tf.identity(x)
  def grad_fn(dresult):
    return [tf.clip_by_norm(dresult, norm), None]
  return y, grad_fn

tf.custom_gradient 通常被用来稳定梯度。

def log1pexp(x):
  return tf.log(1 + tf.exp(x))
grad_log1pexp = tfe.gradients_function(log1pexp)

# The gradient computation works fine at x = 0.
grad_log1pexp(0.)  # => [0.5]

# However, x = 100 fails because of numerical instability.
grad_log1pexp(100.)  # => [nan]

这里,log1pexp 函数可以被简化为一个custom gradient。下面的实现重用了 tf.exp(x) 的值,通过消除冗余的计算,使它变得更高效。

@tf.custom_gradient
def log1pexp(x):
  e = tf.exp(x)
  def grad(dy):
    return dy * (1 - 1 / (1 + e))
  return tf.log(1 + e), grad

grad_log1pexp = tfe.gradients_function(log1pexp)

# As before, the gradient computation works fine at x = 0.
grad_log1pexp(0.)  # => [0.5]

# And the gradient computation also works at x = 100.
grad_log1pexp(100.)  # => [1.0]

6. Eager 模式的性能

在 eager 模式下,模型的计算默认不使用 GPU。如果你想在 GPU 上运行模型,可以使用 tf.device('/gpu:0')

import time

def measure(x, steps):
  # TensorFlow initializes a GPU the first time it's used, exclude from timing.
  tf.matmul(x, x)
  start = time.time()
  for i in range(steps):
    x = tf.matmul(x, x)
    _ = x.numpy()  # Make sure to execute op and not just enqueue it
  end = time.time()
  return end - start

shape = (1000, 1000)
steps = 200
print("Time to multiply a {} matrix by itself {} times:".format(shape, steps))

# Run on CPU:
with tf.device("/cpu:0"):
  print("CPU: {} secs".format(measure(tf.random_normal(shape), steps)))

# Run on GPU, if available:
if tfe.num_gpus() > 0:
  with tf.device("/gpu:0"):
    print("GPU: {} secs".format(measure(tf.random_normal(shape), steps)))
else:
  print("GPU: not found")

性能基准

对于计算量较大的模型(比如 ResNet-50)在一个 GPU 上的计算,eager 模式的性能和 graph 是相当的。但是当 “模型的计算量比较小” 时,两个模式之间的性能差异就会变大。

7. Eager 与 Graph 的兼容

Eager 使得开发、调试的交互性更好,而 graph 在分布式训练、性能优化、生产部署 方面有优势。但是编写 graph 代码比编写普通的 Python 代码难,并且不容易调试。

为了建立、训练 graph 类型的模型,Python 程序首先建立一个计算图,然后调用 Session.run 来执行图。这提供了:

  • 静态的自动微分
  • 简单部署(在平台无关的服务器)
  • 基于图的优化(常见的子表示式的合并,常量合并等)
  • 编译和内核融合
  • 自动分布式和复制(将节点放置在分布式系统上)

为 eager 模式编写部署用的代码是非常困难的。难点1:从 model 中生成一个 graph;难点2:在服务器上直接运行Python控制台 及 代码。

7.1 编写兼容的代码

Eager 模式的代码也可以用于 graph 模式。要实现这个转换,重开一个 Python 控制台,不开启 eager 即可。

大多数的 TensorFlow op 兼容 eager 模式,但是请注意以下几点:

  • 请使用 tf.data,而不是 queues。它更快且更易用。
  • 请使用 “面向对象的 layer API” --------- 比如:tf.keras.layers,因为它们有显式的变量存储。
  • 大多数的模型代码在 eager 和 graph 模式的行为一致,但也有例外(例如,动态模型使用 Python 控制流基于输入改变了模型的计算过程)。
  • Eager 模式的开始不可逆。想返回 graph 模式,请开启一个新的 Python 控制台。

编写的代码最好同时支持 eager 和 graph 模式。这使得你可以在利用 eager 良好的交互、调试;同时拥有 graph 的分布式性能优势。

在 eager 模式下进行写代码、调试、迭代,然后以 graph 来进行生产的部署。使用 tfe.Checkpoint 去保存、恢复模型变量这使得你可以很方便地在 eager 和 graph 模式之间切换。示例详见:tensorflow/contrib/eager/python/examples

7.2 在 graph 的局部使用 eager

当 eager 模式未开启的情况下,想在 graph 的局部使用 eager,可以使用 tfe.py_func

def my_py_func(x):
  x = tf.matmul(x, x)  # You can use tf ops
  print(x)  # but it's eager!
  return x

with tf.Session() as sess:
  x = tf.placeholder(dtype=tf.float32)
  # Call eager function in graph!
  pf = tfe.py_func(my_py_func, [x], tf.float32)
  sess.run(pf, feed_dict={x: [[2.0]]})  # [[4.0]]

猜你喜欢

转载自blog.csdn.net/u014061630/article/details/82319940