TensorFlow 2.7 新功能?
2021 年 11 月 10 日

由 Goldie Gadde 和 Josh Gordon 代表 TensorFlow 团队发布

TensorFlow 2.7 现已发布!此 版本 通过更清晰的错误消息、简化的堆栈跟踪,并为迁移到 TF2 的用户添加了新的工具和文档,提高了可用性。

改善的调试体验

调试代码的过程是机器学习框架用户体验的一个基本部分。在此版本中,我们通过以下三项重大更改,大大改善了 TensorFlow 调试体验,使其更具生产力和趣味性:简化的堆栈跟踪、在源自自定义 Keras 层的错误中显示更多上下文信息,以及对 Keras 和 TensorFlow 中所有错误消息进行广泛的审计。

简化的堆栈跟踪

TensorFlow 现在默认情况下会过滤显示在错误时的堆栈跟踪,以隐藏源自 TensorFlow 内部代码的任何帧,并将信息重点放在对你重要的内容上:你自己的代码。这使得堆栈跟踪更简单、更短,也更容易理解和修复代码中的问题。

如果你实际上是在调试 TensorFlow 代码库本身(例如,因为你要为 TensorFlow 准备一个 PR),你可以通过调用 tf.debugging.disable_traceback_filtering() 来关闭过滤机制。

用于 Keras 层异常的自动上下文注入

编写低级代码的最常见用例之一是创建自定义 Keras 层,因此我们希望使调试你的层尽可能容易和高效。调试层时,你首先要做的是打印其输入的形状和数据类型,以及其 trainingmask 参数的值。现在,我们会自动将此信息添加到所有源自自定义 Keras 层的堆栈跟踪中。

在下面的图像中查看堆栈跟踪过滤和调用上下文信息显示的实际效果。

Simplified stack traces in TensorFlow 2.7
TensorFlow 2.7 中的简化堆栈跟踪

审计和改进 TensorFlow 和 Keras 代码库中的所有错误消息

最后,我们已经审计了 Keras 和 TensorFlow 代码库中的每个错误消息(数千个错误位置!),并对它们进行了改进,以确保它们遵循 UX 最佳实践。好的错误消息应该告诉你框架预期什么,你做了什么与框架的预期不符,并应该提供修复问题的提示。

改进 tf.function 错误消息

我们改进了两种常见的 tf.function 错误消息类型:运行时错误消息和“图”张量错误消息,方法是在用户代码中包含指向错误源的回溯。对于其他模糊且不准确的 tf.function 错误消息,我们也更新了它们,使其更清晰、更准确。

对于用户代码引起的运行时错误消息

@tf.function
def f():
 l = tf.range(tf.random.uniform((), minval=1, maxval=10, dtype=tf.int32))
 return l[20]

旧错误消息的摘要如下所示

# … Python stack trace of the function call …

InvalidArgumentError:  slice index 20 of dimension 0 out of bounds.
         [[node strided_slice (defined at <'ipython-input-8-250c76a76c0e'>:5) ]] [Op:__inference_f_75]

Errors may have originated from an input operation.
Input Source operations connected to node strided_slice:
 range (defined at <ipython-input-8-250c76a76c0e >':4)

Function call stack:
f

新错误消息的摘要如下所示

# … Python stack trace of the function call …

InvalidArgumentError:  slice index 20 of dimension 0 out of bounds.
         [[node strided_slice
 (defined at <ipython-input-3-250c76a76c0e>:5)
]] [Op:__inference_f_15]

Errors may have originated from an input operation.
Input Source operations connected to node strided_slice:
In[0] range (defined at <ipython-input-3-250c76a76c0e>:4)       
In[1] strided_slice/stack:      
In[2] strided_slice/stack_1:    
In[3] strided_slice/stack_2:

Operation defined at: (most recent call last)
# … Stack trace of the error within the function …
>>>   File "<ipython-input-3-250c76a76c0e>", line 7, in <module>
>>>     f()
>>> 
>>>   File "<ipython-input-3-250c76a76c0e>", line 5, in f
>>>     return l[20]
>>> 

主要区别在于,在执行 tf.function 时引发的运行时错误现在包含一个回溯,该回溯显示了错误的来源,即用户代码中的位置。

# … Original error message and information …
# … More stack frames …
>>>   File "<ipython-input-3-250c76a76c0e>", line 7, in <module>
>>>     f()
>>> 
>>>   File "<ipython-input-3-250c76a76c0e>", line 5, in f
>>>     return l[20]
>>> 

对于以下用户代码引起的“图”张量错误消息

x = None

@tf.function
def leaky_function(a):
 global x
 x = a + 1  # Bad - leaks local tensor
 return a + 2

@tf.function
def captures_leaked_tensor(b):
 b += x
 return b

leaky_function(tf.constant(1))
captures_leaked_tensor(tf.constant(2))

旧错误消息的摘要如下所示

# … Python stack trace of the function call …

TypeError: An op outside of the function building code is being passed
a "Graph" tensor. It is possible to have Graph tensors
leak out of the function building context by including a
tf.init_scope in your function building code.
For example, the following function will fail:
  @tf.function
  def has_init_scope():
    my_constant = tf.constant(1.)
    with tf.init_scope():
      added = my_constant * 2
The graph tensor has name: add:0

新错误消息的摘要如下所示

# … Python stack trace of the function call …

TypeError: Originated from a graph execution error.

The graph execution error is detected at a node built at (most recent call last):
# … Stack trace of the error within the function …
>>>  File <ipython-input-5-95ca3a98778f>, line 6, in leaky_function
# … More stack trace of the error within the function …

Error detected in node 'add' defined at: File "<ipython-input-5-95ca3a98778f>", line 6, in leaky_function

TypeError: tf.Graph captured an external symbolic tensor. The symbolic tensor 'add:0' created by node 'add' is captured by the tf.Graph being executed as an input. But a tf.Graph is not allowed to take symbolic tensors from another graph as its inputs. Make sure all captured inputs of the executing tf.Graph are not symbolic tensors. Use return values, explicit Python locals or TensorFlow collections to access it. Please see https://tensorflowcn.cn/guide/function#all_outputs_of_a_tffunction_must_be_return_values for more information.

主要区别在于,尝试捕获从不可达图中泄漏的张量的错误现在包含一个回溯,该回溯显示了在用户代码中创建张量的位置。

# … Original error message and information …
# … More stack frames …
>>>  File <ipython-input-5-95ca3a98778f>, line 6, in leaky_function

Error detected in node 'add' defined at: File "<ipython-input-5-95ca3a98778f>", line 6, in leaky_function

TypeError: tf.Graph captured an external symbolic tensor. The symbolic tensor 'add:0' created by node 'add' is captured by the tf.Graph being executed as an input. But a tf.Graph is not allowed to take symbolic tensors from another graph as its inputs. Make sure all captured inputs of the executing tf.Graph are not symbolic tensors. Use return values, explicit Python locals or TensorFlow collections to access it. Please see https://tensorflowcn.cn/guide/function#all_outputs_of_a_tffunction_must_be_return_values for more information.

介绍 tf.experimental.ExtensionType

用户定义的类型可以使你的项目更具可读性、模块化和可维护性。TensorFlow 2.7.0 引入了 ExtensionType API,它可用于创建与 TensorFlow API 无缝协作的用户定义的面向对象类型。扩展类型是跟踪和组织复杂模型使用的张量的好方法。扩展类型还可以用于定义新的类似张量的类型,这些类型专门化或扩展了“张量”的基本概念。要创建扩展类型,只需定义一个以 tf.experimental.ExtensionType 为基类的 Python 类,并使用 类型注释 为每个字段指定类型。

class TensorGraph(tf.experimental.ExtensionType):
  """A collection of labeled nodes connected by weighted edges."""
  edge_weights: tf.Tensor                      # shape=[num_nodes, num_nodes]
  node_labels: typing.Mapping[str, tf.Tensor]  # shape=[num_nodes]; dtype=any

class MaskedTensor(tf.experimental.ExtensionType):
  """A tensor paired with a boolean mask, indicating which values are valid."""
  values: tf.Tensor
  mask: tf.Tensor       # shape=values.shape; false for missing/invalid values.

class CSRSparseMatrix(tf.experimental.ExtensionType):
  """Compressed sparse row matrix (https://en.wikipedia.org/wiki/Sparse_matrix)."""
  values: tf.Tensor     # shape=[num_nonzero]; dtype=any
  col_index: tf.Tensor  # shape=[num_nonzero]; dtype=int64
  row_index: tf.Tensor  # shape=[num_rows+1]; dtype=int64

ExtensionType 基类添加了一个构造函数和基于字段类型注释的特殊方法(类似于标准 Python 库中的 typing.NamedTuple@dataclasses.dataclass)。你可以选择通过覆盖这些默认值或添加新方法、属性或子类来自定义类型。

以下 TensorFlow API 支持扩展类型。

  • Keras:扩展类型可以用作 Keras ModelsLayers 的输入和输出。
  • Dataset:扩展类型可以包含在 Datasets 中,并由数据集 Iterators 返回。
  • TensorFlow hub:扩展类型可以用作 tf.hub 模块的输入和输出。
  • SavedModel:扩展类型可以用作 SavedModel 函数的输入和输出。
  • tf.function:扩展类型可以用作使用 @tf.function 装饰器包装的函数的参数和返回值。
  • 控制流:扩展类型可以使用控制流操作,例如 tf.condtf.while_loop。这包括由自动生成添加的控制流操作。
  • tf.py_function:扩展类型可以用作 tf.py_functionfunc 参数的参数和返回值。
  • 张量运算:扩展类型可以扩展为支持大多数接受张量输入的 TensorFlow 运算(例如,tf.matmultf.gathertf.reduce_sum),使用 调度装饰器
  • 分布式策略:扩展类型可以用作每个副本的值。

有关扩展类型的更多信息,请参阅 扩展类型指南

注意tf.experimental 前缀表示这是一个新的 API,我们希望收集来自实际使用情况的反馈;除非遇到不可预见的設計問題,否则我们计划根据 TF 实验政策ExtensionType 从实验包中迁移出去。

TF2 迁移更轻松!

为了支持有兴趣将工作负载从 TF1 迁移到 TF2 的用户,我们在 TensorFlow 网站上创建了一个新的 迁移到 TF2 选项卡,其中包含更新的指南和全新的文档,其中包含 Colab 中的具体可运行示例。

已添加了一个 新的 shim 工具,它极大地简化了基于 variable_scope 的模型到 TF2 的迁移。预计它将使大多数 TF1 用户能够在 TF2 管道中按原样运行现有模型架构(或仅进行少量调整),而无需重写你的建模代码。你可以在 模型映射 指南中了解有关它的更多信息。

TensorFlow Hub 上新的社区贡献模型

自从上次 TensorFlow 版本发布以来,社区齐心协力,在 TensorFlow Hub 上提供了许多新的模型。现在你可以找到诸如 MLP-MixerVision TransformersWav2Vec2RoBERTaConvMixerDistillBERTYoloV5 等许多模型。所有这些模型都已准备就绪,可以通过 TensorFlow Hub 使用。你可以在 此处 了解有关发布模型的更多信息。

后续步骤

查看 发行说明 以获取更多信息。要保持最新状态,你可以阅读 TensorFlow 博客、关注 twitter.com/tensorflow 或订阅 youtube.com/tensorflow。如果你构建了一些想分享的内容,请将其提交到 goo.gle/TFCS 中的社区亮点。对于反馈,请在 GitHub 上提交问题或发布到 TensorFlow 论坛。谢谢!

下一篇文章
What's new in TensorFlow 2.7?

由 Goldie Gadde 和 Josh Gordon 代表 TensorFlow 团队发布 TensorFlow 2.7 现已发布!此 版本 通过更清晰的错误消息、简化的堆栈跟踪,并为迁移到 TF2 的用户添加了新的工具和文档,提高了可用性。 改善的调试体验 调试代码的过程是机器学习框架用户体验的一个基本部分。在此版本中,…