谷歌开源 Swift for TensorFlow:我们是不是终于可以放下Python了?
今年3月的TensorFlow开发者峰会上,Google宣布了Swift For TensorFlow项目,提到这一项目将在4月开源。就在4月快过去的时候,Google终于在GitHub上公开了Swift For TensorFlow的源代码。
提到Swift语言,大家第一个想到的是Apple。所以,很自然地,Swift For TensorFlow,乍听起来,似乎仅仅是iOS开发者需要关心的事。
然而,其实iOS开发者并不需要关心Swift For TensorFlow,反而是机器学习开发者都需要关心Swift For TensorFlow.
Swift For TensorFlow并不面向iOS开发
目前而言,iOS应用,如果想集成机器学习功能,那可以通过Apple提供的Core ML框架:
Core ML的工作流程如上图所示,上面的Core ML机器学习模型,可以是你从网上找的现成的,也可能是你自己开发的(一般由基于MXNet、TensorFlow等开发的模型转换而来)。所以,其实Core ML并不在意你的模型是用Python加TensorFlow编写的,还是Swift加TensorFlow编写的,甚至是用MXNet编写的。最终iOS应用都是通过Core ML框架调用Core ML格式的模型。
所以,Swift For TensorFlow并不面向iOS开发,而是要取代Python!
其实这并不奇怪。虽然人们总是把Swift和Apple联系起来,但其实Swift之父Chris Lattner在Google Brain(顺便提下,Python之父Guido Van Rossum在2012年底离开Google去了Dropbox)。
Python有什么不好
虽然Python是现在最流行的机器学习语言,但其实在机器学习场景下,Python的问题还真不少:
- 部署麻烦,运行时依赖太多。首先,移动端应用,带上一大堆Python包,不太现实。其次,很多公司的生产环境,基于运维需求,不想部署大量Python包。目前的补救方法是,使用Python训练模型,而真正的推理(运用)阶段用别的语言,比如C++重写,这导致了重复劳动,拖慢了开发周期。
- 动态类型,没有编译期类型检查。这导致很多错误要到运行时才能发现。在机器学习场景下,这一问题的后果更加严重,因为机器学习模型常常需要训练、运行很长时间。事实上,大型的Python项目都非常依赖单元测试,通过单元测试捕捉很多错误。但在机器学习的场景下,单元测试没有用武之地。普通程序,跑一遍单元测试可能也就不到半小时,发现报错,改过来再跑一遍就是了。而机器学习,模型跑了半个月,报错了,发现是代码编写错误,试求此时心理阴影面积。
- 并发困难,臭名昭著的GIL问题。而机器学习模型对算力的贪婪需求,迫切需要靠并发缓解。
- 性能太差。事实上,像PyTorch这样的框架,挖空心思补救Python的性能问题。而TensorFlow依靠图模型(详见下一节)以及C++、CUDA定制操作来规避Python的性能问题。使用C++、CUDA定制操作带来了两个问题:
- C++是一门复杂的语言。尤其是很多研究人员和数据分析人员,并不具备C++经验。
- 使用C++/CUDA定制TensorFlow操作导致和硬件紧密耦合(CUDA意味着只能在Nvidia的GPU上跑),迁移到新硬件困难。这一点对Google来说尤其关键,因为Google除了用Nvidia的GPU外,自家还有TPU。
使用TensorFlow的时候,你真的在写Python吗?
让我们看一段简短的TensorFlow代码示例:
import tensorflow as tf
x = tf.placeholder(tf.float32, shape=[1, 1])
m = tf.matmul(x, x)
with tf.Session() as sess:
print(sess.run(m, feed_dict={x: [[2.]]}))
这上面是合法的Python代码,但是仔细看看,这些代码实际上干了什么,我们就会发现,其实这些代码构建了一个图m,然后通过tf.Session()
的run
方法运行了图m。
下面一段代码可能更明显,我们想迭代数据集dataset
,在TensorFlow下需要这样写:
dataset = tf.data.Dataset.range(100)
iterator = dataset.make_one_shot_iterator()
next_element = iterator.get_next()
for i in range(100):
value = sess.run(next_element)
assert i == value
我们看到,我们不能直接使用Python迭代数据集,而要通过TensorFlow提供的方法构建迭代器。
这一情况可以类比使用Python访问SQL数据库:
t = ('RHAT',)
q = 'SELECT * FROM stocks WHERE symbol=?'
c.execute(q, t)
这里,我们构造了SQL请求语句,然后通过Python“执行”(execute
)这些语句。表面上你在写Python,其实关键的逻辑在SQL语句里。更准确地说,你是在用Python构造SQL语句,然后运行构造的语句。这称为元编程(meta programming)。
同理,在TensorFlow下,表面上你在写Python,其实关键的逻辑都在TensorFlow图里。更准确地说,你是在用Python构造TensorFlow图,然后运行构造的图。
实际上,2017年万圣节(10月31日),Google发布了TensorFlow Eager Execution(贪婪执行),让你可以直接使用Python编程,而不是使用Python元编程TensorFlow图。
使用Eager Execution,上面两段TensorFlow代码可以改写为:
import tensorflow as tf
import tensorflow.contrib.eager as tfe
# 开启贪婪执行模式
tfe.enable_eager_execution()
x = [[2.]]
m = tf.matmul(x, x)
print(m)
dataset = tf.data.Dataset.from_tensor_slices([1, 2, 3, 4, 5, 6])
dataset = dataset.map(tf.square).shuffle(2).batch(2)
# Python风格的迭代器类
for x in tfe.Iterator(dataset):
print(x)
你看,TensorFlow明明可以“好好地”用Python编程的嘛。之前为什么要这么大费周折地绕一个圈子?
因为性能。
机器学习,尤其是现代的复杂模型,有着极高的算力需求。TensorFlow图可以很好地应对贪婪的算力需求,而Python则对此力不从心。
TensorFlow图专门针对机器学习的需求设计,所以可以很好地优化,以提升性能。然而,性能的优化并不是没有代价的,为了更好地优化,TensorFlow图对模型有着许多假设(这些假设从另一方面来说也是限制),也要求构造、运行分阶段进行(静态图模型)。这影响了模型的灵活性和表达力,因此,不支持动态图模型是TensorFlow的一大痛点。
兼顾性能与灵活性
TensorFlow Eager Executation支持动态图,但是性能很差(还记得我们之前提到的Python的性能和GIL问题吗?);常规的TensorFlow性能好,但灵活性不行。那么,有没有可以兼顾两者的方案呢?
机器学习社区在这方面做了很多探索。
传统上,解释器(TensorFlow Eager Executation本质上是一个解释器)的性能,常常可以通过JIT来提升。PyTorch就尝试通过Tracing JIT(跟踪即时编译)提升性能(基于Python、支持动态图模型的PyTorch饱受Python性能问题的困扰)。简单来说,Tracing JIT统计频繁执行的操作,将其编译为机器码执行,从而优化性能。然而,Tracing JIT有一些问题,包括“展开的”运算可能导致非常长的trace,可能污染trace导致排错困难,无法利用“以后的代码”加以优化,等等。
因此,最终TensorFlow选择了代码生成这一条路。也就是对动态图模型代码进行分析,自动生成对应的TensorFlow图程序。而正是这一选择,导致了Python的出局。
那么,就只有两个选择:
- 对Python语言进行剪裁,得到一个便于静态分析的子集。
- 换语言。
实际上,Google在2017年开源过一个Tangent项目。当时做Tangent,是为了解决自动微分(automatic differentiation)问题——自动微分同样依赖于对代码进行分析,而Python语言很难分析。然而,Python的类高度依赖动态特性,很难在这样的子集上得到支持。而如果连类这样的抽象层级都不支持的话,那基本上已经完全不像Python了。
所以,就换语言吧。
顺带提一下,TensorFlow选择了静态分析后生成代码并编译的路线,但其实生成代码并不一定非得使用编译器。2010年提出的Lightweight Modular Staging(LMS)技术可以支持在运行时进行代码生成,无需编译器。不过,LMS技术下,对控制流程的支持需要一些极少数语言(比如Scala)才支持的奇异特性。所以即使用LMS,也一样需要换掉Python。而TensorFlow之所以没有选择LMS,除了可以进行LMS的语言极少之外,还有一个原因是LMS需要用户介入,比如,在Scala下,需要将数据类型显式地包裹进Rep类型才能支持LMS。
为什么是Swift?
其实,虽然有这么多编程语言,可供选择的范围并不大。
首先,语言的生态系统很重要。选择一门语言,其实也是选择这门语言的生态系统
,包括开发环境、调试工具、文档、教程、库、用户。这就排除了创造一门新语言和使用大多数学术性语言两个选项。
然后,动态性导致大批语言出局。之前已经提过,Python的大量动态特性导致难以可靠地静态分析。同理,像R、Ruby、JavaScript之类的动态语言,也被排除了。
甚至像TypeScript、Java、C#、Scala这样的静态语言也不行,因为在这些语言中,动态分发(dynamic dispatch)非常普遍。具体来说,这些语言的主要抽象特征(类和接口)其实基于高度动态的构造。比如,在Java中,Foo foo = new Bar();foo.m()
调用的是Bar
类的m
方法,而不是Foo
类的m
方法。
Google自家的Go语言也有这个问题。Go的接口也是动态分发的。而且,Go没有泛型,当然,Go的map类型具有一些内建在语言中的类似泛型的特性。如果TensorFlow使用Go语言的话,Tensor
也得像map
一样内建进Go语言才行,而Go语言社区推崇轻量设计,在Go语言中内建Tensor
有悖其核心理念。
- C++
- Rust
- Swift
- Julia
C++很复杂,而且C++有着未定义行为太多的不好名声,另外,C++大量依赖C宏和模板元编程。Rust的学习曲线非常陡峭。Julia是个很有趣的语言,虽然它是动态的,但是Julia有很多类型专门化(type specialization)的黑科技,因此也许可以支持TensorFlow图特征提取。不过,Julia的社区比Swift小,再加上Swift之父在Google Brain,最终TensorFlow选择了Swift.
当然,需要指出的是,Swift的类同样是高度动态的,但是Swift有sturt和enum这些静态的结构,而且这些结构同样支持泛型、方法、协议(Swift的协议提供类似接口的特性及mixin)。这使得Swift既可以被可靠地静态分析,又能有可用的高层抽象。
另外,还记得之前我们提到的Python的缺点吗?让我们看看Swift在这些方面的表现:
- 部署简单。Swift可以编译为机器码,基于Swift编写的ML模型可以编译为简单易部署的
.o/.h
文件。 - 静态类型,提供编译器检查。另一方面,静态类型也让IDE可以更加智能地提示错误。这在实际编程中极有帮助。
- Swift在语言层面还不支持并发,但可以很好地配合pthreads使用。而且Swift即将在语言层面加入并发支持。
- Swift性能很好,对内存的需求也不高。由于Swift在移动端使用很普遍,因此Swift社区很重视性能优化。等显式内存所有权支持加入后,Swift在很多场景下可以取代C++. Swift基于LLVM(别忘了,Swift之父也是LLVM之父),能够直接访问LLVM底层,而LLVM可以为Nvidia和AMD显卡生成GPU核。因此,未来基于Swift定制TensorFlow操作也会是Swift For TensorFlow的优势。
当然,目前机器学习社区积累了很多Python组件,因此,Swift For Python也提供了Python互操作性。比如,下面的代码展示了如何在swift下访问python的numpy库(注释为python代码):
import Python
let np = Python.import("numpy") // import numpy as np
let a = np.arange(15).reshape(3, 5) // a = np.arange(15).reshape(3, 5)
let b = np.array([6, 7, 8]) // b = np.array([6, 7, 8])
Python即将没落?
最后,我们简单展望下Python的前途。
想想现在Python主要用于什么场景?
- 教学。是的,Python作为教学语言很合适,但是,仅仅适合作为教学语言,远远不够。上了年纪的读者可能还记得小时候微机课(是的,那时候还用“微机”称呼电脑)上教的Logo语言(操作小乌龟画图),现在还有多少人用呢?曾经风光无限的Pascal语言就是为教学而开发的,现在还有多少人用呢?而且,教学语言的选择很大程度上受语言流行程度影响,而不是相反。当年闹得沸沸扬扬的MIT计算机科学和编程入门课程从scheme换成python,理由之一就是python更流行。
- 工具。Python用来写一些小工具不错,因为Python的标准库非常出色,写起来不啰嗦,小工具也不太考虑性能问题,项目规模小,缺乏编译期类型检测也不是大问题。但这一块已经逐渐被Go蚕食,因为Go的标准库同样出色,写起来也很简洁,性能还比Python好,部署起来还比Python方便。
- web开发。Python的web开发,其实更多地是因为Python流行,库多,容易招人,而不是真的有多么适合web开发。在web开发方面,Python有太多竞争者。古老的PHP仍有活力,PHP 7修正了很多一直被诟病的缺陷,还有Facebook这样的巨头为其开发配套工具。Ruby也仍然很受欢迎,很火的Python web开发框架Flask,其实最早就是借鉴了Ruby的Sinatra框架的设计。而高性能web开发越来越强调高IO、非阻塞,Node.js和Go在这方面表现出色,Java社区也出现了Netty和Vert.x(要比库多,好招人,谁比得过Java?)。所以,Python在这方面实在没什么优势。
- 科学计算。目前而言,Python在这方面仍有显著优势。然而,曾几何时,Fortran也在科学计算领域占据统治地位。现在还有多少人用呢?而且,由于Python的性能问题,实际上大量Python的科学计算库底层大量依赖C或C++的,有朝一日转移的话,会比当年的Fortran快太多。
- 机器学习。不得不说,AI和ML的浪潮,给Python打了强心针。因为科学计算相对而言,关注度没有那么高。对比一下R,在统计分析领域也很流行,但从来没有Python这么受关注,这是因为R并不适合写工具和开发web. 然而,成也萧何,败也萧何,Swift For TensorFlow的出现,就说明Python的机器学习主流语言的地位并不稳固(Python在机器学习领域的流行,更多地是因为科学计算方面的积累和流行程度,而不是真正适合建模机器学习问题)。
所以说,Python的没落,很有可能哟~
作者:论智原创
你也许感兴趣的:
- 【外评】Python 为何如此糟糕…
- 【外评】用 Python 解释 Rust 背后的思想或理念
- Python 版本之间的主要变化摘要
- 【外评】Python 与苹果应用商店的拒绝作斗争
- 【外评】使用不安全的 Python 将速度提高 100 倍
- 谷歌裁掉整个 Python 团队!PyTorch 创始人急得直骂人:“WTF!核心语言团队无可替换”
- 谷歌Python团队全员被裁——负责内部Python所有基础设施、曾对数亿行代码执行自动重构
- 【译文】Python–一种深受喜爱但永远存在缺陷的语言
- 再同意不过了
- 【译文】减轻 Python 打包的痛苦
你对本文的反应是: