首页 > 科技 > 将Tensorflow调试时间减少90%

将Tensorflow调试时间减少90%

介绍可应用于Tensorflow代码的VeriTensor代码方法,以使调试更加有效。

Image from Pixabay

Tensorflow代码很难调试。 我以前花了数周时间调试代码。 更糟糕的是,在大多数情况下,我不知道如何进行-我可以看到我的代码没有训练好,但是我不知道是因为该模型无法学习,或者是由于实现存在错误。 如果是后者,错误在哪里?

这是许多机器学习从业者面临的挫败感。 本文介绍了我设计用来调试Tensorflow代码的VeriTensor方法。 VeriTensor基于"Design by Contract"方法。 它包括三种技术。 这种方法将我的调试时间从数周缩短至数小时,提高了90%以上。 更好的是,在完成调试后,我知道代码中没有错误。 真是太好了!

通过断言进行规范

有效调试的关键是编写规范以定义代码的正确性。 规范描述了代码应该执行的操作,而实现则描述了如何执行代码。 一段代码仅在其规范方面是正确的。 在Python中,您可以使用断言来编写规范,如下面所示。

def square_root(x):    assert x >= 0    result = math.sqrt(x)    assert result == math.sqrt(x)    return result

你说,但是很难写断言。 我同意你的看法。 我花了15年的时间用断言验证代码。 我开发了基于断言的技术,Microsoft将其包含在Bing搜索引擎中。 我知道规格可能会很棘手。 这就是为什么当我开发VeriTensor时,我确保它是实用的。

有效调试的关键是通过断言告诉调试器代码应该做什么。

VeriTensor方法

VeriTensor包括3种技术。 您可以在编写Tensorflow代码后应用它们。 这意味着这些技术是很简单的,您无需从头开始就可以使用它们。

技术1:张量形状断言

引入张量时,需要编写断言以检查其形状。 关于张量形状的错误假设通常会导致棘手的错误。 而且TensorFlow的广播机制可以将它们隐藏得很深。

例如,在诸如Deep Q Network(DQN)之类的回归算法中,您有一个来自神经网络的预测张量,目标张量和损失张量:

prediction = q_function.output_tensortarget = reward + gamma* bootstrapped_qloss = tf.reduce_mean(tf.square(target - prediction))

该预测代表预测值。 目标张量表示期望值,由奖励张量和bootstrapped_q张量计算得出,而γ是浮点数。损失张量表示我们的训练损失为均方误差。

现在,我们为引入的张量添加断言,如下清单所示。 这些断言检查预测的形状和目标的形状必须在batch_size和action_dimension方面相同。 这些是DQN算法中使用的一些数量。 如果您不熟悉它们,不必担心。 这里重要的是我们编写断言来检查张量形状。 最后,由于损失评估为数字,因此断言声明其形状为[]。

prediction = q_function.output_tensorassert prediction.shape.to_list() == [batch_size, action_dimension]target = reward + gamma* bootstrapped_qassert target.shape.to_list() == [batch_size, action_dimension]loss = tf.reduce_mean(tf.square(target - prediction))assert loss.shape.to_list() == []

如果张量的形状与它们的期望值不匹配,则会违反声明。 您不会相信违反形状声明的可能性会如此的大!

技术2:张量间的依赖

Tensorflow程序是一个计算图。 因此,您需要确保正确构建张量图。 如果张量B的值取决于张量A的值(例如B = A + 1),则图中的节点B到节点A之间应该有一条边。

您使用TensorBoard可视化Tensorflow图。 但是,了解此图很困难,因为实际的张量图通常具有数百个节点和边。 下图显示了典型的TensorBoard可视化。

这里的关键见解是:要检查张量图的结构,只需要可视化所引入的张量之间的关系即可。 而且,您通常可以将许多张量分组到一个节点中。 例如,在具有许多变量的多层神经网络中,每个变量都是张量。 但是您只需要将整个网络可视化为一个节点。

我开发了Python包VeriTensor,以简化张量图的可视化。 我将很快将此程序包开源。 它包含一个TensorGroupDependency类。 此类允许您仅注册要可视化的张量。 它为注册的张量生成一个新的,更小的可视化图像。

下一个清单显示了如何使用TensorGroupDependency。 您首先调用add方法来注册张量。 然后,调用generate_dot_representation方法为您提供可视化效果。 此可视化仅显示已注册的张量及其相关性。

d = TensorGroupDependency()d.add(prediction, 'prediction')d.add(target, 'target')d.add(bootstrapped_q, 'bootstrapped_q')d.add(loss, 'loss')# Generate tensor dependency graph in DOT notation.dot = d.generate_dot_representation()print(dot)# Generate assertions describing the above dependency graph.assertions = d.generate_assertions(target_exp='d')print(assertions)


第1至5行创建一个张量依赖图对象,并注册要可视化其关系的张量。 第8行和第9行以DOT语言生成并打印那些张量依赖关系,这些依赖关系可以以图形方式呈现:

让我们了解以上依赖关系图:

  • 图中的节点表示张量或张量集(例如神经网络中的所有变量)。
  • 如果B中的至少一个张量取决于A中的一个张量,则从节点B到节点A会有一个有向边。在我们的示例中,损耗张量取决于预测和目标张量。 因此,从预测节点和目标节点到损失节点有两个方向性边缘。
  • 在每个节点中,您可以看到其种类,例如[Tensor],[Placeholder],[Variable]。
  • 在每个节点中,您还会看到张量形状,例如(None,1),表示二维张量,其中第一维为动态长度None,第二维为长度1。损耗张量具有形状(),因为它 是标量。

要检查图结构的正确性,您需要解释为什么每个边都存在。 这意味着解释这些张量之间的依赖关系。 如果您无法解释某些边的存在,则您脑海中的想法与您实际构建的图形之间会有差异。 这通常表示一个错误。

解释完所有边缘之后,您可以通过调用generate_assertions方法来生成描述图的断言,如上面片段中的第12行所示。 以下清单显示了生成的断言。 它们描述了相同的依赖图。 它们成为您代码的一部分,并将在以后的所有执行中进行检查:

d.assert_immediate_ancestors('target', {'bootstrapped_q', 'reward'})d.assert_immediate_ancestors('prediction', set())d.assert_immediate_ancestors('bootstrapped_q', set())d.assert_immediate_ancestors('loss', {'prediction', 'target'})d.assert_immediate_ancestors('reward', set())

顺便说一句,如果您在Tensorflow代码中精心设计了名称范围,并且在TensorBoard可视化文件中进行了认真的折叠,您将获得与上述库相同的功能。 但我认为库很不错,因为:

  • 您很可能没有仔细设计名称范围-是吗?
  • 使用该库,您可以生成那些张量依赖断言,这将帮助您在以后的所有执行中进行调试。

技术3:张量方程评估

到目前为止,您已经验证了定义的张量之间的依赖关系。 最后一步是检查张量是否执行正确的数值计算。 例如,方程B = A +1和B = A -1都引入了从B到A的依存关系,因此它们的依存关系图是相同的。 但是您需要指定B = A + 1是正确的实现。 使用张量方程评估对算法中的每个方程执行以下操作:

  • 在每个优化步骤中,通过在session.run中添加它们来评估所涉及的张量。
  • 用这些张量求值以numpy编写相同的方程式,以计算所需的值。 然后断言期望值与实际值相同。

接下来的清单显示了损失张量的张量方程评估。 session.run会评估parameter_update_operations,这是您常用的东西,例如渐变下降步骤。 除了这项常规工作之外,session.run现在还评估预测,目标和损失张量。 您可以从这三个张量评估中计算出所需的损失。 最后,您断言实际损失等于第4行和第5行的期望损失。请注意,第4行和第5行在Python世界中。 在Python世界中,您可以使用循环,调用任意函数; 它比Tensorflow世界中的方法容易得多。

prediction_, target_, loss_, _ = session.run(    [prediction, target, loss, parameter_update_opeations],     feed_dict={...})desired_loss = np.mean(np.power(target_ - prediction_, 2))np.testing.assert_almost_equal(actual=loss_, desired=desired_loss, decimal=3)

这些技术有效且实用吗?

我们已将这些技术应用于所有Tensorflow学习者。 下表报告了我们花在验证五个模型上的时间以及发现的错误数量。

Table 1. Bugs detected with assertion techniques

"学习模块"列列出了机器学习模型的名称。 这些是深度强化学习中的Actor-Critic算法。

"编码时间"列报告了我们花费在编写这些学习者代码上的时间(以小时为单位)。 总共我们花了24个小时。

"验证时间"列报告了我们在验证上花费的时间。 这包括编写断言,运行代码,观察断言冲突并修复检测到的错误。 总共我们花了5个小时。 换句话说,验证需要20%的工作量。

"检测到的错误"列是每种断言技术的细分。 它显示了花费在每种技术上的时间百分比以及检测到的错误数量。 总共,我们仅在5小时内检测到23个错误。 更重要的是,应用这些技术后,我们知道我们的代码是正确的。

我们可以清楚地看到VeriTensor在检测错误方面很有效。

为什么VeriTensor对检测错误有效?

首先,它们要求您通过断言定义代码的正确性。 编写规范并不是一个新主意,但VeriTensor使其实用:

  • 形状断言要求您写下所引入的张量的形状-简单!
  • 张量依赖性仅要求您关注引入的张量。 在此阶段无需检查数值运算。 这样可以将图形从数百个节点减少到十二个左右,从而使人类研究变得切实可行。 自动断言生成减少了写下断言所需的时间。
  • 在张量方程评估中,您将检查Python世界中的每个方程。 Python世界比Tensorflow世界更容易。

其次,在Tensorflow中发现错误的来源令人生畏。 人们花费大部分时间来定位错误的来源。 一旦知道了来源,通常即可轻松修复该错误。 按顺序应用时,VeriTensor技术可帮助您定位故障。 在张量依赖阶段有问题时,您会知道所有涉及的张量都具有正确的形状。 当张量方程式有问题时,您就会知道依赖关系结构是正确的。 简而言之,您可以更好地关注和定位每个问题。

第三,VeriTensor将Tensorflow代码调试从一门艺术变成了一个软件工程过程。 如果遵循简单的任务清单,该过程将确保代码正确:

  • 为您引入的所有张量编写一个形状断言。
  • 解释这些张量之间的所有依赖关系边,并自动生成结构性断言。
  • 编写一个断言以检查算法中的每个方程。

验证和/或测试代码时的常见问题是知道如何进行和何时停止。 您从代码的哪一部分开始? 您应该检查哪些方面? 经过足够的测试,您怎么知道?

我们的三种技术消除了这些疑虑。 您一一应用它们,每种技术都有有限且可管理的步骤。 步骤的数量受您在代码中引入的张量和方程式的数量限制(通常约为12个)。 最后,您知道您的代码是正确的。 这是一个工程过程,而不是一门艺术。 不要忽略这种工程过程的力量。 人们知道确切的步骤后,他们的效率就会大大提高。

不要忽略这种工程过程的力量。 人们知道确切的步骤后,他们的效率就会大大提高。

验证代码正确性,而不是性能

我需要明确说明VeriTensor会验证代码的正确性。 它不会评估代码的性能。 正确性意味着您实现的代码符合您的想法。 绩效是学习有意义模型的能力。 通常通过绘制损失,交叉验证和测试数据来衡量性能。

您必须先确定代码的正确性,然后再查看其性能。 我称这是性能原则之前的正确性。 否则,您需要担心性能不好是因为学习算法不够好,还是代码中存在一些错误。 显然,您需要后者,但是很难证明您的代码没有错误。

性能先于原则:只有在确定正确性之后,才能查看代码的性能。

可悲的是,我看到很多人都采用的模式是使用性能指标来进行调试。 当他们的代码不学习时,他们将通过绘制损失函数来开始调试。 这违反了性能原则之前的正确性,因此无法有效地发现错误。 这是因为:

  • 性能指标是渐近定向的,而不是单调的。 例如,损失函数应随时间减少。 但是在任何时间点,包括调试时,这些数字都可以上升或下降。 没有正确的值使您很难识别出是否有问题。 将此与断言进行比较:您知道发生断言冲突时情况不对。
  • 即使您发现性能指标显然是错误的,它们也不会告诉您错误的来源。 将此与VeriTensor的故障定位支持进行比较。 您可以在阶段中找到错误-张量成形阶段,张量依赖阶段和张量值阶段。 您可以在每个阶段集中精力。
  • 修复错误后,很难为该错误编写回归测试。 这是因为基于性能指标的错误和症状的根源很远。 将此与使用断言的测试用例编写经验进行比较。 您只需要将主学习循环变成具有较小学习时间步长的单元测试,以使测试尽快终止。 您可以使用真实输入,也可以使用随机输入。

影片介绍

一年半以前,我在Tensorflow Deep Dive活动中介绍了VeriTensor。 演讲受到好评。 这是演示。

在那之后的20个月中,我将VeriTensor应用于所有的机器学习代码,并且一次又一次地起作用。 希望对您有帮助。

(本文翻译自Wei Yi的文章《Reducing Tensorflow Debugging Time by 90 Percent》,参考:https://towardsdatascience.com/reducing-tensorflow-debugging-time-by-90-percent-41e8d60f9494)

本文来自投稿,不代表本人立场,如若转载,请注明出处:http://www.souzhinan.com/kj/297221.html