1. 程式人生 > >一、深度學習框架究竟是什麼?

一、深度學習框架究竟是什麼?

1 引言

一直在說深度學習框架,最近也在使用tensorflow進行了簡單的實驗,但是對其中關係的理解還是不夠到位,他們裡面究竟是怎樣的一個執行機制呢?

2 說明

深度學習框架也就像Caffe、tensorflow這些是深度學習的工具,簡單來說就是庫,程式設計時需要import caffe、import tensorflow。作一個簡單的比喻,一套深度學習框架就是這個品牌的一套積木,各個元件就是某個模型或演算法的一部分,你可以自己設計如何使用積木去堆砌符合你資料集的積木。好處是你不必重複造輪子,模型也就是積木,是給你的,你可以直接組裝,但不同的組裝方式,也就是不同的資料集則取決於你。

3 應用優勢

深度學習框架的出現降低了入門的門檻,你不需要從複雜的神經網路開始編程式碼,你可以依據需要,使用已有的模型,模型的引數你自己訓練得到,你也可以在已有模型的基礎上增加自己的layer,或者是在頂端選擇自己需要的分類器和優化演算法(比如常用的梯度下降法)。當然也正因如此,沒有什麼框架是完美的,就像一套積木裡可能沒有你需要的那一種積木,所以不同的框架適用的領域不完全一致。 總的來說深度學習框架提供了一些列的深度學習的元件(對於通用的演算法,裡面會有實現),當需要使用新的演算法的時候就需要使用者自己去定義,然後呼叫深度學習框架的函式介面使用使用者自定義的新演算法.

4 關於元件

大部分深度學習框架都包含以下五個核心元件: 1. 張量(Tensor) 2. 基於張量的各種操作 3. 計算圖(Computation Graph) 4. 自動微分(Automatic Differentiation)工具 5. BLAS、cuBLAS、cuDNN等拓展包

下面對這五大核心元件做一個簡要的解釋

4.1張量(Tensor)

張量是所有深度學習框架中最核心的元件,因為後續的所有運算和優化演算法都是基於張量進行的。幾何代數中定義的張量是基於向量和矩陣的推廣,通俗一點理解的話,我們可以將標量視為零階張量,向量視為一階張量,那麼矩陣就是二階張量。

舉例來說,我們可以將任意一張RGB彩色圖片表示成一個三階張量(三個維度分別是圖片的高度、寬度和色彩資料)。如下圖所示是一張普通的水果圖片,按照RGB三原色表示,其可以拆分為三張紅色、綠色和藍色的灰度圖片,如果將這種表示方法用張量的形式寫出來,就是圖中最下方的那張表格。 這裡寫圖片描述 這裡寫圖片描述 這裡寫圖片描述 圖中只顯示了前5行、320列的資料,每個方格代表一個畫素點,其中的資料[1.0, 1.0, 1.0]即為顏色。假設用[1.0, 0, 0]表示紅色,[0, 1.0, 0]表示綠色,[0, 0, 1.0]表示藍色,那麼如圖所示,前面5行的資料則全是白色。

將這一定義進行擴充套件,我們也可以用四階張量表示一個包含多張圖片的資料集,其中的四個維度分別是:圖片在資料集中的編號,圖片高度、寬度,以及色彩資料。

將各種各樣的資料抽象成張量表示,然後再輸入神經網路模型進行後續處理是一種非常必要且高效的策略。因為如果沒有這一步驟,我們就需要根據各種不同型別的資料組織形式定義各種不同型別的資料操作,這會浪費大量的開發者精力。更關鍵的是,當資料處理完成後,我們還可以方便地將張量再轉換回想要的格式。例如Python NumPy包中numpy.imread和numpy.imsave兩個方法,分別用來將圖片轉換成張量物件(即程式碼中的Tensor物件),和將張量再轉換成圖片儲存起來。

4.2 基於張量的各種操作

有了張量物件之後,下面一步就是一系列針對這一物件的數學運算和處理過程。 這裡寫圖片描述 其實,整個神經網路都可以簡單視為為了達到某種目的,針對輸入張量進行的一系列操作過程。而所謂的“學習”就是不斷糾正神經網路的實際輸出結果和預期結果之間誤差的過程。這裡的一系列操作包含的範圍很寬,可以是簡單的矩陣乘法,也可以是卷積、池化和LSTM等稍複雜的運算。而且各框架支援的張量操作通常也不盡相同,詳細情況可以檢視其官方文件(如下為NumPy、Theano和TensorFlow的說明文件)。

需要指出的是,大部分的張量操作都是基於類實現的(而且是抽象類),而並不是函式(這一點可能要歸功於大部分的深度學習框架都是用面向物件的程式語言實現的)。這種實現思路一方面允許開發者將各種類似的操作彙總在一起,方便組織管理。另一方面也保證了整個程式碼的複用性、擴充套件性和對外介面的統一。總體上讓整個框架更靈活和易於擴充套件,為將來的發展預留了空間。

4.3 計算圖(Computation Graph)

有了張量和基於張量的各種操作之後,下一步就是將各種操作整合起來,輸出我們需要的結果。

但不幸的是,隨著操作種類和數量的增多,管理起來就變得十分困難,各操作之間的關係變得比較難以理清,有可能引發各種意想不到的問題,包括多個操作之間應該並行還是順次執行,如何協同各種不同的底層裝置,以及如何避免各種型別的冗餘操作等等。這些問題有可能拉低整個深度學習網路的執行效率或者引入不必要的Bug,而計算圖正是為解決這一問題產生的。

計算圖首次被引入人工智慧領域是在2009年的論文《Learning Deep Architectures for AI》。當時的圖片如下所示,作者用不同的佔位符(*,+,sin)構成操作結點,以字母x、a、b構成變數結點,再以有向線段將這些結點連線起來,組成一個表徵運算邏輯關係的清晰明瞭的“圖”型資料結構,這就是最初的計算圖。 這裡寫圖片描述 後來隨著技術的不斷演進,加上指令碼語言和低階語言各自不同的特點(概括地說,指令碼語言建模方便但執行緩慢,低階語言則正好相反),因此業界逐漸形成了這樣的一種開發框架:前端用Python等指令碼語言建模,後端用C++等低階語言執行(這裡低階是就應用層而言),以此綜合了兩者的優點。可以看到,這種開發框架大大降低了傳統框架做跨裝置計算時的程式碼耦合度,也避免了每次後端變動都需要修改前端的維護開銷。而這裡,在前端和後端之間起到關鍵耦合作用的就是計算圖。

將計算圖作為前後端之間的中間表示(Intermediate Representations)可以帶來良好的互動性,開發者可以將Tensor物件作為資料結構,函式/方法作為操作型別,將特定的操作型別應用於特定的資料結構,從而定義出類似MATLAB的強大建模語言。

需要注意的是,通常情況下開發者不會將用於中間表示得到的計算圖直接用於模型構造,因為這樣的計算圖通常包含了大量的冗餘求解目標,也沒有提取共享變數,因而通常都會經過依賴性剪枝、符號融合、記憶體共享等方法對計算圖進行優化。

目前,各個框架對於計算圖的實現機制和側重點各不相同。例如Theano和MXNet都是以隱式處理的方式在編譯中由表示式向計算圖過渡。而Caffe則比較直接,可以建立一個Graph物件,然後以類似Graph.Operator(xxx)的方式顯示呼叫。

因為計算圖的引入,開發者得以從巨集觀上俯瞰整個神經網路的內部結構,就好像編譯器可以從整個程式碼的角度決定如何分配暫存器那樣,計算圖也可以從巨集觀上決定程式碼執行時的GPU記憶體分配,以及分散式環境中不同底層裝置間的相互協作方式。除此之外,現在也有許多深度學習框架將計算圖應用於模型除錯,可以實時輸出當前某一操作型別的文字描述。

4.4 自動微分(Automatic Differentiation)工具

計算圖帶來的另一個好處是讓模型訓練階段的梯度計算變得模組化且更為便捷,也就是自動微分法。

正如前面提到的,因為我們可以將神經網路視為由許多非線性過程組成的一個複雜的函式體,而計算圖則以模組化的方式完整表徵了這一函式體的內部邏輯關係,因此微分這一複雜函式體,即求取模型梯度的方法就變成了在計算圖中簡單地從輸入到輸出進行一次完整遍歷的過程。與自動微分對應,業內更傳統的做法是符號微分。

符號微分即常見的求導分析。針對一些非線性過程(如修正線性單元ReLU)或者大規模的問題,使用符號微分法的成本往往非常高昂,有時甚至不可行(即不可微)。因此,以上述迭代式的自動微分法求解模型梯度已經被廣泛採用。並且由於自動微分可以成功應對一些符號微分不適用的場景,目前許多計算圖程式包(例如Computation Graph Toolkit)都已經預先實現了自動微分。

另外,由於每個節點處的導數只能相對於其相鄰節點計算,因此實現了自動微分的模組一般都可以直接加入任意的操作類中,當然也可以被上層的微分大模組直接呼叫。

4.5 BLAS、cuBLAS、cuDNN等拓展包

現在,通過上述所有模組,我們已經可以搭建一個全功能的深度學習框架:將待處理資料轉換為張量,針對張量施加各種需要的操作,通過自動微分對模型展開訓練,然後得到輸出結果開始測試。這時還缺什麼呢?答案是運算效率。

由於此前的大部分實現都是基於高階語言的(如Java、Python、Lua等),而即使是執行最簡單的操作,高階語言也會比低階語言消耗更多的CPU週期,更何況是結構複雜的深度神經網路,因此運算緩慢就成了高階語言的一個天然的缺陷。

目前針對這一問題有兩種解決方案。

第一種方法是模擬傳統的編譯器。就好像傳統編譯器會把高階語言編譯成特定平臺的組合語言實現高效執行一樣,這種方法將高階語言轉換為C語言,然後在C語言基礎上編譯、執行。為了實現這種轉換,每一種張量操作的實現程式碼都會預先加入C語言的轉換部分,然後由編譯器在編譯階段將這些由C語言實現的張量操作綜合在一起。目前pyCUDA和Cython等編譯器都已經實現了這一功能。

第二種方法就是前文提到的,利用指令碼語言實現前端建模,用低階語言如C++實現後端執行,這意味著高階語言和低階語言之間的互動都發生在框架內部,因此每次的後端變動都不需要修改前端,也不需要完整編譯(只需要通過修改編譯引數進行部分編譯),因此整體速度也就更快。

除此之外,由於低階語言的最優化程式設計難度很高,而且大部分的基礎操作其實也都有公開的最優解決方案,因此另一個顯著的加速手段就是利用現成的擴充套件包。例如最初用Fortran實現的BLAS(基礎線性代數子程式),就是一個非常優秀的基本矩陣(張量)運算庫,此外還有英特爾的MKL(Math Kernel Library)等,開發者可以根據個人喜好靈活選擇。 這裡寫圖片描述 值得一提的是,一般的BLAS庫只是針對普通的CPU場景進行了優化,但目前大部分的深度學習模型都已經開始採用並行GPU的運算模式,因此利用諸如NVIDIA推出的針對GPU優化的cuBLAScuDNN等更據針對性的庫可能是更好的選擇。

運算速度對於深度學習框架來說至關重要,例如同樣訓練一個神經網路,不加速需要4天的時間,加速的話可能只要4小時。在快速發展的人工智慧領域,特別是對那些成立不久的人工智慧初創公司而言,這種差別可能就會決定誰是先驅者,而誰是追隨者。