共计 4841 个字符,预计需要花费 13 分钟才能阅读完成。
最近 Google 开源了他们内部使用的深度学习框架 TensorFlow[1],结合之前开源的 MXNet[2]和 Caffe[3],对三个开源库做了一些讨论,其中只有 Caffe 比较仔细的看过源代码,其他的两个库仅阅读官方文档和一些研究者的评论博客有感,本文首先对三个库有个整体的比较,再针对一些三者设计的不同数据结构、计算方式、gpu 的选择方式等方面做了比较详细的讨论。表格 1 是三者的一些基本情况的记录和比较。其中示例指的是官方给出的 example 是否易读易理解,因为 TensorFlow 直接安装 Python 包,所以一开始没有去下源代码,从文档中找 example 不如另外两个下源码直接。实际上 TensorFlow 更加像一套独立的 python 接口,它不止能够完成 CNN/RNN 的功能,还见到过有人用它做 Kmeans 聚类。这个表主观因素比较明显,仅供参考。
(2016 年 6 月 27 日更新)大半年的时间过去了,再回过头来看这篇文章,感觉有点伤感,Caffe 已经很久没有更新过了,曾经的霸主地位果然还是被 tensorflow 给终结了,特别是从 0.8 版本开始,tensorflow 开始支持分布式,一声叹息…MXNet 还是那么拼命,支持的语言新增了四种,Matlab/Javascripts/C++/Scala,文档也变的更漂亮了,还推出了手机上图片识别的 demo[8]。
(原文如下)
库名称 | 开发语言 | 支持接口 | 安装难度(Ubuntu) | 文档风格 | 示例 | 支持模型 | 上手难易 |
---|---|---|---|---|---|---|---|
Caffe | c++/cuda | c++/python/matlab | * | * | * | CNN | ** |
MXNet | c++/cuda | python/R/Julia | ** | * | ** | CNN/RNN | * |
TensorFlow | c++/cuda/python | c++/python | * | ** | * | CNN/RNN/… | * |
- 安装难度: (简单) –> **(复杂)
- 文档风格: (一般) –> **(好看、全面)
- 示例: (给的少) –> **(给的多、全)
- 上手难易: (易) –> **(难)
1. 基本数据结构
库名称 | 数据结构名称 | 设计方式 |
---|---|---|
Caffe | Blob | 存储的数据可以看成 N 维的 c 数组,有 (n,k,h,w) 四个维数,一个 blob 里面有两块数据空间保存前向和后向求导数据 |
MXNet | NDArray | 提供 cpu/gpu 的矩阵和矢量计算,能够自动并行 |
TensorFlow | tensor | 相当于 N 维的 array 或者 list,维数可变,数据类型一旦定义不能改变 |
caffe 的数据存储类 blob,当把数据可以看成是一个 N 维的 c 数组,它们的存储空间连续。例如存储图片是 4 维 (num, channel, height, width), 变量(n,k,h,w) 在数组中存储位置为((n*K+k)*H+h)*W+w。blob 有以下三个特征[4]:
- 两块数据,一个是原始 data,一个是求导值 diff
- 两种内存分配方式,一种是分配在 cpu 上,一种是分配在 gpu 上,通过前缀 cpu、gpu 来区分
- 两种访问方式,一种是不能改变数据,一种能改变数据
Caffe 最让我觉得精妙的地方在于一个 blob 保存前向和后向的数据。虽然就代码本身而言,前向数据是因为输入数据不同而改变,后向求导是因为求导不同而改变,根据 SRP 原则,在一个类里面因为两个原因而改变了数据这种是不合适的设计。但是从逻辑层面,前向数据的改变引起了反向求导的不同,它们实际上是一起在改变,本身应该是一个整体。所以我很喜欢这个设计,虽然基本上其他框架中都是将两个数据给分离出来,caffe2 也不知是否保留。
MXNet 的 NDArray 类似 numpy.ndarray,也支持把数据分配在 gpu 或者 cpu 上进行运算。但是与 numpy 和 caffe 不同的是,当在操作 NDArray,它能自动的将需要执行的数据分配到多台 gpu 和 cpu 上进行计算,从而完成高速并行。在调用者的眼中代码可能只是一个单线程的,数据只是分配到了一块内存中,但是背后执行的过程实际上是并行的。将指令 (加减等) 放入中间引擎,然后引擎来评估哪些数据有依赖关系,哪些能并行处理。定义好数据之后将它绑定到网络中就能处理它了。
TensorFlow 的 tensor,它相当于 N 维的 array 或者 list,与 MXNet 类似,都是采用了以 python 调用的形式展现出来。某个定义好的 tensor 的数据类型是不变的,但是维数可以动态改变。用 tensor rank 和 TensorShape 来表示它的维数(例如 rank 为 2 可以看成矩阵,rank 为 1 可以看成向量)。tensor 是个比较中规中矩的类型。唯一特别的地方在于在 TensorFlow 构成的网络中,tensor 是唯一能够传递的类型,而类似于 array、list 这种不能当成输入。
值得一提的是 cuda-convnet 采用的数据结构是 NVMatrix,NV 表示数据分配在 gpu 上,即将所有变量都当成矩阵来处理,它只有两维,它算是最早用 cuda 实现的深度学习框架,而上面三种框架都采用了多维可变维的思想,这种可变维在用矩阵做卷积运算的时候是很有效的。
2. 网络实现方式
Caffe 是典型的功能(过程)计算方式,它首先按照每一个大功能(可视化、损失函数、非线性激励、数据层)将功能分类并针对部分功能实现相应的父类,再将具体的功能实现成子类,或者直接继承 Layer 类,从而形成了 XXXLayer 的形式。然后将不同的 layer 组合起来就成了 net。
MXNet 是符号计算和过程计算混合[5],它设计了 Symbol 大类,提供了很多符号运算的接口,每个 symbol 定义了对数据进行怎样的处理,symbol 只是定义处理的方式,这步还并未真正的执行运算。其中一个需要注意的是 symbol 里面有 Variable,它作为承载数据的符号,定义了需要传递什么样的数据给某个 Variable,并在后续的操作中将数据绑定到 Variable 上。下面的代码是一个使用示例,它实现了将激励函数连接到前面定义好的 net 后面,并给出了这一个 symbol 的名字和激励函数类型,从而构造出 net。下图左边部分是定义 symbol 的合集,中间将数据绑定到 Variable 上之后变成了右边真正的执行流程图。
net = mx.symbol.Activation(data=net, name='relu1', act_type="relu")
TensorFlow 选择的是符号计算方式,它的程序分为计算构造阶段和执行阶段,构造阶段是构造出 computation graph,computation graph 就是包含一系列符号操作 Operation 和 Tensor 数据对象的流程图,跟 mxnet 的 symbol 类似,它定义好了如何进行计算(加减乘除等)、数据通过不同计算的顺序(也就是 flow,数据在符号操作之间流动的感觉)。但是暂时并不读取输入来计算获得输出,而是由后面的执行阶段启动 session 的 run 来执行已经定义好的 graph。这样的方式跟 mxnet 很相似,应该都是借鉴了 theano 的想法。其中 TensorFlow 还引入了 Variable 类型,它不像 mxnet 的 Variable 属于 symbol(tf 的 operation 类似 mxnet 的 symbol),而是一个单独的类型,主要作用是存储网络权重参数,从而能够在运行过程中动态改变。tf 将每一个操作抽象成了一个符号 Operation,它能够读取 0 个或者多个 Tensor 对象作为输入(输出),操作内容包括基本的数学运算、支持 reduce、segment(对 tensor 中部分进行运算。例如 tensor 长度为 10,可以同时计算前 5 个,中间 2 个,后面三个的和)、对 image 的 resize、pad、crop、filpping、transposing 等。tf 没有像 mxnet 那样给出很好的图形解释或者实例(可能因为我没找到。。),按照自己的理解画了一部分流程图。有点疑惑的是,为什么要设计 Variable,tf 给出的一个 alexnet 的 example 源码中,输入数据和权重都设置成了 Variable,每一层的输出并未直接定义,按照 tf 的说法,只有 tensor 类型能够在网络中传递,输出的类型应该是 tensor,但是由于输入和权重改变了,输出应该也在随着改变,既然如此,为何不只设计一个 tensor,让 tensor 也能动态改变。
就设计而言,TensorFlow 相对于其他两个更像是一种通用的机器学习框架,而不是只针对 cnn 或 rnn,但就现在的性能而言,tf 的速度比很多开源框架都要差一点[6]。
3. 分布式训练
Caffe 和 TensorFlow 没有给出分布式的版本,MXNet 提供了多机分布式,因而前两者只有如何控制使���多 gpu。Caffe 通过直接在执行指令后面加上 -gpu 0,1 来表示调用两个 gpu0 和 1,只实现了数据并行,也就是在不同的 gpu 上执行相同网络和不同数据,caffe 会实例化多个 solver 和 net 让每次处理的 batch_size 加倍。TensorFlow 则能够自己定义某个操作执行在哪个 gpu 上,通过调用 with tf.device(‘/gpu:2’) 表示接下来的操作要在 gpu2 上处理,它也是数据并行。MXNet 通过执行脚本时指定多机节点个数来确定在几台主机上运行,也是数据并行。MXNet 的多 gpu 分配和它们之间数据同步是通过 MXNet 的数据同步控制 KVStore 来完成的。
KVStore 的使用首先要创建一个 kv 空间,这个空间用来在不同 gpu 不同主机间分享数据,最基本的操作是 push 和 pull,push 是把数据放入这个空间,pull 是从这个空间取数据。这个空间内保存的是 key-value([int, NDArray]),在 push/pull 的时候来指定到哪个 key。下面的代码将不同的设备上分配的 b[i]通过 key3 在 kv 空间累加再输出到 a,从而完成了对多 gpu 的处理。这个是个非常棒的设计,提供了很大的自由度,并且为开发者减少了控制底层数据传输的麻烦。
gpus = [mx.gpu(i) for i in range(4)]
b = [mx.nd.ones(shape, gpu) for gpu in gpus]
kv.push(3, b)
kv.pull(3, out = a)
之前有看过一篇论文,如何将卷积网络放在多 gpu 上训练,论文中有两种方法,一种是常用的数据并行,另一种是模型并行。模型并行指的是将一个完整的网络切分成不同块放在不同 gpu 上执行,每个 gpu 可能只处理某一张图的四分之一。采用模型并行很大程度上是因为显存不够放不下整个网络的数据,而现在 gpu 的功能性能提高,一个 gpu 已经能够很好的解决显存不够的问题,再加上模型并行会有额外的通信开销,因此开源框架采用了数据并行,用来提高并行度。
4. 小结
上面针对三个框架的不同方面进行了一些分析与比较,可以看出 TensorFlow 和 MXNet 有一些相似的地方,都是想做成更加通用的深度学习框架,貌似 caffe2 也会采用符号计算[5],说明以后的框架会更加的偏向通用性和高效,个人最喜欢的是 caffe,也仿造它和 cuda-convnet 的结构写过卷积网络,如果是想提高编程能力可以多看看这两个框架的源码。而 MXNet 给人的感觉是非常用心,更加注重高效,文档也非常的详细,不仅上手很容易,运用也非常的灵活。TensorFlow 则是功能很齐全,能够搭建的网络更丰富而不是像 caffe 仅仅局限在 CNN。总之框架都是各有千秋,如何选择也仅凭个人的喜好,然而 google 这个大杀器一出现引起的关注度还是最大的,虽然现在单机性能还不够好,但是看着长长的开发人员名单,也只能说大牛多就是任性。
本文永久更新链接地址:http://www.linuxidc.com/Linux/2016-07/133222.htm