天天看点

从NNVM看2016年深度学习框架发展趋势

雷锋网(公众号:雷锋网)按:本文作者潘汀,合肥工业大学计算机专业大三本科生,中科院深圳先进院集成所mmlab访问学生。原acm-icpc算法竞赛选手,2015年获ccpc铜牌。2015年初开始研究机器学习,研究兴趣集中于对深度学习理论、应用(cv&nlp)及系统架构设计的综合探索。关于深度学习在面部情感分析方面应用的论文被《自动化学报》录用。

| 虚拟框架杀入

从NNVM看2016年深度学习框架发展趋势

从发现问题到解决问题

半年前的这时候,暑假,我在siat mmlab实习。看着同事一会儿跑torch,一会儿跑mxnet,一会儿跑theano。

siat的服务器一般是不给sudo权限的,我看着同事挣扎在编译这一坨框架的海洋中,开始思考是否可以写一个框架:

从NNVM看2016年深度学习框架发展趋势

这样,利用工厂模式只编译执行部件的做法,只需编译唯一的后端即可,框架的不同仅仅在于前端脚本的不同。

caffe2keras的做法似乎是这样,但keras本身是基于theano的编译后端,而我们的更希望theano都不用编译。

当我9月份拍出一个能跑cifar10的大概原型的时候:

从NNVM看2016年深度学习框架发展趋势

我为这种怪异的写法取名叫cgvm(computational graph virtual machine)然后过了几天,在微博上看到了陈天奇在mxnet的进一步工作nnvm的发布 (o(╯□╰)o)......

nnvm使用2000行模拟出了tensorflow,我大概用了500行模拟出了caffe1。

vm(virtual machine)的想法其实是一个很正常的想法,这几年我们搞了很多新框架,名字一个比一个炫,但是本质都差不多,框架的使用者实际上是苦不堪言的:

这篇paper使用了a框架,我要花1天配置a框架。

这篇paper使用了b框架,我要花1天配置b框架。

.......

正如llvm不是一种编译器,nnvm也不是一种框架,看起来更像是框架的屠杀者。

nnvm的可行性恰恰证明了现行的各大框架底层的重复性,而上层的多样性只是一个幌子。

我们真的需要为仅仅是函数封装不同的框架买单吗?这是值得思考的。

| 计算图走向成熟

从NNVM看2016年深度学习框架发展趋势

1、计算图的两种形式

计算图最早的出处应该是追溯到bengio在09年的《learning deep architectures for ai》,bengio使用了有向图结构来描述神经网络的计算:

从NNVM看2016年深度学习框架发展趋势

如图,符号集合{*,+,sin} 构成图的结点,整张图可看成三部分:输入结点、输出结点、从输入到输出的计算函数。

随后在bengio组的theano框架执行中,graph就被隐式应用于op的连接。不过这时候,op还是执行时-动态编译的。

caffe1中计算图其实就是net,因为net可以被graph模拟出来(cgvm和caffe2keras都实现了)。

贾扬清在caffe1中显式化了计算图的表示,用户可以通过编辑net.prototxt来设计计算图。

caffe1在jonathan long和evan shelhamer接手后,他们开发了pycaffe。pycaffe通过python天然的工厂(__getattr__),实现了net.prototxt的隐式生成。

之后的caffe2,也就直接取消了net.prototxt的编辑,同样利用python的(__getattr__)获取符号类型定义。

caffe1带来一种新的计算图组织op的描述方式,不同于theano直接翻译op为c执行代码,然后动态编译,软件工程中的高级设计模式——工厂模式被广泛使用。

计算图被划分为三个阶段,定义阶段、构造阶段、执行阶段:

1、定义阶段:定义layer/op的name、type、bottom(input),top(output)及预设参数。 2、构造阶段:通过工厂模式,由字符串化的定义脚本构造类对象。 3、执行阶段:根据传入的bottom(input),得到额外参数(如shape),此时计算图才能开始执行。阶段划分带来的主要问题是限制了编译代码的完整性和优化程度。

在theano中,c代码生成是最后一步,编译前你可以组合数个细粒度符号,依靠编译器做一次硬件执行上的优化。

而工厂模式编译符号时只考虑了单元,编译器没有上下文可供参考优化,故最终只能顺序执行多个预先编译的符号单元。

当符号粒度过细时,一个layer的实现就会变成连续执行多个子过程,导致“tensorflowslow”。

2、计算图作为中间表示(ir)

pycaffe和caffe2将定义阶段移到python中,而将构造和执行阶段保留在c++中做法,是计算图作为ir的思想启蒙。

python与c++最大的不同在于:一个是脚本代码,用于前端。一个是本地代码,用于后端。

脚本代码创建/修改模型方便(无需因模型变动而重新编译)、执行慢,本地代码则正好相反。

两者取长补短,所以深度学习框架在2016年,迎来了前后端开发的黄金时代。

从NNVM看2016年深度学习框架发展趋势

如上图,无论是9月份先提出的nnvm,还是最近intel曝光的nervana,都分离了前后端。

后端的独立,不仅减少了编译工作,最大的优势在于降低了传统框架做跨设备计算的代码耦合度。

在paper每周都有一大堆的现在,如果后端的每一次变动都要大量修改前端,那么框架的维护开销是非常大的。

在前端定义用于描述输入-输出关系的计算图有着良好的交互性,我们可以通过函数和重载脚本语言的操作符,定义出媲美matlab的运算语言,这些语言以显式的tensor作为数据结构,operator作为计算符和函数,theano和mxnet都是这样隐蔽处理由表达式向计算图过渡的。

而caffe2则比较直接,你需要先创建一个graph,然后显示地调用graph.addoperator(xxx) tensorflow同样可以显式化处理graph。

与用户交互得到的计算图描述字串是唯一的,但是与用户交互的方式却是不唯一的。

所以ir之上,分为两派:

第一派要搞自己的api,函数封装非常有个性,宣示这是自己的专利、独门语言。

第二派不搞自己的api,反而去模拟现有的api,表示我很低调。

显然,用户更喜欢用自己熟悉框架的写法去描述模型,不喜欢天天背着个函数速查手册。

3、计算图优化

用于中间表示得到的计算图描述最好不要直接构造,因为存在冗余的求解目标,且可共享变量尚未提取。

当限制计算图描述为有向无环图(dag)时,一些基本的图论算法便可应用于计算图描述的化简与变换。

陈天奇在今年的msr talk:programming models and systems design for deep learning中,总结了计算图优化的三个点:

从NNVM看2016年深度学习框架发展趋势

①依赖性剪枝

分为前向传播剪枝,例:已知a+b=x,a+b=y,求x?

反向传播剪枝,  例:a+b=x,a+b=y,求x、y,dx/da?

根据用户的求解需求,可以剪掉没有求解的图分支。

②符号融合

符号融合的自动实现是困难的,因为kernel基本不再实时编译了,所以更多体现在符号粗细粒度的设计上。

粗粒度的符号融合了数个细粒度的符号,一次编译出连续多个执行步骤的高效率代码。

粗粒度和细粒度并无好坏区分,一个速度快,一个更灵活。

从贪心角度,vm框架通常会提供粗细粒度两种实现給用户,因而需要更多人力维护编译后端。

③内存共享

caffe1对于激活函数大多使用的inplace处理——即bottom和top是同一个blob。

inplace使用新的输出y立即覆盖的输入x,需要以下两个条件:

1、bottom和top数量都为1,即:计算图中构成一条直线路径,

2、d(y)/d(x)与x是无关的,所以x被y覆盖不影响求导结果。

常见的激活函数都符号以上两个条件,因而可以减少内存的开销。

但是caffe1在多网络内存共享优化上极其糟糕的,以至于caffe1并不适合用来跑gan,以及更复杂的网络。

一个简单例子是交叉验证上的优化:训练网络和验证网络的大部分layer都是可以共享的,但是由于caffe1错误地将blob独立的放在每个net里,使得跨net间很难共享数据。

除此之外,caffe1还错误地将临时变量blob独立放在每个layer里,导致列卷积重复占用几个g内存。

让net和layer都能共享内存,只需要将tensor/blob置于最顶层,采用mvc来写框架即可。

caffe2引入了workspace来管理tensor,并将工作空间的指针传给每一个op、每一个graph的构造函数。

| 新的风暴已经出现

从NNVM看2016年深度学习框架发展趋势

1、vm的侧重点

cgvm和nnvm的侧重点是不太一样的,cgvm更强调前端上的扩展化,后端上的唯一化。所以cgvm不会去支持torch编译后端,也不会去支持caffe编译后端。

在nnvm的知乎讨论帖中,有一种观点认为vm是轻视operator的实现。但实际上,我们手里的一堆框架,在operator、kernel、math级别的不少实现是没有多少区别的。

但恰恰折磨用户的正是这些没有多少区别的编译后端:各种依赖库、装linux、编译各种错。

所以我个人更倾向整个dl社区能够提供一份完善的跨平台、跨设备解决方案,而不是多而杂的备选方案。

从这点来看,cgvm似乎是一个更彻底的框架杀手,但在icml'15上, jürgen schmidhuber指出:

真正运行ai 的代码是非常简短的,甚至高中生都能玩转它。不用有任何担心会有行业垄断ai及其研究。 简短的ai代码,未必就是简单的框架提供的,有可能是自己熟悉的框架,这种需求体现在前端而不是后端。

vm指出了一条多框架混合思路:功能a,框架x写简单。功能b,框架y写简单。

功能a和功能b又要end-to-end,那么显然混起来用不就行了。只有使用频率不高的框架才会消亡,vm将框架混合使用后,熟悉的味道更浓了,那么便构不成”框架屠杀者“。

强大的ai代码,未必就是vm提供的,有可能是庞大的后端提供的。随着paper的快速迭代,后端的扩展仍然是最繁重的编程任务。

vm和后端侧重点各有不同,难分好坏。但分离两者的做法确实是成功的一步。

2、vm的形式

vm及计算图描述方式是连接前后端的桥梁。

即便后端是唯一的,根据支持前端的不同,各家写的vm也很难统一。实际上这就把框架之间的斗争引向了vm之间的斗争。两人见面谈笑风生,与其问对方用什么框架,不如问对方用什么vm。

3、vm的主要工作

合成计算图描述的过程是乏味的,在caffe1中,我们恐怕已经受够了人工编辑prototxt。

api交互方面,即便是mxnet提供给用户的api也是复杂臃肿的,或许仍然需要一个handbook。

tensorflow中的tensorboard借鉴了webos,vm上搞一个交互性更强的操作系统也是可行的。

除此之外,我可能比较熟悉一些经典框架,那么不妨让vm去实现那些耳熟能详的函数吧!

1)模拟theano.function

theano的function是一个非常贴近数学表达计算图掩饰工具。function内部转化表达式为计算图定义,同时返回一个lambda函数引向计算图的执行。总之这是一个百看不腻的api。

2)模拟theano.grad

结合计算图优化,我们现在可以指定任意一对求导二元组(cost, wrt)。因而,放开手,让自动求导在你的模型中飞舞吧。

3)模拟theano.scan

theano.scan是一个用来搭建rnn的神器。尽管最近caffe1更新了rnn,但是只支持固定循环步数的rnn。而theano.scan则可以根据tensor的shape,为rnn建动态的计算图,这适合在nlp任务中处理不定长句子。

4)模拟pycaffe

pycaffe近来在rcnn、fcn、deepdream中得到广泛应用,成为搞cv小伙伴们的最爱。pycaffe大部分是由c++数据结构通过boost.python导出的,不幸的是,boost.thread导出之后与python的gil冲突,导致pycaffe里无法执行c++线程。尝试模拟移除boost.python后的pycaffe,在python里把solver、net、layer給写出来吧。

5)模拟你熟悉的任意框架

.......等等,怎么感觉在写模拟器.....当然写模拟器基本就是在重复造轮子,这个在nnvm的知乎讨论帖中已经指明了。

4、vm的重要性

vm是深度学习框架去中心化、解耦化发展迈出的重要一步。同时暴露了目前框架圈混乱的本质:计算图之下,众生平等。计算图之上,群魔乱舞。

在今年我们可以看多许多框架pk对比的文章,然而大多只是从用户观点出发的简单评测。对比之下,nnvm关注度不高、反对者还不少这种情况,确实让人感到意外。

| 回顾与展望

从NNVM看2016年深度学习框架发展趋势

1、回顾2016:框架圈减肥大作战的开始

高调宣布开源xxx框架,再封装一些api,实际上已经多余了。

vm的出现,将上层接口的编写引向模拟经典的框架,从而达到减肥的目的。

框架维护者应当将大部分精力主要放在kernel的编写上,而不是考虑搞一些大新闻。

2、展望2017:dl社区能否联合开源出跨平台、跨设备的后端解决方案

后端上,随着arm、神经芯片的引入,我们迫切需要紧跟着硬件来完成繁重的编程。

后端是一个敏感词,因为硬件可以拿来卖钱,所以更倾向于闭源。

除此之外,即便出现了开源的后端,在山寨和混战之前是否能普及也是一个问题。

3、展望2017:来写框架吧

vm的出现,带来另一个值得思考的问题:现在是不是人人应该学写框架了?

传统框架编写的困难在代码耦合度高,学习成本昂贵。vm流框架分离了前后端之后,前端编写难度很低,后端的则相对固定。

这样一来,框架的编程层次更加分明,keras地位似乎要危险了。

4、展望2017:更快迭代的框架,更多变的风格,更难的垄断地位

相比于paper的迭代,框架的迭代似乎更快了一点。

余凯老师前段时间发出了tensorflow垄断的担忧,但我们可以很乐观地看到:越来越多的用户,在深入框架的底层。

tensorflow并不是最好的框架,mxnet也不是,最好的框架是自己用的舒服的框架,最好是一行行自己敲出来的。如果你已经积累的数个框架的使用经验,是时候把它们无缝衔接在一起了。

雷锋网注:本文由深度学习大讲堂授权雷锋网发布,如需转载请注明作者和出处,不得删减内容。

本文作者:深度学习大讲堂