23

Clojure和Haskell——深度学习中的函数式语言之美-InfoQ

 4 years ago
source link: https://www.infoq.cn/article/u4eNCEwHEfBZUHpE4RVo
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

深度学习是基于人工神经网络的机器学习方法的一个子集。这些方法的灵感来自于大脑等生物系统中的信息处理方式和分布式通信节点。在深度学习中,每一层都要通过学习将输入数据转换成更加抽象和综合的表示形式。例如在人脸识别系统中,系统的某一层可能是像素,而另一层可能是图像中的边缘,眼睛和脸也可能分别都用一层来表示。深度学习方法如此复杂,所以编程社区习惯使用现成的包。基于 Python 语言的的 TensorFlow 和 PyTorch 很受欢迎,基于 R 语言的 Keras 包也很流行。然而,在深度学习系统的生产环境中,性能和安全性这两问题促使公司倾向选择 Clojure 和 Haskell 等函数式编程语言

Clojure和Haskell——深度学习中的函数式语言之美

深度学习实现的难点

在将深度学习系统投入生产环境时,神经网络可能包含多达一百万个参数。为了训练这些参数,训练数据可能会呈现爆炸式增长。如此庞大的数据需求,让深度学习的训练只能通过具有安全并发性和并行能力的高效编程语言来实现。神经网络的数据是一层层传递的,这种复杂性意味着编程语言处理这些数据时应该足够简单和一致。这里所说的安全性是指以一致的方式保留原始数据状态的能力,而简单性是指能够轻松地读取和维护代码库,同时最大化性能指标。

为什么函数式编程更适合深度学习

为了解决实现深度学习时可能出现的困难,程序员发现函数式编程语言可以提供解决一些方案

在计算机科学中,函数式编程是一种将计算作为数学函数评估,避免状态变化和数据变化的编程范式。它是一种更接近数学思维的编程模式。

深度学习模型本质上是数学模型。例如,人工神经网络由相互连接的节点组成,每个节点执行简单的数学运算。通过使用函数式编程语言,程序员能够用更接近操作本身的语言描述这些数学操作。这些程序的显式编写方式使得读取和维护代码库变得容易许多。

与此同时,深度学习算法的合成性质意味着,在神经网络的每一层上,这些层或函数往往会链接在一起执行任务。这可以通过函数式编程语言的函数链轻松实现。

Clojure和Haskell——深度学习中的函数式语言之美

此外,在深度学习中,当函数应用于数据时数据不会改变。新值可能是沿行顺序往下输出的,但是数据本身会保持一致。函数式编程语言的不可变特性允许程序员在每次生成新值时创建一个新的数据集,而不需要修改原始的不可变数据集。这使得维护整个神经网络数据的一致性变得更加容易。

最后,实现深度学习涉及到大量的参数和训练数据,这意味着并行性和并发性是创建生产级深度学习系统的关键所在。并行性意味着在不同的 CPU 上运行线程以加速学习过程。并发性则是指管理线程以避免冲突的能力。函数式编程允许并发和并行,而不引入额外开销。这意味着,从本质上讲,函数式编程(纯函数是无状态的)将始终为特定的输入生成相同的输出,因此我们能隔离任何函数,并随时执行它。这使得并发性和并行性更容易管理。您不必处理诸如死锁和条件竞争之类的问题。运行在不同 CPU 上的不同线程能够独立运行,没有竞争。

Clojure

随着函数式编程在深度学习中越来越受欢迎,同时出现了许多用于深度学习的健壮包。Clojure 现在受到了沃尔玛和 Facebook 等公司的青睐。它是一种基于 LISP 编程语言的高级动态函数式编程语言,还带有编译器,可以运行在 Java 和.NET 运行时环境中。

Clojure 中强大的并发编程

Clojure 并非 Java 线程系统的替代品,而是与后者协同工作。由于核心数据结构是不可变的,因此可以在线程之间轻松地共享它们。与此同时,程序中的状态可以更改的,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)

源代码

Clojure 中的并行性很廉价

在深度学习中,模型必须训练大量的数据。并行性意味着在不同的 CPU 上可以运行多个线程。廉价的并行性意味着显著的性能改进将分区与映射结合使用可以实现成本更低的并行性。

复制代码
(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*))))))
work)]
(doall (apply concat result))))

源代码

Clojure 中的链式函数意味着更清晰的代码

在 Clojure 中,很多函数只有很少的数据类型。函数也可以作为参数传递给其他函数,于是深度学习中就可以使用链式函数了。因为 Clojure 的实现更接近实际的数学模型,其代码也更容易阅读和维护。

复制代码
;; pipe arg to function
(-> "x" f1) ; "x1"
;; pipe. function chaining
(-> "x" f1 f2) ; "x12"

源代码

Clojure 中的标识和状态提供了安全性

在 Clojure 中,每个模型的标识在任何时候都有一个状态。这种状态是一个永远不会改变的真实值。如果标识看起来出现了变化,那是因为它关联到了不同的状态。新值是旧值的函数。在神经网络的每一层中,原始数据的状态始终保持不变。每组具有新值(函数的输出)的数据都可以独立操作。这意味着可以在这些数据集上安全地执行操作,不用考虑计算资源竞争。我们可以往回追溯,随时查阅数据的原始状态。因此在这种情况下,一致性即意味着安全性。

库和局限性

一直以来, Cortex 机器学习库包含了在 Clojure 中实现机器学习算法所需的所有内容。随着最近用于深度学习的开源 MXNet 框架的日益流行,使用 MXNet- C lojure API 更容易实现深度学习。

尽管现在有许多不同的 API 和机器学习库可供 Clojure 使用,但是要熟练地使用 Clojure 仍然有一个陡峭的学习曲线。机器学习代码的报错信息可能是令人费解的,企业需要提前做一些投资,才能真正熟练使用它们,进而扩大机器学习系统的规模。随着使用 Clojure 编写的可用于生产系统的案例越来越多,这种语言将在未来几年变得越来越流行,但前提是使用 Clojure 语言的库的数量和规模持续增长。

Haskell

Haskell 是一种函数式语言,使用类型推断和延迟计算进行静态类型化。它在 Miranda 编程语言的语义之上,实现了一种更有表现力、更快、更安全的机器学习语言。

Haskell 中的类型安全提供了安全性和灵活性

类型安全定义了一种约束,即变量对应的值类型上的约束。这将有助于防止非法操作,提供更好的内存安全性,并减少逻辑错误。延迟计算意味着 Haskell 会延迟计算一个表达式,直到某个地方需要用到后再去计算它。它还避免了重复计算以节省运行时间。同时,延迟计算允许定义数量无限的数据结构。这给了程序员无限的数学可能性。

Haskell 中简单的显式代码提供了清晰的实现

Haskell 最大的优点之一是它可以用非常明确的数学结构描述算法。您可以用几行代码表示一个模型。您也可以像阅读数学方程一样阅读代码。这在复杂的算法中非常有用,比如机器学习中的深度学习算法就是一个典型例子。例如,下面实现的单层前馈神经网络就能看出代码的可读性优势。

复制代码
import Numeric.LinearAlgebra.Static.Backprop
logistic :: Floating a => a -> a
logistic x = 1 / (1 + exp (-x))
feedForwardLog
:: (KnownNat i, KnownNat o)
=> Model (L o i :& R o) (R i) (R o)
feedForwardLog (w :&& b) x = logistic (w #> x + b)

源代码

Haskell 中的多核并行提供了高性能

在深度学习中,典型的神经网络定义的模型包含一百万个参数。而且需要大量的数据来学习这些参数,这在计算上是非常耗时的。在一台机器上使用多个核并行地共享内存和进程对于实现深度学习来说是非常重要的。而在 Haskell 中,实现多核并行很容易

库和局限性

Haskell 的 HLearn 库包含机器学习算法实现,同时也可以使用 Tensorflow 中的 Haskell 绑定来做深度学习计算,并行和并发库则用于并行和并发需求。

尽管有一些机器学习库是用 Haskell 开发的,但生产级的 Haskell 实现还是需要做一些基础工作。因为用于特定深度学习和机器学习任务的公共库数量不多,Haskell 在人工智能中的应用也是受限的。Aetion Technologies 和瑞士信贷全球建模和分析集团(Credit Suisse Global Modeling and Analytics Group)等公司正在实践中使用 Haskell——使用 Haskell 的组织的完整列表在此

深度学习模型是复杂的数学模型,需要特定的函数层。函数式编程语言(如 Clojure 和 Haskell)通常可以用更简洁、更接近模型数学表达式的代码表示复杂内容。这将节省时间、提高效率,并简化代码库的管理。函数式编程的特定属性使这些语言的实现比其他语言的实现更安全。随着人工智能技术的发展,越来越多的人们将根据 AI 大规模系统开发项目的需求来评估这些语言。

本文来自 Behind the Code。访问 Behind the Code 发现更多的文章和视频。

原文链接
https://www.welcometothejungle.co/fr/articles/btc-deep-learning-clojure-haskell


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK