谷歌開(kāi)源 Swift for TensorFlow:我們是不是終于可以放下Python了?
今年3月的TensorFlow開(kāi)發(fā)者峰會(huì)上,Google宣布了Swift For TensorFlow項(xiàng)目,提到這一項(xiàng)目將在4月開(kāi)源。就在4月快過(guò)去的時(shí)候,Google終于在GitHub上公開(kāi)了Swift For TensorFlow的源代碼。
提到Swift語(yǔ)言,大家第一個(gè)想到的是Apple。所以,很自然地,Swift For TensorFlow,乍聽(tīng)起來(lái),似乎僅僅是iOS開(kāi)發(fā)者需要關(guān)心的事。
然而,其實(shí)iOS開(kāi)發(fā)者并不需要關(guān)心Swift For TensorFlow,反而是機(jī)器學(xué)習(xí)開(kāi)發(fā)者都需要關(guān)心Swift For TensorFlow.
Swift For TensorFlow并不面向iOS開(kāi)發(fā)
目前而言,iOS應(yīng)用,如果想集成機(jī)器學(xué)習(xí)功能,那可以通過(guò)Apple提供的Core ML框架:
Core ML的工作流程如上圖所示,上面的Core ML機(jī)器學(xué)習(xí)模型,可以是你從網(wǎng)上找的現(xiàn)成的,也可能是你自己開(kāi)發(fā)的(一般由基于MXNet、TensorFlow等開(kāi)發(fā)的模型轉(zhuǎn)換而來(lái))。所以,其實(shí)Core ML并不在意你的模型是用Python加TensorFlow編寫(xiě)的,還是Swift加TensorFlow編寫(xiě)的,甚至是用MXNet編寫(xiě)的。最終iOS應(yīng)用都是通過(guò)Core ML框架調(diào)用Core ML格式的模型。
所以,Swift For TensorFlow并不面向iOS開(kāi)發(fā),而是要取代Python!
其實(shí)這并不奇怪。雖然人們總是把Swift和Apple聯(lián)系起來(lái),但其實(shí)Swift之父Chris Lattner在Google Brain(順便提下,Python之父Guido Van Rossum在2012年底離開(kāi)Google去了Dropbox)。
Lattner在Swift發(fā)布前發(fā)推:“下個(gè)月我將是第一個(gè)也是唯一一個(gè)有4年swift編程經(jīng)驗(yàn)的人 :-)”
Python有什么不好
雖然Python是現(xiàn)在最流行的機(jī)器學(xué)習(xí)語(yǔ)言,但其實(shí)在機(jī)器學(xué)習(xí)場(chǎng)景下,Python的問(wèn)題還真不少:
- 部署麻煩,運(yùn)行時(shí)依賴(lài)太多。首先,移動(dòng)端應(yīng)用,帶上一大堆Python包,不太現(xiàn)實(shí)。其次,很多公司的生產(chǎn)環(huán)境,基于運(yùn)維需求,不想部署大量Python包。目前的補(bǔ)救方法是,使用Python訓(xùn)練模型,而真正的推理(運(yùn)用)階段用別的語(yǔ)言,比如C++重寫(xiě),這導(dǎo)致了重復(fù)勞動(dòng),拖慢了開(kāi)發(fā)周期。
- 動(dòng)態(tài)類(lèi)型,沒(méi)有編譯期類(lèi)型檢查。這導(dǎo)致很多錯(cuò)誤要到運(yùn)行時(shí)才能發(fā)現(xiàn)。在機(jī)器學(xué)習(xí)場(chǎng)景下,這一問(wèn)題的后果更加嚴(yán)重,因?yàn)闄C(jī)器學(xué)習(xí)模型常常需要訓(xùn)練、運(yùn)行很長(zhǎng)時(shí)間。事實(shí)上,大型的Python項(xiàng)目都非常依賴(lài)單元測(cè)試,通過(guò)單元測(cè)試捕捉很多錯(cuò)誤。但在機(jī)器學(xué)習(xí)的場(chǎng)景下,單元測(cè)試沒(méi)有用武之地。普通程序,跑一遍單元測(cè)試可能也就不到半小時(shí),發(fā)現(xiàn)報(bào)錯(cuò),改過(guò)來(lái)再跑一遍就是了。而機(jī)器學(xué)習(xí),模型跑了半個(gè)月,報(bào)錯(cuò)了,發(fā)現(xiàn)是代碼編寫(xiě)錯(cuò)誤,試求此時(shí)心理陰影面積。
- 并發(fā)困難,臭名昭著的GIL問(wèn)題。而機(jī)器學(xué)習(xí)模型對(duì)算力的貪婪需求,迫切需要靠并發(fā)緩解。
- 性能太差。事實(shí)上,像PyTorch這樣的框架,挖空心思補(bǔ)救Python的性能問(wèn)題。而TensorFlow依靠圖模型(詳見(jiàn)下一節(jié))以及C++、CUDA定制操作來(lái)規(guī)避Python的性能問(wèn)題。使用C++、CUDA定制操作帶來(lái)了兩個(gè)問(wèn)題:
- C++是一門(mén)復(fù)雜的語(yǔ)言。尤其是很多研究人員和數(shù)據(jù)分析人員,并不具備C++經(jīng)驗(yàn)。
- 使用C++/CUDA定制TensorFlow操作導(dǎo)致和硬件緊密耦合(CUDA意味著只能在Nvidia的GPU上跑),遷移到新硬件困難。這一點(diǎn)對(duì)Google來(lái)說(shuō)尤其關(guān)鍵,因?yàn)镚oogle除了用Nvidia的GPU外,自家還有TPU。
使用TensorFlow的時(shí)候,你真的在寫(xiě)Python嗎?
讓我們看一段簡(jiǎn)短的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代碼,但是仔細(xì)看看,這些代碼實(shí)際上干了什么,我們就會(huì)發(fā)現(xiàn),其實(shí)這些代碼構(gòu)建了一個(gè)圖m,然后通過(guò)tf.Session()的run方法運(yùn)行了圖m。
下面一段代碼可能更明顯,我們想迭代數(shù)據(jù)集dataset,在TensorFlow下需要這樣寫(xiě):
- 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迭代數(shù)據(jù)集,而要通過(guò)TensorFlow提供的方法構(gòu)建迭代器。
這一情況可以類(lèi)比使用Python訪(fǎng)問(wèn)SQL數(shù)據(jù)庫(kù):
- t = ('RHAT',)
- q = 'SELECT * FROM stocks WHERE symbol=?'
- c.execute(q, t)
這里,我們構(gòu)造了SQL請(qǐng)求語(yǔ)句,然后通過(guò)Python“執(zhí)行”(execute)這些語(yǔ)句。表面上你在寫(xiě)Python,其實(shí)關(guān)鍵的邏輯在SQL語(yǔ)句里。更準(zhǔn)確地說(shuō),你是在用Python構(gòu)造SQL語(yǔ)句,然后運(yùn)行構(gòu)造的語(yǔ)句。這稱(chēng)為元編程(meta programming)。
同理,在TensorFlow下,表面上你在寫(xiě)Python,其實(shí)關(guān)鍵的邏輯都在TensorFlow圖里。更準(zhǔn)確地說(shuō),你是在用Python構(gòu)造TensorFlow圖,然后運(yùn)行構(gòu)造的圖。
實(shí)際上,2017年萬(wàn)圣節(jié)(10月31日),Google發(fā)布了TensorFlow Eager Execution(貪婪執(zhí)行),讓你可以直接使用Python編程,而不是使用Python元編程TensorFlow圖。
使用Eager Execution,上面兩段TensorFlow代碼可以改寫(xiě)為:
- import tensorflow as tf
- import tensorflow.contrib.eager as tfe
- # 開(kāi)啟貪婪執(zhí)行模式
- 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風(fēng)格的迭代器類(lèi)
- for x in tfe.Iterator(dataset):
- print(x)
你看,TensorFlow明明可以“好好地”用Python編程的嘛。之前為什么要這么大費(fèi)周折地繞一個(gè)圈子?
因?yàn)樾阅堋?/p>
機(jī)器學(xué)習(xí),尤其是現(xiàn)代的復(fù)雜模型,有著極高的算力需求。TensorFlow圖可以很好地應(yīng)對(duì)貪婪的算力需求,而Python則對(duì)此力不從心。
TensorFlow圖專(zhuān)門(mén)針對(duì)機(jī)器學(xué)習(xí)的需求設(shè)計(jì),所以可以很好地優(yōu)化,以提升性能。然而,性能的優(yōu)化并不是沒(méi)有代價(jià)的,為了更好地優(yōu)化,TensorFlow圖對(duì)模型有著許多假設(shè)(這些假設(shè)從另一方面來(lái)說(shuō)也是限制),也要求構(gòu)造、運(yùn)行分階段進(jìn)行(靜態(tài)圖模型)。這影響了模型的靈活性和表達(dá)力,因此,不支持動(dòng)態(tài)圖模型是TensorFlow的一大痛點(diǎn)。
兼顧性能與靈活性
TensorFlow Eager Executation支持動(dòng)態(tài)圖,但是性能很差(還記得我們之前提到的Python的性能和GIL問(wèn)題嗎?);常規(guī)的TensorFlow性能好,但靈活性不行。那么,有沒(méi)有可以兼顧兩者的方案呢?
機(jī)器學(xué)習(xí)社區(qū)在這方面做了很多探索。
傳統(tǒng)上,解釋器(TensorFlow Eager Executation本質(zhì)上是一個(gè)解釋器)的性能,常??梢酝ㄟ^(guò)JIT來(lái)提升。PyTorch就嘗試通過(guò)Tracing JIT(跟蹤即時(shí)編譯)提升性能(基于Python、支持動(dòng)態(tài)圖模型的PyTorch飽受Python性能問(wèn)題的困擾)。簡(jiǎn)單來(lái)說(shuō),Tracing JIT統(tǒng)計(jì)頻繁執(zhí)行的操作,將其編譯為機(jī)器碼執(zhí)行,從而優(yōu)化性能。然而,Tracing JIT有一些問(wèn)題,包括“展開(kāi)的”運(yùn)算可能導(dǎo)致非常長(zhǎng)的trace,可能污染trace導(dǎo)致排錯(cuò)困難,無(wú)法利用“以后的代碼”加以?xún)?yōu)化,等等。
因此,最終TensorFlow選擇了代碼生成這一條路。也就是對(duì)動(dòng)態(tài)圖模型代碼進(jìn)行分析,自動(dòng)生成對(duì)應(yīng)的TensorFlow圖程序。而正是這一選擇,導(dǎo)致了Python的出局。
Python有一大堆動(dòng)態(tài)特性,使得Python無(wú)法被可靠地靜態(tài)分析。
那么,就只有兩個(gè)選擇:
- 對(duì)Python語(yǔ)言進(jìn)行剪裁,得到一個(gè)便于靜態(tài)分析的子集。
- 換語(yǔ)言。
實(shí)際上,Google在2017年開(kāi)源過(guò)一個(gè)Tangent項(xiàng)目。當(dāng)時(shí)做Tangent,是為了解決自動(dòng)微分(automatic differentiation)問(wèn)題——自動(dòng)微分同樣依賴(lài)于對(duì)代碼進(jìn)行分析,而Python語(yǔ)言很難分析。然而,Python的類(lèi)高度依賴(lài)動(dòng)態(tài)特性,很難在這樣的子集上得到支持。而如果連類(lèi)這樣的抽象層級(jí)都不支持的話(huà),那基本上已經(jīng)完全不像Python了。
所以,就換語(yǔ)言吧。
順帶提一下,TensorFlow選擇了靜態(tài)分析后生成代碼并編譯的路線(xiàn),但其實(shí)生成代碼并不一定非得使用編譯器。2010年提出的Lightweight Modular Staging(LMS)技術(shù)可以支持在運(yùn)行時(shí)進(jìn)行代碼生成,無(wú)需編譯器。不過(guò),LMS技術(shù)下,對(duì)控制流程的支持需要一些極少數(shù)語(yǔ)言(比如Scala)才支持的奇異特性。所以即使用LMS,也一樣需要換掉Python。而TensorFlow之所以沒(méi)有選擇LMS,除了可以進(jìn)行LMS的語(yǔ)言極少之外,還有一個(gè)原因是LMS需要用戶(hù)介入,比如,在Scala下,需要將數(shù)據(jù)類(lèi)型顯式地包裹進(jìn)Rep類(lèi)型才能支持LMS。
為什么是Swift?
其實(shí),雖然有這么多編程語(yǔ)言,可供選擇的范圍并不大。
首先,語(yǔ)言的生態(tài)系統(tǒng)很重要。選擇一門(mén)語(yǔ)言,其實(shí)也是選擇這門(mén)語(yǔ)言的生態(tài)系統(tǒng)
,包括開(kāi)發(fā)環(huán)境、調(diào)試工具、文檔、教程、庫(kù)、用戶(hù)。這就排除了創(chuàng)造一門(mén)新語(yǔ)言和使用大多數(shù)學(xué)術(shù)性語(yǔ)言?xún)蓚€(gè)選項(xiàng)。
然后,動(dòng)態(tài)性導(dǎo)致大批語(yǔ)言出局。之前已經(jīng)提過(guò),Python的大量動(dòng)態(tài)特性導(dǎo)致難以可靠地靜態(tài)分析。同理,像R、Ruby、JavaScript之類(lèi)的動(dòng)態(tài)語(yǔ)言,也被排除了。
甚至像TypeScript、Java、C#、Scala這樣的靜態(tài)語(yǔ)言也不行,因?yàn)樵谶@些語(yǔ)言中,動(dòng)態(tài)分發(fā)(dynamic dispatch)非常普遍。具體來(lái)說(shuō),這些語(yǔ)言的主要抽象特征(類(lèi)和接口)其實(shí)基于高度動(dòng)態(tài)的構(gòu)造。比如,在Java中,F(xiàn)oo foo = new Bar();foo.m()調(diào)用的是Bar類(lèi)的m方法,而不是Foo類(lèi)的m方法。
Google自家的Go語(yǔ)言也有這個(gè)問(wèn)題。Go的接口也是動(dòng)態(tài)分發(fā)的。而且,Go沒(méi)有泛型,當(dāng)然,Go的map類(lèi)型具有一些內(nèi)建在語(yǔ)言中的類(lèi)似泛型的特性。如果TensorFlow使用Go語(yǔ)言的話(huà),Tensor也得像map一樣內(nèi)建進(jìn)Go語(yǔ)言才行,而Go語(yǔ)言社區(qū)推崇輕量設(shè)計(jì),在Go語(yǔ)言中內(nèi)建Tensor有悖其核心理念。
那么,剩下的選擇就屈指可數(shù)了:
- C++
- Rust
- Swift
- Julia
C++很復(fù)雜,而且C++有著未定義行為太多的不好名聲,另外,C++大量依賴(lài)C宏和模板元編程。Rust的學(xué)習(xí)曲線(xiàn)非常陡峭。Julia是個(gè)很有趣的語(yǔ)言,雖然它是動(dòng)態(tài)的,但是Julia有很多類(lèi)型專(zhuān)門(mén)化(type specialization)的黑科技,因此也許可以支持TensorFlow圖特征提取。不過(guò),Julia的社區(qū)比Swift小,再加上Swift之父在Google Brain,最終TensorFlow選擇了Swift.
當(dāng)然,需要指出的是,Swift的類(lèi)同樣是高度動(dòng)態(tài)的,但是Swift有sturt和enum這些靜態(tài)的結(jié)構(gòu),而且這些結(jié)構(gòu)同樣支持泛型、方法、協(xié)議(Swift的協(xié)議提供類(lèi)似接口的特性及mixin)。這使得Swift既可以被可靠地靜態(tài)分析,又能有可用的高層抽象。
另外,還記得之前我們提到的Python的缺點(diǎn)嗎?讓我們看看Swift在這些方面的表現(xiàn):
- 部署簡(jiǎn)單。Swift可以編譯為機(jī)器碼,基于Swift編寫(xiě)的ML模型可以編譯為簡(jiǎn)單易部署的.o/.h文件。
- 靜態(tài)類(lèi)型,提供編譯器檢查。另一方面,靜態(tài)類(lèi)型也讓IDE可以更加智能地提示錯(cuò)誤。這在實(shí)際編程中極有幫助。
- Swift在語(yǔ)言層面還不支持并發(fā),但可以很好地配合pthreads使用。而且Swift即將在語(yǔ)言層面加入并發(fā)支持。
- Swift性能很好,對(duì)內(nèi)存的需求也不高。由于Swift在移動(dòng)端使用很普遍,因此Swift社區(qū)很重視性能優(yōu)化。等顯式內(nèi)存所有權(quán)支持加入后,Swift在很多場(chǎng)景下可以取代C++. Swift基于LLVM(別忘了,Swift之父也是LLVM之父),能夠直接訪(fǎng)問(wèn)LLVM底層,而LLVM可以為Nvidia和AMD顯卡生成GPU核。因此,未來(lái)基于Swift定制TensorFlow操作也會(huì)是Swift For TensorFlow的優(yōu)勢(shì)。
當(dāng)然,目前機(jī)器學(xué)習(xí)社區(qū)積累了很多Python組件,因此,Swift For Python也提供了Python互操作性。比如,下面的代碼展示了如何在swift下訪(fǎng)問(wèn)python的numpy庫(kù)(注釋為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即將沒(méi)落?
最后,我們簡(jiǎn)單展望下Python的前途。
想想現(xiàn)在Python主要用于什么場(chǎng)景?
教學(xué)。是的,Python作為教學(xué)語(yǔ)言很合適,但是,僅僅適合作為教學(xué)語(yǔ)言,遠(yuǎn)遠(yuǎn)不夠。上了年紀(jì)的讀者可能還記得小時(shí)候微機(jī)課(是的,那時(shí)候還用“微機(jī)”稱(chēng)呼電腦)上教的Logo語(yǔ)言(操作小烏龜畫(huà)圖),現(xiàn)在還有多少人用呢?曾經(jīng)風(fēng)光無(wú)限的Pascal語(yǔ)言就是為教學(xué)而開(kāi)發(fā)的,現(xiàn)在還有多少人用呢?而且,教學(xué)語(yǔ)言的選擇很大程度上受語(yǔ)言流行程度影響,而不是相反。當(dāng)年鬧得沸沸揚(yáng)揚(yáng)的MIT計(jì)算機(jī)科學(xué)和編程入門(mén)課程從scheme換成python,理由之一就是python更流行。
工具。Python用來(lái)寫(xiě)一些小工具不錯(cuò),因?yàn)镻ython的標(biāo)準(zhǔn)庫(kù)非常出色,寫(xiě)起來(lái)不啰嗦,小工具也不太考慮性能問(wèn)題,項(xiàng)目規(guī)模小,缺乏編譯期類(lèi)型檢測(cè)也不是大問(wèn)題。但這一塊已經(jīng)逐漸被Go蠶食,因?yàn)镚o的標(biāo)準(zhǔn)庫(kù)同樣出色,寫(xiě)起來(lái)也很簡(jiǎn)潔,性能還比Python好,部署起來(lái)還比Python方便。
web開(kāi)發(fā)。Python的web開(kāi)發(fā),其實(shí)更多地是因?yàn)镻ython流行,庫(kù)多,容易招人,而不是真的有多么適合web開(kāi)發(fā)。在web開(kāi)發(fā)方面,Python有太多競(jìng)爭(zhēng)者。古老的PHP仍有活力,PHP 7修正了很多一直被詬病的缺陷,還有Facebook這樣的巨頭為其開(kāi)發(fā)配套工具。Ruby也仍然很受歡迎,很火的Python web開(kāi)發(fā)框架Flask,其實(shí)最早就是借鑒了Ruby的Sinatra框架的設(shè)計(jì)。而高性能web開(kāi)發(fā)越來(lái)越強(qiáng)調(diào)高IO、非阻塞,Node.js和Go在這方面表現(xiàn)出色,Java社區(qū)也出現(xiàn)了Netty和Vert.x(要比庫(kù)多,好招人,誰(shuí)比得過(guò)Java?)。所以,Python在這方面實(shí)在沒(méi)什么優(yōu)勢(shì)。
科學(xué)計(jì)算。目前而言,Python在這方面仍有顯著優(yōu)勢(shì)。然而,曾幾何時(shí),F(xiàn)ortran也在科學(xué)計(jì)算領(lǐng)域占據(jù)統(tǒng)治地位。現(xiàn)在還有多少人用呢?而且,由于Python的性能問(wèn)題,實(shí)際上大量Python的科學(xué)計(jì)算庫(kù)底層大量依賴(lài)C或C++的,有朝一日轉(zhuǎn)移的話(huà),會(huì)比當(dāng)年的Fortran快太多。
機(jī)器學(xué)習(xí)。不得不說(shuō),AI和ML的浪潮,給Python打了強(qiáng)心針。因?yàn)榭茖W(xué)計(jì)算相對(duì)而言,關(guān)注度沒(méi)有那么高。對(duì)比一下R,在統(tǒng)計(jì)分析領(lǐng)域也很流行,但從來(lái)沒(méi)有Python這么受關(guān)注,這是因?yàn)镽并不適合寫(xiě)工具和開(kāi)發(fā)web. 然而,成也蕭何,敗也蕭何,Swift For TensorFlow的出現(xiàn),就說(shuō)明Python的機(jī)器學(xué)習(xí)主流語(yǔ)言的地位并不穩(wěn)固(Python在機(jī)器學(xué)習(xí)領(lǐng)域的流行,更多地是因?yàn)榭茖W(xué)計(jì)算方面的積累和流行程度,而不是真正適合建模機(jī)器學(xué)習(xí)問(wèn)題)。
所以說(shuō),Python的沒(méi)落,很有可能喲~