Skip to content

Latest commit

 

History

History
392 lines (277 loc) · 22.3 KB

File metadata and controls

392 lines (277 loc) · 22.3 KB

七、从 TensorFlow 1.x 迁移到 2.0

本章将介绍如何将 TensorFlow 1.xTF 1.x)代码转换为 TensorFlow 2.0TF 2.0) 代码有两种方式。 第一种方法是使用更新脚本,该脚本会更改大多数 TF 1.x 代码,以便可以在 TF 2.0 中运行。 但是,这仅将所有tf.x API 调用转换为tf.compat.v1.x格式。 另一种方法是,考虑到对库所做的核心更改,将 TF 1.x 代码转换为惯用的 TF2.0 代码。 我们将讨论 TF 1.x 和 TF 2.0 之间的概念差异,它们之间的兼容性标准以及我们在语法和语义上进行迁移的方式。 我们还将展示从 TF 1.x 到 TF 2.0 的语法和语义迁移的几个示例,我们将通过它们提供参考和将来的信息。

本章将涵盖以下主题:

  • TF 2.0 的主要变化
  • 适用于 TF 2.0 的推荐技术
  • 使代码 TF 2.0 原生
  • 常见问题
  • TF 2.0 的未来

TF 2.0 的主要变化

从 TF 1.x 迁移到 TF 2.0 时,您将遇到的主要变化涉及 API 清理。

TF 2.0 中的许多 API 都已被删除或移动。 主要更改包括删除tf.apptf.flagstf.logging,以支持其他 Python 模块,例如absl-py和内置的日志记录系统。

TF 2.0 在代码方面所做的最大更改之一就是急切执行。 TF 1.x 要求用户使用tf.*调用来手工拼接抽象语法树,以构建计算图,该图将与session.run()一起运行。 这意味着 TF 2.0 代码逐行运行,因此不再需要tf.control_dependancies()

TF 1.x 中的session.run()调用与...非常相似。

适用于 TF 2.0 的推荐技术

第一条建议涉及在 TF 2.0 中处理常规代码工作流。 TF 1.x 中常见的工作流程是使用瀑布策略,其中所有计算都布置在默认图上。 然后,使用session.run()运行选定的张量。 在 TF 2.0 中,应将代码重构为较小的函数,这些函数将在需要时调用。 这些函数可以是普通的 Python 函数,但如果在另一个以tf.function注解的函数中调用它们,则仍可以在图模式下运行。 这意味着tf.function仅应用于注解高级计算,例如模型的前向传递或单个训练步骤。

以前,模型和训练循环所需的所有计算都将预先确定并编写,并使用session.run()执行。 这使得 TF 1.x 代码对于大多数编码人员来说很难遵循,因为模型的流程可能与图的编码方式完全不同,因为该图是在最后运行的。 急切执行和tf.function专门用于简化 TensorFlow 代码动态过程,并使其他开发人员更容易理解预编写的代码。

管理和跟踪变量是 TF 1.x 中另一个复杂的过程。 使用了许多方法来控制和访问这些变量,这为线性代码增加了更多的维度。 TF 2.0 更加强调使用tf.keras层和tf.estimator模型来管理模型中的变量。

这与手动滚动神经网络层和手动创建变量形成对比。 在以下示例中,必须跟踪权重和偏差变量,其形状的定义应远离模型的创建。 这使得难以更改模型并使模型适应不同的架构和数据集:

def dense(x, W, b):
 return tf.nn.sigmoid(tf.matmul(x, W) + b)

@tf.function
def multilayer_perceptron(x, w0, b0, w1, b1, w2, b2 ...):
  x = dense(x, w0, b0)
  x = dense(x, w1, b1)
  x = dense(x, w2, b2)
  ...

此代码的tf.keras实现非常简单明了,并确保开发人员不必担心变量和变量名的组织和管理。 它还可以轻松访问模型中的可训练变量:

layers = [tf.keras.layers.Dense(hidden_size, activation=tf.nn.sigmoid) for _ in range(n)]
perceptron = tf.keras.Sequential(layers)

# layers[3].trainable_variables => returns [w3, b3]
# perceptron.trainable_variables => returns [w0, b0, ...]

tf.keras模型还继承了tf.train.Checkpointable模型的方法,并与tf.function集成在一起,因此可以将它们直接保存到检查点并导出到SavedModels

以下是迁移学习实现的示例,并显示tf.keras如何使收集相关值的子集,计算其梯度以及基于梯度对其进行调整变得容易:

trunk = tf.keras.Sequential([...])
head1 = tf.keras.Sequential([...])
head2 = tf.keras.Sequential([...])

path1 = tf.keras.Sequential([trunk, head1])
path2 = tf.keras.Sequential([trunk, head2])

# Train on primary dataset
for x, y in main_dataset:
  with tf.GradientTape() as tape:
    prediction = path1(x)
    loss = loss_fn_head1(prediction, y)
  # Simultaneously optimize trunk and head1 weights.
  gradients = tape.gradient(loss, path1.trainable_variables)
  optimizer.apply_gradients(zip(gradients, path1.trainable_variables))

# Fine-tune second head, reusing the trunk
for x, y in small_dataset:
  with tf.GradientTape() as tape:
    prediction = path2(x)
    loss = loss_fn_head2(prediction, y)
  # Only optimize head2 weights, not trunk weights
  gradients = tape.gradient(loss, head2.trainable_variables)
  optimizer.apply_gradients(zip(gradients, head2.trainable_variables))

# You can publish just the trunk computation for other people to reuse.
tf.saved_model.save(trunk, output_path)

所有尚未存储在内存中的数据集都应使用tf.dataset进行存储和流传输。 数据集在 TF 2.0 中是可迭代的,因此在急切的执行模式下,它们可以像任何其他 Python 可迭代的一样使用,例如列表和元组。 您还可以通过使用tf.function包装数据集迭代来利用数据集异步预取和流传输功能,该迭代将 Python 交互转换为与 AutoGraph 等效的图操作。 正如我们在本书前面所提到的,AutoGraph 采用默认的 Python 流并将其转换为基于图的代码。 例如,诸如if...else块之类的控制流将转换为tf.condition语句。 以下代码块向您展示了如何使用for块训练模型:

@tf.function
def train(model, dataset, optimizer):
  for x, y in dataset:
    with tf.GradientTape() as tape:
      prediction = model(x)
      loss = loss_fn(prediction, y)
    gradients = tape.gradient(loss, model.trainable_variables)
    optimizer.apply_gradients(zip(gradients, model.trainable_variables))

但是,如果您正在使用 Keras 的model.fit,则不必担心。 要使用model.fit在数据集上训练模型,只需将数据集传递给方法。 它将处理其他所有事项:

model.compile(optimizer=optimizer, loss=loss_fn)
model.fit(dataset)

使代码 TF 2.0 原生

使 TF 1.x 代码与 TF 2.0 代码兼容的最简单方法是运行系统上安装的更新脚本以及 TF 2.0 安装。 更新脚本使用tf.compat.v1模块。

为了向 TF 1.x 编写的代码提供向后兼容性,在 TF 2.0 中引入了tf.compat.v1模块。 tf.compat.v1模块替换了所有 TF 1.x 符号,例如tf.footf.compat.v1.foo。 此模块允许转换为 TF 1.x 编写的大多数代码,以便可以在 TF 2.0 中运行。

作为简化此过程的一种方式,TensorFlow 提供了tf_upgrade_v2工具,该工具有助于尽可能简化转换。 该工具已预装...

转换 TF 1.x 模型

第一步是将所有tf.Session.run()调用替换为 Python 函数。 这意味着将tf.placeholderfeed_dict转换为函数参数。 这些成为函数的返回值。 此更改意味着与 TF 1.x 不同,可以使用标准的 Python 工具(例如pdb)来逐步调试该功能。 构建函数后,可以添加tf.function注解以在图模式下运行该函数,以及 TF 1.x 中等效的tf.Session.run调用的效率。

使用tf.layers API 创建的 TF 1.x 模型可以相对容易地转换为 TF 2.0。 tf.layers模块用于包含依赖于tf.variable_scope定义和重用变量的层函数。

以下代码块是使用tf.layers API 编写的 TF 1.x 中小型卷积神经网络的实现:

def model(x, training, scope='model'):
  with tf.variable_scope(scope, reuse=tf.AUTO_REUSE):
    x = tf.layers.conv2d(x, 32, 3, activation=tf.nn.relu,
          kernel_regularizer=tf.contrib.layers.l2_regularizer(0.04))
    x = tf.layers.max_pooling2d(x, (2, 2), 1)
    x = tf.layers.flatten(x)
    x = tf.layers.dropout(x, 0.1, training=training)
    x = tf.layers.dense(x, 64, activation=tf.nn.relu)
    x = tf.layers.batch_normalization(x, training=training)
    x = tf.layers.dense(x, 10, activation=tf.nn.softmax)
    return x

train_out = model(train_data, training=True)
test_out = model(test_data, training=False)

将模型转换为 TF 2.0 的最简单方法是使用tf.keras.Sequential,因为该模型由线性层组成。 从tf.layerstf.keras.layers有一对一的转换,但有一些区别。 在 TF 2.0 代码中,训练参数不再传递给每个层,因为模型会自动处理该参数。

这是 TF 2.0 中的代码:

model = tf.keras.Sequential([
    tf.keras.layers.Conv2D(32, 3, activation='relu',
                           kernel_regularizer=tf.keras.regularizers.l2(0.04),
                           input_shape=(28, 28, 1)),
    tf.keras.layers.MaxPooling2D(),
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dropout(0.1),
    tf.keras.layers.Dense(64, activation='relu'),
    tf.keras.layers.BatchNormalization(),
    tf.keras.layers.Dense(10, activation='softmax')
])

train_data = tf.ones(shape=(1, 28, 28, 1))
test_data = tf.ones(shape=(1, 28, 28, 1))

train_out = model(train_data)

test_out = model(test_data, training=False)

如我们所见,tf.variable_scope没有用于组织为模型创建的变量。 在 TF 1.x 中,该范围将用于从模型中恢复变量。 在 TF 2.0 中,可以使用model.trainable_variables列出模型变量。

尽管从tf.layerstf.keras.layers的转换相对简单,但是由于代码流的差异,转换变得更加复杂。

TF 1.x 中的低级 API 的一些示例包括使用变量作用域来控制重用,使用tf.get_variable创建变量,使用tf.placeholdersession.run定期访问集合以及手动初始化变量。 由于引入了系统范围内的急切执行,这些技术和策略中的许多现在已过时,因此以低级 API 编写的代码比以高级 API 编写的代码(例如tf.kerastf.layers)需要更大的更改。 。

以下是使用 TF 1.x 的低级 API 编写的一些代码的示例:

in_a = tf.placeholder(dtype=tf.float32, shape=(2))
in_b = tf.placeholder(dtype=tf.float32, shape=(2))

def forward(x):
  with tf.variable_scope("matmul", reuse=tf.AUTO_REUSE):
    W = tf.get_variable("W", initializer=tf.ones(shape=(2,2)),
                       regularizer=tf.contrib.layers.l2_regularizer(0.04))
    b = tf.get_variable("b", initializer=tf.zeros(shape=(2)))
    return W * x + b

out_a = forward(in_a)
out_b = forward(in_b)

reg_loss = tf.losses.get_regularization_loss(scope="matmul")

with tf.Session() as sess:
  sess.run(tf.global_variables_initializer())
  outs = sess.run([out_a, out_b, reg_loss],
                feed_dict={in_a: [1, 0], in_b: [0, 1]})

可以通过将前向函数更改为用tf.function注解的函数进行基于图的计算,删除session.run函数和变量范围并添加简单的函数调用来转换此代码。 将不会在W变量上全局调用正则化; 相反,它将被手动调用,而无需引用全局集合:

W = tf.Variable(tf.ones(shape=(2,2)), name="W")
b = tf.Variable(tf.zeros(shape=(2)), name="b")

@tf.function
def forward(x):
  return W * x + b

out_a = forward([1,0])
out_b = forward([0,1])

regularizer = tf.keras.regularizers.l2(0.04)
reg_loss = regularizer(W)

正如我们所看到的,TF 2.0 代码比以前的 TF 1.x 代码更加 Python 化和简洁。

使用tf.placeholder的好处之一是可以控制图输入的形状,如果输入与预定形状不匹配,则会返回错误。 在 TF 2.0 中,仍然可以通过使用 Python 内置的assert命令来完成此操作。 这可以用来断言该函数的输入自变量的形状与输入自变量所期望的形状匹配。

现有的 TF 1.x 代码通常同时包含较低级别的 TF 1.x 变量和具有较高级别tf.layers的操作。 这意味着上述示例都不足以转换 TF 1.x 代码,并且需要tf.keras编程的更复杂形式,称为模型或层子类。

以下是在 TF 1.x 中使用tf.get_variabletf.layers编写的原始代码:

def model(x, training, scope='model'):
  with tf.variable_scope(scope, reuse=tf.AUTO_REUSE):
    W = tf.get_variable(
      "W", dtype=tf.float32,
      initializer=tf.ones(shape=x.shape),
      regularizer=tf.contrib.layers.l2_regularizer(0.04),
      trainable=True)
    if training:
      x = x + W
    else:
      x = x + W * 0.5
    x = tf.layers.conv2d(x, 32, 3, activation=tf.nn.relu)
    x = tf.layers.max_pooling2d(x, (2, 2), 1)
    x = tf.layers.flatten(x)
    return x

train_out = model(train_data, training=True)
test_out = model(test_data, training=False)

通过将所有低层操作和变量包装在自定义创建的 Keras 层中,可以转换此代码。 这可以通过创建一个从tf.keras.layers.Layer类继承的类来完成:

# Create a custom layer for part of the model
class CustomLayer(tf.keras.layers.Layer):
  def __init__(self, *args, **kwargs):
    super(CustomLayer, self).__init__(*args, **kwargs)

  def build(self, input_shape):
    self.w = self.add_weight(
        shape=input_shape[1:],
        dtype=tf.float32,
        initializer=tf.keras.initializers.ones(),
        regularizer=tf.keras.regularizers.l2(0.02),
        trainable=True)

  # Call method will sometimes get used in graph mode,
  # training will get turned into a tensor
  @tf.function
  def call(self, inputs, training=None):
    if training:
      return inputs + self.w
    else:
      return inputs + self.w * 0.5

前面的代码创建了一个名为CustomLayer的类,该类继承了tf.keras.layers.Layer类的属性。 此技术允许在tf.keras模型内部使用任何类型的低级代码,而不管它是使用Sequential API 还是functional API 的模型。 此类中有两种方法:

  • build():此方法修改继承的类的默认生成方法。 在这种方法中,应该创建模型所需的所有变量。 尽管可以在模型的the __init__()方法中完成此操作,但建议使用build(),以便在正确的最佳时间构建变量。 可以使用self.add_weight函数完成此操作,以使 Keras 跟踪变量和正则化损失。
  • call():在输入张量上调用模型时,将运行此方法。 此方法通常采用两个参数:inputstraining。 尽管inputs参数是不言自明的,但training参数可能不会一直使用,但是对于在该层中使用批量规范化和丢弃的情况而言是必不可少的。 该功能由tf.function装饰器注解,以实现签名,基于图的优点以及自动控件的依赖关系。

写入此自定义层后,即可在tf.keras模块中的任何位置使用它。 对于此转换,将使用Sequential API:

train_data = tf.ones(shape=(1, 28, 28, 1))
test_data = tf.ones(shape=(1, 28, 28, 1))

# Build the model including the custom layer
model = tf.keras.Sequential([
    CustomLayer(input_shape=(28, 28, 1)),
    tf.keras.layers.Conv2D(32, 3, activation='relu'),
    tf.keras.layers.MaxPooling2D(),
    tf.keras.layers.Flatten(),
])

train_out = model(train_data, training=True)
test_out = model(test_data, training=False)

升级训练循环

将 TF 1.x 代码转换为惯用的 TF 2.0 代码的第二步是升级训练管道。 TF 1.x 训练管道涉及对优化器,损失和预测的多个tf.Session.run()调用。 这样的训练循环还涉及样板代码,该样板代码被编写为将训练结果记录到控制台以方便监督。

在 TF 2.0 中,可以使用三种类型的训练循环。 这些循环中的每一个都有不同的优点和缺点,并且难度,API 级别和复杂性各不相同。 它们如下:

  • 第一种训练循环是tf.keras.Model.fit()。 这是一个内置的训练循环,可处理训练的所有方面,并为各种 Keras 提供统一的接口...

转换时要注意的其他事项

从 TF 1.x 迁移到 TF 2.0 时,还需要进行其他几个主要转换。 比起我们先前描述的对话,要困难得多的对话是将以 TF-Slim 编写的代码转换为 TF 2.0。

由于 TF-Slim 打包在tf.contrib.layers库下,因此即使在兼容性模块中,它也无法在 TF 2.0 中使用。 这意味着要将 TF-Slim 代码转换为 TF 2.0 格式,通常需要更改整个代码动态。

这包括从代码中删除参数范围,因为所有参数在 TF 2.0 中都应明确。 normalizer_fnactivation_fn函数应分为各自的层。 请注意,TF-Slim 层的参数名称和默认值与tf.keras层不同。

将 TF-Slim 模型转换为 TF 2.0 的最简单方法是将其转换为 TF 1.x 中的tf.layers API,然后将其转换为tf.keras.layers

另一个需要注意的转换细节是,在 TF 2.0 中,所有指标都是具有三种主要方法的对象:update_state()(添加新的观察值),result()(获取指标的当前结果)和reset_states()( 清除所有观察结果。

度量对象也是可调用的,并且在新观察值上调用时,它们会累加值并返回最新结果。

以下示例向我们展示了如何在自定义训练循环中使用指标:

  1. 创建度量标准对象,该度量标准对象在每次调用时都会累积度量标准数据:
loss_metric = tf.keras.metrics.Mean(name='train_loss')
accuracy_metric = tf.keras.metrics.SparseCategoricalAccuracy(name='train_accuracy')

@tf.function
def train_step(inputs, labels):
  with tf.GradientTape() as tape:
    predictions = model(inputs, training=True)
    regularization_loss = tf.math.add_n(model.losses)
    pred_loss = loss_fn(labels, predictions)
    total_loss = pred_loss + regularization_loss

  gradients = tape.gradient(total_loss, model.trainable_variables)
  optimizer.apply_gradients(zip(gradients, model.trainable_variables))
  1. 更新指标:
loss_metric.update_state(total_loss)
  accuracy_metric.update_state(labels, predictions)

for epoch in range(NUM_EPOCHS):
  1. 重置指标:
loss_metric.reset_states()
  accuracy_metric.reset_states()

  for inputs, labels in train_data:
    train_step(inputs, labels)
  1. 获取度量结果:
mean_loss = loss_metric.result()
mean_accuracy = accuracy_metric.result()

print('Epoch: ', epoch)
print(' loss: {:.3f}'.format(mean_loss))
print(' accuracy: {:.3f}'.format(mean_accuracy))

常见问题

在本节中,将解决有关从 TF 1.x 迁移到 TF 2.0 的一些常见问题。

用 TF 2.0 编写的代码的速度是否与基于图的 TF 1.x 代码相同?

是的,使用tf.functiontf.keras在 TF 2.0 中编写的代码将具有与 TF 1.x 相同的速度和最优性。 正如我们在本章前面提到的那样,使用tf.function注解主要功能允许模型以图模式运行,并且该功能中的所有计算和逻辑都将编译为一个计算图。 使用tf.keras定义和训练 TensorFlow 模型也是如此。 使用model.fit方法还将在图模式下训练模型,并具有所有优点和优化功能,这些优点和优点包括:

TF 2.0 的未来

TF 2.0 目前处于 beta 版本,因此仍在开发中。 即将出现的一些关键功能包括对包的修改,例如 TensorBoard,TensorFlow Lite,TensorFlow.js,用于 TensorFlow 的 Swift 和 TensorFlow Extended,以及对基本 API 的微小更改。 TensorBoard 将看到增强功能,例如改进的超参数调优功能,引入托管功能以使共享仪表板变得容易,并使插件能够使用不同的前端技术,例如 ReactJS。 TensorFlow Lite 将扩大支持的操作范围,将 TF 2.0 模型更轻松地转换为 TFLite,并扩展对 Edge TPU 和 AIY 板的支持。 TensorFlow.js 和用于 TensorFlow 的 Swift 都将看到速度和性能方面的改进,并且很快将包含一组丰富的示例和带有端到端教程的入门指南。 TF Extended 即将与 TF 2.0 基本 API 完全集成,并将包括完全协调的端到端工作流程和训练函数。

TF 2.0 基本 API 将包括针对任务的更多预制估计器,例如增强树,随机森林,最近邻搜索和 k 均值聚类。 tf.distribute.Strategy模型将扩展其对 Keras 子模型,TPU 和多节点训练的支持,以在多个处理器上实现更优化和更快的训练。

当前正在开发的另一个主要附加功能是tf-agents模块。 该模块将核心强化学习算法实现为智能体,该算法定义了与环境进行交互的策略并从集体经验中训练了该策略。 TF-agents与 OpenAI Gym 框架一起实现,并抽象了许多用于开发的关键强化学习算法。 该模块当前处于预发布状态,但将于今年晚些时候发布。

可看的更多资源

可以在 TensorFlow Beta 网站上找到教程和许多其他资源,其中包含有关创建和训练机器学习模型的关键因素的信息。 该页面还为该领域的许多重要技术提供了许多有用的端到端教程

可以在网站上找到 TF 2.0 的官方文档,以及该模块中每个 API 的详细文档。 该站点还具有指向其他 TensorFlow 模块和功能的链接

TensorFlow Medium 博客还提供有关 TensorFlow 库和服务状态的许多更新,并且源源不断的有用新闻和...

总结

本章介绍了两种将 TF 1.x 代码转换为 TF 2.0 代码的方法。 第一种方法是使用随附的升级脚本,该脚本会将所有 API 调用从tf.x更改为tf.compat.v1.x。 这允许 TF 1.x 代码在 TF 2.0 中运行,但不会从 TF 2.0 中带来的升级中受益。 第二种方法是将 TF 1.x 更改为惯用的 TF 2.0 代码,这涉及两个步骤。 第一步是将所有模型创建代码更改为 TF 2.0 代码,这涉及使用对函数的sess.run调用,以及将占位符和字典馈入函数的参数来更改张量。 使用tf.layers API 创建的模型与tf.keras.layers具有一对一的比较。 第二步是通过使用tf.keras.Model.fit或带有tf.GradientTape的自定义训练循环来升级训练管道。

TF 2.0 改变了 TensorFlow 代码的编写和组织方式。 TF 2.0 中的一些主要更改是对主模块中 API 的重组和清理。 这包括删除tf.contrib模块。 其他更改包括增加了代码范围内的急切执行,以简化调试和使用范围。 由于急切执行,因此在 TF 2.0 中创建的变量的行为类似于普通的 Python 变量。 这意味着用于处理全局变量的 TF 1.x API 已过时,因此已在 TF 2.0 中删除。 这使我们到书的结尾!