Google Article
分享我们升级 OpenNMT 到 TensorFlow 2.0 的经验
2019 年 11 月 15 日

SYSTRAN 的研究工程师 Guillaume Klein 的客座文章。

OpenNMT-tf 是一个于 2017 年发布的用于 TensorFlow 的神经机器翻译工具包。当时,该项目使用了 TensorFlow 提供的许多功能和能力:使用 tf.estimator 进行训练和评估,变量作用域,图形集合,tf.contrib 等等。我们很高兴能一起使用这些功能超过 2 年。

我们花了几个月的时间将 10,000 多行代码从 TensorFlow 1.0 完全迁移到 TensorFlow 2.0 及其新的推荐做法。我们认为应该写这篇文章来分享我们升级的经验,以及您可能也想要考虑进行升级的一些原因。

迭代过渡

升级任何大型软件都是漫长而昂贵的,尤其是在像 OpenNMT-tf 这样有用户依赖的情况下。在这种情况下,我们在第一个 TensorFlow 2.0 宣布之后不久就早早地规划了这次过渡,当时这个版本的概要开始被定义。

我们选择了一种自下而上的方法来迭代更新所有 TensorFlow API 的使用。特别是,我们定期发布与 TensorFlow 1.0 和 2.0 都兼容的代码,并相应地设置了自动化测试。兼容性模块 tf.compat.v1tf.compat.v2 在此迭代过程中提供了极大的帮助。

遵循 TensorFlow 2.0 的最佳实践,我们决定从 tf.estimator 转移,即使这需要对代码进行大量重新设计。幸运的是,我们发现编写自定义训练循环相对容易,同时仍然满足性能要求(使用 tf.function)并支持高级功能,如多 GPU 训练(使用 tf.distribute)和混合精度训练(使用自动混合精度图)。

梯度累积是另一个经常用于训练最先进的序列到序列模型(例如 Transformer)的功能。现在,由于渴望执行,在 TensorFlow 2.0 中实现它变得很简单。在我们的自定义训练循环中,我们将训练步骤分成两个函数:“forward”(累积梯度)和“step”(应用梯度)。

以下是它们在 OpenNMT-tf 中的外观。

前向
@tf.function
def forward(source, target):
    """Forwards a training example into the model, computes the 
    loss, and accumulates the gradients.
    """
    logits = model(source, target, training=True)
    loss = model.compute_loss(logits, target)
    gradients = optimizer.get_gradients(loss, 
model.trainable_variables)
    if not accum_gradients:
        # Initialize the variables to accumulate the gradients.
        accum_gradients.extend([
            tf.Variable(tf.zeros_like(gradient), 
            trainable=False)
            for gradient in gradients])
    for accum_gradient, step_gradient in 
    zip(accum_gradients, gradients):
        accum_gradient.assign_add(step_gradient)
    return loss
步骤
@tf.function
def step():
    """Applies the accumulated gradients and advances 
the training step."""
    grads_and_vars = [
        (gradient / accum_steps, variable)
        for gradient, variable in zip(accum_gradients, 
model.trainable_variables)]
    optimizer.apply_gradients(grads_and_vars)
    for accum_gradient in accum_gradients:
        accum_gradient.assign(tf.zeros_like(accum_gradient))

for i, (source, target) in enumerate(dataset):
  forward(source, target)
  # Apply gradients every accum_steps examples.
  if (i + 1) % accum_steps == 0:
    step()

实际上,在使用分布策略运行时,有一些额外的细节,但总体逻辑相同。

管理变更

在根据Keras 层(或 tf.Module)重构您的模型并使用 tf.train.Checkpoint加载和保存 检查点后,您很可能会破坏与现有检查点的兼容性。为了减轻 OpenNMT-tf 中的这一变化,我们在加载时使用此过程静默地转换旧检查点
  1. 使用 tf.train.load_checkpoint 加载 V1 变量
  2. 创建一个新的 V2 模型
  3. 将 V1 变量名映射到模型中的 V2 变量
  4. 使用它们的 V1 等效项分配 V2 变量和 V2 优化器插槽
  5. 使用 tf.train.Checkpoint 保存 V2 模型
使用这种方法,现有用户的流程保持不变,他们可以像以前一样继续训练。

参与 TensorFlow 开发

在此开发过程中,我们在 TensorFlow 2.0 的预览版中发现了一些错误或不完整的特性。TensorFlow 团队一直乐于助人,问题 通常会在几天内得到解决。由于早期采用者,现在有很多与 TensorFlow 2.0 相关的资源,这些资源应该可以帮助其他人进行升级。

通过参与,我们希望确保与序列到序列模型相关的用例能够在 TensorFlow 2.0 中高效地使用。这包括参与 tensorflow_addons.seq2seq 模块的维护,该模块是 tf.contrib.seq2seq 的 TensorFlow 2.0 等效项。

为什么要升级到 TensorFlow 2.0?

升级到 TensorFlow 2.0 既有趣又具有挑战性。根据您的项目,这可能是一个漫长的过程,但结果总体上是积极的:代码明显更简单,同时更具可扩展性。开发人员将更容易维护他们的项目并添加新功能,而用户将更好地理解执行路径,这得益于渴望模式,并且更有可能回馈贡献。

作为库开发人员,我们希望使用 TensorFlow 2.0 更快地迭代新开发并为我们的用户提供更一致的体验。我们还很高兴看到生态系统随着社区开发的更多可重用和可组合模块而增长,例如在TensorFlow Addons 中。

您可以在 https://github.com/OpenNMT/OpenNMT-tf 上了解有关 OpenNMT-tf 的更多信息

关于 OpenNMT

OpenNMT 是一个用于神经机器翻译和神经序列生成的开源生态系统。它具有 2 个主要实现:OpenNMT-tf OpenNMT-py,分别由 TensorFlow 和 PyTorch 提供支持。该项目还包括涵盖 NMT 工作流程其他方面的组件,例如Tokenizer, 一个文本标记化库,以及CTranslate2,一个自定义的 C++ 推理引擎。
下一篇文章
Article Image Placeholder

SYSTRAN 的研究工程师 Guillaume Klein 的客座文章。

OpenNMT-tf 是一个于 2017 年发布的用于 TensorFlow 的神经机器翻译工具包。当时,该项目使用了 TensorFlow 提供的许多功能和能力:使用 tf.estimator 进行训练和评估,变量作用域,图形集合,tf.contrib 等等。我们很高兴能一起使用这些功能超过 2 年。

我们花费了……