函数式语言在深度学习领域应用很普遍,由于函数式与深度学习模型的契合度很高,The Beauty of Functional Languages in Deep Learning — Clojure and Haskell 就很好的诠释了这个道理。html
经过这篇文章能够加深咱们对深度学习与函数式编程的理解。前端
深度学习是机器学习中基于人工神经网络模型的一个分支,经过模拟多层神经元的自编码神经网络,将特征逐步抽象化,这须要多维度、大数据量的输入。TensorFlow 和 PyTorch 是比较著名的 Python 深度学习框架,一样 Keras 在 R 语言中也很著名。然而在生产环境中,基于 性能和安全性 的考虑,通常会使用函数式语言 Clojure 或 Haskell。java
在生产环境中,可能要并发出里几百万个参数,所以面临的挑战是:如何高效、安全的执行这些运算。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)))
复制代码
能够看到,x
与 y
分别是 1,2,3,4,5,6...
与 2,4,6,8...
的无限数组,而 zip
函数将其整合为一个新数组 (1,2),(2,4),(3,6),(4,8)...
这也是无限数组,若是将 zip
函数执行完那么程序就会永远执行下去。但 Haskell 却不会陷入死循环,而是直接输出第一位数字 1
。这就是惰性计算的特性,不管数组有多长,只有真正用到某项时才对其进行计算,因此哪怕初始数据量或计算量很大,实际消耗的运算资源只取决于此次计算实际用到的部分。
因为深度学习数据量巨大,惰性求值能够忽略海量数据输入,大大提高计算性能。
本文介绍了为何深度学习更适合使用函数式语言,以及介绍了 Clojure 与 Haskell 语言的共性:安全性、高性能,以及各自独有的特性,证实了为什么这两种语言更适合用在深度学习中。
在前端领域说到函数式或函数之美,大部分时候想到的是 Class Component 与 Function Component 的关系,这个理解是较为片面的。经过本文咱们能够了解到,函数式的思想与数学表达式思想一模一样,以写数学公式的思惟方式写代码,就是一种较好的函数式编程思路。
函数式应该只有表达式,没有语句,这是由于函数式是为了处理运算而诞生的,所以很适合用在深度学习领域。
若是你想参与讨论,请 点击这里,每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。
关注 前端精读微信公众号
版权声明:自由转载-非商用-非衍生-保持署名(创意共享 3.0 许可证)