源码

首页 » 归档 » 源码 » 谷歌开源 Swift for TensorFlow:我们是不是终于可以放下Python了?

谷歌开源 Swift for TensorFlow:我们是不是终于可以放下Python了?

今年3月的TensorFlow开发者峰会上,Google宣布了Swift For TensorFlow项目,提到这一项目将在4月开源。就在4月快过去的时候,Google终于在GitHub上公开了Swift For TensorFlow的源代码。

2846162549-5ae3274dd5ae6-1000x388.gif

提到Swift语言,大家第一个想到的是Apple。所以,很自然地,Swift For TensorFlow,乍听起来,似乎仅仅是iOS开发者需要关心的事。

然而,其实iOS开发者并不需要关心Swift For TensorFlow,反而是机器学习开发者都需要关心Swift For TensorFlow.

Swift For TensorFlow并不面向iOS开发

目前而言,iOS应用,如果想集成机器学习功能,那可以通过Apple提供的Core ML框架:

4025711511-5ae2e6700001d.gif

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

3608592457-5ae2f0fdebfcf.gif

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代码示例:

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

1583072946-5ae30281cb688.gif

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

Python有一大堆动态特性,使得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有悖其核心理念。

3214817523-5ae31956e1d0a.gif

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

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

  1. C++

  2. Rust

  3. Swift

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

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的没落,很有可能哟~

作者:论智原创

(0)

本文由 投稿者 创作,文章地址:https://blog.isoyu.com/archives/gugekaiyuan-swift-for-tensorflowwomenshibushizhongyukeyifangxiapythonle.html
采用知识共享署名4.0 国际许可协议进行许可。除注明转载/出处外,均为本站原创或翻译,转载前请务必署名。最后编辑时间为:5月 9, 2018 at 06:01 下午

热评文章

发表回复

[必填]

我是人?

提交后请等待三秒以免造成未提交成功和重复