一位从 TensorFlow 开放以来就一直使用它进行研究和编程的工程师对它进行了全面评估:好的、坏的、令人讨厌的的。评估之前,这位研究员非常走心地研究了自己能找到的所有案例、教程、文档、代码片段,因此,较之媒体上对 TensorFlow 的主观性报道,相信这篇测评会更有说服力。
一、好的一面
参与社区是最重要的事
提到机器学习,很容易把重点放在技术本身(特征、性能、基准等)。但是,优秀的程序员都知道,写一段人使用的代码,要比写一段机器编译、执行的代码要困难的多。关于 TensorFlow ,我最喜欢的一点是这样一个简单事实:机器学习社区中的每个人也都知道它,大多数人都以开放的心态去实现它,并且希望有足够的人可以用它做有用的事。解决问题的想法越多,巨人的肩膀就越多!
现在,很多开发人员和学生对深度学习感兴趣,源自听说了 TensorFlow。最近,谷歌 Deepmind 声称,他们将从 Torch 迁移到 Tensorflow 上,所以,在不远的未来,我们将看到升级版 TensorFlow 强化学习模型的发布。当社区拥有更多的开放、简洁的 API 、更多有用的模块、再加上网络上乐于助人的态度,未来还是很光明的。
大多数技术限制因素已被消除
去年十一月,我们撰写了第一版 TensorFlow 测评文章,那时,的确存在着不少真实的或潜在的限制因素。现在,我很高兴地告诉大家,大多数限制因素已经被解决了。
多 GPU 支持。现在已经可以使用了;相关文档简洁清楚。你仍需要思考如何分解和克服问题,但这不也是一项乐趣么?
利用分布式资源训练(也就是云计算)。v 0.8 版本已经支持分布式计算。
将例如数据读取和图片预处理这样的操作排入队列。
使用 TensorBoard 就可实现图形可视化。当建立和调试新模型时,容易迷失在杂项之中。对于我来说,为了解决一个困难问题而建立一个新框架或模型时,维持一个好的精神状态,很不容易,所以,检视模型的一个完全不同的表现方式,真的有用;TensorBoard 图形可视化化最适合做这个。
与 TensorBoard 交互的日志记录文件。在 UNIX/Linux ,我喜欢在命令行使用 tail -f <log_file> 来监测任务输出,做出快速的总体检查。TensorFlow 中的日志记录文件同样可以做到这一点,通过从图表中发出事件、总结,然后通过 TensorBoard 不停的监测输出数据(例如学习率、损失值、训练/测试准确性)。
模型检查点。训练一会儿模型,停下来做个评估,然后从检查点载入,继续训练。
性能和 GPU 内存的用法与 Theano 相似,其他的部分使用了 CUDNN 。对早期发布版本性能的抱怨主要是因为使用了 CUDNNv2,所以,TensorFlow v 0.8 版本在这方面有很大提升(使用了CUDNNv4)。
一些高质量的元框架(metaframeworks)
Keras 在 TensorFlow 和 Theano 后端都存在。如果你不想深究 TensorFlow 的内部细节而是只要模块化使用的话,Keras 是一个好的选择。
TensorFlow Slim 是图像模型的一个很好的参考。即使你更喜欢编写自己低水平 Tensorflow 代码,Slim 的文档仍然可以作为 Tensorflow 接口使用、模型设计等的参考文献。
Skflow 将 Tensorflow 的方法隐藏在一个 scikit-learn 类型的 API 中。 相比只是为了多种 sklearn metrics 导入和插入 python 代码,我用 Skflow 有点奇怪。
PrettyTensor 提供了表现类似于 tensor 的目标,并有着一个可链接语法,所以,你能快速的混合某些类型的模型。
发布计划
维护一个流行的开源工程是一项挑战,特别是像 Tensorflow 这种技术复杂的工程。向这些维护人员致敬!我们感谢他们的这个策略:在还没写入文档之前,就把新特性和测试整合起来,这样,用户就可以早早尝试这些新东西。如果你对版本发布的细节和日期感兴趣,就来看看这个版本语义日志吧:https://www.tensorflow.org/versions/r0.8/resources/versions.html.
测试非常的棒!
对于确认功能是否有效,以及了解事物是如何运作的,测试非常有价值。当你发现 TensorFlow 里的一些功能不如你的预期,或者你正在学习一个方法或参数的 quirks……请上 Github 上搜索一个测试,看看测试是怎么做的!
二、坏的一面
相比 Theano,Tensorflow 的递归神经网络(RNNS)仍然有些不足
这些年,Theano 团队花了很多功夫去优化他们递归神经网络的实现。可喜的是,这个差距在缩小,几个月之内 TensorFlow 就有可能成为运行递归神经网络的一个不错平台。特别是:
我们还没见过处理可变长度序列输入的精致方法。Bucketing 可行,却以大多数模型都不需要的复杂性为代价。在许多案例中,将所有序列填补成一个固定的长度,也是一种有效的方法(特别是使用 batches 和 GPU 时),但是,有人认为这个方案不令人满意。递归神经网络的动态展开可能是一个解决方案,但是,在 tensorflow.python.ops.rnn 模块中动态 RNN 的使用还非常新,也没有任何相关记录。我们仍在对此试验。
性能和内存使用。尽管很难做一个准确的同类比较,但在两个框架之下运行了很多相同模型之后,我们的看法是,对于递归神经网络,在一块 GPU 的情形下,Theano 可能比 TensorFlow 快一些,而且占用 GPU 内存小一点,这可能是由于元素智能(element-wise)特性。在多 GPU 情形下和编译时间方面,Tensorflow 胜出。
缺少数据输入的权威案例
TensorFlow 文档和案例主要使用一些著名的学术数据集来阐述各种特性或功能。这是有一定道理的,是刻画系统总体特性的优先方法。但是,现实中的问题几乎不能简单替换这些数据集。当学习一个新的深度学习框架时,运用张量输入和形状(shapes)可能成为一个真正的障碍,所以,一两个展示如何运用杂乱的输入数据(奇怪的形状、分布、填充、标记等)的例子,可能会为未来的开发者和工程师省去很多麻烦。
文档可能不一致
里面有很多 TensorFlow 的教程,代码本身也被注释得很好。但是,机器学习/深度学习范围很广,在解释如何建立模型方面,新功能和文档/教程之间存在滞后现象。一些我们喜爱的教程如下:
Nathan’s Github repo 简单教程。它展示了机器学习基本工作原理。如果你熟悉 numpy 或 Theano,可以从这开始。
Udacity 课程,出自谷歌的 Vincent Vanhoucke。 如果你刚接触深度学习,可以从这开始。
官方 MNIST 教程。作为新手学完了 Udacity 教程之后,就可以看这个了。MNIST 是「机器学习的果蝇(Drosophila of machine learning)」,也是一个好的测试基准和完整性检查。
Tensorflow 接口文档。针对 TensorFlow的 参考文献。使用Control+F 来找东西!
不幸的是,特别是对于递归神经网络来说,文档和教程之间仍然存在概念上的鸿沟,例如,简单或不重要的例子与全面的、最高级的例子之间的鸿沟。对于同时学习概念和框架的人来说,这是一个实际存在的障碍。比如 ,Udacity 教程和递归神经网络教程,非常清楚阐释了使用 Penn TreeBank 数据建立语言模型,感谢它们的简单明了。如果学习概念的话,它们是很好的例证,但是,对现实世界建模的话,它们还是太简单了。
我们发现的另一个权威的 TensorFlow 递归神经网络教程是一个全面的 seq2seq 模型,模型所使用的多单元递归神经网络(GRU 或 LSTM)了加入了attention、bucketing 和 sampled softmax 算法。哇哦!这就像学习滑雪,你刚在训练道上学习了滑雪就直接跑到山顶展示雪上特技(危险并可怕!?)……你可能不应该从最简单的模型直接跳到最复杂的。应该根据你要解决的问题逐步增加复杂度。
高质量的教程会逐步增加复杂度:从简单的递归神经网络语言模型,到可以学习倒换词语的的普通 seq2seq 递归神经网络解码架构,到更精妙的带有注意(attention)的翻译 seq2seq LSTM,再到使用多单元递归神经网络,对于初期的 TensorFlow 用户来说,这些所有技巧非常有用。我猜,循序渐进实例的缺乏,可能就是这一现象的原因:为什么社区在 TensorFlow 上重现了许多的流行模型,却并没看到许多新奇的架构或巧妙的融合。
TensorFlow 团队首要关注于功能和特性,然后才是文档……,我们对此表示理解,要是我们可能也会这么做!而好的文档是一项投资,我见过的最好文档反而不是出自这些官方文档作者之手,因为好的文档要保证至少有一个外人能理解。如果 TensorFlow 社区撰写文档,就像他们要求新特性一样急切,那会非常酷!
我们仍然在等待轨迹监控工具,EEG。
三、令人讨厌的的一面
利用异质资源增加复杂性
在控制和简单性之间,一个经典的工程学折中——如果你想精细化控制运算的执行(例如,使用哪块GPU),那么,你需要维持这些限制。在有些情况下,对于性能最大化来说,精细化控制是必要的。例如,在供给(feed)GPU之前,使用多线程获取并预处理批量数据,这样 ,GPU 就不会等待这类操作。使用CPU的异步通道来供给GPU,或测试你的队列,这方面更多细节请见 Luke 这篇非常好的论文,TensorFlow Data Input (Part 2): Extensions。
TensorFlow 贪婪占用 GPU
同样的,在初始化时, TensorFlow 尝试去分配所有可用的GPU内存给自己。这是一把双刃剑,取决于你的情景。如果你正积极的开发一个模型,使用的是本地计算机上的 GPU,你可能会想分配一部分 GPU 到其他的事上。然而,如果你在云计算环境下配置模型,那么,你就想要了解可运行你的模型的硬件资源,与其他也可以接入相同硬件代码之间,没有不可预期的相互作用。
你可以使用诸如下面这样的代码,为一个特定进程的GPU内存设定上限,但是,如果你的一台机器有多个GPU,我们还没留意过怎么控制每块GPU的具体分配。
gpu_options = tf.GPUOptions(per_process_gpu_memory_fraction = 0.5)
将它作为配置参数传入你的会话:
sess = tf.Session(config = tf.ConfigProto(gpu_options = gpu_options))
在默认情况下, Theano 和 TensorFlow 会冲突
我们有很多基于 Theano 的代码,从读取数据到多种用途的功能。我们也读了很多在 Theano 中执行的研究代码。然而,如果你在相同资源下运行 Theano 和 TensorFlow ,它们将会争夺 GPU 内存,不好的事就会发生。为了在不同的 GPU 上运行完全不同的环境(比如,两个 GPU 运行两种模式),你可以限值 CUDA 在特定的环境中只能看到特定的设备。这样,当你运行 python 代码时,它只能看见(并且分配到)流处理器 CUDA 能看见的 GPU。如果你使用bash,下面这条代码将起作用:
export CUDA_VISIBLE_DEVICES=0,1 # only the first two GPUs are usable
注意:上面所示的 CUDA 设备数字可能与你使用nvidia-smi所看到的设备ID不同!如果你想要 Theano 只在CPU上运行,你可以在 Python 代码中完成。下面是具体的代码,将这行代码放在导入(imports)行的最上方:
import os
os.environ['THEANO_FLAGS'] = "floatX=float32,device=cpu,fastmath=True,ldflags=-lopenblas"
当然,你也可以为 CUDA 内联(inline)环境标识,但是,对于我的模型研发流程来说,更容易记住「一块GPU运行一个程序。」
四、总结
在任何框架中执行端到端的工作流都非常的费劲,TensorFlow 也不例外。 TensorFlow 中的一些事,比如,队列(queues)、某些图运算(graph operations)、资源分配/情境管理(resource allocation/context management)、图标可视化(graph visualization))),对于深度学习场景来说,都是相当陌生的,我们仍在学习利用这些特征的最好方式。其他框架已经提供了其他的一些东西。即使总体概念相似,但是,执行细节还是有区别的。
当社区内的一些人实现了一次非常聪明的攻击或者发现了解决问题的新方法的时候,开源工具最妙的部分就显现出来了。即使大部分人现在仍然处于学习使用 TensorFlow 阶段,但是,我想机会已经出现!让我们展望下一个纪元!