深度學習框架太抽象?其實不外乎這五大核心組件

許多初學者以爲深度學習框架抽象,雖然調用了幾個函數/方法,計算了幾個數學難題,但始終不能理解這些框架的全貌。html

 

爲了更好地認識深度學習框架,也爲了給一些想要本身親手搭建深度學習框架的朋友提供一些基礎性的指導,日前來自蘇黎世聯邦理工學院計算機科學系的碩士研究生Gokula Krishnan Santhanam在博客上撰文,歸納了大部分深度學習框架都會包含的五大核心組件,爲咱們詳細剖析了深度學習框架通常性的內部組織結構。如下由AI科技評論編譯。前端

 

Gokula Krishnan Santhanam認爲,大部分深度學習框架都包含如下五個核心組件:python

 

1. 張量(Tensor)算法

2. 基於張量的各類操做編程

3. 計算圖(Computation Graph)後端

4. 自動微分(Automatic Differentiation)工具api

5. BLAS、cuBLAS、cuDNN等拓展包網絡

 

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對象),和將張量再轉換成圖片保存起來。

 

2. 基於張量的各類操做

 

有了張量對象以後,下面一步就是一系列針對這一對象的數學運算和處理過程。

 

 

其實,整個神經網絡均可以簡單視爲爲了達到某種目的,針對輸入張量進行的一系列操做過程。而所謂的「學習」就是不斷糾正神經網絡的實際輸出結果和預期結果之間偏差的過程。這裏的一系列操做包含的範圍很寬,能夠是簡單的矩陣乘法,也能夠是卷積、池化和LSTM等稍複雜的運算。並且各框架支持的張量操做一般也不盡相同,詳細狀況能夠查看其官方文檔(以下爲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/

 

須要指出的是,大部分的張量操做都是基於類實現的(並且是抽象類),而並非函數(這一點可能要歸功於大部分的深度學習框架都是用面向對象的編程語言實現的)。這種實現思路一方面容許開發者將各類相似的操做彙總在一塊兒,方便組織管理。另外一方面也保證了整個代碼的複用性、擴展性和對外接口的統一。整體上讓整個框架更靈活和易於擴展,爲未來的發展預留了空間。

 

3. 計算圖(Computation Graph)

 

有了張量和基於張量的各類操做以後,下一步就是將各類操做整合起來,輸出咱們須要的結果。

 

但不幸的是,隨着操做種類和數量的增多,有可能引起各類意想不到的問題,包括多個操做之間應該並行仍是順次執行,如何協同各類不一樣的底層設備,以及如何避免各類類型的冗餘操做等等。這些問題有可能拉低整個深度學習網絡的運行效率或者引入沒必要要的Bug,而計算圖正是爲解決這一問題產生的。

 

據AI科技評論瞭解,計算圖首次被引入人工智能領域是在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. 自動微分(Automatic Differentiation)工具

 

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

 

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

 

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

 

另外,因爲每一個節點處的導數只能相對於其相鄰節點計算,所以實現了自動微分的模塊通常均可以直接加入任意的操做類中,固然也能夠被上層的微分大模塊直接調用。

 

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優化的cuBLAS和cuDNN等更據針對性的庫多是更好的選擇。

 

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

 

總結

 

原文做者在文末指出:爲了向開發者提供儘可能簡單的接口,大部分深度學習框架一般都會將普通的概念抽象化,這多是形成許多用戶感知不到上述五點核心組件的重要緣由。

 

而這也正是做者寫本文的初衷:他但願開發者可以經過了解不一樣框架之間的一些類似特性,更好地認識和使用一個深度學習框架。另外一方面,對於那些不只對學會使用深度學習框架感興趣,還打算親手搭建一個深度框架的朋友,做者認爲了解各框架的內部組成和一些共性的特徵也是邁向成功的重要一步。他真誠地相信,一個優秀的工程師不只應該「知其然」,更應該「知其因此然」。

 

來源:medium

相關文章
相關標籤/搜索