小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

谷歌為何要養(yǎng)蘋果的親兒子Swift?原來意在可微分編程

 李端有 2020-04-10

選自tryolabs

作者:JoaquínThu

機器之心編譯

參與:Panda、Racoon

Python 并不完美,而 Swift 則正在谷歌和蘋果的共同養(yǎng)育下茁壯成長,有望成長為深度學習領(lǐng)域一門新的主要語言。近日,Tryolabs 的研究工程師 Joaquín Alori 發(fā)布了一篇長文,從 Python 的缺點一路談到了谷歌在 Swift 機器學習方面的大計劃,并且文中還給出了相當多一些具體的代碼實例??晌⒎志幊陶嫒?Yann LeCun 所言的那樣會成為新一代的程序開發(fā)范式嗎?Swift 又將在其中扮演怎樣的角色?也許你能在這篇文章中找到答案。

谷歌為何要養(yǎng)蘋果的親兒子Swift?原來意在可微分編程

近日,國外一小哥在 tryolabs 上寫了一篇博文,為我們詳盡地介紹了 Python 的缺陷與相比之下 Swift 的優(yōu)勢,解釋了為什么 Swift 版的 TensorFlow 未來在機器學習領(lǐng)域有非常好的發(fā)展前景。其中包含大量代碼示例,展示了如何用 Swift 優(yōu)雅地編寫機器學習程序。

兩年之前,谷歌的一個小團隊開始研究讓 Swift 語言成為首個在語言層面上一流地整合了可微分編程能力的主流語言。該項目的研究范圍著實與眾不同,而且也取得了一些出色的初期研究成果,似乎離公眾應(yīng)用也并不很遠了。

盡管如此,該項目卻并未在機器學習社區(qū)引起多大反響,而且很多實踐者還對此渾然不覺。造成這種結(jié)果的主要原因之一是語言的選擇。機器學習社區(qū)的很多人很大程度上并不關(guān)心 Swift,谷歌研究它也讓人們感到疑惑;因為 Swift 主要用來開發(fā) iOS 應(yīng)用而已,在數(shù)據(jù)科學生態(tài)系統(tǒng)中幾乎毫無存在感。

不過,事實卻并非如此,只需粗略地看看谷歌這個項目,就能發(fā)現(xiàn)這是一個龐大且雄心勃勃的計劃,甚至足以將 Swift 確立為機器學習領(lǐng)域的關(guān)鍵成員。此外,即使我們 Tryolabs 也主要使用 Python,但我們還是認為 Swift 是一個絕佳的選擇;也因此,我們決定寫這篇文章以幫助世人了解谷歌的計劃。

但在深入 Swift 以及「可微分編程」的真正含義之前,我們應(yīng)該先回顧一下當前的狀況。

Python,你怎么了?!

到目前為止,Python 都依然是機器學習領(lǐng)域最常被使用的語言,谷歌也有大量用 Python 編寫的機器學習軟件庫和工具。那么,為什么還要用 Swift?Python 有什么問題嗎?

直接說吧,Python 太慢了。另外,Python 的并行性表現(xiàn)并不好。

為了應(yīng)對這些缺點,大多數(shù)機器學習項目在運行計算密集型算法時,都會使用用 C/C++/Fortran/CUDA 寫的軟件庫,然后再使用 Python 將不同的底層運算組合到一起。對于大部分項目而言,這種做法其實效果很好;但總體概況而言,這會產(chǎn)生一些問題。我們先看看其中一些問題。

外部二進制文件

為每個計算密集型運算都調(diào)用外部二進制文件會限制開發(fā)者的工作,讓他們只能在算法的表層的一小部分上進行開發(fā)。比如,編寫自定義的卷積執(zhí)行方式是無法實現(xiàn)的,除非開發(fā)者愿意使用 C 等語言來進行開發(fā)。大部分程序員都不會選擇這么做,要么是因為他們沒有編寫低層高性能代碼的經(jīng)驗,要么則是因為在 Python 開發(fā)環(huán)境與某個低層語言環(huán)境之間來回切換會變得過于麻煩。

這會造成一種不幸的情況:程序員會盡力盡量少地寫復雜代碼,并且默認情況更傾向于調(diào)用外部軟件庫的運算。對于機器學習這樣動態(tài)發(fā)展的領(lǐng)域來說,這并不是一個好現(xiàn)象,因為很多東西都還并未確定下來,還非常需要新想法。

對軟件庫的抽象理解

讓 Python 代碼調(diào)用更低層代碼并不如將 Python 函數(shù)映射成 C 函數(shù)那么簡單。不幸的現(xiàn)實是:機器學習軟件庫的創(chuàng)建者必須為了性能而做出一些開發(fā)上的選擇,而這又會讓事情變得更加復雜。舉個例子,在 TensorFlow 圖(graph)模式中(這是該軟件庫中唯一的性能模式),你的 Python 代碼在你認為會運行時常常并不運行。在這里,Python 實際上的作用是底層 TensorFlow 圖的某種元編程(metaprogramming)語言。

其開發(fā)流程為:開發(fā)者首先使用 Python 定義一個網(wǎng)絡(luò),然后 TensorFlow 后端使用該定義來構(gòu)建網(wǎng)絡(luò)并將其編譯為一個 blob,而開發(fā)者卻再也無法訪問其內(nèi)部。編譯之后,該網(wǎng)絡(luò)才終于可以運行,開發(fā)者可以開始向其饋送數(shù)據(jù)以便訓練和推理。這種工作方式讓調(diào)試工作變得非常困難,因為在網(wǎng)絡(luò)運行時,你沒法使用 Python 了解其中究竟發(fā)生了什么。你也沒法使用 pdb 等方法。即使你想使用古老但好用的 print 調(diào)試方法,你也只能使用 tf.print 并在你的網(wǎng)絡(luò)中構(gòu)建一個 print 節(jié)點,這又必須連接到網(wǎng)絡(luò)中的另一個節(jié)點,而且在 print 得到任何信息之前還必須進行編譯。

不過也存在更加直接的解決方案。用 PyTorch 時,你的代碼必須像用 Python 一樣命令式地運行,唯一不透明的情況是運行在 GPU 上的運算是異步式地執(zhí)行的。這通常不會有問題,因為 PyTorch 對此很智能,它會等到用戶交互操作所依賴的所有異步調(diào)用都結(jié)束之后才會轉(zhuǎn)讓控制權(quán)。盡管如此,也還是有一些問題存在,尤其是在基準評測(benchmarking)等任務(wù)上。

行業(yè)滯后

所有這些可用性問題不僅讓寫代碼更困難,而且還會導致產(chǎn)業(yè)界毫無必要地滯后于學術(shù)界。一直以來都有論文在研究如何調(diào)整神經(jīng)網(wǎng)絡(luò)中所用的低層運算,并在這一過程中將準確度提升幾個百分點,但是產(chǎn)業(yè)界仍然需要很長時間才能實際應(yīng)用這些進展。

一個原因是即使這些算法上的改變可能本身比較簡單,但上面提到的工具問題還是讓它們非常難以實現(xiàn)。因此,由于這些改進可能只能將準確度提升 1%,所以企業(yè)可能會認為為此進行投入并不值得。對于小型機器學習開發(fā)團隊而言,這個問題尤為明顯,因為他們往往缺乏可負擔實現(xiàn)/整合成本的規(guī)模經(jīng)濟。

因此,企業(yè)往往會直接忽略這些進步,直到這些改進被加入到 PyTorch 或 TensorFlow 等軟件庫中。這能節(jié)省企業(yè)的實現(xiàn)和整合成本,但也會導致產(chǎn)業(yè)界滯后學術(shù)界一兩年時間,因為這些軟件庫的維護者基本不會立即實現(xiàn)每篇新論文提出的新方法。

舉個具體的例子,可變形卷積似乎可以提升大多數(shù)卷積神經(jīng)網(wǎng)絡(luò)(CNN)的性能表現(xiàn),但論文發(fā)布大概 2 年之后才出現(xiàn)第一個開源的實現(xiàn)。不僅如此,將可變形卷積的實現(xiàn)整合進 PyTorch 或 TensorFlow 的過程非常麻煩,而且最后這個算法也并沒得到廣泛的使用。PyTorch 直到最近才加入對它的支持,至于官方的 TensorFlow 版本,至今仍沒有見到。

現(xiàn)在,假設(shè)說有 n 篇能將準確度提升 2% 的論文都遇到了這種情況,那么產(chǎn)業(yè)界將錯失準確度顯著提升 (1.02^n)% 的機會,而原因不過是沒有合適的工具罷了。如果 n 很大,那就太讓人遺憾了。

速度

在某些情況中,同時使用 Python 與快速軟件庫依然還是會很慢。確實,如果是用 CNN 來執(zhí)行圖像分類,那么使用 Python 與 PyTorch/TensorFlow 會很快。此外,就算在 CUDA 環(huán)境中編寫整個網(wǎng)絡(luò),性能也可能并不會得到太多提升,因為大卷積占據(jù)了大部分的推理時間,而大卷積又已經(jīng)有了經(jīng)過良好優(yōu)化的代碼實現(xiàn)。但情況并非總是如此。

如果不是完全用低層語言實現(xiàn)的,那么由很多小運算組成的網(wǎng)絡(luò)往往最容易出現(xiàn)性能問題。舉個例子,F(xiàn)ast.AI 的 Jeremy Howard 曾在一篇博客文章中表達了自己對用 Swift 來做深度學習開發(fā)的熱愛,他表示盡管使用了 PyTorch 那出色的 JIT 編譯器,他仍然無法讓 RNN 的工作速度比肩完全用 CUDA 實現(xiàn)的版本。

此外,對于延遲程度很重要的情況,Python 也不是一種非常好的語言;而且 Python 也不能很好地應(yīng)用于與傳感器通信等非常底層的任務(wù)。為了解決這個問題,一些公司的做法是僅用 Python 和 PyTorch/TensorFlow 開發(fā)模型。這樣,在實驗和訓練新模型時,他們就能利用 Python 的易用性優(yōu)勢。而在之后的生產(chǎn)部署時,他們會用 C++ 重寫他們的模型。不確定他們是會完全重寫,還是會使用 PyTorch 的 tracing 功能或 TensorFlow 的圖模式來簡單地將其串行化,然后再圍繞它使用 C++ 來重寫 Python。不管是哪種方式,都需要重寫大量 Python 代碼。對于小公司而言,這樣做往往成本過高。

所有這些問題都是眾所周知的。公認的深度學習教父之一 Yann LeCun 就曾說機器學習需要一種新語言。他與 PyTorch 的創(chuàng)建者之一 Soumith Chintala 曾在一組推文中討論了幾種可能的候選語言,其中提到了 Julia、Swift 以及改進 Python。另一方面,F(xiàn)ast.AI 的 Jeremy Howard 似乎已經(jīng)下定決心站隊 Swift。

谷歌接受了挑戰(zhàn)

幸運的是,谷歌的 Swift for TensorFlow(S4TF)團隊接過了這一難題。不僅如此,他們的整個項目進展還非常透明。他們還發(fā)布了一份非常詳實的文檔(https://github.com/tensorflow/swift/blob/master/docs/WhySwiftForTensorFlow.md),其中詳細地介紹了他們做出這一決定的歷程,并解釋了他們?yōu)檫@一任務(wù)考慮過的其它語言并最終選中 Swift 的原因。

在他們考慮過的語言中,最值得關(guān)注的包括:

Go:在這份文檔中,他們表示 Go 過于依賴其接口提供的動態(tài)調(diào)度,而且如果要實現(xiàn)他們想要的特性,必須對這門語言進行大刀闊斧的修改。這與 Go 語言的保持簡單和小表面積的哲學不符。相反,Swift 的協(xié)議和擴展都有很高的自由度:你想要調(diào)度有多靜態(tài),就能有多靜態(tài)。另外,Swift 也相當復雜,而且還在越來越復雜,所以再讓它復雜點以滿足谷歌想要的特性并不是什么大問題。

C++ 和 Rust:谷歌的目標用戶群是那些大部分工作都使用 Python 的人,他們更感興趣的是花時間思考模型和數(shù)據(jù),而不是思考如何精細地管理內(nèi)存或所有權(quán)(ownership)。Rust 和 C++ 的復雜度都足夠,但都很注重底層細節(jié),而這在數(shù)據(jù)科學和機器學習開發(fā)中通常是不合理的。

Julia:如果你在 HackerNews 或 Reddit 上讀到過任何有關(guān) S4TF 的帖子,那么最??吹降脑u論是:「為啥不選 Julia?」在前面提到的那份文檔中,谷歌提到 Julia 看起來也很有潛力,但他們并未給出不選 Julia 的靠譜理由。他們提到 Swift 的社區(qū)比 Julia 大得多,事實確實如此,然而 Julia 的科研社區(qū)和數(shù)據(jù)科學社區(qū)卻比 Swift 大得多,而這些社區(qū)的人才更可能更多地使用 S4TF。要記住,谷歌團隊的 Swift 專業(yè)人才更多,畢竟發(fā)起 S4TF 項目的正是 Swift 的創(chuàng)建者 Chris Lattner,相信這在谷歌的決定中起到了重大的作用。

一種新語言:作者認為他們在宣言中說得很好:「創(chuàng)建一種語言的工作量多得嚇人。」這需要太長的時間,而機器學習又發(fā)展得太快。

那么,Swift 的優(yōu)勢在哪里?

簡單來說,Swift 讓你可幾乎完全用 Python 的方式在非常高的層面上進行編程,同時又可以保證非??斓乃俣取?shù)據(jù)科學家可像使用 Python 一樣來使用 Swift,同時可用 Swift 內(nèi)置的已優(yōu)化機器學習庫來進行更加精細的開發(fā),比如管理內(nèi)存,甚至當常用的 Swift 代碼約束太大時還能降至指針層面進行操作。

本文的目的不是介紹 Swift 語言,所以不會連篇累牘地詳細介紹其特性。如果你想詳細了解這門語言,看官方文檔就夠了。這里只會介紹 Swift 的幾個亮點,并希望這能吸引人們?nèi)L試它。下面幾節(jié)將按隨機順序介紹 Swift 的一些亮點,所以排序與它們的重要程度無關(guān)。之后,本文將深入介紹可微分編程,并聊聊谷歌在 Swift 上的大計劃。

亮點一

Swift 速度很快。這是作者在開始使用 Swift 時所做的第一項測試。作者寫了一些短腳本來評估 Swift 與 Python 和 C 的相對表現(xiàn)。說實話,這些測試并不特別復雜。也就是用整型數(shù)填充一個數(shù)組,然后再將它們?nèi)考悠饋?。這個測試本身并不能透徹地了解 Swift 在各種情況下的速度表現(xiàn),但作者想了解的是 Swift 能否達到 C 一樣的速度,而不是 Swift 是否總能和 C 一樣快。

第一組比較作者選的是 Swift vs Python。為了讓對應(yīng)的每一行所執(zhí)行的任務(wù)一致,作者對某些地方的花括號的位置進行了調(diào)整。

谷歌為何要養(yǎng)蘋果的親兒子Swift?原來意在可微分編程

import time | import Foundation|result = [] | var result = [Int]()for it in range(15): | for it in 0.. start = time.time() | let start = CFAbsoluteTimeGetCurrent() for _ in range(3000): | for _ in 0.. result.append(it) | result.append(it)} sum_ = sum(result) | let sum = result.reduce(0, +) end = time.time() | let end = CFAbsoluteTimeGetCurrent() print(end - start, sum_) | print(end - start, sum) result = [] | result = []}

盡管在這個特定的代碼段中,Python 與 Swift 代碼看起來句法相近,但運行結(jié)果表明這個 Swift 腳本的運行速度比 Python 腳本的運行速度快 25 倍。在這個 Python 腳本中,最外層的循環(huán)每執(zhí)行一次平均耗時 360 μs,相比之下 Swift 的是 14 μs。差別非常明顯。

另外,也還有其它一些事情值得注意。比如,+ 既是一個運算符也是一個函數(shù),它會被傳遞給 reduce(后面我會詳細介紹);CFAbsoluteTimeGetCurrent 揭示了 Swift 在傳承下來的 iOS 命名空間方面的怪異特性;.< 范圍運算符讓你可以選擇該范圍是否包含區(qū)間端點以及哪個端點。

但是,這個測試并不能說明 Swift 有多快。要知道 Swift 有多快,我們得將其與 C 來比比看。我也這樣做了,但讓人失望的是,初始結(jié)果并不好。用 C 編寫的版本平均耗時 1.5 μs,比我們的 Swift 代碼快 10 倍。Uh oh.

不過老實講,這樣比較其實并不公平。這段 Swift 代碼并沒使用動態(tài)數(shù)組,因此當數(shù)組規(guī)模變大時,它會在內(nèi)存堆中不斷重新分配位置。這也意味著它會在每個附加(append)的數(shù)組上執(zhí)行邊界檢查。為了佐證這一點,我們來看看相關(guān)定義。Swift 的標準類型包括整型、浮點數(shù)和數(shù)組,它們并沒有硬編碼到編譯器中,而是標準庫中所定義的結(jié)構(gòu)體(struct)。因此,根據(jù)數(shù)組的附加(append)定義,我們可以了解到很多信息。知道了這一點后,我的測試方式甚至可以包括預分配數(shù)組的內(nèi)存以及使用指針來填充數(shù)組。這樣得到的腳本其實也并不是很長:

import Foundation// Preallocating memoryvar result = ContiguousArray(repeating: 0, count: 3001)for it in 0.. let start = CFAbsoluteTimeGetCurrent()

// Using a buffer pointer for assignment result.withUnsafeMutableBufferPointer({ buffer infor i in 0.. buffer[i] = it } }) let sum = result.reduce(0, +) let end = CFAbsoluteTimeGetCurrent() print(end - start, sum)

這段新代碼耗時 3 μs,速度已經(jīng)達到 C 的一半,可以說是很不錯的結(jié)果了。不過為了進行完整的比較,作者繼續(xù)對代碼進行了剖析,以便了解該代碼的 Swift 版本和 C 版本的差異究竟可以做到多小。事實證明,作者之前使用的 reduce 方法會毫無必要地間接使用 nextPartialResult 函數(shù)執(zhí)行一些計算,這可以提供非必需的泛化能力。在使用指針重寫了這段代碼之后,作者最終讓這段代碼達到了與 C 同等的速度。但是,這顯然不符合我們使用 Swift 的目的,因為這種操作本質(zhì)上就是寫更冗長更丑陋的 C 語言。盡管如此,知道在確實需要時可以達到 C 的速度也是一件好事。

總結(jié):使用 Swift,你沒法在執(zhí)行 Python 層面的工作時獲得 C 語言等級的速度,但你能在兩者之間取得良好的平衡。

亮點二

Swift 采用的函數(shù)簽名方法也很有趣。它們的最基本形式其實相當簡單:

func greet(person: String, town: String) -> String { return 'Hello \(person)! Glad you could visit from \(town).'}

greet(person: 'Bill', town: 'Cupertino')

其函數(shù)簽名由參數(shù)名加它們的類型構(gòu)成,沒其它多余花哨的東西。唯一不同尋常的是 Swift 需要你在調(diào)用該函數(shù)時提供參數(shù)名,因此你在調(diào)用上面的 greet 時必須寫下 person 和 town,如上面代碼段中最后一行所示。

當我們向其中引入?yún)?shù)標簽時,情況還會變得更加有趣。

func greet(_ person: String, from town: String) -> String { return 'Hello \(person)! Glad you could visit from \(town).'}

greet('Bill', from: 'Cupertino')

顧名思義,參數(shù)標簽就是函數(shù)的參數(shù)的標簽,而且它們是在函數(shù)簽名中各自的參數(shù)之前聲明的。在上面的示例中,from 是 town 的參數(shù)標簽,_ 是 person 的參數(shù)標簽。對于最后一個標簽,作者使用的是,因為 _ 在 Swift 中是一個特殊字母,其含義是:「在調(diào)用這個參數(shù)時不提供任何參數(shù)名?!?/p>

有了參數(shù)標簽,每個參數(shù)都有兩個不同的名字:一個是參數(shù)標簽,在調(diào)用該函數(shù)時使用;另一個是參數(shù)名,在函數(shù)的主體定義中使用。這看起來似乎有些任性,但會讓你的代碼更易讀。

看看上面的函數(shù)簽名,基本就像是在讀英語?!窯reet person from town.」上面的函數(shù)調(diào)用看起來也同樣清楚直白:「Greet Bill from Cupertino.」如果沒有參數(shù)標簽,就有些含混不清了:「Greet person town.」我們不知道這里的 town 是什么意思。這是我們現(xiàn)在所處的城鎮(zhèn)嗎?還是我們?yōu)榱嗣嬉娺@個人而將要前去的城鎮(zhèn)?又或是這個人原本來處的城鎮(zhèn)?如果沒有參數(shù)標簽,我們就必須閱讀函數(shù)主體才能知曉實際情況,或者采用讓函數(shù)名或參數(shù)名更長更直白的方法。如果你有大量參數(shù),那么情況將變得非常復雜;在作者看來這會導致代碼變得更丑而且會讓函數(shù)名變得毫無必要地長。參數(shù)標簽更加好看,而且也更容易擴展,而且幸運的是它們也在 Swift 中得到了廣泛的應(yīng)用。

亮點三

Swift 廣泛地使用了閉包(closure)。因此,有一些捷徑可讓該語言的使用更接近人的直覺。這個來自 Swift 的文檔的示例展現(xiàn)了這些捷徑簡潔明了又具有很強的表現(xiàn)力的特性。

我們的目標是將下面的數(shù)組向后排序:

let names = ['Chris', 'Alex', 'Ewa', 'Barry', 'Daniella']

如果用不那么地道的 Swift 代碼形式,可為數(shù)組使用 sorted 方法,并采用一個自定義函數(shù)來定義按逐對順序比較數(shù)組元素的方式,就像這樣:

func backward(_ s1: String, _ s2: String) -> Bool { return s1 > s2}var reversedNames = names.sorted(by: backward)

backward 函數(shù)一次可比較兩項,如果這兩項的順序與所需順序一樣,則返回 true;否則便返回 false。sorted 數(shù)組方法需要這樣一個函數(shù)作為一個輸入才能知道如何對數(shù)組進行排序。順便一提,我們還可以看到這里使用了參數(shù)標簽 by——這是如此的簡潔明了。

如果我們采用更地道的 Swift,可以發(fā)現(xiàn)使用閉包能更好地完成這項任務(wù)。

reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )

{} 之間的代碼是一個正被定義的閉包,同時也被傳遞用作 sorted 的一個參數(shù)。你也許從未聽說過閉包,但其實很簡單,閉包就是一個獲取上下文的未命名的函數(shù)你可以將其看作是增強版的 Python lambda。該閉包中的關(guān)鍵詞 in 的作用是分開該閉包的參數(shù)及其主體。: 等更直觀的關(guān)鍵詞已被簽名類型定義所占用(在這個案例中,該閉包的參數(shù)類型是從 sorted 的簽名中自動推導出來的,因此可以避免使用 :),而且我們都知道命名是編程中最艱難的事情之一,所以為此只能繼續(xù)使用不那么直觀的關(guān)鍵詞了。

不管從哪個角度看,這段代碼都已經(jīng)簡潔了許多。

但我們還可能做得更好:

reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )

這里我們移除了 return 語句,這是因為在 Swift 中,單行閉包就暗含了 return。

即便如此,我們還能繼續(xù)更進一步:

reversedNames = names.sorted(by: { $0 > $1 } )

Swift 也有暗含的命名位置參數(shù),所以在上面的案例中,$0 是第一個參數(shù),$1 是第二個參數(shù),$2 是第三個參數(shù)等等。這個代碼已經(jīng)很緊湊了,而且非常容易理解,但是我們甚至還能做得更好:

reversedNames = names.sorted(by: >)

在 Swift 中,> 運算符就是一個名為 > 的函數(shù)。因此,我們可以將其傳遞給 sorted 方法,使我們的代碼達到極端簡潔和可讀的程度。

這種操作適用于 +=、-=、、== 和 = 等運算符,你可以在標準庫中查看它們的定義。這些函數(shù)/運算符與普通函數(shù)之間的差異是前者已在標準庫中使用 infix、prefix 或 suffix 關(guān)鍵詞顯式地聲明為運算符。舉個例子,+= 函數(shù)在 Swift 標準庫的這一行(https://github.com/apple/swift/blob/1ed846d8525679d2811418a5ba29405200f6e85a/stdlib/public/core/Policy.swift#L468)中被定義成了一個運算符??梢钥吹?,這個運算符遵循多個不同的協(xié)議,比如 Array 和 String,因為很多不同的類型都有自己的 += 函數(shù)實現(xiàn)。

更進一步,我們還能定義自己的自定義運算符。GPUImage2 軟件庫就是一個很好的例子。這個軟件庫讓用戶可以加載圖像,使用一系列變換來修改它,然后再以某種方式來輸出它。很自然,這些變換序列的定義會在該庫中不斷反復出現(xiàn),因此這個庫的創(chuàng)建者決定定義一個新的運算符 →,可用于將這些變換鏈接到一起。

func -->(source:T, destination:T) -> T { source.addTarget(destination) return destination}infix operator --> : AdditionPrecedence

在以上稍微簡化過的代碼中,首先聲明了 --> 函數(shù),然后其被定義為了一個 infix 運算符。infix 的意思是如果要使用這個運算符,就必須將其放置在兩個參數(shù)之間。這讓你可以寫出如下的代碼:

let testImage = UIImage(named:'WID-small.jpg')!let toonFilter = SmoothToonFilter()let luminanceFilter = Luminance()let filteredImage = testImage.filterWithPipeline{input, output in input --> toonFilter --> luminanceFilter --> output // Interesting part}

比起一大堆互相鏈接的方法或一長串 source.addTarget(...) 函數(shù),上面的代碼要簡短和容易多了。

亮點四

前面作者已經(jīng)提到過,Swift 的基本類型是標準庫中定義的結(jié)構(gòu)體,而且并沒有硬編碼到編譯器中,因為它們通常是用其它語言寫的。這很有用處,一大原因是讓我們可以使用名叫擴展(extension)的 Swift 特性,其讓我們可以向任意類型添加新特性,包括基本類型。操作方式是這樣的:

extension Double { var radians: Double { return self * (Double.pi / 180) }}360.radians // -> 6.28319

盡管這個例子并不是很有用,但也展示 Swift 這門語言的擴展能力,因為這能讓你做很多事情,比如向 Swift 解釋器輸入任何數(shù)字以及在其上調(diào)用任何你想用的自定義方法。

最后一個亮點

除了擁有編譯器之外,Swift 還具有解釋器并且支持 Jupyter Notebook。在學習這門語言時,解釋器尤其好用,因為它支持直接在命令提示符處輸入 swift,然后立馬開始代碼測試。Python 也具備差不多一樣的功能。另一方面,由于整合了 Jupyter Notebook,因此可以輕松進行可視化、執(zhí)行數(shù)據(jù)探索和編寫報告。最后,當你需要運行生產(chǎn)代碼時,你可以編譯它并利用 LLVM 提供的出色優(yōu)化能力。

谷歌的大計劃

作者在前面的章節(jié)中提到了 Swift 的一些特性,但其中有一個特性與其它不同:Jupyter Notebook 是新加入的,而且事實上正是由 S4TF 團隊加入的。這非常值得一說,因為這能讓我們一窺谷歌投入這個項目時的想法:他們不僅想為 Swift 語言本身創(chuàng)建一個軟件庫,而且他們還想深入地改進這門語言本身以及相關(guān)工具,然后再使用這門語言的改進版本創(chuàng)建一個新的 TensorFlow 軟件庫。

只要看看 S4TF 團隊在哪些工作上投入的時間最多就能看出這一點。他們到目前為止做的大部分工作都是在蘋果公司的 Swift 編譯器代碼庫本身上完成的。更具體而言,谷歌目前完成的大部分工作都在 Swift 編譯器代碼庫中的一個 dev 分支中。谷歌正為 Swift 語言本身添加新特性——他們首先會在自己的分支中創(chuàng)建和測試這些新特性,然后會將它們合并到蘋果的主分支中。這意味著運行在世界各地的 iOS 設(shè)備上的標準 Swift 語言最終將能集成這些改進。

現(xiàn)在來談?wù)劯鼘嵲诘臇|西:谷歌正為 Swift 構(gòu)建什么特性?

首先說個大特性。

可微分編程

近來,可微分編程炒得確實很熱。特斯拉的人工智能負責人 Andrej Karpathy 稱之為軟件 2.0(Software 2.0),Yann LeCun 甚至宣稱:「深度學習已死,可微分編程萬歲?!沽硪恍┤藙t說有必要創(chuàng)建一套全新的工具了,包括新的 Git、新的 IDE 以及新的編程語言。Wink wink.

所以,什么是可微分編程?

簡而言之,可微分編程是一種程序自身可被微分的編程范式。這讓你可以設(shè)定一個你想要優(yōu)化的具體目標,讓你的程序可以根據(jù)這個目標自動計算自己的梯度,然后再在這個梯度的方向上優(yōu)化自己。這和訓練神經(jīng)網(wǎng)絡(luò)完全一樣。

如果能讓程序自己優(yōu)化自己,我們也許就能創(chuàng)造出我們自己完全無法編寫出來的程序。想想這一點還挺有趣:你的程序可以使用梯度針對特定任務(wù)優(yōu)化自身,因此它的編程能力比你還強。過去幾年的發(fā)展已經(jīng)表明在越來越多的案例已經(jīng)出現(xiàn)了這種情況,而且目前我們還看不到這一發(fā)展趨勢的終點。

一種可微分的語言

寫了這么長的介紹之后,終于可以談?wù)劰雀铻?Swift 開發(fā)的原生可微分編程版本了。

func cube(_ x: Float) -> Float { return x * x * x}let cube = gradient(of: cube)cube(2) // 8.0cube(2) // 12.0

這里我們首先定義了一個簡單的函數(shù) cube,其返回的結(jié)果是輸入的立方。接下來就是激動人心的部分了:我們只需在原始函數(shù)上調(diào)用 gradient,就能創(chuàng)建原始函數(shù)的導數(shù)函數(shù)。這里沒有使用任何軟件庫或外部代碼,gradient 只是由 S4TF 團隊為 Swift 語言引入的一個新函數(shù)。該函數(shù)利用了 S4TF 團隊對 Swift 內(nèi)核進行的修改,可以實現(xiàn)梯度函數(shù)的自動計算。

這是 Swift 的一個重大新特性。對于任意 Swift 代碼,只要是可微分的,都可以自動計算梯度。上面的代碼沒有導入任何東西或奇怪的依賴包,就只是純粹的 Swift。PyTorch、TensorFlow 或其它任何大型機器學習庫都支持這一功能,但前提是你要使用特定于庫的特定運算。而且在這些 Python 庫中操作梯度并不如單純用 Swift 那樣輕量、透明,而且那些庫集成也不如 Swift 原生集成那么好。

這是 Swift 語言的一個重大新特性;而且可以說 Swift 是首個為這一特性提供原生支持的主流語言

為了進一步說明這在實際應(yīng)用中的使用方式,以下應(yīng)用于一個標準機器學習訓練流程的腳本更完整透徹展示了這一新特性:

struct Perceptron: @memberwise Differentiable { var weight: SIMD2 = .random(in: -1.. var bias: Float = 0

@differentiable func callAsFunction(_ input: SIMD2) -> Float { (weight * input).sum() + bias }}var model = Perceptron()let andGateData: [(x: SIMD2, y: Float)] = [ (x: [0, 0], y: 0), (x: [0, 1], y: 0), (x: [1, 0], y: 0), (x: [1, 1], y: 1),]for _ in 0.. let (loss, loss) = valueWithGradient(at: model) { model -> Float invar loss: Float = 0for (x, y) in andGateData { let ? = model(x) let error = y - ? loss = loss + error * error / 2 } return loss } print(loss) model.weight -= loss.weight * 0.02 model.bias -= loss.bias * 0.02}

同樣,上面的代碼完全是用 Swift 寫的,不帶任何依賴包。在這段代碼中,我們可以看到谷歌為 Swift 引入的兩個新特性:callAsFunction 和 valueWithGradient。第一個很簡單,其作用是實例化類和結(jié)構(gòu)體,讓我們可以像調(diào)用函數(shù)一樣調(diào)用它們。這里,Perceptron 結(jié)構(gòu)體被實例化為了 model,然后 model 又在 let ? = model(x) 中被作為一個函數(shù)而調(diào)用。在這樣操作時,實際上調(diào)用的是 callAsFunction 方法。如果你曾經(jīng)用過 Keras 或 PyTorch 模型,你一定知道這是一種處理模型/層的常用方式。但 Keras 和 PyTorch 這兩個庫使用了 Python 的 *call* 方法來實現(xiàn)它們各自的 call 和 forward;Swift 之前沒有這樣的特性,于是谷歌把它加了進去。

上面的腳本中還有一個有趣的新特性:valueWithGradient。該函數(shù)會返回在特定點評估的函數(shù)或閉包的結(jié)果值和梯度。在以上案例中,我們定義并用作 valueWithGradient 的輸入的閉包實際上是我們的損失函數(shù)。這個損失函數(shù)的輸入是我們的模型,所以當我們說 valueWithGradient 會在特定的點評估我們的函數(shù)時,我們的意思是其會使用有特定權(quán)重配置的模型評估我們的損失函數(shù)。計算了上述的值和梯度之后,我們可以把值打印出來(這是我們的損失)并使用梯度更新模型的權(quán)重。重復這一過程一百次,我們就訓練了一個模型。我們還可以訪問損失函數(shù)內(nèi)部的 andGateData,這是 Swift 閉包可以獲取其周圍上下文的又一案例。

微分外部代碼

Swift 還有一個神奇的特性:我們不僅可以微分 Swift 運算,還能微分外部的、非 Swift 的軟件庫——只需我們在 Swift 中手動定義這些運算操作的導數(shù)。這意味著你可以使用 C 軟件庫中一些非??焖俚膶崿F(xiàn)或一些 Swift 還不具備的運算操作。你只需將其導入到你的項目中、編寫導數(shù)代碼,然后就可以在你的大型神經(jīng)網(wǎng)絡(luò)中使用這些運算操作,讓反向傳播等功能無縫運行。

此外,這件事做起來其實非常簡單:

import Glibc // we import pow and log from herefunc powerOf2(_ x: Float) -> Float { return pow(2, x)}

@derivative(of: powerOf2)func dPowerOf2d(_ x: Float) -> (value: Float, pullback: (Float) -> Float) { let d = powerOf2(x) * log(2) return (value: d, pullback: { v in v * d })}

powerOf2(3), // 8gradient(of: powerOf2)(3) // 5.545

Glibc 是一個 C 軟件庫,因此 Swift 編譯器并不知道其運算操作的導數(shù)是什么。通過使用 @derivative,我們可以為編譯器提供有關(guān)這些外部運算操作的導數(shù)的信息,然后搭配 Swift 的原生運算,可以非常輕松地構(gòu)建出大型的可微分網(wǎng)絡(luò)。在這個示例中,我們導入了 Glibc 的 pow 和 log,并用它們創(chuàng)建了 powerOf2 函數(shù)及其導數(shù)。

為 Swift 開發(fā)的新 TensorFlow 軟件庫的當前版本就正在使用這一特性進行開發(fā)。這個庫從 TF Eager 軟件庫的 C API 導入了其所有運算操作,但其不是將 TensorFlow 的自動微分系統(tǒng)直接接上去,而是要指定每個基礎(chǔ)運算操作的導數(shù),然后再讓 Swift 處理。但是,并非所有運算都需要這種操作,因為許多運算都是更基本運算組合而成的,因此 Swift 可以自動推斷它們的導數(shù)。但由于這個庫的當前版本基于 TF Eager,因此存在一個大缺點:TF Eager 非常慢,因此 Swift 的這個版本也很慢。這個問題應(yīng)該只是暫時性的,隨著與 XLA(通過 x10)和 MLIR 的整合,這個問題可以得到解決。

話雖如此,實際上 Swift TensorFlow API 已經(jīng)初具規(guī)模,谷歌的開發(fā)者已經(jīng)可以使用這個 API 進行開發(fā)了。使用它,你可以這樣訓練一個簡單模型:

import TensorFlowlet hiddenSize: Int = 10struct IrisModel: Layer { var layer1 = Dense(inputSize: 4, outputSize: hiddenSize, activation: relu) var layer2 = Dense(inputSize: hiddenSize, outputSize: hiddenSize, activation: relu) var layer3 = Dense(inputSize: hiddenSize, outputSize: 3)

@differentiable func callAsFunction(_ input: Tensor) -> Tensor { return input.sequenced(through: layer1, layer2, layer3) }}var model = IrisModel()let optimizer = SGD(for: model, learningRate: 0.01)let (loss, grads) = valueWithGradient(at: model) { model -> Tensor inlet logits = model(firstTrainFeatures) return softmaxCrossEntropy(logits: logits, labels: firstTrainLabels)}print('Current loss: \(loss)')

可以看到,這與之前的無導入的模型訓練腳本非常相似。它的設(shè)計非常類似 PyTorch,真是太棒了。

與 Python 的互操作性

Swift 目前仍面臨的一大問題是當前的機器學習和數(shù)據(jù)科學生態(tài)系統(tǒng)仍處于起步階段。幸運的是,谷歌正在解決這個問題,其方式是為 Swift 納入 Python 互操作性。其想法是讓開發(fā)者可在 Swift 代碼中編寫 Python 代碼;通過這種方式,數(shù)量龐大的 Python 軟件庫就能為 Swift 所用了。

這種操作的一種典型用例是用 Swift 訓練模型,然后用 Python 的 matplotlib 來繪制圖表:

import Pythonprint(Python.version)let np = Python.import('numpy')let plt = Python.import('matplotlib.pyplot')// let time = np.arange(0, 10, 0.01)let time = Array(stride(from: 0, through: 10, by: 0.01)).makeNumpyArray()let amplitude = np.exp(-0.1 * time)let position = amplitude * np.sin(3 * time)

plt.figure(figsize: [15, 10])

plt.plot(time, position)plt.plot(time, amplitude)plt.plot(time, -amplitude)

plt.xlabel('Time (s)')plt.ylabel('Position (m)')plt.title('Oscillations')

plt.show()

這看起來就像是單純的 Python 代碼加了一點 let 和 var 語句。這是由谷歌提供的一段代碼示例。作者只做了一項修改,即注釋掉了一行 Python 代碼,并用 Swift 對其進行了重寫??梢钥吹?,這兩者在這里竟然可以交互得如此之好。這項任務(wù)完成起來并不如完全使用 Python 那樣清晰簡潔,因為我們必須使用 makeNumpyArray() 和 Array();但這種操作是可行的。

谷歌成功實現(xiàn) Python 互操作性的方法是引入了 PythonObject 類型,其可表示 Python 中的任何對象。Python 互操作性被限定在單個 Swift 軟件庫中,因此 S4TF 團隊僅需為 Swift 語言本身添加少量功能,比如添加少量改進以適應(yīng) Python 的極端動態(tài)性。至于現(xiàn)在的 Python 支持已經(jīng)達到了何種程度,目前尚不清楚他們將如何處理 with 語句等更地道的 Python 元素,而且可以肯定地說還有其它一些極端情況有待考慮;盡管如此,現(xiàn)在已經(jīng)實現(xiàn)的成果就已經(jīng)很不錯了。

而在 Swift 與其它語言的整合方面,作者對 Swift 的最早的興趣點之一就是想看看它在處理實時計算機視覺任務(wù)上的表現(xiàn)。因為這個原因,作者最終找到了 OpenCV 的一個 Swift 版本,而通過 FastAI 的論壇,最終找到了一個大有潛力的 OpenCV 封裝類(wrapper):SwiftCV。但是,這個庫很奇怪。OpenCV 是用 C++ 構(gòu)建的(并且剛剛廢棄了其 C API),而 Swift 目前并不支持 C++(不過將會支持)。因此,SwiftCV 必須將 OpenCV 代碼封裝在 C++ 代碼的一個兼容 C 的子集中,然后再以 C 軟件包的形式導入。之后,才能將其封裝到 Swift 中。

S4TF 項目的當前狀態(tài)

盡管作者對 S4TF 項目一直不吝贊美之辭,但也必須承認其還不足以支持一般的生產(chǎn)使用。其新的 API 仍在不斷變化,這個新的 TensorFlow 庫的性能也仍然不是很好;即便其數(shù)據(jù)科學生態(tài)系統(tǒng)正在發(fā)展壯大,但總體仍處于起步階段。最重要的是,其 Linux 支持情況很奇怪,目前官方僅支持 Ubuntu??紤]到所有這些問題,要保證所有這些問題及時得到解決,還有很多工作要做。

谷歌正在努力提升其性能,包括最近添加的 x10 以及在讓 MLIR 達到標準方面所做的工作。另外,谷歌還有一些項目致力于在 Swift 中復制許多 Python 數(shù)據(jù)科學生態(tài)系統(tǒng)的功能,比如 SwiftPlot、類似 Pandas 的 Penguin、類似 Scikit-learn 的 swiftML。

但最讓人驚訝的是蘋果公司也與谷歌在同一方向上推動 Swift 的發(fā)展。在蘋果的 Swift 發(fā)展路線圖上,下一個重大版本的主要目標是在非蘋果平臺上建立不斷發(fā)展增長的 Swift 軟件生態(tài)系統(tǒng)。這一目標也反映在了蘋果對多個項目的支持上,比如 Swift Server Work Group、類似 numpy 的 Numerics、一個運行在 Linux 上的官方語言服務(wù)器以及將 Swift 移植到 Windows 系統(tǒng)的工作。

此外,F(xiàn)ast.ai 的 Sylvain Gugger 也正為 FastAI 構(gòu)建一個 Swift 版本,而 Jeremy Howard 也已經(jīng)將 Swift 課程納入到了他們的廣受歡迎的在線課程中。另外,第一批基于 S4TF 相關(guān)軟件庫的學術(shù)論文也正陸陸續(xù)續(xù)發(fā)表出來。

總結(jié)

在作者本人看來,盡管 Swift 很有可能發(fā)展成機器學習生態(tài)系統(tǒng)的一大關(guān)鍵角色,但風險仍然存在。其中最大的風險是:盡管 Python 存有缺陷,但對于大部分機器學習任務(wù)來說已經(jīng)足夠好了。對于許多已經(jīng)熟悉 Python 的人來說,慣性可能太大,也沒有換成另一種語言的理由。另外,谷歌已經(jīng)不是一次兩次放棄大型項目了,而 S4TF 的一些關(guān)鍵人員的脫離也讓人擔憂。

給出了這些免責聲明之后,作者仍然覺得 Swift 是一門很棒的語言,這些新增的功能也極具創(chuàng)新性,相信它們最終能在機器學習社區(qū)找到自己的位置。因此,如果你也想為這個潛力無窮的項目添磚加瓦,現(xiàn)在就是很好的時機。Swift 在機器學習領(lǐng)域的地位還遠未確立,還有很多工具有待開發(fā)。隨著 Swift 機器學習生態(tài)系統(tǒng)的持續(xù)發(fā)展,現(xiàn)在的小項目也許未來可以成長為巨大的社區(qū)項目。

原文鏈接:https:///blog/2020/04/02/swift-googles-bet-on-differentiable-programming/

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多