李诗萌 Chita编译

你真的会正确地调试TensorFlow代码吗?

到底是选 TensorFlow 还是 PyTorch?萝卜青菜各有所爱。虽然很多人吐槽 TensorFlow 框架的复杂以及调试代码的痛苦,但选择 TensorFlow 人还是很多。大概,这就是真爱吧!本文作者通过对 TensorFlow 代码进行百般调戏,哦调试,总结了一套让你感觉不那么痛苦的调试方法,趁热围观吧↓↓

当谈到在 TensorFlow 上写代码时,我们总会将它和 PyTorch 进行对比,然后讨论 TensorFlow 框架是多么的复杂以及 tf.contrib 的某些部分为什么那么糟糕。此外,我还认识许多数据科学家,他们只用预先写好的、可以克隆的 GitHub 库和 TensorFlow 交互,然后成功使用它们。对 TensorFlow 框架持有这种态度的原因各不相同,想要说清楚的话恐怕还得另外写个长篇,现在我们要关注的是更实际的问题:调试用 TensorFlow 写的代码,并理解其主要特性。

核心概念

计算图。计算图 tf.Graph 让框架能够处理惰性求值范式(不是 eager execution,一种命令式编程环境)。基本上,这种方法允许程序员创建 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 会话将会作为上下文管理器调用:使用 with tf.Session() as sess: 句法。如果传递给构造函数的会话没有参数,那么就只会使用本地机器的资源和默认的 TensorFlow 图,但它也可以通过分布式 TensorFlow 运行时使用远程设备。事实上,没有会话,图就不能存在(图没有会话就无法执行),而且会话一般都有一个指向全局图的指针。

更深入地研究运行会话的细节,值得注意的要点是它的句法:tf.Session.run()。它可以将张量、运算或类似张量的对象作为参数(或参数列表)提取。此外,feed_dict(这个可选参数是 tf.placeholder 对象到其值的映射)可以和一组选项一起传递。

可能遇到的问题及其解决方案

通过预训练模型加载会话并进行预测。这是一个瓶颈,我花了好几周来理解、调试和修改这个问题。我高度关注这个问题,并提出了两个重新加载和使用预训练模型(图和会话)的技巧。

首先,我们谈到加载模型时我们真正的意思是什么?当然,为了实现这一点,我们需要先训练和保存模型。后者一般是通过 tf.train.Saver.save 功能实现的,因此,我们有三个二进制文件,它们的扩展名分别是 .index,.m*e*ta 和 .data-00000-of-00001,这其中包含了还原会话和图所需的所有数据。

为了加载以这种方式保存的模型,首先要通过 tf.train.import_meta_graph()(参数是扩展名为 .meta 的文件)还原图。按照前面的描述操作后,会将所有变量(包括后面将会提到的「隐藏」变量)传入当前的图中。执行 graph.get_tensor_by_name 来检索具有名称的张量(记住,由于张量的创建范围和运算,它可能和你初始化后的那个不同)。这是第一种方法。

第二种方法更明确,但是也更难实现(我一直都在研究模型架构,但我从没成功地用这种方法执行图),这种方法的主要思路是在 .npy 或 .npz 文件中明确地存储图的边(张量),之后再将它们加载回图中(同时根据它们的创建范围给它们分配恰当的名称)。这种方法有两个巨大的缺点:首先,当模型架构变得非常复杂时,控制和保持所有的权重矩阵也变得很难。其次,还有一类「隐藏」张量,它们是在没有明确初始化的情况下创建的。例如,当你创建 tf.nn.rnn_cell.BasicLSTMCell 时,它为了实现 LSTM 单元,会偷偷创建所有必需的权重和偏差。变量名称也是自动分配的。

这种行为看似没什么问题(只要这两个张量权重,且它们是用框架处理而非手动创建的),但是事实上,在许多情况下都并非如此。该方法的主要问题是当你看图的集合时,你也会看到一大堆来源不明的变量,实际上你并不知道应该把什么保存下来,也不知道应该从哪加载它。坦率地讲,将隐变量放在图中正确的位置并恰当地操作是很难的。这比你本身的需求还要难。

在没有任何警告的情况下创建了两个名字相同的张量(通过自动添加_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 的特点,而且是我很不喜欢的一点。

在写单元测试还有一些其他问题时要手动重置图形。由于一些原因,很难测试用 TensorFlow 写的代码。第一个——也是最明显的一点在本段开头已经提到了,这听起来可能很傻,但对我来说,它太令人恼火了。举个例子,由于在运行时访问的所有模块的所有张量只有一个默认的 tensorflow 图,因此无法在不重置图的情况下用不同的参数测试相同的功能。虽然 tf.reset_default_graph() 写成代码只有一行,但是它要写在大多数方法的顶部,这个解决方法变成了重复性的工作,即明显的复制代码。我没发现任何可以解决这个问题的方法(除了使用范围的 reuse 参数,这个会在后面讨论),只要将所有张量链接到默认图即可,但是没有方法可以将它们分隔开(当然,每种方法都可以用单独的 TensorFlow 图,但在我看来,它们都不是最佳实现)。

关于 TensorFlow 代码的单元测试问题也让我困扰已久:当不需要执行构建图的一部分(因为模型尚未训练所以其中有未初始化的张量)时,我不知道应该测试些什么。我的意思是 self.assertEqual() 的参数不清楚(我们是否要测试输出张量的名字或形状?如果形状是 None 呢?如果仅凭张量名称或形状无法推断代码是否运行良好呢?)。就我个人而言,我只是简单地测试了张量的名称、形状和维度,但我确信,在一些没有执行图的情况中,只检查这部分功能并不合理。

令人困惑的张量名称。许多人可能认为这样评价 TensorFlow 的性能不太好,但有时没人说得出来在执行某些操作后得到的张量名称是什么。举个例子,你知道 bidirectional_rnn/bw/bw/while/Exit_4:0 是什么意思吗?对我来说,这简直莫名其妙。我知道这个张量是对动态双向 RNN 的后向单元进行某种运算得到的结果,但如果没有明确地调试代码,你就无法得知到底是按什么样的顺序执行了什么样的运算。此外,索引的结尾也令人无法理解,如果想知道数字 4 来自哪里,你得阅读 TensorFlow 文档并深入研究计算图。

对前面讨论过的「隐」变量来说,情况也是一样的:为什么我们会有 bias 和 kernel 的名称呢?也许这是我的资历和技术水平问题,但对我来说这样的调试情况是很不自然的。

tf.AUTO_REUSU 是可训练变量,可以重新编译库和其他不好的东西。这部分的最后一点是简要介绍我通过错误和尝试方法学到的一些小细节。首先是范围的参数 reuse=tf.AUTO_REUSE,它允许自动处理已经创建的变量,如果这些变量已经存在的话就不会进行二次创建。事实上,在许多情况下,它都可以解决本段提出的第二个问题。但在实际情况中,只有当开发人员知道代码的某些部分需要运行两次或两次以上时,才应该谨慎地使用这一参数

第二点是关于可训练变量,这里最重要的点是:默认情况下所有张量都是可训练的。有时候你可能不需要对其进行训练,而且很容易会忘记它们都可以训练。这一点有时令人头疼。

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

总结

希望本文能够帮助那些首次开发 TensorFlow 模型的数据科学家。他们可能正挣扎于框架的某些部分,这些部分很难理解而且调试起来很复杂。我想说的是,不要担心在使用这个库时犯很多错误(也别担心其他的),只要提出问题,深入研究官方文档,调试出错的代码就可以了。

这些与跳舞或者游泳一样,都需要熟能生巧,我希望能够让这种练习变得更愉快也更有趣一些。

原文链接:https://towardsdatascience.com/debugging-your-tensorflow-code-right-without-so-many-painful-mistakes-b48bd9145d5c

入门代码调试TensorFlow
8
相关数据
权重技术

线性模型中特征的系数,或深度网络中的边。训练线性模型的目标是确定每个特征的理想权重。如果权重为 0,则相应的特征对模型来说没有任何贡献。

机器学习技术

机器学习是人工智能的一个分支,是一门多领域交叉学科,涉及概率论、统计学、逼近论、凸分析、计算复杂性理论等多门学科。机器学习理论主要是设计和分析一些让计算机可以自动“学习”的算法。因为学习算法中涉及了大量的统计学理论,机器学习与推断统计学联系尤为密切,也被称为统计学习理论。算法设计方面,机器学习理论关注可以实现的,行之有效的学习算法。

参数技术

在数学和统计学裡,参数(英语:parameter)是使用通用变量来建立函数和变量之间关系(当这种关系很难用方程来阐述时)的一个数量。

数据科学技术

数据科学,又称资料科学,是一门利用数据学习知识的学科,其目标是通过从数据中提取出有价值的部分来生产数据产品。它结合了诸多领域中的理论和技术,包括应用数学、统计、模式识别、机器学习、数据可视化、数据仓库以及高性能计算。数据科学通过运用各种相关的数据来帮助非专业人士理解问题。

Apache Spark技术

Apache Spark是一款快速、灵活且对开发者友好的工具,也是大型SQL、批处理、流处理和机器学习的领先平台。它是一个围绕速度、易用性和复杂分析构建的大数据处理框架,提供了一个全面、统一的框架用于管理各种不同性质(文本数据、图表数据等)数据集和数据源(批量数据或实时的流数据)的大数据处理的需求。

TensorFlow技术

TensorFlow是一个开源软件库,用于各种感知和语言理解任务的机器学习。目前被50个团队用于研究和生产许多Google商业产品,如语音识别、Gmail、Google 相册和搜索,其中许多产品曾使用过其前任软件DistBelief。

张量技术

张量是一个可用来表示在一些矢量、标量和其他张量之间的线性关系的多线性函数,这些线性关系的基本例子有内积、外积、线性映射以及笛卡儿积。其坐标在 维空间内,有 个分量的一种量,其中每个分量都是坐标的函数,而在坐标变换时,这些分量也依照某些规则作线性变换。称为该张量的秩或阶(与矩阵的秩和阶均无关系)。 在数学里,张量是一种几何实体,或者说广义上的“数量”。张量概念包括标量、矢量和线性算子。张量可以用坐标系统来表达,记作标量的数组,但它是定义为“不依赖于参照系的选择的”。张量在物理和工程学中很重要。例如在扩散张量成像中,表达器官对于水的在各个方向的微分透性的张量可以用来产生大脑的扫描图。工程上最重要的例子可能就是应力张量和应变张量了,它们都是二阶张量,对于一般线性材料他们之间的关系由一个四阶弹性张量来决定。

映射技术

映射指的是具有某种特殊结构的函数,或泛指类函数思想的范畴论中的态射。 逻辑和图论中也有一些不太常规的用法。其数学定义为:两个非空集合A与B间存在着对应关系f,而且对于A中的每一个元素x,B中总有有唯一的一个元素y与它对应,就这种对应为从A到B的映射,记作f:A→B。其中,y称为元素x在映射f下的象,记作:y=f(x)。x称为y关于映射f的原象*。*集合A中所有元素的象的集合称为映射f的值域,记作f(A)。同样的,在机器学习中,映射就是输入与输出之间的对应关系。

逻辑技术

人工智能领域用逻辑来理解智能推理问题;它可以提供用于分析编程语言的技术,也可用作分析、表征知识或编程的工具。目前人们常用的逻辑分支有命题逻辑(Propositional Logic )以及一阶逻辑(FOL)等谓词逻辑。

隐变量技术

在统计学中,隐变量或潜变量指的是不可观测的随机变量。隐变量可以通过使用数学模型依据观测得的数据被推断出来。

推荐文章
暂无评论
暂无评论~