正确debug的TensorFlow的姿势

共 4922字,需浏览 10分钟

 ·

2022-03-23 10:42

点击上方小白学视觉”,选择加"星标"或“置顶

重磅干货,第一时间送达

导读

TensorFlow代码很难调试,这个大家已达成共识,不过,就算是难,也还是需要调试的,毕竟谁也没有把握不出bug,看看这篇文章能不能让你减轻一点调试时的痛苦。

当讨论在tensorflow上编写代码时,总是将其与PyTorch进行比较,讨论框架有多复杂,以及为什么要使用tf.contrib的某些部分,做得太烂了。此外,我认识很多数据科学家,他们只用Github上已有的repo来用tensorflow。对这个框架持这种态度的原因是非常不同的,但是今天让我们关注更实际的问题:调试用tensorflow编写的代码并理解它的主要特性。

 

核心抽象


  • 计算图。第一个抽象是计算图tf.Graph,它使框架能够处理惰性评估模式(不是立即执行,这是“传统”命令式Python编程实现的)。基本上,这种方法允许程序员创建tf.Tensor(边)和tf.Operation (节点),它不是立即计算的,而是在执行图形时才计算的。这种构造机器学习模型的方法在许多框架中都很常见(例如,在Apache Spark中使用了类似的思想),并且具有不同的优缺点,这在编写和运行代码时表现得非常明显。最主要和最重要的优点是,数据流图可以很容易地实现并行性和分布式执行,而无需显式地使用multiprocessing 模块。在实践中,编写良好的tensorflow模型在启动时立即使用所有核心的资源,而不需要任何额外的配置。

    然而,这个工作流的一个非常明显的缺点是,一旦你构建图的时候,没有用提供的输入来运行,你就不能确保它不会崩溃。它肯定会崩溃。此外,除非你已经执行了图形,否则你无法估计它的运行时间。

    计算图的主要组成部分是图集合和图结构。严格地说,图的结构是前面讨论的特定的节点集和边集,而图集合是可以逻辑地分组的变量集。例如,检索图形的可训练变量的常用方法是'tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES)

  • 会话 。第二个抽象与第一个抽象高度相关,并且有更复杂的解释:tensorflow会话的tf.Session用于客户端程序和c++运行时之间的连接(如你所知,tensorflow是用c++编写的)。为什么是c++ ?答案是,通过这种语言实现的数学运算可以得到很好的优化,因此,计算图运算可以得到很好的处理。

    如果你正在使用低级的tensorflow API(大多数Python开发人员都在使用),则tensorflow session将作为上下文管理器调用:with tf.Session() as sess:。没有参数传递给构造函数(如前一个示例)的会话仅使用本地机器的资源和默认的tensorflow图,但它也可以通过分布式tensorflow运行时访问远程设备。在实践中,如果没有会话,计算图就不能存在(没有会话,它就不能执行),而会话总是有一个指向全局图的指针。

    深入研究运行会话的细节,值得注意的主要一点是它的语法:tf.Session.run()。它可以作为张量、操作或类张量对象的参数获取(或获取列表)。另外,可以传递feed_dict(这个可选参数是tf.placeholder的对象及其值)以及一组选项。

 

实验中一些可能的问题以及可能的解决方案


  1. 会话加载并通过预先训练的模型进行预测。这就是瓶颈,我花了几周的时间来理解、调试和修复它。我想高度关注这个问题,并描述两种重新加载预训练模型(图和会话)并使用它的可能技术。

    首先,当我们谈论加载模型时,我们真正的意思是什么?当然,为了做到这一点,我们需要事先训练并保存它。后者通常是通过tf.train.Saver.save完成的。因此,我们有3个二进制文件.index.meta.data-00000-of-00001,其中包含恢复会话和图所需的所有数据。

    要加载以这种方式保存的模型,需要通过tf.train.import_meta_graph() (参数是.meta的文件)来恢复。按照前一段描述的步骤之后,所有变量(包括所谓的“隐藏”变量,稍后将讨论)都将被移植到当前图中。检索某个有自己名字的张量(记住,它可能不同于你初始化它时使用的张量,这取决于创建张量的范围和操作的结果)应该执行graph.get_tensor_by_name()。这是第一种方法。

    第二种方法更显式,也更难实现(对于我一直在使用的模型的架构,我还没有成功用起来),它的主要思想是将图的边(张量)显式地保存到.npy.npz文件中,然后将它们加载回图中(并根据创建它们的范围分配适当的名称)。这种方法的问题在于它有两个巨大的缺点:首先,当模型架构变得非常复杂时,它也变得很难控制和保存所有的权重矩阵。其次,有一种“隐藏的”张量,它是在没有显式初始化的情况下创建的。例如,当你创建 tf.nn.rnn_cell.BasicLSTMCell时。它创建了所有需要的权值和偏差来实现LSTM cell。变量名也是自动分配的。

    这种行为看起来还可以,但实际上,在很多情况下,并不是很好用。这种方法的主要问题是,当你查看图的集合时,看到一堆变量,你不知道它们的来源,你实际上不知道应该保存什么以及在哪里加载它们。坦率地说,很难将隐藏变量放到图中正确的位置并适当地操作它们。

  2. 在没有任何警告的情况下两次创建同名张量(通过自动添加_index结尾)。我认为这个问题不像前一个问题那么重要,但是这个问题确实困扰着我,因为它会导致很多图执行错误。为了更好地解释这个问题,我们来看一下这个例子。

    例如,你用tf.get_variable(name=’char_embeddings’, dtype=…)来创建张量。然后保存它并在新会话中加载。你已经忘记了这个变量是可训练的,并通过tf.get_variable()以相同的方式再次创建了它。在图执行过程中,会发生的这样的错误:FailedPreconditionError (see above for traceback): Attempting to use uninitialized value char_embeddings_2。原因是,你已经创建了一个空变量,但是并没有将它放到模型的适当的位置,而实际上只要它已经包含在计算图中,就可以放到模型的某个地方。

    正如你所看到的,由于开发人员创建了同名张量两次(甚至Windows也会这样做),所以没有出现错误或警告。也许这一点只对我很重要,但这是tensorflow的特性,我并不喜欢它的这个行为。

  3. 在编写单元测试和其他问题时手动重置图。由于许多原因,测试用tensorflow编写的代码总是很困难。第一个— 也是最明显的一个,已经在这一段的开头提到过了,可能听起来很傻,但对我来说,让人很恼火。由于在运行期间访问的所有模块的所有张量只有一个默认的tensorflow图,因此不可能在不重置这个图的情况下,使用不同的参数(例如)测试相同的功能。它只是一行代码tf.reset_default_graph(),但是知道它应该写在大多数方法的顶部,这个解决方案就变成了某种恶作剧,当然,一个明显的代码复制示例。我还没有发现任何可能的方法处理这个问题(除了使用reuse参数,我们将在后面讨论),只要所有的张量与默认图并没有办法隔离(当然,可以有一个单独的tensorflow图方法,但在我看来这不是最佳实践)。

    为tensorflow写单元测试的代码也困扰我很多的事情,在这种情况下,计算图的一部分是不应该被执行的(里面有张量未初始化,因为模型没有训练)。但是,计算图本身并不知道我们应该测试什么。我的意思是 self.assertEqual()的参数不清楚(我们应该测试输出张量的名称或它们的形状吗?如果形状为None呢?如果张量的名字或形状不足以得出这个结论,那该怎么办?)。在我的例子中,我只是简单地断言张量的名称、形状和维数,但是我确信,在不执行图的情况下,只检查这部分功能是不合理的。

  4. 混淆张量的名字。很多人会说这个关于tensorflow的评论是一种另外的抱怨方式,但是我们并不能说出来在做了某种运算之后得到的张量的名字是什么。我的意思是,名称bidirectional_rnn/bw/bw/while/Exit_4:0清楚吗?对我来说,绝对不清楚。我得到这个张量是对动态双向RNN的后向单元进行某种操作的结果,但是如果没有显式地调试代码,就无法知道执行了哪些操作以及操作的顺序。另外,索引的结尾也不太好理解,只要你想知道数字4是从哪里来的,就需要阅读tensorflow文档并深入研究计算图。

    前面讨论的“隐藏”变量的情况也是一样的:为什么这里有biaskernel名称?也许这是我的水平不够,但是这样的调试案例对我来说很不自然。

  5. tf.AUTO_REUSE,可训练的变量,重新编译的库和其他的东西。这个列表的最后,我们看一些小的细节,这些细节只有在错误中才能学习到。第一件事是reuse=tf.AUTO_REUSE参数的作用域,它允许自动处理已经创建的变量,如果它们已经存在,就不会创建两次。事实上,在许多情况下,它可以解决本段第二点所述的问题。但是,在实践中,这个参数应该谨慎使用,并且只有在开发人员知道代码的某些部分需要运行两次或多次时才使用。

    第二点是可训练变量,这里最重要的一点是:所有的张量在默认情况下都是可训练的。有时这可能是一个头痛的行为,因为有时候我们并不想要所有的张量都可训练,但是又很容易忘记,他们是都可以训练的。

    第三件事只是一个优化技巧,我建议每个人都这么做:几乎在所有情况下,当你使用通过pip安装的包时,你都会收到这样的警告:Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX AVX2。如果你看到这类消息,最好卸载tensorflow,然后使用你喜欢的选项通过bazel重新编译它。这样做的主要好处是计算速度的提高和更好的总体性能。

 

总结


我希望这篇文章能够对那些正在开发他们的第一个tensorflow模型的数据科学家有所帮助,他们正在努力解决框架中某些部分的不明显的行为,这些行为很难理解,而且调试起来相当复杂。要点我想说的是,使用这个库工作的时候,犯一些错误也不是坏事(对于其他的事情也是一样的),这可以让我们多问些问题,深入查看文档和调试每一行代码,这样也是很好的。

就像跳舞或游泳一样,任何事情都需要练习,我希望我能让这种练习变得更愉快和有趣。

下载1:OpenCV-Contrib扩展模块中文版教程
在「小白学视觉」公众号后台回复:扩展模块中文教程即可下载全网第一份OpenCV扩展模块教程中文版,涵盖扩展模块安装、SFM算法、立体视觉、目标跟踪、生物视觉、超分辨率处理等二十多章内容。

下载2:Python视觉实战项目52讲
小白学视觉公众号后台回复:Python视觉实战项目即可下载包括图像分割、口罩检测、车道线检测、车辆计数、添加眼线、车牌识别、字符识别、情绪检测、文本内容提取、面部识别等31个视觉实战项目,助力快速学校计算机视觉。

下载3:OpenCV实战项目20讲
小白学视觉公众号后台回复:OpenCV实战项目20讲即可下载含有20个基于OpenCV实现20个实战项目,实现OpenCV学习进阶。

交流群


欢迎加入公众号读者群一起和同行交流,目前有SLAM、三维视觉、传感器自动驾驶、计算摄影、检测、分割、识别、医学影像、GAN算法竞赛等微信群(以后会逐渐细分),请扫描下面微信号加群,备注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交大 + 视觉SLAM“。请按照格式备注,否则不予通过。添加成功后会根据研究方向邀请进入相关微信群。请勿在群内发送广告,否则会请出群,谢谢理解~


浏览 34
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报