[关闭]
@liuhui0803 2017-12-11T10:16:17.000000Z 字数 5900 阅读 2179

机器学习与编程语言

机器学习 开发


任何足够复杂的机器学习系统中,都会包含一种草率选择、非正式指定、充斥着Bug、慢吞吞地实现了一半的编程语言。[1]

作者:Mike Innes (Julia Computing)、David Barber (UCL)、Tim Besard (UGent)、James Bradbury (Salesforce Research)、Valentin Churavy (MIT)、Simon Danisch (MIT)、Alan Edelman (MIT)、Stefan Karpinski (Julia Computing)、Jon Malmaud (MIT)、Jarrett Revels (MIT)、Viral Shah (Julia Computing)、Pontus Stenetorp (UCL)以及Deniz Yuret (Koç University)。

作为研究编程语言的人,我们极为关注机器学习技术本身,以及机器学习模型的复杂度和人们构建模型所用框架的发展。最先进的模型看起来越来越象是程序,并开始支持诸如循环和递归等编程构造,这就导致我们创建模型所使用的工具,即编程语言,开始遇到很多有趣的问题。

虽然目前还没有出现专门用于机器学习的编程语言,但很多人正在基于Python API创造类似于新语言的东西(例如TensorFlow),也有一些人正在将Python用作建模语言(例如PyTorch)。我们不禁想问:我们真的需要一种专门面向机器学习的全新语言吗?如果需要,原因何在?更重要的是,未来可能出现的理想的机器学习语言会是什么样的?

Pig Latin以及其他“隐藏”的语言

尽管有着诸多局限,但TensorFlow(TF)及其同族技术[2]其实已经是一种编程语言了。这种说法可能会让很多人吃惊,但毕竟人们会使用Python进行TF编程。然而到TF要求我们编写Python代码在语言内部构建表达树(Expression tree),随后还要进行评估。

实际上,我们可以用任何语言以“取巧”的方式进行类似TensorFlow那样的编程,例如下列JavaScript代码,就用这种方式实现了一个Trivial函数(add):

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

此时我们是在进行元编程(Metaprogramming):编写用来写代码的代码。此时元语言和目标语言是相同的(都是JavaScript),但也可以是不同语言(例如C语言中的C预处理器),或者可以试用数据结构(一个AST)来代替字符串,总的原理是相同的。在TensorFlow中,Python可以充当用TF的图语言(Graph-based language)写程序所用的元语言。[3] 如果不相信,可以这样想:TensorFlow的图甚至可以支持诸如Variable scopingControl flow这样的构造,但此时并不需要使用Python语法,我们可以通过API来操作这些构造。

TensorFlow与类似的工具本身可以视作“纯粹的库”,但这种情况非常罕见。大部分库可以提供一系列简单的函数和数据结构集,而不会提供一整套全新的编程系统和运行时。那么为何需要如此复杂的方法?

为何要创建一种新的语言?

创建一种新语言的核心原因很简单:有关机器学习的研究会产生巨大的计算需求,简化建模语言,可以让我们更简单地围绕特定领域和特征进行优化。模型的训练需要大量硬件的支持,需要足够好的训练数据,需要尽量降低解释器的开销,还要处理各种类型的并行问题。类似Python这种面向常规用途的语言虽然也在尽可能实现这些特征,但TensorFlow可以更加无缝地做到这一切。

不过我们还会遇到其他困难。这种让人印象深刻的优化需要对假设进行简化(机器学习模型不能递归,或者需要自定义的梯度,对吧?),因此我们可以更容易地进行优化,或部署到小型设备上。然而对工程师来说,模型的复杂度其实增加了,而研究人员也十分喜欢违背这些假设。因此模型需要包含条件分支(已经可以足够轻松地操作了),通过循环来再现(不那么容易但依然可行),甚至需要通过树进行递归(几乎不可能实现)。在机器学习的很多方面,包括神经网络概率编程(Probabilistic programming)中,模型开始变得越来越像程序,甚至通过一个程序推导出另一个程序(例如程序生成器以及解释器),并且可能包含一些不可微分的组件,例如Monte Carlo Tree Search。为了构建一个可以提供所需灵活性,同时维持一流性能的运行时,我们开始面临越来越大的挑战,而越来越强大的模型和突破式的结果越来越要求运行时同时兼顾这两种特点。

sentiment-treebank.png-228.3kB
为机器学习使用复杂的树结构数据,例如Stanford Sentiment Treebank,需要使用可微分的递归算法。

至少从目前的情况来看,这种方法在实用性方面还有另一个不足:需要具备上文提到的某种类型的元编程。构建并评估表达树会对程序设计人员和编译器额外造成巨大的负担。推导过程变得越来越难,因为代码现在需要执行两次,每次使用不同的语言语义,而诸如分布调试这样的工作也变得愈加困难。为新的运行时创建一种句法语言(Syntactic language)也许可以解决这种问题,但这样的工作和完全新建一种完整的编程语言也差不了多少了。在已经具备多种数值语言(Numerical language)的情况下,真的值得这样做吗?

是否可以直接使用Python?

随着机器学习模型开始需要越来越强大的编程语言,Chainer等人开始倡导一种“运行时定义(Define-by-run)”的方法,这种方法会将Python程序本身用作模型,并通过运行时自动微分(Automatic Differentiation,AD)的方式实现衍生。从易用性的角度来看,这种方法很棒:如果需要一种通过树运行的递归模型,直接按照顺序编写就行,其他事情可交由AD搞定!这样做的效果甚至难以用语言来描述,通过这种简洁顺畅的方式尝试各种新颖的想法,也为研究工作贡献了巨大的价值。

然而,让Python顺利应对机器学习过程中的计算密集型需求,具体过程远比你想象中更困难。目前人们已经通过大量工作重现那些速度足够快的语言所实现的优化,而为了让Python变得更快,很多人已经进行了大量高调公布最终失败的努力。Python的语义本身也使得这个语言非常难以实现模型级别的并行度,或面向小型设备编译模型。

诸如Gluon等围绕MXNet进行的项目正在设法将两种方式的优势结合在一起,至少希望在某种程度上实现这一点。这种做法意在将基本的动态AD与代码追踪方法结合在一起,获得可优化的“静态子图(Static sub-graph)”。然而这样的方法实际上只是将原本就不相干的实现与API“混搭”在一起。同时这种方法也有局限,MXNet不仅会将图用作内核级别的优化,而且会用作高级图调度,例如将一个模型分散到多个GPU上]20。这种“杂合”的方法如何解决这类问题还不明确,除非为节点可以进行动态计算的图容器添加另一个新的API。

专门为机器学习打造的语言应该是什么样的?

很少有其他领域会像机器学习这样对语言级别的设计问题如此苛求,但这也并非首例,例如在形式推理和验证集群计算等领域,就有使用量身打造全新语言并获得更好效果的先例。同理,我们也期待看到在机器学习领域,能够针对这种类型的数值型、可微分、可并行,甚至概率性的计算需求产生新的语言,或让现有语言据此进行优化和完善。

对于机器学习语言来说,目前还面临一个明显的挑战:在改善性能的同时实现通用性,而目前这些依然显得原始的“杂合”方法还需要进一步完善。我们认为,未来的机器学习运行时需要能支持可以随意混合的方法(计算图静中有动,动中有静……),并且为了便于部署,还需要改进动态代码的编译能力。理想情况下,只需要一个灵活的“图格式”(或AST)就够了。这样的AST需要有自己的语法,并且能以静态的方式描述动态行为(例如可以写一个for循环)。换句话说,这样的语言看起来应该更像是标准的编程语言。

可编程语义(Programmable semantic)可以进一步改善灵活性,并且可以通过类似“宏”的方式提供。借此即可让诸如多GPU训练这样的功能在核心系统的基础上直接实现,我们只需要指定代码的纯数据流语义在什么位置即可(而不需要使用标准的命令式[Imperative]语义,这种语义虽然更灵活,但可能包含副作用,导致无法安全地进行优化)。此外还应支持概率性编程语言需要的操作操作,以及通常在NLP模型中手工实现的Vectorisation(批处理)传递。

与PL社区类似,机器学习工程师需要密切关注传统的自动微分(AD)社区。机器学习语言同样能从真正为一类派生物设计的语言等创造性工作中得到启发。这样的语言可以轻松地将符号和运行时技术混合在一起(有助于上文提到的权衡),混合正向和反向模型AD(可改善性能和内存用量),并获得可区别GPU的内核,而这一切并不需要牺牲性能作为代价。

机器学习领域的研究会越来越需要更强大的类型系统、用户定义的类型以及扩展方法。在NVIDIA GPU上为Strided数组提供硬编码支持即可满足需求的日子早已一去不复返。面对诸如稀疏(Sparse)机器学习等前沿技术、TPU等新硬件、NervanaFPGA,以及诸如ARM芯片iPhone的CoreML芯片等多样化的部署目标,它们都需要更高程度的灵活性。针对每种部署进行核心C++代码的大规模重构,这种方式无法满足对规模的需求。

假设需要增加对新硬件的支持,或需要支持新的数据表示类型,如果一个人即可通过高级代码轻松实现而不需要更改原始系统,这样的方式该有多美妙。因此我们期待着机器学习系统能够从现有的数值型计算语言中获得启发,其实现在已经可以轻松地处理这些任务了。

类型系统还有助于改善安全性,但目前的此类系统并不适合包含大量数组,并且数组维度有一定意义的代码(例如图片中的空间、通道,以及批处理维度)。这些差异完全出于习惯,而“潦草”的维度变换代码根本无法预防出错,这也导致我们需要更多可感知数组的类型系统。希望动态类型的发展势头能够继续,[4] 虽然这主要出于实践者们对交互和脚本语言方面的偏好,但希望能看到类似CNTK的可选动态维度这样的进一步创新。

机器学习工程师开始越来越关注传统的软件工程问题,例如生产系统的维护和扩展。机器学习编程模型使得我们更加难以在不同组件之间创建抽象壁垒和接口,而模型的重新训练可能很容易破坏向后兼容性。机器学习语言很可能会纳入针对这些问题的解决方案,具体做法和普通的编程语言差不多,不过这依然是一个围绕设计领域的开放问题。

machine_learning_2x.png-62.2kB
软件工程2.0?(来源:XKCD
图中对话,从上到下依次为:
- 这就是你的机器学习系统?
- 没错!把你的数据倾倒给这一大堆线性代数组成的东西,然后就可以在另一头收集答案了。
- 如果答案错误怎么办?
- 那就不停搅拌吧,直到获得看似正确的结果。

对任何新语言来说,最大的不足在于,需要一个全新的库生态系统,而只有针对新运行时编写的代码才能从中获益。例如,假设我们不重复使用现有的Python生态,TensorFlow开发者就需要针对诸如图片处理以及文件IO之类的操作,使用图语言重新编写所需的库,这会导致诸如SciPy等项目中所做的诸多努力全无用处。虽然这可能是我们继续前进的唯一方法,但机器学习领域的实践者不应该脱离广博的数值型和HPC计算社区。理想的机器学习生态系统应该也是理想的数值型社区,反之亦然,不同社区之间的协作可以让每个人的努力获得倍增的效果。

我们期待着看到不同领域可以取得这样的进展。Graph IR和诸如XLAONNX以及NNVM等格式正在逐渐成熟,并且可能为传统语言的设计提供更多启发,[5] 甚至可能通过增加表层语法(Surface syntax)成为真正成熟的编程语言。TensorFlow的XLA已经开始向着专用编译器栈的方向发力,现已包含TVMDLVMmyelin以及其他依然进行中的工作成果。同时诸如PyTorch JITGluonTangent等项目正在努力让Python本身可以成为更优秀的建模语言,不过这一过程中还将面临很大的挑战。尽管有争议认为机器学习也是一种数值型编程语言问题,但Julia社区认为这是一个绝佳的机会,可以让我们借机对各种语言层面的问题进行实验,同时这也有助于让KnetFluxCassetteCUDAnativeDataFlow.jl等项目进一步完善。

结论:有关机器学习的一个推论

机器学习模型正在逐渐成为一种常规的信息处理系统,其中包含各种高级和更复杂的抽象;重现、递归、高阶模型,甚至栈机器(Stack machine)语言解释器,所有这一切都是通过基本组件相互组合实现的。机器学习已成为一种全新编程范式,只不过因为其数值型、可微分、并行等特性而显得较为陌生。与任何工程领域类似,工具的丰富程度将对这一领域未来的适用范围和成果产生深远的影响。

这一切都要求机器学习系统的设计者必须首先克服各种严峻挑战。尽管如此,依然有一些让人激动人心的消息:就算尚未成功解决,但相同问题早已有人着手深入研究,过去几十年来编程语言领域的研究人员正在做这些事情!为了让这个全新的领域能够全面发挥出所有潜能,机器学习和编程语言社区必须携手共进,此时最大的难题在于如何将两个群体原本各不相干的经验和技能有机结合在一起。

我们能否构建出将数值、派生物,以及并行度视作第一类特性的系统,同时不以牺牲传统编程技术的创意和智慧作为代价?未来十年里,编程语言必须回答这一最重要的基础问题。


[1] 援引Philip Greenspun
[2] 此处使用TensorFlow作为例子,其实也可以替换为其他“运行前定义(Define-before-run)”的框架,例如CNTK或MXNet。↩
[3] TensorFlow的图实际上是一种基于数据流的AST(抽象语法树)。↩
[4] 话虽如此,但从内部来说,目前的系统已横跨从完全动态(PyTorch及其Aten后端)到非常静态(TensorFlow的XLA和MXNet,在图实际运行前所有维度都是已知的)的完整范围。↩
[5] Google Brain正在招募编程语言专家,例如Chris Lattner目前正在从事相关开发。↩
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注