观点|哈哈,TensorFlow被吐槽了吧
作者 | Nico
参与 | shawn
今天,一篇吐槽TensorFlow的文章在网上刷屏,到底是怎么回事呢?来看这位作者的抱怨有没有道理。
每隔几个月,我都会Google一下“Tensorflow sucks(Tensorflow 烂透了)”或“f*** Tensorflow”,希望能找到志同道合的黑粉。尽管Tensorflow面世已约两年,我还是找不到一个令我满意的批评Tensorflow的言论,也许是我用的搜索引擎不对,但是我认为罪魁祸首是一种被称为“谷歌嫉妒”的现象,具体表现为全世界的工程师都臆想:
在谷歌工作的人比你聪明,比你有能力。
如果懂Tensorflow,就能在谷歌谋得一份深度学习的职位(年轻人,醒醒吧)
你所在的初创公司在使用Tensorflow,如果在博客上夸奖Tensorflow的优点,谷歌看到后也许会想收购你的公司。
如果get不到Tensorflow那反直觉的设计,你就资质平庸。
现在,让我们把这些臆想抛在脑后,客观地看看Tensorflow。
当Tensorflow刚面世的时候,其开发者向我们承诺:有了Tensorflow,就再也不用忍受那些不是设计蹩脚就是缺乏维护的深度学习框架了(例如 https://github.com/BVLC/caffe/issues)。结果,我们得到的这个深度学习框架虽然可以与Java相提并论(“一次编写,到处运行” ——译者注:这是Sun Microsystem(于2010年被Oracle收购)为宣传Java语言的跨平台特性而提出的口号),但是完全陈述性的模式(paradigm)却使它用起来没那么有趣。真是讨厌!
哪里出错了呢?虽然谷歌的目的是开发出一个让所有人都能使用的工具,但是它做出的这个产品似乎并不能让所有人都满意。
对于研究人员而言,Tensorflow不仅学起来难,而且用起来也难。研究注重的是灵活性,缺乏灵活性这个缺陷已深入到Tensorflow 的“骨髓”。
在Tensorflow中,如果想提取神经网络中间层的值,你需要先定义一个graph,然后将数据以字典(dictionary)的形式输入到这个graph中,执行graph。噢,别忘了将中间层作为graph的输出添加到网络中,否则就无法得出中间层的值。好吧,虽然很麻烦,但是行得通。
想有条件地执行层(例如:在每生成一个end-of-sentence (EOS) token时停止运行的循环神经网络)?哪一天等你完成了,使用Pytorch的人也败了3家AI初创公司了。
对于像我这样的机器学习从业人员,Tensorflow也算不上明智之选。这个框架陈述性的设计语言使得调试(debugging)更为困难。能在安卓或iOS系统上运行模型这个优点固然很好,但是当你看到框架的二进制文件(binaries)有多大(20MB以上),或者试图检查几乎不存在的C++文件,或者当你想进行某种条件式的网络执行(在mobile这种资源不足的情况中非常有用)时,你就不会这样认为了。
对比其他框架
Tensorflow的开发者都是深度学习领域的大拿,这一点毋庸置疑。但是,名望最大的原Tensorflow开发者贾扬清(Yangquing Jia)最近离开谷歌转投Facebook大营,专注于快速崛起的Caffe2项目(https://github.com/caffe2/caffe2/graphs/contributors, https://github.com/caffe2/caffe2/issues)。和Tensorflow不同,Caffe2允许用户通过一行代码用某一条数据执行某一网络层。
此外,Pytorch正越来越受到顶尖AI研究人员的青睐。Torch用户因编写Lua代码执行简单的string操作而饱受重复性劳损(RSI)折磨,但是他们并没有涌向Tensorflow,而是转投Pytorch。看来对于顶尖AI实验室而言,Tensorflow还是不够好。抱歉了,谷歌。
最让我感兴趣的问题是:为什么谷歌偏偏为Tensorflow选择了一个完全陈述性的模式,全然不顾这种模式显而易见的缺陷。他们是否认为,将所有计算囊括到一个计算图(computation graph)中可以使模型在自家TPU(张量处理器)上运行,从而在深度学习驱动型应用的云计算服务上与英伟达(Nvidia)展开竞争,切断其数百万美元的财路?这很难说。总的来说,对公众而言Tensorflow算不上是一个完全开源的项目。如果开发者能完善Tensorflow的设计,那我就没话可说。但是,与谷歌的其他开源项目(如Protobuf、Golang和Kubernetes)相比,Tensorflow落后的不是一点点。
虽然陈述性模式对于UI编程而言很有用,但是对于深度学习而言它并不是一个理想选择,原因有很多。
拿React Javascript库来说,它是当今交互式网页应用程序设计的标准选择。在React中,开发者不知道数据在应用程序中如何传递是合理的,因为Javascript的执行速度要比DOM (Document Object Model,文档对象模型)的更新速度快好几个数量级。只要终端用户的体验“足够好”,Reacr开发者就不必操心状态的传播机制。
但是在深度学习中,一个层就可以执行数百万个FLOP!深度学习研究人员的目标就是理解计算过程并实现精确控制,因为他们要不断突破极限(例如动态神经网络),并找出获得中间层结果的简易途径。
实例
举个简单的例子:训练一个模型,让模型将其输入乘以3
首先,我们先看Tensorflow的代码:
import tensorflow as tf
import numpy as np
X = tf.placeholder("float")
Y = tf.placeholder("float")
W = tf.Variable(np.random.random(), name="weight")
pred = tf.multiply(X, W)
cost = tf.reduce_sum(tf.pow(pred-Y, 2))
optimizer = tf.train.GradientDescentOptimizer(0.01).minimize(cost)
init = tf.global_variables_initializer()
with tf.Session() as sess:
sess.run(init)
for t in range(10000):
x = np.array(np.random.random()).reshape((1, 1, 1, 1))
y = x * 3
(_, c) = sess.run([optimizer, cost], feed_dict={X: x, Y: y})
print c
然后再看Pytorch完成相同任务的代码:
import numpy as np
import torch
from torch.autograd import Variable
model = torch.nn.Linear(1, 1)
loss_fn = torch.nn.MSELoss(size_average=False)
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
for t in range(10000):
x = Variable(torch.from_numpy(np.random.random((1,1)).astype(np.float32)))
y = x * 3
y_pred = model(x)
loss = loss_fn(y_pred, y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
print loss.data[0]
虽然Pytorch的代码比TensorFlow代码少一行,但是它的操作过程更加简洁,训练过程中的语法结构与实际的学习流程配合得更加紧密。
向前传递输入
生成损失
计算梯度
反向传播
在TensorFlow中,核心操作则是神秘的sess.run调用。
为什么要再写一些结尾部分让人难以理解且难以维护的代码呢?客观而言,Pytorch的界面要比Tensorflow好很多。好的不是一点点。
结论
对于快速原型(rapid prototyping)而言,谷歌创造的TensorFlow框架太过于低级,无法使用;但是在尖端研究或者资源有限的生产环境中,TensorFlow却因太高级而无法轻松使用。
老实说,当你为了确保你的库能够使用,于是在这个高级库的基础上再多建了好几个开源高级库,你一定知道哪里肯定出大错了:
http://tflearn.org/
https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/slim
https://github.com/fchollet/keras
https://github.com/tensorflow/skflow
注意:我承认Tensorboard(TensorFlow的监测工具)真的是一个很好的工具。如果你想为你的机器学习项目制定一个很好的监测方案,你可以看看Losswise (https://losswise.com)。我开发这个工具的目的是,为了让像我一样的机器学习开发者能在任何机器学习库中解耦追踪(decouple track)模型的性能,并且能够用上许多Tensorboard不提供的好功能。
原文地址:
http://nicodjimenez.github.io/2017/10/08/tensorflow.html