精讀《深度學習 - 函數式之美》

1 引言

函數式語言在深度學習領域應用很普遍,由於函數式與深度學習模型的契合度很高,The Beauty of Functional Languages in Deep Learning — Clojure and Haskell 就很好的詮釋了這個道理。html

經過這篇文章能夠加深咱們對深度學習與函數式編程的理解。前端

2 概述與精讀

深度學習是機器學習中基於人工神經網絡模型的一個分支,經過模擬多層神經元的自編碼神經網絡,將特徵逐步抽象化,這須要多維度、大數據量的輸入。TensorFlowPyTorch 是比較著名的 Python 深度學習框架,一樣 Keras 在 R 語言中也很著名。然而在生產環境中,基於 性能和安全性 的考慮,通常會使用函數式語言 ClojureHaskelljava

在生產環境中,可能要併發出裏幾百萬個參數,所以面臨的挑戰是:如何高效、安全的執行這些運算。git

因此爲何函數式編程語言能夠勝任深度學習的計算要求呢? 深度學習的計算模型本質上是數學模型,而數學模型本質上和函數式編程思路是一致的:數據不可變且函數間能夠任意組合。這意味着使用函數式編程語言能夠更好的表達深度學習的計算過程,所以更容易理解與維護,同時函數式語言內置的 Immutable 數據結構也保障了併發的安全性。github

另外函數式語言的函數之間都是相互隔離的,即使在多線程環境下也不會發生競爭和死鎖的狀況,函數式編程語言會自動處理這些狀況。編程

好比說 Clojure它甚至可在兩個同時修改同一引用的程序併發運行時,自動重試其中之一,而不須要手動加鎖數組

(import ‘(java.util.concurrent Executors))
(defn test-stm [nitems nthreads niters]
 (let [refs (map ref (repeat nitems 0))
   pool (Executors/newFixedThreadPool nthreads)
   tasks (map (fn [t]
               (fn []
                (dotimes [n niters]
                  (dosync
                   (doseq [r refs]
                    (alter r + 1 t))))))
              (range nthreads))]
   (doseq [future (.invokeAll pool tasks)]
      (.get future))
   (.shutdown pool)
   (map deref refs)))
(test-stm 10 10 10000) -> (550000 550000 550000 550000 550000 550000 550000 550000 550000 550000)
複製代碼

上面的代碼建立了引用(refs),同時建立了多個線程自增這個引用對象,按理說每一個線程都修改這個引用會致使競爭狀態出現,但從結果來看是正常的,說明 Clojure 引擎在執行時會自動解決這個問題。實際上當兩個線程出現競爭而失敗時,Clojure 會自動重試其中之一。安全

原文介紹微信

Clojure 的另外一個優點是並行效率高:網絡

(defn calculate-pixels-2 []
 (let [n (* *width* *height*)
       work (partition (/ n 16) (range 0 n))
       result (pmap (fn [x]
                  (doall (map
                   (fn [p]
                     (let [row (rem p *width*) col (int (/ p *height*))]
                       (get-color (process-pixel (/ row (double *width*)) (/ col (double *height*))))))
                   x)))
                  work)]
   (doall (apply concat result))))
複製代碼

使用 partition 結合 pmap 可使併發效率達到最大化,也就是 CPU 幾乎都消耗在實際計算上,而不是並行的任務管理與上下文切換。Clojure 憑藉 partition 對計算進行分區,採起分而治之並對分區計算結果進行合併的思路優化了併發性能。

原文介紹

Clojure 另外一個特性是函數鏈式調用:

;; pipe arg to function
(-> "x" f1) ; "x1"

;; pipe. function chaining
(-> "x" f1 f2) ; "x12"
複製代碼

其中 (-> "x" f1 f2) 等價於 f2(f1("x")),這種描述不只更簡潔清晰,也更接近於實際數學模型。

原文介紹

最後,Clojure 還具有計算安全性,計算過程不會修改已有的數據,所以在神經網絡的任何一層的原始值都會保留,每層計算均可以獨立運行且函數永遠冪等。

Haskell 也有獨特的優點,它具備類型推斷、惰性求值等特性,被認爲更適合用於機器學習。

類型推斷即 Haskell 類型都是靜態的,若是試圖賦予錯誤的類型會報錯。

Haskell 的另外一個優點是能夠很是清晰的描述數學模型。

想一想通常數學模型是怎麼描述函數的:

fn =>
 f1 = 1
 f2 = 9
 f3 = 16
 n > 2, fn = 3fn-3 + 2fn-2 + fn-1
複製代碼

通常語言用 if-else 描述等價關係,但 Haskell 能夠幾乎原汁原味的還原函數定義過程:

solve :: Int -> Interger
solve 1 = 1
solve 2 = 9
solve 3 = 16
solve n = 3 * solve (n - 3) + 2 * solve (n - 2) + solve (n - 1)
複製代碼

這使得閱讀 Haskell 代碼和閱讀數學公式同樣輕鬆。

原文

Haskell 另外一個優點是惰性求值,即計算會在真正用到時才進行,而不會在計算前提早消費掉,好比:

let x = [1..]
let y = [2,4 ..]
head (tail tail( (zip x y)))
複製代碼

能夠看到,xy 分別是 1,2,3,4,5,6...2,4,6,8... 的無限數組,而 zip 函數將其整合爲一個新數組 (1,2),(2,4),(3,6),(4,8)... 這也是無限數組,若是將 zip 函數執行完那麼程序就會永遠執行下去。但 Haskell 卻不會陷入死循環,而是直接輸出第一位數字 1。這就是惰性計算的特性,不管數組有多長,只有真正用到某項時纔對其進行計算,因此哪怕初始數據量或計算量很大,實際消耗的運算資源只取決於此次計算實際用到的部分。

因爲深度學習數據量巨大,惰性求值能夠忽略海量數據輸入,大大提高計算性能。

3 總結

本文介紹了爲何深度學習更適合使用函數式語言,以及介紹了 Clojure 與 Haskell 語言的共性:安全性、高性能,以及各自獨有的特性,證實了爲什麼這兩種語言更適合用在深度學習中。

在前端領域說到函數式或函數之美,大部分時候想到的是 Class Component 與 Function Component 的關係,這個理解是較爲片面的。經過本文咱們能夠了解到,函數式的思想與數學表達式思想一模一樣,以寫數學公式的思惟方式寫代碼,就是一種較好的函數式編程思路。

函數式應該只有表達式,沒有語句,這是由於函數式是爲了處理運算而誕生的,所以很適合用在深度學習領域。

討論地址是:精讀《深度學習 - 函數式之美》 · Issue #212 · dt-fe/weekly

若是你想參與討論,請 點擊這裏,每週都有新的主題,週末或週一發佈。前端精讀 - 幫你篩選靠譜的內容。

關注 前端精讀微信公衆號

版權聲明:自由轉載-非商用-非衍生-保持署名(創意共享 3.0 許可證

相關文章
相關標籤/搜索