发布人:TensorFlow 团队 Goldie Gadde 和 Josh Gordon
近期重磅上线的 TensorFlow 2.7 通过更加清晰的错误消息、简化的堆栈信息提升了易用性,并为迁移到 TF2 的用户增加了新工具和文档。
改善调试体验
调试代码的过程,是机器学习框架用户体验的一个基本组成部分。在 TensorFlow 2.7 中,我们大幅改善了 TensorFlow 的调试体验,提高了其效率和用户体验,这些改善包括以下三个主要变化:简化堆栈错误信息、在自定义 Keras 层的错误中显示额外的上下文信息,以及对 Keras 和 TensorFlow 中所有错误消息进行广泛审查。
简化堆栈错误信息
TensorFlow 现在默认对出现错误时显示的堆栈信息进行过滤,以隐藏任何来自 TensorFlow 内部代码的报错信息,让信息集中在对您而言比较重要的地方:您自己的代码。如此一来,堆栈信息变得更简单、更简短,让您能够更加轻松地理解和修复代码中的问题。
如果您实际上是在调试 TensorFlow 代码库本身(例如准备 TensorFlow 的 PR),您可以通过调用 tf.debugging.disable_traceback_filtering()
来关闭过滤机制。
针对 Keras 层异常的自动上下文注入
编写低阶代码最常见的用例之一是创建自定义的 Keras 层,所以我们想要尽可能地降低您调试层的难度,提高调试的效率。对层进行调试时,您要做的第一件事就是打印其输入的形状和 dtype,以及其 training
和 mask
参数的值。现在,我们将这些信息自动添加到所有自定义 Keras 层的堆栈信息中。
在下图中可以看到堆栈信息过滤和调用上下文信息显示的实际效果:
TensorFlow 2.7 中简化的堆栈信息
审查并改进 TensorFlow 和 Keras 代码库中的所有错误消息
最后,我们审查了 Keras 和 TensorFlow 代码库中的每一条错误消息(数以千计的错误位置!),并对它们进行了改进,以确保其遵循用户体验的最佳实践。一条合格的错误消息需要能够告诉您框架的预期,指出不符合框架预期的操作,并给出修复问题的相应提示。
改进 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':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: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: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 "", line 7, in
>>> f()
>>>
>>> File "", 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, line 6, in leaky_function
# … More stack trace of the error within the function …
Error detected in node 'add' defined at: File "", 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 isnot 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://www.tensorflow.org/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 isnot 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://www.tensorflow.org/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 Models
和 Layers
的输入和输出。
● 数据集: 可以在 Datasets
中加入扩展程序类型,并通过数据集 Iterators
进行返回。
● TensorFlow hub: 可以将扩展程序类型用作 tf.hub
模块的输入和输出。
● SavedModel: 可以将扩展程序类型用作 SavedModel
函数的输入和输出。
● tf.function: 可以将扩展程序类型用作与 @tf.function
修饰器一起打包的函数的参数和返回值。
● 控制流: 可以通过 tf.cond
和 tf.while_loop
之类的控制流算子来使用扩展程序类型。其中包括通过 AutoGraph 添加的控制流算子。
● tf.py_function: 可以将扩展程序类型用作 func
参数至 tf.py_function
的参数和返回值。
● Tensor 算子: 可以使用 分派装饰器 对扩展程序类型进行扩展,以支持大多数接收 Tensor 输入的 TensorFlow 算子(如,tf.matmul
、tf.gather
和 tf.reduce_sum
)。
● 分发策略: 可以将扩展程序类型用作每个副本的值。
若要了解更多有关扩展程序类型的信息,请参阅 扩展程序类型指南。
注意: tf.experimental
前缀表明这是一个新的 API,我们希望从实际使用中收集反馈;除非有任何不可预见的设计问题,我们计划根据 TF 实验性政策 将 ExtensionType
迁移出实验性软件包。
TF2 迁移更加简单!
为了支持有兴趣将工作负载从 TF1 迁移到 TF2 的用户,我们在 TensorFlow 网站上创建了一个新的 Migrate to TF2
标签,其中包括更新的指南和全新的文档,以及 Colab 中具体、可运行的示例。
我们还增加了一个 新的 Shim 工具,可显著简化 variable_scope-based 模型向 TF2 的迁移。它有望使大多数 TF1 用户在 TF2 管道中按原样(或仅进行微小调整)运行现有模型架构,而无需重写建模代码。您可以在 模型映射 指南中了解更多相关信息。
TensorFlow Hub 上 新的社区贡献模型
自上一版 TensorFlow 发布以来,整个社区热切合作,在 TensorFlow Hub 上提供了许多新模型。
现在您可以找到 MLP-Mixer、Vision Transformers、Wav2Vec2、RoBERTa、ConvMixer、DistillBERT、YoloV5 等诸多模型。
所有这些模型都可以通过 TensorFlow Hub 使用。您可以在 此处 进一步了解有关发布模型的更多信息。
相关信息
请参阅 版本说明 了解更多信息。
欢迎随时关注 TensorFlow 博客,Twitter 或 Youtube,获悉最新动态。
您可以通过 Community Spotlight 计划 向我们提交作品,分享构建成果。通过 GitHub 提交问题,或在 TensorFlow 论坛 上发帖,分享您的反馈。我们欢迎您的贡献和参与,谢谢!