雷鋒網(公衆号:雷鋒網)按:本文作者潘汀,合肥工業大學計算機專業大三大學生,中科院深圳先進院內建所mmlab通路學生。原acm-icpc算法競賽選手,2015年獲ccpc銅牌。2015年初開始研究機器學習,研究興趣集中于對深度學習理論、應用(cv&nlp)及系統架構設計的綜合探索。關于深度學習在面部情感分析方面應用的論文被《自動化學報》錄用。
| 虛拟架構殺入
從發現問題到解決問題
半年前的這時候,暑假,我在siat mmlab實習。看着同僚一會兒跑torch,一會兒跑mxnet,一會兒跑theano。
siat的伺服器一般是不給sudo權限的,我看着同僚掙紮在編譯這一坨架構的海洋中,開始思考是否可以寫一個架構:
這樣,利用工廠模式隻編譯執行部件的做法,隻需編譯唯一的後端即可,架構的不同僅僅在于前端腳本的不同。
caffe2keras的做法似乎是這樣,但keras本身是基于theano的編譯後端,而我們的更希望theano都不用編譯。
當我9月份拍出一個能跑cifar10的大概原型的時候:
我為這種怪異的寫法取名叫cgvm(computational graph virtual machine)然後過了幾天,在微網誌上看到了陳天奇在mxnet的進一步工作nnvm的釋出 (o(╯□╰)o)......
nnvm使用2000行模拟出了tensorflow,我大概用了500行模拟出了caffe1。
vm(virtual machine)的想法其實是一個很正常的想法,這幾年我們搞了很多新架構,名字一個比一個炫,但是本質都差不多,架構的使用者實際上是苦不堪言的:
這篇paper使用了a架構,我要花1天配置a架構。
這篇paper使用了b架構,我要花1天配置b架構。
.......
正如llvm不是一種編譯器,nnvm也不是一種架構,看起來更像是架構的屠殺者。
nnvm的可行性恰恰證明了現行的各大架構底層的重複性,而上層的多樣性隻是一個幌子。
我們真的需要為僅僅是函數封裝不同的架構買單嗎?這是值得思考的。
| 計算圖走向成熟
1、計算圖的兩種形式
計算圖最早的出處應該是追溯到bengio在09年的《learning deep architectures for ai》,bengio使用了有向圖結構來描述神經網絡的計算:
如圖,符号集合{*,+,sin} 構成圖的結點,整張圖可看成三部分:輸入結點、輸出結點、從輸入到輸出的計算函數。
随後在bengio組的theano架構執行中,graph就被隐式應用于op的連接配接。不過這時候,op還是執行時-動态編譯的。
caffe1中計算圖其實就是net,因為net可以被graph模拟出來(cgvm和caffe2keras都實作了)。
賈揚清在caffe1中顯式化了計算圖的表示,使用者可以通過編輯net.prototxt來設計計算圖。
caffe1在jonathan long和evan shelhamer接手後,他們開發了pycaffe。pycaffe通過python天然的工廠(__getattr__),實作了net.prototxt的隐式生成。
之後的caffe2,也就直接取消了net.prototxt的編輯,同樣利用python的(__getattr__)擷取符号類型定義。
caffe1帶來一種新的計算圖組織op的描述方式,不同于theano直接翻譯op為c執行代碼,然後動态編譯,軟體工程中的進階設計模式——工廠模式被廣泛使用。
計算圖被劃分為三個階段,定義階段、構造階段、執行階段:
1、定義階段:定義layer/op的name、type、bottom(input),top(output)及預設參數。 2、構造階段:通過工廠模式,由字元串化的定義腳本構造類對象。 3、執行階段:根據傳入的bottom(input),得到額外參數(如shape),此時計算圖才能開始執行。階段劃分帶來的主要問題是限制了編譯代碼的完整性和優化程度。
在theano中,c代碼生成是最後一步,編譯前你可以組合數個細粒度符号,依靠編譯器做一次硬體執行上的優化。
而工廠模式編譯符号時隻考慮了單元,編譯器沒有上下文可供參考優化,故最終隻能順序執行多個預先編譯的符号單元。
當符号粒度過細時,一個layer的實作就會變成連續執行多個子過程,導緻“tensorflowslow”。
2、計算圖作為中間表示(ir)
pycaffe和caffe2将定義階段移到python中,而将構造和執行階段保留在c++中做法,是計算圖作為ir的思想啟蒙。
python與c++最大的不同在于:一個是腳本代碼,用于前端。一個是本地代碼,用于後端。
腳本代碼建立/修改模型友善(無需因模型變動而重新編譯)、執行慢,本地代碼則正好相反。
兩者取長補短,是以深度學習架構在2016年,迎來了前後端開發的黃金時代。
如上圖,無論是9月份先提出的nnvm,還是最近intel曝光的nervana,都分離了前後端。
後端的獨立,不僅減少了編譯工作,最大的優勢在于降低了傳統架構做跨裝置計算的代碼耦合度。
在paper每周都有一大堆的現在,如果後端的每一次變動都要大量修改前端,那麼架構的維護開銷是非常大的。
在前端定義用于描述輸入-輸出關系的計算圖有着良好的互動性,我們可以通過函數和重載腳本語言的操作符,定義出媲美matlab的運算語言,這些語言以顯式的tensor作為資料結構,operator作為計算符和函數,theano和mxnet都是這樣隐蔽處理由表達式向計算圖過渡的。
而caffe2則比較直接,你需要先建立一個graph,然後顯示地調用graph.addoperator(xxx) tensorflow同樣可以顯式化處理graph。
與使用者互動得到的計算圖描述字串是唯一的,但是與使用者互動的方式卻是不唯一的。
是以ir之上,分為兩派:
第一派要搞自己的api,函數封裝非常有個性,宣示這是自己的專利、獨門語言。
第二派不搞自己的api,反而去模拟現有的api,表示我很低調。
顯然,使用者更喜歡用自己熟悉架構的寫法去描述模型,不喜歡天天背着個函數速查手冊。
3、計算圖優化
用于中間表示得到的計算圖描述最好不要直接構造,因為存在備援的求解目标,且可共享變量尚未提取。
當限制計算圖描述為有向無環圖(dag)時,一些基本的圖論算法便可應用于計算圖描述的化簡與變換。
陳天奇在今年的msr talk:programming models and systems design for deep learning中,總結了計算圖優化的三個點:
①依賴性剪枝
分為前向傳播剪枝,例:已知a+b=x,a+b=y,求x?
反向傳播剪枝, 例:a+b=x,a+b=y,求x、y,dx/da?
根據使用者的求解需求,可以剪掉沒有求解的圖分支。
②符号融合
符号融合的自動實作是困難的,因為kernel基本不再實時編譯了,是以更多展現在符号粗細粒度的設計上。
粗粒度的符号融合了數個細粒度的符号,一次編譯出連續多個執行步驟的高效率代碼。
粗粒度和細粒度并無好壞區分,一個速度快,一個更靈活。
從貪心角度,vm架構通常會提供粗細粒度兩種實作給使用者,因而需要更多人力維護編譯後端。
③記憶體共享
caffe1對于激活函數大多使用的inplace處理——即bottom和top是同一個blob。
inplace使用新的輸出y立即覆寫的輸入x,需要以下兩個條件:
1、bottom和top數量都為1,即:計算圖中構成一條直線路徑,
2、d(y)/d(x)與x是無關的,是以x被y覆寫不影響求導結果。
常見的激活函數都符号以上兩個條件,因而可以減少記憶體的開銷。
但是caffe1在多網絡記憶體共享優化上極其糟糕的,以至于caffe1并不适合用來跑gan,以及更複雜的網絡。
一個簡單例子是交叉驗證上的優化:訓練網絡和驗證網絡的大部分layer都是可以共享的,但是由于caffe1錯誤地将blob獨立的放在每個net裡,使得跨net間很難共享資料。
除此之外,caffe1還錯誤地将臨時變量blob獨立放在每個layer裡,導緻列卷積重複占用幾個g記憶體。
讓net和layer都能共享記憶體,隻需要将tensor/blob置于最頂層,采用mvc來寫架構即可。
caffe2引入了workspace來管理tensor,并将工作空間的指針傳給每一個op、每一個graph的構造函數。
| 新的風暴已經出現
1、vm的側重點
cgvm和nnvm的側重點是不太一樣的,cgvm更強調前端上的擴充化,後端上的唯一化。是以cgvm不會去支援torch編譯後端,也不會去支援caffe編譯後端。
在nnvm的知乎讨論帖中,有一種觀點認為vm是輕視operator的實作。但實際上,我們手裡的一堆架構,在operator、kernel、math級别的不少實作是沒有多少差別的。
但恰恰折磨使用者的正是這些沒有多少差別的編譯後端:各種依賴庫、裝linux、編譯各種錯。
是以我個人更傾向整個dl社群能夠提供一份完善的跨平台、跨裝置解決方案,而不是多而雜的備選方案。
從這點來看,cgvm似乎是一個更徹底的架構殺手,但在icml'15上, jürgen schmidhuber指出:
真正運作ai 的代碼是非常簡短的,甚至高中生都能玩轉它。不用有任何擔心會有行業壟斷ai及其研究。 簡短的ai代碼,未必就是簡單的架構提供的,有可能是自己熟悉的架構,這種需求展現在前端而不是後端。
vm指出了一條多架構混合思路:功能a,架構x寫簡單。功能b,架構y寫簡單。
功能a和功能b又要end-to-end,那麼顯然混起來用不就行了。隻有使用頻率不高的架構才會消亡,vm将架構混合使用後,熟悉的味道更濃了,那麼便構不成”架構屠殺者“。
強大的ai代碼,未必就是vm提供的,有可能是龐大的後端提供的。随着paper的快速疊代,後端的擴充仍然是最繁重的程式設計任務。
vm和後端側重點各有不同,難分好壞。但分離兩者的做法确實是成功的一步。
2、vm的形式
vm及計算圖描述方式是連接配接前後端的橋梁。
即便後端是唯一的,根據支援前端的不同,各家寫的vm也很難統一。實際上這就把架構之間的鬥争引向了vm之間的鬥争。兩人見面談笑風生,與其問對方用什麼架構,不如問對方用什麼vm。
3、vm的主要工作
合成計算圖描述的過程是乏味的,在caffe1中,我們恐怕已經受夠了人工編輯prototxt。
api互動方面,即便是mxnet提供給使用者的api也是複雜臃腫的,或許仍然需要一個handbook。
tensorflow中的tensorboard借鑒了webos,vm上搞一個互動性更強的作業系統也是可行的。
除此之外,我可能比較熟悉一些經典架構,那麼不妨讓vm去實作那些耳熟能詳的函數吧!
1)模拟theano.function
theano的function是一個非常貼近數學表達計算圖掩飾工具。function内部轉化表達式為計算圖定義,同時傳回一個lambda函數引向計算圖的執行。總之這是一個百看不膩的api。
2)模拟theano.grad
結合計算圖優化,我們現在可以指定任意一對求導二進制組(cost, wrt)。因而,放開手,讓自動求導在你的模型中飛舞吧。
3)模拟theano.scan
theano.scan是一個用來搭建rnn的神器。盡管最近caffe1更新了rnn,但是隻支援固定循環步數的rnn。而theano.scan則可以根據tensor的shape,為rnn建動态的計算圖,這适合在nlp任務中處理不定長句子。
4)模拟pycaffe
pycaffe近來在rcnn、fcn、deepdream中得到廣泛應用,成為搞cv小夥伴們的最愛。pycaffe大部分是由c++資料結構通過boost.python導出的,不幸的是,boost.thread導出之後與python的gil沖突,導緻pycaffe裡無法執行c++線程。嘗試模拟移除boost.python後的pycaffe,在python裡把solver、net、layer給寫出來吧。
5)模拟你熟悉的任意架構
.......等等,怎麼感覺在寫模拟器.....當然寫模拟器基本就是在重複造輪子,這個在nnvm的知乎讨論帖中已經指明了。
4、vm的重要性
vm是深度學習架構去中心化、解耦化發展邁出的重要一步。同時暴露了目前架構圈混亂的本質:計算圖之下,衆生平等。計算圖之上,群魔亂舞。
在今年我們可以看多許多架構pk對比的文章,然而大多隻是從使用者觀點出發的簡單評測。對比之下,nnvm關注度不高、反對者還不少這種情況,确實讓人感到意外。
| 回顧與展望
1、回顧2016:架構圈減肥大作戰的開始
高調宣布開源xxx架構,再封裝一些api,實際上已經多餘了。
vm的出現,将上層接口的編寫引向模拟經典的架構,進而達到減肥的目的。
架構維護者應當将大部分精力主要放在kernel的編寫上,而不是考慮搞一些大新聞。
2、展望2017:dl社群能否聯合開源出跨平台、跨裝置的後端解決方案
後端上,随着arm、神經晶片的引入,我們迫切需要緊跟着硬體來完成繁重的程式設計。
後端是一個敏感詞,因為硬體可以拿來賣錢,是以更傾向于閉源。
除此之外,即便出現了開源的後端,在山寨和混戰之前是否能普及也是一個問題。
3、展望2017:來寫架構吧
vm的出現,帶來另一個值得思考的問題:現在是不是人人應該學寫架構了?
傳統架構編寫的困難在代碼耦合度高,學習成本昂貴。vm流架構分離了前後端之後,前端編寫難度很低,後端的則相對固定。
這樣一來,架構的程式設計層次更加分明,keras地位似乎要危險了。
4、展望2017:更快疊代的架構,更多變的風格,更難的壟斷地位
相比于paper的疊代,架構的疊代似乎更快了一點。
餘凱老師前段時間發出了tensorflow壟斷的擔憂,但我們可以很樂觀地看到:越來越多的使用者,在深入架構的底層。
tensorflow并不是最好的架構,mxnet也不是,最好的架構是自己用的舒服的架構,最好是一行行自己敲出來的。如果你已經積累的數個架構的使用經驗,是時候把它們無縫銜接在一起了。
雷鋒網注:本文由深度學習大講堂授權雷鋒網釋出,如需轉載請注明作者和出處,不得删減内容。
本文作者:深度學習大講堂