关于机器学习与编程语言

2017 年 12 月 6 日 | 作者:Mike Innes (JuliaHub)、David Barber (UCL)、Tim Besard (UGent)、James Bradbury (Salesforce Research)、Valentin Churavy (MIT)、Simon Danisch (MIT)、Alan Edelman (MIT)、Stefan Karpinski (JuliaHub)、Jon Malmaud (MIT)、Jarrett Revels (MIT)、Viral Shah (JuliaHub)、Pontus Stenetorp (UCL) 和 Deniz Yuret (Koç University)

任何足够复杂的机器学习系统都包含着对半编程语言的临时、非正式规范、漏洞百出且运行缓慢的实现。[1]

作为编程语言 (PL) 人员,我们对机器学习 (ML) 的爆炸式增长充满了兴趣,以及随之而来的 ML 模型的复杂性和人们用来构建它们的框架。最先进的模型越来越像 程序,支持循环和递归等编程结构,这在用于创建它们的工具(即编程语言)中带来了许多有趣的问题。

虽然机器学习还没有专门的语言,但一些努力实际上是在 Python API 之下创建隐藏的新语言(如 TensorFlow),而另一些则将 Python 作为建模语言(如 PyTorch)。我们想问一下 - 是否需要新的针对 ML 的语言,如果是,为什么?更重要的是,未来的理想 ML 语言会是什么样子呢?

  1. 猪拉丁语和其他隐藏的语言
  2. 为什么创建新的语言?
  3. 我们能直接使用 Python 吗?
  4. 定制的 ML 语言可能是什么样子?
  5. 结论:关于机器学习的推论
  6. 脚注

猪拉丁语和其他隐藏的语言

TensorFlow (TF) 及其同类[2]编程语言,尽管是有限的。考虑到人们使用 Python 来编程 TF,这似乎令人惊讶。但是,请考虑一下,TF 需要您编写 Python 代码来在它的内部语言中 构建表达式树,然后它才进行求值。

实际上,您可以在任何语言中以“惰性”TensorFlow 风格进行编程。请考虑以下 JavaScript 代码,它以这种风格实现了一个微不足道的函数(add

function add(a,b) {
  return `${a}+${b}`;
}
x = 1; y = 2
z = add('x', 'y') // 'x+y'
eval(z) // 3
x = 4
eval(z) // 6

在这里,我们正在进行 元编程 - 编写编写代码的代码。在这种情况下,元语言和目标语言都是相同的(JavaScript),但它们也可能是不同的语言(如 C 语言中的 C 预处理器),或者我们可以使用数据结构(AST)而不是字符串 - 原理是一样的。在 TensorFlow 中,Python 充当元语言,用于在 TF 的基于图的语言中编写程序。[3] 如果你不相信,请考虑一下,TensorFlow 的图甚至支持 变量作用域控制流 等结构 - 但你不是使用 Python 语法,而是通过 API 来操作这些结构。

TensorFlow 和类似的工具将自己呈现为“仅仅是库”,但它们是非常不寻常的库。大多数库提供了一组简单的函数和数据结构,而不是全新的编程系统和运行时。为什么要采用这种复杂的方法呢?

为什么创建新的语言?

构建新语言的核心原因很简单:ML 研究对计算的需求非常高,简化建模语言可以更容易地添加特定于领域的优化和功能。训练模型需要出色的硬件支持、良好的数值计算、低解释器开销和多种类型的并行性。在 Python 等通用语言难以提供这些功能的地方,TensorFlow 可以无缝地处理它们。

不过,有一个问题。这些令人印象深刻的优化依赖于简化的假设(ML 模型不会是递归的,或者需要自定义梯度,对吗?),这使得更容易应用优化或部署到小型设备上。不幸的是,对于工程师来说,模型的复杂性已经增加,研究人员非常喜欢违反这些假设。模型现在需要条件分支(好吧,很容易在其中进行破解)、循环以实现递归(不太容易但可能)、甚至 树上的递归(实际上不可能处理)。在 ML 的许多领域,包括 神经网络概率编程,模型越来越像程序,包括那些对 其他 程序进行推理的程序(例如,程序生成器解释器),以及包含不可微分的组件,例如蒙特卡洛树搜索。构建能够提供完全灵活性并同时实现最佳性能的运行时非常具有挑战性,但越来越多的强大模型和突破性成果都需要两者。

使用具有复杂树状结构数据的 ML,例如 斯坦福情感树库,需要可微分的递归算法。

这种方法的另一个实际弊端,至少在目前的形式中,是需要上述类型的元编程。构建和评估表达式树给程序员和编译器带来了很大的额外负担。它变得难以推理,因为代码现在有两个执行时间,每个时间都有不同的语言语义,并且诸如逐步调试之类的事情变得更加困难。这可以通过为新的运行时创建一个语法语言来解决,但这意味着无异于创建一个全新的编程语言。当我们已经拥有流行的数值语言时,这样做值得吗?

我们能直接使用 Python 吗?

随着 ML 模型开始需要编程语言的全部功能,Chainer 和其他人开创了一种 "定义-运行" 方法,其中 Python 程序本身就是模型,使用运行时自动微分 (AD) 来计算导数。从可用性角度来看,这非常棒:如果你想要一个在树上运行的递归模型,只需将其写下来,然后让 AD 发挥它的魔力!感觉上的差异 难以言喻,并且,对于研究来说,玩弄新想法的无障碍方法是无价的。

但是,让 Python 扩展到 ML 的繁重计算需求远比你想象的要困难得多。大量的工作 用于复制快速语言免费获得的优化,并且 PL 坟场里充满了 高调失败 的努力,试图让 Python 变得更快。 Python 的语义 也使得从根本上难以提供模型级并行性或为小型设备编译模型。

Gluon 这样的努力正在找到获得两者最佳的方法,至少在一定程度上是如此。想法是将基本的动态 AD 与代码跟踪方法相结合,这些方法会产生可以优化的“静态子图”。不幸的是,这有点像将不同的实现和 API 混合在一起。它也有局限性;MXNet 使用它的图不仅用于内核级优化,还用于高级图调度,例如 将模型拆分到多个 GPU 上。目前尚不清楚这些混合体将如何处理这种情况,除非添加另一个用于图容器的新 API,这些容器的节点可以是动态计算。

定制的 ML 语言可能是什么样子?

在机器学习中,很少有领域像机器学习这样对语言级设计问题要求苛刻。但这并非前所未有,在 形式推理和验证集群计算 等领域,新的定制语言已被证明是有效的解决方案。类似地,我们预计将看到新的或现有的语言被定制为 ML 中需要的数值、可微分、可并行化甚至概率计算类型。

目前,ML 语言面临的一个明显挑战是在性能的同时实现通用性,并且早期的混合方法需要更多的开发。我们预计,未来的 ML 运行时需要支持方法的任意混合(静态、动态、静态内的计算图……),并且需要在编译动态代码以进行部署方面变得更好。理想情况下,将只存在单个、灵活的“图格式”(或 AST)。AST 应该具有一种语法,并静态地描述动态行为(例如,用书面 for 循环) - 换句话说,它应该看起来更像标准的编程语言。

可编程语义 将开辟新的灵活性水平,并且可以通过类似于宏的功能来提供。这将允许在核心系统之上构建多 GPU 训练等功能,方法是指定代码应该在哪里具有纯数据流语义(而不是更灵活但可能包含不安全优化的副作用的标准命令式语义)。它还可以允许概率编程语言所需的程序操作类型,或者 向量化(批处理)传递,这些传递通常在 NLP 模型中手动实现。

除了 PL 社区之外,ML 工程师还应该密切关注传统的自动微分 (AD) 社区。ML 语言可以从 为真正的头等导数 支持而设计的语言的开创性工作中汲取灵感。此类语言可以轻松地将符号与运行时技术混合使用(有助于解决上述权衡问题),混合正向和反向模式 AD(以提高性能和内存使用率),以及 对 GPU 内核进行微分 - 所有这些都不会损失性能。

ML 研究将越来越需要更强大的类型系统、用户定义类型和更多扩展方法。硬编码对 NVIDIA GPU 上步长数组的支持已经成为过去;稀疏机器学习 等尖端技术、TPUNervanaFPGA 等新型硬件,以及 ARM 芯片iPhone 的 CoreML 芯片等各种部署目标,都需要更高的灵活性。 对核心 C++ 代码进行大规模重构 以适应每个新的开发,将无法扩展。

想象一个世界,在其中,用户可以通过高级代码轻松地添加新的硬件支持 - 或新的数据表示形式 - 而不必更改原始系统。在这里,我们希望 ML 系统从现有的数值计算语言中汲取灵感,这些语言可以 轻松地处理这些任务

类型系统也可以带来安全性优势,但目前的类型系统不适合数组密集型代码,在这种代码中,数组维度具有意义(例如,图像中的空间维度与通道维度与批处理维度)。这些区别留给了 纯粹的约定,并且难以处理的维度置换代码没有受到错误的保护,这留下了更多空间来构建更加感知数组的类型系统。我们预计动态类型的趋势将继续下去,[4] 这主要是因为从业者更喜欢交互性和脚本,但希望看到进一步的创新,例如 CNTK 的可选动态维度

机器学习工程师越来越关注传统的软件工程问题,比如生产系统的维护和扩展。而机器学习编程模型使得组件之间更难创建抽象屏障和接口,模型的重新训练很容易破坏向后兼容性。机器学习语言可能会像普通语言一样将这些问题的解决方案整合进来,但这仍然是一个开放的设计问题。

软件工程 2.0?(来自 XKCD)

任何新语言的缺点是需要一个新的库生态系统,因为只有为新运行时编写的代码才能从中受益。例如,TensorFlow 开发人员需要为图像处理等任务重写库,而不是重用 Python 生态系统,例如 图像处理文件 IO 在图语言中,抛弃了 SciPy 等项目背后的巨大努力。这可能是唯一的出路,但机器学习从业者不应该与更广泛的数值和 HPC 社区分离。一个理想的机器学习生态系统是一个理想的数值生态系统,反之亦然,这些社区之间的合作将成倍地提高每个人的努力。

我们预计这些发展将从多个角度出现。像 XLAONNXNNVM 这样的图 IR 和格式正变得越来越复杂,并将从传统语言设计中汲取更多灵感,[5] 甚至可能添加表面语法来成为成熟的编程语言。TensorFlow 的 XLA 已经开始推向专用编译器栈,其中现在包括 TVMDLVMmyelin 以及其他正在进行的工作。与此同时,像 PyTorch JITGluonTangent 这样的项目正努力使 Python 本身成为一种更好的建模语言,尽管存在重大挑战。我们刚刚论证了机器学习是一个数值编程语言问题,我们 Julia 社区认为它是尝试这些语言级问题的极佳基底,并将继续推动 Knet 等项目的边界,KnetFluxCassetteCUDAnativeDataFlow.jl 等等。

结论:关于机器学习的推论

机器学习模型已成为极其通用的信息处理系统,构建越来越高层和更复杂的抽象;递归、递归、高阶模型,甚至 堆栈机语言解释器,都作为基本组件的组合实现。机器学习是一种新的编程范式,尽管它是一种奇怪的范式,它在很大程度上是数值、可微分的和并行的。与任何工程领域一样,可用的工具将对未来工作的范围和质量产生深远的影响。

所有这些都表明,机器学习系统的设计者面前面临着重大的挑战。但事实是,过去几十年来,语言研究人员已经深入研究了这些问题,如果不是已经解决了的话!为了真正将这个新领域发挥到极致,机器学习和编程语言社区必须联合起来,真正的挑战是整合这两个群体的不同专业知识。

我们能否构建将数值、导数和并行性作为一等特征处理的系统,而不牺牲传统的编程思想和智慧?这是未来十年语言必须回答的根本问题。

脚注

[1] 菲利普·格林斯潘之后
[2] 我们以 TensorFlow 为例,但可以替换其他“定义前运行”框架,例如 CNTK 或 MXNet。
[3] TensorFlow 的图实际上是基于数据流的 AST(抽象语法树)。
[4] 尽管我们注意到,在内部,当前系统从完全动态(PyTorch 及其 ATen 后端)到异常静态(TensorFlow 的 XLA 和 MXNet,其中所有维度都在图运行之前已知)都有所跨越。
[5] Google Brain 越来越多地聘用编程语言专家,例如 克里斯·拉特纳,是关于这一点的一个有趣的进展。