Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
tensorflow
GitHub Repository: tensorflow/docs-l10n
Path: blob/master/site/zh-cn/guide/effective_tf2.ipynb
25115 views
Kernel: Python 3
#@title Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License.

高效的 TensorFlow 2

概述

本指南提供了使用 TensorFlow 2 (TF2) 编写代码的最佳做法列表,此列表专为最近从 TensorFlow 1 (TF1) 切换过来的用户编写。有关将 TF1 代码迁移到 TF2 的更多信息,请参阅指南的迁移部分

设置

为本指南中的示例导入 TensorFlow 和其他依赖项。

import tensorflow as tf import tensorflow_datasets as tfds

惯用 TensorFlow 2 的建议

将代码重构为更小的模块

一种良好做法是将代码重构为根据需要调用的更小函数。为了获得最佳性能,您应当尝试在 tf.function 中装饰最大的计算块(请注意,由 tf.function 调用的嵌套 Python 函数不需要自己单独的装饰,除非您想为 tf.function 使用不同的 jit_compile 设置)。根据您的用例,这可能是多个训练步骤,甚至是整个训练循环。对于推断用例,它可能是单个模型前向传递。

调整某些 tf.keras.optimizer 的默认学习率

在 TF2 中,某些 Keras 优化器具有不同的学习率。如果您发现模型的收敛行为发生变化,请检查默认学习率。

optimizers.SGDoptimizers.Adamoptimizers.RMSprop 没有任何变更。

以下优化器的默认学习率已更改:

  • optimizers.Adagrad0.01 更改为 0.001

  • optimizers.Adadelta1.0 更改为 0.001

  • optimizers.Adamax0.002 更改为 0.001

  • optimizers.Nadam0.002 更改为 0.001

使用 tf.Module 和 Keras 层管理变量

tf.Moduletf.keras.layers.Layer 提供了方便的 variablestrainable_variables 属性,它们以递归方式收集所有因变量。这样便可轻松在使用变量的地方对它们进行本地管理。

Keras 层/模型继承自 tf.train.Checkpointable 并与 @tf.function 集成,这样便有可能从 Keras 对象直接导出 SavedModel 或为其添加检查点。您不必使用 Keras的 Model.fit API 来利用这些集成。

阅读 Keras 指南中有关迁移学习和微调的部分,了解如何使用 Keras 收集相关变量的子集。

结合 tf.data.Datasettf.function

TensorFlow Datasets 软件包 (tfds) 包含用于将预定义数据集作为 tf.data.Dataset 对象加载的的实用工具。对于此示例,您可以使用 tfds 加载 MNIST 数据集:

datasets, info = tfds.load(name='mnist', with_info=True, as_supervised=True) mnist_train, mnist_test = datasets['train'], datasets['test']

然后,准备用于训练的数据:

  • 重新缩放每个图像;

  • 重排样本顺序。

  • 收集图像和标签批次。

BUFFER_SIZE = 10 # Use a much larger value for real code BATCH_SIZE = 64 NUM_EPOCHS = 5 def scale(image, label): image = tf.cast(image, tf.float32) image /= 255 return image, label

为了使样本简短,将数据集修剪为仅返回 5 个批次:

train_data = mnist_train.map(scale).shuffle(BUFFER_SIZE).batch(BATCH_SIZE) test_data = mnist_test.map(scale).batch(BATCH_SIZE) STEPS_PER_EPOCH = 5 train_data = train_data.take(STEPS_PER_EPOCH) test_data = test_data.take(STEPS_PER_EPOCH)
image_batch, label_batch = next(iter(train_data))

使用常规 Python 迭代来迭代适合装入内存的训练数据。除此之外,tf.data.Dataset 是从磁盘流式传输训练数据的最佳方式。数据集是可迭代对象(但不是迭代器),就像其他 Eager Execution 中的 Python 可迭代对象一样。您可以通过将代码封装在 tf.function 中来充分利用数据集异步预提取/流式传输功能,此代码将 Python 迭代替换为使用 AutoGraph 的等效计算图运算。

@tf.function def train(model, dataset, optimizer): for x, y in dataset: with tf.GradientTape() as tape: # training=True is only needed if there are layers with different # behavior during training versus inference (e.g. Dropout). prediction = model(x, training=True) loss = loss_fn(prediction, y) gradients = tape.gradient(loss, model.trainable_variables) optimizer.apply_gradients(zip(gradients, model.trainable_variables))

如果您使用 Keras Model.fit API,则不必担心数据集迭代。

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

使用 Keras 训练循环

如果您不需要对训练过程进行低级控制,建议使用 Keras 的内置 fitevaluatepredict 方法。无论实现方式(顺序、函数或子类化)如何,这些方法都能提供统一的接口来训练模型。

这些方法的优点包括:

  • 接受 Numpy 数组、Python 生成器和 tf.data.Datasets

  • 自动应用正则化和激活损失。

  • 支持 tf.distribute无论硬件配置如何,训练代码都保持不变。

  • 支持将任意可调用对象作为损失和指标。

  • 支持 tf.keras.callbacks.TensorBoard 之类的回调以及自定义回调。

  • 性能出色,可以自动使用 TensorFlow 计算图。

下面是使用 Dataset 训练模型的示例。要详细了解工作原理,请参阅教程

model = tf.keras.Sequential([ tf.keras.layers.Conv2D(32, 3, activation='relu', kernel_regularizer=tf.keras.regularizers.l2(0.02), 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) ]) # Model is the full model w/o custom layers model.compile(optimizer='adam', loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True), metrics=['accuracy']) model.fit(train_data, epochs=NUM_EPOCHS) loss, acc = model.evaluate(test_data) print("Loss {}, Accuracy {}".format(loss, acc))

自定义训练并编写自己的循环

如果 Keras 模型适合您,但您需要更大的灵活性和对训练步骤或外层训练循环的控制,您可以实现自己的训练步骤甚至整个训练循环。如需了解详情,请参阅有关自定义 fit 的 Keras 指南。

此外 ,您还可以将许多内容作为 tf.keras.callbacks.Callback 实现。

这种方法具有前面提到的许多优点,但可以让您控制训练步骤甚至外层循环。

标准训练循环分为三个步骤:

  1. 迭代 Python 生成器或 tf.data.Dataset 来获得样本批次。

  2. 使用 tf.GradientTape 收集梯度。

  3. 使用 tf.keras.optimizers 之一将权重更新应用于模型的变量。

请记住:

  • 始终在子类化层和模型的 call 方法上包含一个 training 参数。

  • 确保在 training 参数正确设置的情况下调用模型。

  • 根据用法,在对一批数据运行模型之前,模型变量可能不存在。

  • 您需要手动处理模型的正则化损失这类问题。

无需运行变量初始值设定项或添加手动控制依赖项。tf.function 会在创建时为您处理自动控制依赖项和变量初始化。

model = tf.keras.Sequential([ tf.keras.layers.Conv2D(32, 3, activation='relu', kernel_regularizer=tf.keras.regularizers.l2(0.02), 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) ]) optimizer = tf.keras.optimizers.Adam(0.001) loss_fn = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True) @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)) for epoch in range(NUM_EPOCHS): for inputs, labels in train_data: train_step(inputs, labels) print("Finished epoch", epoch)

通过 Python 控制流充分利用 tf.function

tf.function 提供了一种将依赖于数据的控制流转换为计算图模式等效项(如 tf.condtf.while_loop)的方法。

数据依赖控制流出现的一个常见位置是序列模型。tf.keras.layers.RNN 封装一个 RNN 单元,允许您以静态或动态方式展开递归。例如,您可以按照下文所述重新实现动态展开。

class DynamicRNN(tf.keras.Model): def __init__(self, rnn_cell): super(DynamicRNN, self).__init__(self) self.cell = rnn_cell @tf.function(input_signature=[tf.TensorSpec(dtype=tf.float32, shape=[None, None, 3])]) def call(self, input_data): # [batch, time, features] -> [time, batch, features] input_data = tf.transpose(input_data, [1, 0, 2]) timesteps = tf.shape(input_data)[0] batch_size = tf.shape(input_data)[1] outputs = tf.TensorArray(tf.float32, timesteps) state = self.cell.get_initial_state(batch_size = batch_size, dtype=tf.float32) for i in tf.range(timesteps): output, state = self.cell(input_data[i], state) outputs = outputs.write(i, output) return tf.transpose(outputs.stack(), [1, 0, 2]), state
lstm_cell = tf.keras.layers.LSTMCell(units = 13) my_rnn = DynamicRNN(lstm_cell) outputs, state = my_rnn(tf.random.normal(shape=[10,20,3])) print(outputs.shape)

阅读 tf.function 指南以了解更多信息。

新型指标和损失

指标和损失均为对象,两者都在 Eager 模式下工作,且都位于 tf.function 中。

损失对象是可调用对象,并使用 (y_true, y_pred) 作为参数:

cce = tf.keras.losses.CategoricalCrossentropy(from_logits=True) cce([[1, 0]], [[-1.0,3.0]]).numpy()

使用指标收集和显示数据

您可以使用 tf.metrics 聚合数据,使用 tf.summary 记录摘要并使用上下文管理器将其重定向到编写器。摘要会直接发送到编写器,这意味着您必须在调用点提供 step 值。

summary_writer = tf.summary.create_file_writer('/tmp/summaries') with summary_writer.as_default(): tf.summary.scalar('loss', 0.1, step=42)

要在将数据记录为摘要之前对其进行聚合,请使用 tf.metrics。指标是有状态的;它们积累值并在您调用 result 方法(例如 Mean.result)时返回累积结果。可以使用 Model.reset_states 清除累积值。

def train(model, optimizer, dataset, log_freq=10): avg_loss = tf.keras.metrics.Mean(name='loss', dtype=tf.float32) for images, labels in dataset: loss = train_step(model, optimizer, images, labels) avg_loss.update_state(loss) if tf.equal(optimizer.iterations % log_freq, 0): tf.summary.scalar('loss', avg_loss.result(), step=optimizer.iterations) avg_loss.reset_states() def test(model, test_x, test_y, step_num): # training=False is only needed if there are layers with different # behavior during training versus inference (e.g. Dropout). loss = loss_fn(model(test_x, training=False), test_y) tf.summary.scalar('loss', loss, step=step_num) train_summary_writer = tf.summary.create_file_writer('/tmp/summaries/train') test_summary_writer = tf.summary.create_file_writer('/tmp/summaries/test') with train_summary_writer.as_default(): train(model, optimizer, dataset) with test_summary_writer.as_default(): test(model, test_x, test_y, optimizer.iterations)

通过将 TensorBoard 指向摘要日志目录来呈现生成的摘要:

tensorboard --logdir /tmp/summaries

使用 tf.summary API 编写要在 TensorBoard 中呈现的摘要数据。有关更多信息,请阅读 tf.summary 指南

# Create the metrics 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)) # Update the metrics loss_metric.update_state(total_loss) accuracy_metric.update_state(labels, predictions) for epoch in range(NUM_EPOCHS): # Reset the metrics loss_metric.reset_states() accuracy_metric.reset_states() for inputs, labels in train_data: train_step(inputs, labels) # Get the metric results 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))

Keras 指标名称

Keras 模型以一致方式处理指标名称。当您在指标列表中传递字符串时,该确切字符串会用作指标的 name。这些名称在 model.fit 返回的历史对象中可见,而在传递给 keras.callbacks 的日志中,它们被设置为您在指标列表中传递的字符串。

model.compile( optimizer = tf.keras.optimizers.Adam(0.001), loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True), metrics = ['acc', 'accuracy', tf.keras.metrics.SparseCategoricalAccuracy(name="my_accuracy")]) history = model.fit(train_data)
history.history.keys()

调试

使用 Eager Execution 可以分步运行代码来检查形状、数据类型和值。某些 API(如 tf.functiontf.keras 等)设计为使用计算图执行来提高性能和可移植性。调试时,使用 tf.config.run_functions_eagerly(True) 可以在此代码内使用 Eager Execution。

例如:

@tf.function def f(x): if x > 0: import pdb pdb.set_trace() x = x + 1 return x tf.config.run_functions_eagerly(True) f(tf.constant(1))
>>> f() -> x = x + 1 (Pdb) l 6 @tf.function 7 def f(x): 8 if x > 0: 9 import pdb 10 pdb.set_trace() 11 -> x = x + 1 12 return x 13 14 tf.config.run_functions_eagerly(True) 15 f(tf.constant(1)) [EOF]

这也可以在 Keras 模型和其他支持 Eager Execution 的 API 中使用:

class CustomModel(tf.keras.models.Model): @tf.function def call(self, input_data): if tf.reduce_mean(input_data) > 0: return input_data else: import pdb pdb.set_trace() return input_data // 2 tf.config.run_functions_eagerly(True) model = CustomModel() model(tf.constant([-2, -4]))
>>> call() -> return input_data // 2 (Pdb) l 10 if tf.reduce_mean(input_data) > 0: 11 return input_data 12 else: 13 import pdb 14 pdb.set_trace() 15 -> return input_data // 2 16 17 18 tf.config.run_functions_eagerly(True) 19 model = CustomModel() 20 model(tf.constant([-2, -4]))

注释:

  • tf.keras.Model 方法(例如 fitevaluatepredict)作为计算图执行,并且 tf.function 位于底层。

  • 使用 tf.keras.Model.compile时,设置 run_eagerly = True 以禁止 Model 逻辑被封装在 tf.function 中。

  • 使用 tf.data.experimental.enable_debug_modetf.data 启用调试模式。阅读 API 文档,了解详细信息。

不要在您的对象中保留 tf.Tensors

这些张量对象可能会在 tf.function 或 Eager 上下文中创建,并且这些张量的行为有所不同。始终仅将 tf.Tensor 用于中间值。

要跟踪状态,请使用 tf.Variable,因为它们始终可用于两种上下文。阅读 tf.Variable 指南以了解更多信息。

资源和延伸阅读

  • 阅读 TF2 指南教程以详细了解如何使用 TF2。

  • 如果您以前使用过 TF1.x,强烈建议您将代码迁移到 TF2。阅读迁移指南以了解更多信息。