# 从 TensorFlow 1.x 迁移到 2.0 本章将介绍如何将 **TensorFlow 1.x**(**TF 1.x**)代码转换为 **TensorFlow 2.0**(**TF 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.app`,`tf.flags`和`tf.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`模型来管理模型中的变量。 这与手动滚动神经网络层和手动创建变量形成对比。 在以下示例中,必须跟踪权重和偏差变量,其形状的定义应远离模型的创建。 这使得难以更改模型并使模型适应不同的架构和数据集: ```py 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`实现非常简单明了,并确保开发人员不必担心变量和变量名的组织和管理。 它还可以轻松访问模型中的可训练变量: ```py 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`如何使收集相关值的子集,计算其梯度以及基于梯度对其进行调整变得容易: ```py 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`块训练模型: ```py @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`在数据集上训练模型,只需将数据集传递给方法。 它将处理其他所有事项: ```py 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.foo`和`tf.compat.v1.foo`。 此模块允许转换为 TF 1.x 编写的大多数代码,以便可以在 TF 2.0 中运行。 作为简化此过程的一种方式,TensorFlow 提供了`tf_upgrade_v2`实用程序,该实用程序有助于尽可能简化转换。 该实用程序已预装... # 转换 TF 1.x 模型 第一步是将所有`tf.Session.run()`调用替换为 Python 函数。 这意味着将`tf.placeholder`和`feed_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 中小型卷积神经网络的实现: ```py 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.layers`到`tf.keras.layers`有一对一的转换,但有一些区别。 在 TF 2.0 代码中,训练参数不再传递给每个层,因为模型会自动处理该参数。 这是 TF 2.0 中的代码: ```py 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.layers`到`tf.keras.layers`的转换相对简单,但是由于代码流的差异,转换变得更加复杂。 TF 1.x 中的低级 API 的一些示例包括使用变量作用域来控制重用,使用`tf.get_variable`创建变量,使用`tf.placeholder`和`session.run`定期访问集合以及手动初始化变量。 由于引入了系统范围内的急切执行,这些技术和策略中的许多现在已过时,因此以低级 API 编写的代码比以高级 API 编写的代码(例如`tf.keras`和`tf.layers`)需要更大的更改。 。 以下是使用 TF 1.x 的低级 API 编写的一些代码的示例: ```py 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`变量上全局调用正则化; 相反,它将被手动调用,而无需引用全局集合: ```py 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_variable`和`tf.layers`编写的原始代码: ```py 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`类继承的类来完成: ```py # 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()`:在输入张量上调用模型时,将运行此方法。 此方法通常采用两个参数:`inputs`和`training`。 尽管`inputs`参数是不言自明的,但`training`参数可能不会一直使用,但是对于在该层中使用批量规范化和丢弃的情况而言是必不可少的。 该功能由`tf.function`装饰器批注,以实现签名,基于图的优点以及自动控件的依赖关系。 写入此自定义层后,即可在`tf.keras`模块中的任何位置使用它。 对于此转换,将使用`Sequential` API: ```py 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_fn`和`activation_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. 创建度量标准对象,该度量标准对象在每次调用时都会累积度量标准数据: ```py 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)) ``` 2. 更新指标: ```py loss_metric.update_state(total_loss) accuracy_metric.update_state(labels, predictions) for epoch in range(NUM_EPOCHS): ``` 3. 重置指标: ```py loss_metric.reset_states() accuracy_metric.reset_states() for inputs, labels in train_data: train_step(inputs, labels) ``` 4. 获取度量结果: ```py 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.function`或`tf.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 网站上找到教程和许多其他资源,其中包含有关创建和训练机器学习模型的关键因素的信息。 该页面还为该领域的许多重要技术提供了[许多有用的端到端教程](https://www.tensorflow.org/beta)。 可以在网站上找到 TF 2.0 的官方文档,以及该模块中每个 API 的详细文档。 该站点还具有[指向其他 TensorFlow 模块和功能的链接](https://www.tensorflow.org/versions/r2.0/api_docs/python/tf)。 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 中删除。 这使我们到书的结尾!