當前位置:
首頁 > 知識 > 告別選擇困難症,我來帶你剖析這些深度學習框架基本原理

告別選擇困難症,我來帶你剖析這些深度學習框架基本原理

本文為 AI 研習社編譯的技術博客,原標題 The Anatomy of Deep Learning Frameworks,作者為 Gokula Krishnan Santhanam。

翻譯 | Lamaric 莫青悠 整理 | 凡江

無論你喜歡或不喜歡,深度學習就在這裡等著你來學習,伴隨著技術淘金熱而來的過多的可選項,讓新手望而生畏。

如果你已經決定開始踏進深度學習的領域,首先要解決的問題之一就是:學習哪一種深度學習框架呢?如果你想要去理解所有這些框架的構建架構,我接下來介紹的內容可以替代簡單的試錯法,來幫助你在獲得了足夠信息的基礎上去做出決定,常見的深度學習框架包括:Theano、TensorFlow、Torch 和 Keras。所有的這些框架都各有利弊,也都有各自的方法去實現機器學習。在研究過白皮書和開發文檔之後,我理解了它們的在一些設計上的選擇,並總結出了對這些框架共通的基本原理概念。

在這個帖子中,我嘗試概括出這些共通的原則,這將幫助你更好的理解這些框架,並為你的勇敢的心,提供一個關於如何實現你自己的深度學習框架的一個指引。一個有趣的事實是,這些原則並不是單單特定服務於深度學習的,它們適用於任何你想要進行一系列數據計算的場景下。因此,大多數的深度學習框架也可以被用於非深度學習任務中(參見:https://www.tensorflow.org/tutorials/mandelbrot/)。

以下是深度學習框架的部分核心組件:

1.張量

2.基於張量的操作

3.計算圖和優化

4.自動微分工具

5. BLAS/cuBLAS和cuDNN的擴展

這些組件可以完善你的框架,但是你需要進行個性化的打磨去使你的框架使用起來更加的方便。在這篇文章中,我將使用Python的NumPy包作為參考使它更容易去理解。如果你之前從未使用過NumPy,無需焦躁,即使你跳過Numpy這一部分,這篇文章也是很好理解的。我堅信要從多層次的抽象概念去理解一個系統,所以你將會看到很多關於低階最優化的討論穿插著高階微積分和線性代數。如果需要更多的說明,請跳過下面的聲明。

請注意:我是Theano的投稿者,因此可能在引用文獻中傾向於它。話雖如此,theano是我訪問過的網站中,關於所有框架信息最豐富的網站之一。


張量

張量是一個框架的核心所在。張量是N維矩陣的概括(參考numpy中的ndarrays)。換一個方式來說,矩陣是是2維矩陣(行,列)。簡單的理解張量,可以認為它是N維數組。

拿一張彩色圖片舉例。圖片是一張258*320(高*寬)大小的RGB點陣圖圖像。這是一個3維張量(高,寬,通道數)。看下圖能更好的理解(摘自https://github.com/parambharat/CarND-helpers/blob/master/image_processing/Image_processing_tutorial.ipynb)。

普通的RGB圖片

同一張圖片的紅,綠,藍通道圖片

相同的圖像以 3D 張量的形式表示

作為擴展,一組100個圖像可以表示為4D張量(圖像的ID,高度,寬度,通道)。

同樣,我們將所有輸入數據表示為張量,然後將它們輸入神經網路。 在我們將數據提供給網路之前,這是一個必要的操作,否則我們必須定義適用於每種類型的操作,這會浪費大量時間。 我們還需要能夠以我們想要的形式獲取數據。

因此,我們需要一個張量對象,它支持以張量形式存儲數據。 不僅如此,我們希望該對象能夠將其他數據類型(圖像,文本,視頻)轉換為張量形式返回。 想想像 numpy.imread 和 numpy.imsave 這樣的東西,他們將圖像作為 ndarrays 讀取並分別將 ndarrays 存儲為圖像。

基本張量對象需要支持以張量形式表示數據。 這意味著支持索引,重載運算符,具有空間有效的方式來存儲數據等等。 根據進一步的設計選擇,您可能還需要添加更多功能。


張量對象的操作

神經網路可以被認為是在輸入張量上執行的一系列操作以給出輸出。 學習是通過糾正網路產生的輸出和預期輸出之間的誤差來完成的。 這些操作可能很簡單,如矩陣乘法(在sigmoids中)或更複雜,如卷積,池化或 LSTM。

Sigmoid 層表示為 Matrix Operations,來自http://www.datasciencecentral.com/profiles/blogs/matrix-multiplication-in-neural-networks

查看以下鏈接,可以了解更多:

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/

您可以跳過此部分並自己實現這些操作,但這樣做太麻煩而且效率低下。 此外,大多數操作都足夠普遍,以至於可以證明它們是框架的一部分。 NumPy 做得很好,已經實現了很多操作(它也非常快),並且有一個關於怎樣合併更多操作的運行 theano 的問題,這表明框架支持更多操作是多麼重要。

它們通常作為類實現,而不是將操作實現為函數。 這允許我們存儲有關操作的更多信息,如計算的輸出形狀(對於完整性檢查有用),如何計算梯度或梯度本身(用於自動微分),有辦法決定是否進行 GPU或CPU等上的運算。 同樣,這個想法類似於 scikit-learn 實現的各種演算法所使用的類。 您可以定義一個名為 compute 的方法來執行實際計算,並在計算完成後返回張量。

這些類通常派生自一個抽象類(在theano中,它是 Opclass)。 這將在Ops 中強制實施統一界面,並提供稍後添加新操作的方法。 這使得框架非常靈活,並確保即使在新的網路架構和非線性出現時人們也可以使用它。


計算圖和優化

到目前為止,我們有代表對他們的張量和操作的類。 神經網路的力量在於能夠將多個這樣的操作鏈接起來形成強大的逼近器。

因此,標準用例是您可以初始化張量,對它們執行操作後執行操作,最後將生成的張量解釋為標籤或實際值。 聽起來很簡單,夠嗎?

將不同的操作鏈接在一起,取自 https://colah.github.io/posts/2015-08-Backprop/

不幸的是,當您將越來越多的操作鏈接在一起時,會出現一些問題,這些問題可能會大大減慢代碼速度併產生錯誤。

1、只有在完成上一個操作後才開始下一步操作或者並行操作?

2、如何分配到不同的設備並在它們之間進行協調?

3、你如何避免冗餘操作(乘以1,添加零),緩存有用的中間值,並將多個操作減少為一個(用mul替換mul(mul(mul(Tensor,2),2),2)(Tensor, 8))

還有更多這樣的問題,有必要能夠更好地了解這些問題是否存在。 我們需要一種方法來優化空間和時間的結果操作鏈。

為了獲得更大的圖景,我們引入了一個計算圖,它基本上是一個對象,包含各種 Ops 實例的鏈接以及哪個操作獲取哪個操作的輸出以及附加信息之間的關係。 根據所討論的框架,這可以以不同的方式實現。

例如:

Theano http://deeplearning.net/software/theano/extending/graphstructures.html

TensorFlow http://download.tensorflow.org/paper/whitepaper2015.pdf

Caffe http://caffe.berkeleyvision.org/tutorial/net_layer_blob.html

像深度學習中的許多概念想法一樣,計算圖的概念已經存在了很長一段時間。 查看任何編譯教科書,您可以在用於優化的抽象語法樹和中間表示中找到類似的概念。 這些概念已經擴展並適應深度學習場景,為我們提供了計算圖。 在代碼生成之前優化圖形的想法(將在後面介紹)很簡單。 優化本身可以再次實現為類或函數,並且可以根據您是否希望代碼快速編譯或快速運行而有選擇地應用。

此外,由於您可以鳥瞰網路中將會發生的事情,因此圖表類可以決定如何在分散式環境中部署時分配 GPU 內存(如編譯器中的寄存器分配)以及在各種機器之間進行協調。 這有助於我們有效地解決上述三個問題。


自動差異化工具

使用計算圖的另一個好處是計算學習階段中使用的梯度變得模塊化並且可以直接計算。 這要歸功於鏈規則,它允許您以系統的方式計算函數組合的導數。 正如我們之前看到的,神經網路可以被認為是簡單非線性的組合,從而產生更複雜的函數。 區分這些功能只是將圖形從輸出回到輸入。 符號微分或自動微分是一種編程方式,通過它可以在計算圖中計算梯度。

符號微分是指通過分析計算衍生物,即得到梯度的表達式。 要使用它,只需將值插入到派生中並使用它即可。 不幸的是,像 ReLU(整流線性單位)這樣的一些非線性在某些點上是不可微分的。 因此,我們改為以迭代方式計算梯度。 由於第二種方法可以普遍使用,大多數計算圖形軟體包如 Computation Graph Toolkit(http://rll.berkeley.edu/cgt/)實現了自動區分,但如果要創建自己的區分,則可以使用符號區分。

推出自己的梯度計算模塊通常不是一個好主意,因為工具包更容易,更快速地將其作為包的一部分提供。 因此,要麼擁有自己的 Computation Graph 工具包和自動差異化模塊,要麼使用外部包。

由於每個節點的導數必須僅相對於其相鄰節點計算,因此計算梯度的方法可以加到類中,並且可以由微分模塊調用。


BLAS / cuBLAS 和 cuDNN 擴展

使用上述所有組件,您可以立即停止並擁有功能齊全的深度學習框架。 它可以將數據作為輸入並轉換為張量,以有效的方式對它們執行操作,計算漸變以學習並返回測試數據集的結果。 然而,問題在於,由於您最有可能以高級語言(Java / Python / Lua)實現它,因此您可以獲得加速的固有上限。 這是因為即使是高級語言中最簡單的操作也比使用低級語言完成時間更長(CPU周期)。

在這些情況下,我們可以採取兩種不同的方法。

第一個是編譯器的另一個類推。 編譯過程的最後一步是在 Assembly 中生成硬體特定代碼。 類似地,不是運行用高級語言編寫的圖形,而是在 C 中生成網路的相應代碼,並且編譯和執行該代碼。 其代碼存儲在每個Ops 中,可以在編譯階段中組合在一起。 通過 pyCUDA 和 Cython 之類的包裝器將數據由低級代碼傳輸到高級代碼。

第二種方法是使用 C++ 等低級語言實現後端,這意味著低級語言 - 高級語言交互是框架內部的,與之前的方法不同,可能更快,因為我們不需要每次都編譯整個圖。 相反,我們可以用適當的參數來調用編譯的方法。

非最佳行為的另一個來源是低級語言的慢速實現。 很難編寫有效的代碼,我們最好使用具有這些方法優化實現的庫。 BLAS 或基本線性代數子程序是優化矩陣運算的集合,最初用 Fortran 編寫。 這些可用於執行非常快速的矩陣(張量)操作,並可提供顯著的加速。 還有許多其他軟體包,如英特爾 MKL,ATLAS,它們也執行類似的功能。 選擇哪一個是個人偏好。

假設指令將在 CPU 上運行,BLAS 包通常會進行優化。 在深度學習的情況下,情況並非如此,BLAS 可能無法充分利用GPGPU提供的並行性。 為了解決這個問題,NVIDIA 發布了針對 GPU 優化的 cuBLAS。 現在它已包含在 CUDA 工具包中,這可能是很多人沒有聽說過的原因。 最後,cuDNN 是一個基於 cuBLAS 功能集的庫,提供優化的神經網路特定操作,如 Winograd 卷積和 RNN。

因此,通過使用這些軟體包,您可以在框架中獲得顯著的加速。 加速在機器學習中很重要,因為它是在四小時而不是四天內訓練神經網路之間的差異。 在人工智慧初創公司快速發展的世界中,這就是成為先鋒和追趕遊戲之間的區別。 因此,儘可能利用並行性和優化庫!


結論

我們終於走到了一個很長的帖子的結尾,非常感謝各位閱讀它。 我希望我已經揭開了許多人對深度學習框架怎樣剖析的神秘面紗。 我寫這篇文章的主要目的是讓我更好地理解不同的框架如何做同樣的事情。 對於那些高於初級水平但低於專業水平的人(如果你願意的話,半專業人士),這將是一個非常有用的練習。 一旦你能夠理解背後的工作方式,他們就會更容易接近和掌握。 框架在抽象出大多數想法方面做得很好,以便為程序員提供簡單的界面。 難怪在學習框架時,大多數概念都不是很明顯。

作為一個不僅對深度學習的應用感興趣而且對該領域的基本挑戰感興趣的人,我相信知道如何在幕後工作是邁向掌握主旨的重要一步,因為它清除了許多誤解並提供了一種更簡單的方法來思考為什麼事情就是這樣。 我真誠地相信,優秀的員工不僅知道使用哪種工具,而且還知道為什麼該工具是最佳選擇。 這篇博客是朝這個方向邁出的一步。


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 AI研習社 的精彩文章:

免費資源正式對外開放,快來下載!
想要訓練專屬人臉識別模型?先掌握構建人臉數據集的三種絕招

TAG:AI研習社 |