0
本文作者: 恒亮 | 2017-01-20 17:03 |
許多初學(xué)者覺得深度學(xué)習(xí)框架抽象,雖然調(diào)用了幾個函數(shù)/方法,計算了幾個數(shù)學(xué)難題,但始終不能理解這些框架的全貌。
為了更好地認(rèn)識深度學(xué)習(xí)框架,也為了給一些想要自己親手搭建深度學(xué)習(xí)框架的朋友提供一些基礎(chǔ)性的指導(dǎo),日前來自蘇黎世聯(lián)邦理工學(xué)院計算機(jī)科學(xué)系的碩士研究生Gokula Krishnan Santhanam在博客上撰文,概括了大部分深度學(xué)習(xí)框架都會包含的五大核心組件,為我們詳細(xì)剖析了深度學(xué)習(xí)框架一般性的內(nèi)部組織結(jié)構(gòu)。以下由雷鋒網(wǎng)編譯。
Gokula Krishnan Santhanam認(rèn)為,大部分深度學(xué)習(xí)框架都包含以下五個核心組件:
1. 張量(Tensor)
2. 基于張量的各種操作
3. 計算圖(Computation Graph)
4. 自動微分(Automatic Differentiation)工具
5. BLAS、cuBLAS、cuDNN等拓展包
張量是所有深度學(xué)習(xí)框架中最核心的組件,因為后續(xù)的所有運(yùn)算和優(yōu)化算法都是基于張量進(jìn)行的。幾何代數(shù)中定義的張量是基于向量和矩陣的推廣,通俗一點(diǎn)理解的話,我們可以將標(biāo)量視為零階張量,矢量視為一階張量,那么矩陣就是二階張量。
舉例來說,我們可以將任意一張RGB彩色圖片表示成一個三階張量(三個維度分別是圖片的高度、寬度和色彩數(shù)據(jù))。如下圖所示是一張普通的水果圖片,按照RGB三原色表示,其可以拆分為三張紅色、綠色和藍(lán)色的灰度圖片,如果將這種表示方法用張量的形式寫出來,就是圖中最下方的那張表格。
圖中只顯示了前5行、320列的數(shù)據(jù),每個方格代表一個像素點(diǎn),其中的數(shù)據(jù)[1.0, 1.0, 1.0]即為顏色。假設(shè)用[1.0, 0, 0]表示紅色,[0, 1.0, 0]表示綠色,[0, 0, 1.0]表示藍(lán)色,那么如圖所示,前面5行的數(shù)據(jù)則全是白色。
將這一定義進(jìn)行擴(kuò)展,我們也可以用四階張量表示一個包含多張圖片的數(shù)據(jù)集,其中的四個維度分別是:圖片在數(shù)據(jù)集中的編號,圖片高度、寬度,以及色彩數(shù)據(jù)。
將各種各樣的數(shù)據(jù)抽象成張量表示,然后再輸入神經(jīng)網(wǎng)絡(luò)模型進(jìn)行后續(xù)處理是一種非常必要且高效的策略。因為如果沒有這一步驟,我們就需要根據(jù)各種不同類型的數(shù)據(jù)組織形式定義各種不同類型的數(shù)據(jù)操作,這會浪費(fèi)大量的開發(fā)者精力。更關(guān)鍵的是,當(dāng)數(shù)據(jù)處理完成后,我們還可以方便地將張量再轉(zhuǎn)換回想要的格式。例如Python NumPy包中numpy.imread和numpy.imsave兩個方法,分別用來將圖片轉(zhuǎn)換成張量對象(即代碼中的Tensor對象),和將張量再轉(zhuǎn)換成圖片保存起來。
有了張量對象之后,下面一步就是一系列針對這一對象的數(shù)學(xué)運(yùn)算和處理過程。
其實(shí),整個神經(jīng)網(wǎng)絡(luò)都可以簡單視為為了達(dá)到某種目的,針對輸入張量進(jìn)行的一系列操作過程。而所謂的“學(xué)習(xí)”就是不斷糾正神經(jīng)網(wǎng)絡(luò)的實(shí)際輸出結(jié)果和預(yù)期結(jié)果之間誤差的過程。這里的一系列操作包含的范圍很寬,可以是簡單的矩陣乘法,也可以是卷積、池化和LSTM等稍復(fù)雜的運(yùn)算。而且各框架支持的張量操作通常也不盡相同,詳細(xì)情況可以查看其官方文檔(如下為NumPy、Theano和TensorFlow的說明文檔)。
NumPy:http://www.scipy-lectures.org/intro/numpy/operations.html
Theano:http://deeplearning.net/software/theano/library/tensor/basic.html
TensorFlow:https://www.tensorflow.org/api_docs/python/math_ops/
需要指出的是,大部分的張量操作都是基于類實(shí)現(xiàn)的(而且是抽象類),而并不是函數(shù)(這一點(diǎn)可能要?dú)w功于大部分的深度學(xué)習(xí)框架都是用面向?qū)ο蟮木幊陶Z言實(shí)現(xiàn)的)。這種實(shí)現(xiàn)思路一方面允許開發(fā)者將各種類似的操作匯總在一起,方便組織管理。另一方面也保證了整個代碼的復(fù)用性、擴(kuò)展性和對外接口的統(tǒng)一??傮w上讓整個框架更靈活和易于擴(kuò)展,為將來的發(fā)展預(yù)留了空間。
有了張量和基于張量的各種操作之后,下一步就是將各種操作整合起來,輸出我們需要的結(jié)果。
但不幸的是,隨著操作種類和數(shù)量的增多,有可能引發(fā)各種意想不到的問題,包括多個操作之間應(yīng)該并行還是順次執(zhí)行,如何協(xié)同各種不同的底層設(shè)備,以及如何避免各種類型的冗余操作等等。這些問題有可能拉低整個深度學(xué)習(xí)網(wǎng)絡(luò)的運(yùn)行效率或者引入不必要的Bug,而計算圖正是為解決這一問題產(chǎn)生的。
據(jù)雷鋒網(wǎng)了解,計算圖首次被引入人工智能領(lǐng)域是在2009年的論文《Learning Deep Architectures for AI》。當(dāng)時的圖片如下所示,作者用不同的占位符(*,+,sin)構(gòu)成操作結(jié)點(diǎn),以字母x、a、b構(gòu)成變量結(jié)點(diǎn),再以有向線段將這些結(jié)點(diǎn)連接起來,組成一個表征運(yùn)算邏輯關(guān)系的清晰明了的“圖”型數(shù)據(jù)結(jié)構(gòu),這就是最初的計算圖。
后來隨著技術(shù)的不斷演進(jìn),加上腳本語言和低級語言各自不同的特點(diǎn)(概括地說,腳本語言建模方便但執(zhí)行緩慢,低級語言則正好相反),因此業(yè)界逐漸形成了這樣的一種開發(fā)框架:前端用Python等腳本語言建模,后端用C++等低級語言執(zhí)行(這里低級是就應(yīng)用層而言),以此綜合了兩者的優(yōu)點(diǎn)??梢钥吹?,這種開發(fā)框架大大降低了傳統(tǒng)框架做跨設(shè)備計算時的代碼耦合度,也避免了每次后端變動都需要修改前端的維護(hù)開銷。而這里,在前端和后端之間起到關(guān)鍵耦合作用的就是計算圖。
將計算圖作為前后端之間的中間表示(Intermediate Representations)可以帶來良好的交互性,開發(fā)者可以將Tensor對象作為數(shù)據(jù)結(jié)構(gòu),函數(shù)/方法作為操作類型,將特定的操作類型應(yīng)用于特定的數(shù)據(jù)結(jié)構(gòu),從而定義出類似MATLAB的強(qiáng)大建模語言。
需要注意的是,通常情況下開發(fā)者不會將用于中間表示得到的計算圖直接用于模型構(gòu)造,因為這樣的計算圖通常包含了大量的冗余求解目標(biāo),也沒有提取共享變量,因而通常都會經(jīng)過依賴性剪枝、符號融合、內(nèi)存共享等方法對計算圖進(jìn)行優(yōu)化。
目前,各個框架對于計算圖的實(shí)現(xiàn)機(jī)制和側(cè)重點(diǎn)各不相同。例如Theano和MXNet都是以隱式處理的方式在編譯中由表達(dá)式向計算圖過渡。而Caffe則比較直接,可以創(chuàng)建一個Graph對象,然后以類似Graph.Operator(xxx)的方式顯示調(diào)用。
因為計算圖的引入,開發(fā)者得以從宏觀上俯瞰整個神經(jīng)網(wǎng)絡(luò)的內(nèi)部結(jié)構(gòu),就好像編譯器可以從整個代碼的角度決定如何分配寄存器那樣,計算圖也可以從宏觀上決定代碼運(yùn)行時的GPU內(nèi)存分配,以及分布式環(huán)境中不同底層設(shè)備間的相互協(xié)作方式。除此之外,現(xiàn)在也有許多深度學(xué)習(xí)框架將計算圖應(yīng)用于模型調(diào)試,可以實(shí)時輸出當(dāng)前某一操作類型的文本描述。
計算圖帶來的另一個好處是讓模型訓(xùn)練階段的梯度計算變得模塊化且更為便捷,也就是自動微分法。
正如前面提到的,因為我們可以將神經(jīng)網(wǎng)絡(luò)視為由許多非線性過程組成的一個復(fù)雜的函數(shù)體,而計算圖則以模塊化的方式完整表征了這一函數(shù)體的內(nèi)部邏輯關(guān)系,因此微分這一復(fù)雜函數(shù)體,即求取模型梯度的方法就變成了在計算圖中簡單地從輸入到輸出進(jìn)行一次完整遍歷的過程。與自動微分對應(yīng),業(yè)內(nèi)更傳統(tǒng)的做法是符號微分。
符號微分即常見的求導(dǎo)分析。針對一些非線性過程(如修正線性單元ReLU)或者大規(guī)模的問題,使用符號微分法的成本往往非常高昂,有時甚至不可行(即不可微)。因此,以上述迭代式的自動微分法求解模型梯度已經(jīng)被廣泛采用。并且由于自動微分可以成功應(yīng)對一些符號微分不適用的場景,目前許多計算圖程序包(例如Computation Graph Toolkit)都已經(jīng)預(yù)先實(shí)現(xiàn)了自動微分。
另外,由于每個節(jié)點(diǎn)處的導(dǎo)數(shù)只能相對于其相鄰節(jié)點(diǎn)計算,因此實(shí)現(xiàn)了自動微分的模塊一般都可以直接加入任意的操作類中,當(dāng)然也可以被上層的微分大模塊直接調(diào)用。
現(xiàn)在,通過上述所有模塊,我們已經(jīng)可以搭建一個全功能的深度學(xué)習(xí)框架:將待處理數(shù)據(jù)轉(zhuǎn)換為張量,針對張量施加各種需要的操作,通過自動微分對模型展開訓(xùn)練,然后得到輸出結(jié)果開始測試。這時還缺什么呢?答案是運(yùn)算效率。
由于此前的大部分實(shí)現(xiàn)都是基于高級語言的(如Java、Python、Lua等),而即使是執(zhí)行最簡單的操作,高級語言也會比低級語言消耗更多的CPU周期,更何況是結(jié)構(gòu)復(fù)雜的深度神經(jīng)網(wǎng)絡(luò),因此運(yùn)算緩慢就成了高級語言的一個天然的缺陷。
目前針對這一問題有兩種解決方案。
第一種方法是模擬傳統(tǒng)的編譯器。就好像傳統(tǒng)編譯器會把高級語言編譯成特定平臺的匯編語言實(shí)現(xiàn)高效運(yùn)行一樣,這種方法將高級語言轉(zhuǎn)換為C語言,然后在C語言基礎(chǔ)上編譯、執(zhí)行。為了實(shí)現(xiàn)這種轉(zhuǎn)換,每一種張量操作的實(shí)現(xiàn)代碼都會預(yù)先加入C語言的轉(zhuǎn)換部分,然后由編譯器在編譯階段將這些由C語言實(shí)現(xiàn)的張量操作綜合在一起。目前pyCUDA和Cython等編譯器都已經(jīng)實(shí)現(xiàn)了這一功能。
第二種方法就是前文提到的,利用腳本語言實(shí)現(xiàn)前端建模,用低級語言如C++實(shí)現(xiàn)后端運(yùn)行,這意味著高級語言和低級語言之間的交互都發(fā)生在框架內(nèi)部,因此每次的后端變動都不需要修改前端,也不需要完整編譯(只需要通過修改編譯參數(shù)進(jìn)行部分編譯),因此整體速度也就更快。
除此之外,由于低級語言的最優(yōu)化編程難度很高,而且大部分的基礎(chǔ)操作其實(shí)也都有公開的最優(yōu)解決方案,因此另一個顯著的加速手段就是利用現(xiàn)成的擴(kuò)展包。例如最初用Fortran實(shí)現(xiàn)的BLAS(基礎(chǔ)線性代數(shù)子程序),就是一個非常優(yōu)秀的基本矩陣(張量)運(yùn)算庫,此外還有英特爾的MKL(Math Kernel Library)等,開發(fā)者可以根據(jù)個人喜好靈活選擇。
值得一提的是,一般的BLAS庫只是針對普通的CPU場景進(jìn)行了優(yōu)化,但目前大部分的深度學(xué)習(xí)模型都已經(jīng)開始采用并行GPU的運(yùn)算模式,因此利用諸如NVIDIA推出的針對GPU優(yōu)化的cuBLAS和cuDNN等更據(jù)針對性的庫可能是更好的選擇。
運(yùn)算速度對于深度學(xué)習(xí)框架來說至關(guān)重要,例如同樣訓(xùn)練一個神經(jīng)網(wǎng)絡(luò),不加速需要4天的時間,加速的話可能只要4小時。在快速發(fā)展的人工智能領(lǐng)域,特別是對那些成立不久的人工智能初創(chuàng)公司而言,這種差別可能就會決定誰是先驅(qū)者,而誰是追隨者。
原文作者在文末指出:為了向開發(fā)者提供盡量簡單的接口,大部分深度學(xué)習(xí)框架通常都會將普通的概念抽象化,這可能是造成許多用戶感知不到上述五點(diǎn)核心組件的重要原因。
而這也正是作者寫本文的初衷:他希望開發(fā)者能夠通過了解不同框架之間的一些相似特性,更好地認(rèn)識和使用一個深度學(xué)習(xí)框架。另一方面,對于那些不僅對學(xué)會使用深度學(xué)習(xí)框架感興趣,還打算親手搭建一個深度框架的朋友,作者認(rèn)為了解各框架的內(nèi)部組成和一些共性的特征也是邁向成功的重要一步。他真誠地相信,一個優(yōu)秀的工程師不僅應(yīng)該“知其然”,更應(yīng)該“知其所以然”。
來源:medium,雷鋒網(wǎng)編譯
雷峰網(wǎng)版權(quán)文章,未經(jīng)授權(quán)禁止轉(zhuǎn)載。詳情見轉(zhuǎn)載須知。