Swift For TensorFlow开源,敲响了Python的丧钟?

Posted 论智

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Swift For TensorFlow开源,敲响了Python的丧钟?相关的知识,希望对你有一定的参考价值。

作者:weakish
来源:论智

今年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框架:

Swift For TensorFlow开源,敲响了Python的丧钟?

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)。

Swift For TensorFlow开源,敲响了Python的丧钟?

Lattner在Swift发布前发推:“下个月我将是第一个也是唯一一个有4年swift编程经验的人 :-)”


Python有什么不好

虽然Python是现在最流行的机器学习语言,但其实在机器学习场景下,Python的问题还真不少:

  1. 部署麻烦,运行时依赖太多。首先,移动端应用,带上一大堆Python包,不太现实。其次,很多公司的生产环境,基于运维需求,不想部署大量Python包。目前的补救方法是,使用Python训练模型,而真正的推理(运用)阶段用别的语言,比如C++重写,这导致了重复劳动,拖慢了开发周期。

  2. 动态类型,没有编译期类型检查。这导致很多错误要到运行时才能发现。在机器学习场景下,这一问题的后果更加严重,因为机器学习模型常常需要训练、运行很长时间。事实上,大型的Python项目都非常依赖单元测试,通过单元测试捕捉很多错误。但在机器学习的场景下,单元测试没有用武之地。普通程序,跑一遍单元测试可能也就不到半小时,发现报错,改过来再跑一遍就是了。而机器学习,模型跑了半个月,报错了,发现是代码编写错误,试求此时心理阴影面积。

  3. 并发困难,臭名昭著的GIL问题。而机器学习模型对算力的贪婪需求,迫切需要靠并发缓解。

  4. 性能太差。事实上,像PyTorch这样的框架,挖空心思补救Python的性能问题。而TensorFlow依靠图模型(详见下一节)以及C++、CUDA定制操作来规避Python的性能问题。使用C++、CUDA定制操作带来了两个问题:

    • C++是一门复杂的语言。尤其是很多研究人员和数据分析人员,并不具备C++经验。

    • 使用C++/CUDA定制TensorFlow操作导致和硬件紧密耦合(CUDA意味着只能在Nvidia的GPU上跑),迁移到新硬件困难。这一点对Google来说尤其关键,因为Google除了用Nvidia的GPU外,自家还有TPU。

使用TensorFlow的时候,你真的在写Python吗?

让我们看一段简短的TensorFlow代码示例:

 
   
   
 
  1. import tensorflow as tf

  2. x = tf.placeholder(tf.float32, shape=[1, 1])

  3. m = tf.matmul(x, x)

  4. with tf.Session() as sess:

  5.    print(sess.run(m, feed_dict={x: [[2.]]}))

这上面是合法的Python代码,但是仔细看看,这些代码实际上干了什么,我们就会发现,其实这些代码构建了一个图m,然后通过tf.Session()run方法运行了图m。

下面一段代码可能更明显,我们想迭代数据集dataset,在TensorFlow下需要这样写:

 
   
   
 
  1. dataset = tf.data.Dataset.range(100)

  2. iterator = dataset.make_one_shot_iterator()

  3. next_element = iterator.get_next()

  4. for i in range(100):

  5.  value = sess.run(next_element)

  6.  assert i == value

我们看到,我们不能直接使用Python迭代数据集,而要通过TensorFlow提供的方法构建迭代器。

这一情况可以类比使用Python访问SQL数据库:

 
   
   
 
  1. t = ('RHAT',)

  2. q = 'SELECT * FROM stocks WHERE symbol=?'

  3. 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代码可以改写为:

 
   
   
 
  1. import tensorflow as tf

  2. import tensorflow.contrib.eager as tfe

  3. # 开启贪婪执行模式

  4. tfe.enable_eager_execution()

  5. x = [[2.]]

  6. m = tf.matmul(x, x)

  7. print(m)

 
   
   
 
  1. dataset = tf.data.Dataset.from_tensor_slices([1, 2, 3, 4, 5, 6])

  2. dataset = dataset.map(tf.square).shuffle(2).batch(2)

  3. # Python风格的迭代器类

  4. for x in tfe.Iterator(dataset):

  5.  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的出局。

Swift For TensorFlow开源,敲响了Python的丧钟?

图程序提取(黄色方框)是Swift For TensorFlow的关键技术

Python有一大堆动态特性,使得Python无法被可靠地静态分析。

那么,就只有两个选择:

  1. 对Python语言进行剪裁,得到一个便于静态分析的子集。

  2. 换语言。

实际上,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有悖其核心理念。

Swift For TensorFlow开源,敲响了Python的丧钟?

在Swift For TensorFlow发布的同一天,Go发布了新设计的logo

那么,剩下的选择就屈指可数了:

  • 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在这些方面的表现:

  1. 部署简单。Swift可以编译为机器码,基于Swift编写的ML模型可以编译为简单易部署的.o/.h文件。

  2. 静态类型,提供编译器检查。另一方面,静态类型也让IDE可以更加智能地提示错误。这在实际编程中极有帮助。

  3. Swift在语言层面还不支持并发,但可以很好地配合pthreads使用。而且Swift即将在语言层面加入并发支持。

  4. 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代码):

 
   
   
 
  1. import Python

  2. let np = Python.import("numpy")             // import numpy as np

  3. let a = np.arange(15).reshape(3, 5)         // a = np.arange(15).reshape(3, 5)

  4. let b = np.array([6, 7, 8])                 // b = np.array([6, 7, 8])


Python即将没落?

最后,我们简单展望下Python的前途。

想想现在Python主要用于什么场景?

  1. 教学。是的,Python作为教学语言很合适,但是,仅仅适合作为教学语言,远远不够。上了年纪的读者可能还记得小时候微机课(是的,那时候还用“微机”称呼电脑)上教的Logo语言(操作小乌龟画图),现在还有多少人用呢?曾经风光无限的Pascal语言就是为教学而开发的,现在还有多少人用呢?而且,教学语言的选择很大程度上受语言流行程度影响,而不是相反。当年闹得沸沸扬扬的MIT计算机科学和编程入门课程从scheme换成python,理由之一就是python更流行。

  2. 工具。Python用来写一些小工具不错,因为Python的标准库非常出色,写起来不啰嗦,小工具也不太考虑性能问题,项目规模小,缺乏编译期类型检测也不是大问题。但这一块已经逐渐被Go蚕食,因为Go的标准库同样出色,写起来也很简洁,性能还比Python好,部署起来还比Python方便。

  3. 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在这方面实在没什么优势。

  4. 科学计算。目前而言,Python在这方面仍有显著优势。然而,曾几何时,Fortran也在科学计算领域占据统治地位。现在还有多少人用呢?而且,由于Python的性能问题,实际上大量Python的科学计算库底层大量依赖C或C++的,有朝一日转移的话,会比当年的Fortran快太多。

  5. 机器学习。不得不说,AI和ML的浪潮,给Python打了强心针。因为科学计算相对而言,关注度没有那么高。对比一下R,在统计分析领域也很流行,但从来没有Python这么受关注,这是因为R并不适合写工具和开发web. 然而,成也萧何,败也萧何,Swift For TensorFlow的出现,就说明Python的机器学习主流语言的地位并不稳固(Python在机器学习领域的流行,更多地是因为科学计算方面的积累和流行程度,而不是真正适合建模机器学习问题)。

所以说,Python的没落,很有可能哟~

以上是关于Swift For TensorFlow开源,敲响了Python的丧钟?的主要内容,如果未能解决你的问题,请参考以下文章

谷歌宣布Swift for TensorFlow今日开源(附安装包)

Swift for TensorFlow 已在 GitHub 上开源,Tensor 成为 Swift 语言里面的一等公民

谷歌又开源了: Swift for TensorFlow

可以抛弃 Python 了?Google 开源 Swift for TensorFlow 意味着什么

TensorFlow Lite for Android示例

Code For Better 谷歌开发者之声 ——Tensorflow与深度学习