许多初学者以为深度学习框架抽象,虽然调用了几个函数/方法,计算了几个数学难题,但始终不能理解这些框架的全貌。html
为了更好地认识深度学习框架,也为了给一些想要本身亲手搭建深度学习框架的朋友提供一些基础性的指导,日前来自苏黎世联邦理工学院计算机科学系的硕士研究生Gokula Krishnan Santhanam在博客上撰文,归纳了大部分深度学习框架都会包含的五大核心组件,为咱们详细剖析了深度学习框架通常性的内部组织结构。如下由AI科技评论编译。前端
Gokula Krishnan Santhanam认为,大部分深度学习框架都包含如下五个核心组件:python
1. 张量(Tensor)算法
2. 基于张量的各类操做编程
3. 计算图(Computation Graph)后端
4. 自动微分(Automatic Differentiation)工具api
5. BLAS、cuBLAS、cuDNN等拓展包网络
1. 张量(Tensor)数据结构
张量是全部深度学习框架中最核心的组件,由于后续的全部运算和优化算法都是基于张量进行的。几何代数中定义的张量是基于向量和矩阵的推广,通俗一点理解的话,咱们能够将标量视为零阶张量,矢量视为一阶张量,那么矩阵就是二阶张量。框架
举例来讲,咱们能够将任意一张RGB彩色图片表示成一个三阶张量(三个维度分别是图片的高度、宽度和色彩数据)。以下图所示是一张普通的水果图片,按照RGB三原色表示,其能够拆分为三张红色、绿色和蓝色的灰度图片,若是将这种表示方法用张量的形式写出来,就是图中最下方的那张表格。
图中只显示了前5行、320列的数据,每一个方格表明一个像素点,其中的数据[1.0, 1.0, 1.0]即为颜色。假设用[1.0, 0, 0]表示红色,[0, 1.0, 0]表示绿色,[0, 0, 1.0]表示蓝色,那么如图所示,前面5行的数据则全是白色。
将这必定义进行扩展,咱们也能够用四阶张量表示一个包含多张图片的数据集,其中的四个维度分别是:图片在数据集中的编号,图片高度、宽度,以及色彩数据。
将各类各样的数据抽象成张量表示,而后再输入神经网络模型进行后续处理是一种很是必要且高效的策略。由于若是没有这一步骤,咱们就须要根据各类不一样类型的数据组织形式定义各类不一样类型的数据操做,这会浪费大量的开发者精力。更关键的是,当数据处理完成后,咱们还能够方便地将张量再转换回想要的格式。例如Python NumPy包中numpy.imread和numpy.imsave两个方法,分别用来将图片转换成张量对象(即代码中的Tensor对象),和将张量再转换成图片保存起来。
2. 基于张量的各类操做
有了张量对象以后,下面一步就是一系列针对这一对象的数学运算和处理过程。
其实,整个神经网络均可以简单视为为了达到某种目的,针对输入张量进行的一系列操做过程。而所谓的“学习”就是不断纠正神经网络的实际输出结果和预期结果之间偏差的过程。这里的一系列操做包含的范围很宽,能够是简单的矩阵乘法,也能够是卷积、池化和LSTM等稍复杂的运算。并且各框架支持的张量操做一般也不尽相同,详细状况能够查看其官方文档(以下为NumPy、Theano和TensorFlow的说明文档)。
NumPy: http://www.scipy-lectures.org/intro/numpy/operations.html
Theano: http://deeplearning.net/software/theano/library/tensor/basic.html
TensorFlow: https://www.tensorflow.org/api_docs/python/math_ops/
须要指出的是,大部分的张量操做都是基于类实现的(并且是抽象类),而并非函数(这一点可能要归功于大部分的深度学习框架都是用面向对象的编程语言实现的)。这种实现思路一方面容许开发者将各类相似的操做汇总在一块儿,方便组织管理。另外一方面也保证了整个代码的复用性、扩展性和对外接口的统一。整体上让整个框架更灵活和易于扩展,为未来的发展预留了空间。
3. 计算图(Computation Graph)
有了张量和基于张量的各类操做以后,下一步就是将各类操做整合起来,输出咱们须要的结果。
但不幸的是,随着操做种类和数量的增多,有可能引起各类意想不到的问题,包括多个操做之间应该并行仍是顺次执行,如何协同各类不一样的底层设备,以及如何避免各类类型的冗余操做等等。这些问题有可能拉低整个深度学习网络的运行效率或者引入没必要要的Bug,而计算图正是为解决这一问题产生的。
据AI科技评论了解,计算图首次被引入人工智能领域是在2009年的论文《Learning Deep Architectures for AI》。当时的图片以下所示,做者用不一样的占位符(*,+,sin)构成操做结点,以字母x、a、b构成变量结点,再以有向线段将这些结点链接起来,组成一个表征运算逻辑关系的清晰明了的“图”型数据结构,这就是最初的计算图。
后来随着技术的不断演进,加上脚本语言和低级语言各自不一样的特色(归纳地说,脚本语言建模方便但执行缓慢,低级语言则正好相反),所以业界逐渐造成了这样的一种开发框架:前端用Python等脚本语言建模,后端用C++等低级语言执行(这里低级是就应用层而言),以此综合了二者的优势。能够看到,这种开发框架大大下降了传统框架作跨设备计算时的代码耦合度,也避免了每次后端变更都须要修改前端的维护开销。而这里,在前端和后端之间起到关键耦合做用的就是计算图。
将计算图做为先后端之间的中间表示(Intermediate Representations)能够带来良好的交互性,开发者能够将Tensor对象做为数据结构,函数/方法做为操做类型,将特定的操做类型应用于特定的数据结构,从而定义出相似MATLAB的强大建模语言。
须要注意的是,一般状况下开发者不会将用于中间表示获得的计算图直接用于模型构造,由于这样的计算图一般包含了大量的冗余求解目标,也没有提取共享变量,于是一般都会通过依赖性剪枝、符号融合、内存共享等方法对计算图进行优化。
目前,各个框架对于计算图的实现机制和侧重点各不相同。例如Theano和MXNet都是以隐式处理的方式在编译中由表达式向计算图过渡。而Caffe则比较直接,能够建立一个Graph对象,而后以相似Graph.Operator(xxx)的方式显示调用。
由于计算图的引入,开发者得以从宏观上俯瞰整个神经网络的内部结构,就好像编译器能够从整个代码的角度决定如何分配寄存器那样,计算图也能够从宏观上决定代码运行时的GPU内存分配,以及分布式环境中不一样底层设备间的相互协做方式。除此以外,如今也有许多深度学习框架将计算图应用于模型调试,能够实时输出当前某一操做类型的文本描述。
4. 自动微分(Automatic Differentiation)工具
计算图带来的另外一个好处是让模型训练阶段的梯度计算变得模块化且更为便捷,也就是自动微分法。
正如前面提到的,由于咱们能够将神经网络视为由许多非线性过程组成的一个复杂的函数体,而计算图则以模块化的方式完整表征了这一函数体的内部逻辑关系,所以微分这一复杂函数体,即求取模型梯度的方法就变成了在计算图中简单地从输入到输出进行一次完整遍历的过程。与自动微分对应,业内更传统的作法是符号微分。
符号微分即常见的求导分析。针对一些非线性过程(如修正线性单元ReLU)或者大规模的问题,使用符号微分法的成本每每很是高昂,有时甚至不可行(即不可微)。所以,以上述迭代式的自动微分法求解模型梯度已经被普遍采用。而且因为自动微分能够成功应对一些符号微分不适用的场景,目前许多计算图程序包(例如Computation Graph Toolkit)都已经预先实现了自动微分。
另外,因为每一个节点处的导数只能相对于其相邻节点计算,所以实现了自动微分的模块通常均可以直接加入任意的操做类中,固然也能够被上层的微分大模块直接调用。
5. BLAS、cuBLAS、cuDNN等拓展包
如今,经过上述全部模块,咱们已经能够搭建一个全功能的深度学习框架:将待处理数据转换为张量,针对张量施加各类须要的操做,经过自动微分对模型展开训练,而后获得输出结果开始测试。这时还缺什么呢?答案是运算效率。
因为此前的大部分实现都是基于高级语言的(如Java、Python、Lua等),而即便是执行最简单的操做,高级语言也会比低级语言消耗更多的CPU周期,更况且是结构复杂的深度神经网络,所以运算缓慢就成了高级语言的一个自然的缺陷。
目前针对这一问题有两种解决方案。
第一种方法是模拟传统的编译器。就好像传统编译器会把高级语言编译成特定平台的汇编语言实现高效运行同样,这种方法将高级语言转换为C语言,而后在C语言基础上编译、执行。为了实现这种转换,每一种张量操做的实现代码都会预先加入C语言的转换部分,而后由编译器在编译阶段将这些由C语言实现的张量操做综合在一块儿。目前pyCUDA和Cython等编译器都已经实现了这一功能。
第二种方法就是前文提到的,利用脚本语言实现前端建模,用低级语言如C++实现后端运行,这意味着高级语言和低级语言之间的交互都发生在框架内部,所以每次的后端变更都不须要修改前端,也不须要完整编译(只须要经过修改编译参数进行部分编译),所以总体速度也就更快。
除此以外,因为低级语言的最优化编程难度很高,并且大部分的基础操做其实也都有公开的最优解决方案,所以另外一个显著的加速手段就是利用现成的扩展包。例如最初用Fortran实现的BLAS(基础线性代数子程序),就是一个很是优秀的基本矩阵(张量)运算库,此外还有英特尔的MKL(Math Kernel Library)等,开发者能够根据我的喜爱灵活选择。
值得一提的是,通常的BLAS库只是针对普通的CPU场景进行了优化,但目前大部分的深度学习模型都已经开始采用并行GPU的运算模式,所以利用诸如NVIDIA推出的针对GPU优化的cuBLAS和cuDNN等更据针对性的库多是更好的选择。
运算速度对于深度学习框架来讲相当重要,例如一样训练一个神经网络,不加速须要4天的时间,加速的话可能只要4小时。在快速发展的人工智能领域,特别是对那些成立不久的人工智能初创公司而言,这种差异可能就会决定谁是先驱者,而谁是追随者。
总结
原文做者在文末指出:为了向开发者提供尽可能简单的接口,大部分深度学习框架一般都会将普通的概念抽象化,这多是形成许多用户感知不到上述五点核心组件的重要缘由。
而这也正是做者写本文的初衷:他但愿开发者可以经过了解不一样框架之间的一些类似特性,更好地认识和使用一个深度学习框架。另外一方面,对于那些不只对学会使用深度学习框架感兴趣,还打算亲手搭建一个深度框架的朋友,做者认为了解各框架的内部组成和一些共性的特征也是迈向成功的重要一步。他真诚地相信,一个优秀的工程师不只应该“知其然”,更应该“知其因此然”。
来源:medium