阅读笔记:
仅希望对底层有一定必要的感性认识,包括一些基本核心概念。
Here只关注Graph相关,因为对编程有益。
TF – Kernels模块部分参见:https://mp.weixin.qq.com/s/vwSlxxD5Ov0XwQCKy1oyuQ
TF – Session部分,也可以在起专题总结:https://mp.weixin.qq.com/s/Bi6Rg-fEwyN4uIyRHDPhXg
Tensorflow Download: https://github.com/tensorflow/tensorflow/releases
From: https://zhuanlan.zhihu.com/p/25646408
-- 大纲 --
本文依据对Tensorflow(简称TF)
-
- 白皮书[1]、
- TF Github[2]
- TF官方教程[3]的理解,
从系统和代码实现角度讲解TF的内部实现原理。
以Tensorflow r0.8.0为基础,本文由浅入深的阐述Tensor和Flow的概念。
先介绍了TensorFlow的核心概念和基本概述,然后剖析了OpKernels模块、Graph模块、Session模块。
1.2 TF系统架构
若干要点:
| 第五、六层 | 应用层 | 实现相关实验和应用 | |
| 第四层 | API接口层 | 对TF功能模块的接口封装,便于其他语言平台调用 | |
| 第三层 | 图计算层(Graph) | 本地计算流图 and 分布式计算流图的实现 | 包含:Graph的创建、编译、优化和执行等部分,Graph中每个节点都是OpKernels类型表示。 |
| 第二层 | OpKernels | 以Tensor为处理对象,实现了各种Tensor操作或计算 | 包含:MatMul等计算操作,也包含Queue等非计算操作 |
| 第一层 | gRPC | 网络通信依赖gRPC通信协议实现不同设备间的数据传输和更新 |
1.3TF代码目录组织
Tensorflow/core目录 - 包含了TF核心模块代码。
- public: API接口头文件目录,用于外部接口调用的API定义,主要是session.h 和tensor_c_api.h。
- client: API接口实现文件目录。
- platform: OS系统相关接口文件,如file system, env等。
- protobuf: 均为.proto文件,用于数据传输时的结构序列化.
- common_runtime: 公共运行库,包含session, executor, threadpool, rendezvous, memory管理, 设备分配算法等。
- distributed_runtime: 分布式执行模块,如rpc session, rpc master, rpc worker, graph manager。
- framework: 包含基础功能模块,如log, memory, tensor
- graph: 计算流图相关操作,如construct, partition, optimize, execute等
- kernels: 核心Op,如matmul, conv2d, argmax, batch_norm等
- lib: 公共基础库,如gif、gtl(google模板库)、hash、histogram等。
- ops: 基本ops运算,ops梯度运算,io相关的ops,控制流和数据流操作
Tensorflow/stream_executor目录 - 并行计算框架,由google stream executor团队开发。
Tensorflow/contrib目录 - contributor开发目录。
Tensroflow/python目录 - python API客户端脚本。
Tensorflow/tensorboard目录 - 可视化分析工具,不仅可以模型可视化,还可以监控模型参数变化。
third_party目录 - TF第三方依赖库。
- eigen3: eigen矩阵运算库,TF基础ops调用
- gpus: 封装了cuda/cudnn编程库
2. TF核心概念
TF的核心是围绕Graph展开的,
简而言之,就是Tensor 完成Flow的过程。
所以,在介绍Graph之前需要讲述一下 (1) 符号编程、(2) 计算流图、(3) 梯度计算、(4) 控制流的概念。
2.1 Tensor
在数学上,Matrix表示二维线性映射,Tensor表示多维线性映射,Tensor是对Matrix的泛化,可以表示1-dim、2-dim、N-dim的高维空间。
图 2 1对比了矩阵乘法(Matrix Product)和张量积(Tensor Contract),可以看出Tensor的泛化能力,其中张量积运算在TF的MatMul和Conv2D运算中都有用到,
Tensor在高维空间数学运算比Matrix计算复杂,计算量也非常大,加速张量并行运算是TF优先考虑的问题,如add, contract, slice, reshape, reduce, shuffle等运算。
TF中Tensor的维数描述为阶,数值是0阶,向量是1阶,矩阵是2阶,以此类推,可以表示n阶高维数据。
TF中Tensor支持的数据类型有很多,如tf.float16, tf.float32, tf.float64, tf.uint8, tf.int8, tf.int16, tf.int32, tf.int64, tf.string, tf.bool, tf.complex64等,所有Tensor运算都使用泛化的数据类型表示。
TF的Tensor定义和运算主要是调用 Eigen矩阵 计算库完成的。
【Eigen矩阵运算库】
Eigen is a C++ template library for linear algebra: matrices, vectors, numerical solvers, and related algorithms.
Download: http://eigen.tuxfamily.org/index.php?title=Main_Page
简单说一下Eigen的特点:
(1) 使用方便、无需预编译,调用开销小
(2) 函数丰富,风格有点近似MATLAB,易上手;
(3) 速度中规中矩,比OpenCV快,比MKL、openBLAS慢;
TF中Tensor的UML定义如图 2 2。其中TensorBuffer指针指向Eigen::Tensor类型。其中,Eigen::Tensor[5][6]不属于Eigen官方维护的程序,由贡献者提供文档和维护,所以Tensor定义在Eigen unsupported模块中。
图 2 2中,Tensor主要包含两个变量m_data和m_dimension,
-
- m_data保存了Tensor的数据块,T是泛化的数据类型,
- m_dimensions保存了Tensor的维度信息。
Eigen::Tensor的成员变量很简单,却支持非常多的基本运算,再借助Eigen的加速机制实现快速计算,参考章节3.2。
Eigen::Tensor主要包含了
一元运算(Unary),如sqrt、square、exp、abs等。
二元运算(Binary),如add,sub,mul,div等
选择运算(Selection),即if/else条件运算
归纳运算(Reduce),如reduce_sum, reduce_mean等
几何运算(Geometry),如reshape,slice,shuffle,chip,reverse,pad,concatenate,extract_patches,extract_image_patches等
张量积(Contract)和卷积运算(Convolve)是重点运算,后续会详细讲解。
2.2 符号编程
编程模式通常分为命令式编程(imperative style programs)和符号式编程(symbolic style programs)。
-
- 命令式编程容易理解和调试,命令语句基本没有优化,按原有逻辑执行。 --> Torch是典型的命令式风格
- 符号式编程涉及较多的嵌入和优化,不容易理解和调试,但运行速度有同比提升。 --> theano和Tensorflow都使用了符号式编程。
caffe、mxnet 则采用了两种编程模式混合的方法。
* 命令式编程是常见的编程模式,编程语言如python/C++都采用命令式编程。
* 符号式编程将计算过程抽象为计算图,计算流图可以方便的描述计算过程,所有输入节点、运算节点、输出节点均符号化处理。
计算图通过建立输入节点到输出节点的传递闭包,从输入节点出发,沿着传递闭包完成数值计算和数据流动,until达到输出节点。
这个过程经过计算图优化,以数据(计算)流方式完成,节省内存空间使用,计算速度快,但不适合程序调试,通常不用于编程语言中。
举上面的例子,先根据计算逻辑编写符号式程序并生成计算图
其中A和B是输入符号变量,C和D是运算符号变量,compile函数生成计算图F,如图 2 3所示。
最后得到A=10, B=10时变量D的值,这里D可以复用C的内存空间,省去了中间变量的空间存储。
图 2 4是TF中的计算流图,C=F(Relu(Add(MatMul(W, x), b))),其中每个节点都是符号化表示的。
通过session创建graph,在调用session.run执行计算。
和目前的符号语言比起来,TF最大的特点是强化了数据流图,引入了mutation的概念。这一点是TF和包括Theano在内的符号编程框架最大的不同。
所谓mutation,就是可以在计算的过程更改一个变量的值,而这个变量在计算的过程中会被带入到下一轮迭代里面去。
Mutation是机器学习优化算法几乎必须要引入的东西(虽然也可以通过immutable replacement来代替,但是会有效率的问题)。
Theano的做法是引入了update statement来处理mutation。
小总结
TF选择了纯符号计算的路线,并且直接把更新引入了数据流图中去。(两大特点)
2.3 梯度计算
梯度计算主要应用在误差反向传播和数据更新,是深度学习平台要解决的核心问题。
梯度计算涉及每个计算节点,每个自定义的前向计算图都包含一个隐式的反向计算图。
从数据流向上看,
-
- 正向计算图:数据从输入节点到输出节点的流向过程,
- 反向计算图:数据从输出节点到输入节点的流向过程。
图 2 5是2.2节中图 2 3对应的反向计算图。图中,由于C=A*B,则dA=B*dC, dB=A*dC。
在反向计算图中,输入节点dD,输出节点dA和dB,计算表达式为dA=B*dC=B*dD, dB=A*dC=A*dD。每一个正向计算节点对应一个隐式梯度计算节点。
反向计算限制了符号编程中内存空间复用的优势,因为在正向计算中的计算数据在反向计算中也可能要用到。
从这一点上讲,粗粒度的计算节点比细粒度的计算节点更有优势,而TF大部分为细粒度操作,虽然灵活性很强,但细粒度操作涉及到更多的优化方案,在工程实现上开销较大,不及粗粒度简单直接。在神经网络模型中,TF将逐步侧重粗粒度运算。
2.4 控制流
TF的计算图如同数据流一样,数据流向表示计算过程,如图 2 6。数据流图可以很好的表达计算过程,为了扩展TF的表达能力,TF中引入控制流。
// 感受”条件判断“的区别
在编程语言中,if…else…是最常见的逻辑控制,在TF的数据流中也可以通过这种方式控制数据流向。接口函数如下,
-
- pred为判别表达式,
- fn1和fn2为运算表达式。
当pred为true是,执行fn1操作;当pred为false时,执行fn2操作。
tf.cond(pred, fn1, fn2, name=None)
TF还可以协调多个数据流,在存在依赖节点的场景下非常有用,例如节点B要读取模型参数θ更新后的值,而节点A负责更新参数θ,则节点B必须等节点A完成后才能执行,否则读取的参数θ为更新前的数值,这时需要一个运算控制器。接口函数如下,tf.control_dependencies函数可以控制多个数据流执行完成后才能执行接下来的操作,通常与tf.group函数结合使用。
tf.control_dependencies(control_inputs)
TF支持的控制算子有Switch、Merge、Enter、Leave和NextIteration等。
TF不仅支持逻辑控制,还支持循环控制。TF使用和MIT Token-Tagged machine相似的表示系统,将循环的每次迭代标记为一个tag,迭代的执行状态标记为一个frame,但迭代所需的数据准备好的时候,就可以开始计算,从而多个迭代可以同时执行。
反向计算限制了符号编程中内存空间复用的优势,因为在正向计算中的计算数据在反向计算中也可能要用到。从这一点上讲,粗粒度的计算节点比细粒度的计算节点更有优势,而TF大部分为细粒度操作,虽然灵活性很强,但细粒度操作涉及到更多的优化方案,在工程实现上开销较大,不及粗粒度简单直接。在神经网络模型中,TF将逐步侧重粗粒度运算。
From: https://mp.weixin.qq.com/s/-sYn6j3Xiljzw3T6DoJeCA
3. TF 代码分析初步
3.1 TF总体概述
如图 3 1所示是一个简单线性模型的TF正向计算图和反向计算图。图中
-
- x是输入,
- W是参数权值,
- b是偏差值,
- MatMul和Add是计算操作,
- dMatMul和dAdd是梯度计算操作,
- C是正向计算的目标函数,
- 1是反向计算的初始值,
- dC/dW和dC/dx是模型参数的梯度函数。
图 3 1 tensorflow计算流图示例
以图 3 1为例实现的TF代码见图 3 2(略)。
-
- 首先声明参数变量W、b和输入变量x,构建线性模型y=W*x+b,
- 目标函数loss采用误差平方和最小化方法,
- 优化函数optimizer采用随机梯度下降方法。
- 然后初始化全局参数变量,
- 利用session与master交互实现图计算。
图 3 2 TF线性模型示例的实现代码
图 3 2中summary可以记录graph元信息和tensor数据信息,再利用tensorboard分析模型结构和训练参数。
图 3 3是上述代码在Tensorboard中记录下的Tensor跟踪图。Tensorboard可以显示scaler和histogram两种形式。跟踪变量走势可更方便的分析模型和调整参数。
图 3 3 Tensorboard显示的TF线性模型参数跟踪
图 3 4是图 3 1示例在Tensorboard中显示的graph图。
左侧子图描述的正向计算图和反向计算图,正向计算的输出被用于反向计算的输入,其中MatMul对应MatMul_grad,Add对应Add_grad等。
右上侧子图指明了目标函数最小化训练过程中要更新的模型参数W、b,右下侧子图是参数节点W、b展开后的结果。
图 3 4中,参数W是命名空间(Namespace)类型,展开后的W主要由Assign和Read两个OpNode组成,分别负责W的赋值和读取任务。
命名空间gradients是隐含的反向计算图,定义了反向计算的计算逻辑。
从图 3 1可以看出,更新参数W需要先计算dMatMul,即图 3 4中的MatMul_grad操作,而Update_W节点负责更新W操作。
为了进一步了解UpdateW的逻辑,图 3 5对MatMul_grad和update_W进行了展开分析。
图 3 5 MatMul_grad计算逻辑
图 3 5中,
- 子图(a)描述了MatMul_grad计算逻辑,
- 子图(b)描述了MatMul_grad输入输出,
- 子图(c)描述了update_W的计算逻辑。
首先明确MatMul矩阵运算法则,假设 z=MatMul(x, y),则有dx = MatMul(dz, y),dy = MatMul(x, dz),由此可以推出dW=MatMul(dAdd, x)。
在子图(a)中左下侧的节点b就是输入节点x,dAdd由Add_grad计算输出。
update_W的计算逻辑由最优化函数指定,而其中的minimize/update_W/ApplyGradientDescent变量决定,即子图(b)中的输出变量Outputs。
另外,在MatMul_grad/tuple命名空间中还隐式声明了control dependencies控制依赖操作,这在章节2.4控制流中相关说明。
看到这里,对计算流图的理解需要深入一些:http://blog.csdn.net/tinyzhao/article/details/52755647
核心概念的补充:
计算图的若干概念
在TensorFlow中,算法都被表示成计算图(computational graphs)。计算图也叫数据流图,可以把计算图看做是一种有向图,图中的节点表示操作,图中的边代表在不同操作之间的数据流动。
在这样的数据流图中,有四个主要的元素:
* 操作 (operations)
* 张量 (tensors)
* 变量 (variables)
* 会话 (sessions)
操作
把算法表示成一个个操作的叠加,可以非常清晰地看到数据之间的关系,而且这样的基本操作也具有普遍性。
在TensorFlow中,当数据流过操作节点的时候就可以对数据进行操作。一个操作可以有零个或多个输入,产生零个或多个输出。
一个操作可能是一次数学计算,一个变量或常量,一个数据流走向控制,一次文件IO或者是一次网络通信。
其中,一个常量可以看做是没有输入,只有一个固定输出的操作。具体操作如下所示:
| 操作类型 | 例子 |
|---|---|
| 元素运算 | Add,Mul |
| 矩阵运算 | MatMul,MatrixInverse |
| 数值产生 | Constant,Variable |
| 神经网络单元 | SoftMax,ReLU,Conv2D |
| I/O | Save,Restore |
每一种操作都需要相对应的底层计算支持,比如在GPU上使用就需要实现在GPU上的操作符,在CPU上使用就要实现在CPU上的操作符。
(多维的数组或者高维的矩阵,是个引用)
在计算图中,每个边就代表数据从一个操作流到另一个操作。这些数据被表示为张量,一个张量可以看做是多维的数组或者高维的矩阵。
关于TensorFlow中的张量,需要注意的是张量本身并没有保存任何值,张量仅仅提供了访问数值的一个接口,可以看做是数值的一种引用。
在TensorFlow实际使用中我们也可以发现,在run之前的张量并没有分配空间,此时的张量仅仅表示了一种数值的抽象,用来连接不同的节点,表示数据在不同操作之间的流动。
TensorFlow中还提供了SparseTensor数据结构,用来表示稀疏张量。
变量
变量是计算图中可以改变的节点。比如当计算权重的时候,随着迭代的进行,每次权重的值会发生相应的变化,这样的值就可以当做变量。
在实际处理时,一般把需要训练的值指定为变量。在使用变量的时候,需要指定变量的初始值,变量的大小和数据类型就是根据初始值来推断的。
在构建计算图的时候,指定一个变量实际上需要增加三个节点:
* 实际的变量节点
* 一个产生初始值的操作,通常是一个常量节点
* 一个初始化操作,把初始值赋予到变量
初始化一个变量:
如图所示,v代表的是实际的变量,i是产生初始值的节点,上面的assign节点将初始值赋予变量,assign操作以后,产生已经初始化的变量值v'。
('操作'在其中)
在TensorFlow中,所有操作都必须在会话(session)中执行,会话负责分配和管理各种资源。
在会话中提供了一个run方法,可以用它来执行计算图整体或者其中的一部分节点。
在进行run的时候,还需要用feed_dict把相关数据输入到计算图。
当run被调用的时候,TensorFlow将会从指定的输出节点开始,向前查找所有的依赖界节点,所有依赖节点都将被执行。
这些操作随后将被分配到物理执行单元上(比如CPU或GPU),这种分配规则由TensorFlow中的分配算法决定。
反向传播的计算图
在神经网络训练中,需要使用到反向传播算法。
在TensorFlow等深度学习框架中,梯度计算都是自动进行的,不需要人工进行梯度计算,
-
- 这样只需要使用者定义网络的结构,
- 其他工作都由深度学习框架自动完成,大大简化了算法验证。
在TensorFlow中,梯度计算也是采用了计算图的结构。
如图,在神经网络中常常需要对权重w进行求导。这样的函数前向计算的式子为:
z=h(y),y=g(x),x=f(w)
对应链式求导法则:
![[TF] Architecture - Computational Graphs [TF] Architecture - Computational Graphs](/default/index/img?u=L2RlZmF1bHQvaW5kZXgvaW1nP3U9YUhSMGNITTZMeTlwYldGblpYTXlNREUxTG1OdVlteHZaM011WTI5dEwySnNiMmN2TXpFd01ERTFMekl3TVRjd05pOHpNVEF3TVRVdE1qQXhOekEyTVRZeE5qSXhOVE00TlRNdE1UYzBOakE0TURNek55NXdibWM9)
z=h(g(f(w)))
在计算图中,每个节点边上会自动增加梯度节点,然后每个梯度节点与前一个梯度节点相乘,最终在右下角可以得到w的值。
3.2 Eigen介绍 (略)
3.3 设备内存管理 (略)
3.4 TF开发工具介绍
TF系统开发使用了:
- bazel工具实现工程代码自动化管理,
- protobuf实现了跨设备数据传输,
- swig库实现python接口封装。
SWIG 是Simple Wrapper and Interface Generator的缩写,是一个帮助使用C或者C++编写的软件创建其他编语言的API的工具。例如,我想要为一个C++编写的程序创建.NET API,一般情况下我必须使用托管C++(Managed C++)去编写大量的代码才能生成它的.NET API。有了SWIG,这个机械的工作将变得非常简单。你只须要使用一个接口文件告诉SWIG要为那些类创建.NET API,SWIG就会自动帮你生成它的.NET API。 当 然,SWIG不仅仅支持创建.NET API。最新版本的SWIG支持常用脚本语言Perl、PHP、Python、Tcl、Ruby和非脚本语言C#, Common Lisp (CLISP, Allegro CL, CFFI, UFFI), Java, Modula-3, OCAML以及R,甚至是编译器或者汇编的计划应用(Guile, MzScheme, Chicken)。
![[TF] Architecture - Computational Graphs [TF] Architecture - Computational Graphs](https://pic1.zhimg.com/v2-85374a1abe35c6fe87f9f84262c97584_r.png)
![[TF] Architecture - Computational Graphs [TF] Architecture - Computational Graphs](https://pic3.zhimg.com/v2-04963c9e12e0c10a664fb70c422bda06_b.png)
![[TF] Architecture - Computational Graphs [TF] Architecture - Computational Graphs](https://pic2.zhimg.com/v2-080a192a0d0a8fef5bdd6d6b78411d59_r.png)
![[TF] Architecture - Computational Graphs [TF] Architecture - Computational Graphs](https://pic3.zhimg.com/v2-2ec035b10393bdbf876eb7e14307b90e_b.png)
![[TF] Architecture - Computational Graphs [TF] Architecture - Computational Graphs](https://pic2.zhimg.com/v2-37f2f7d0e112c7637fa74dbc4ce99d39_b.png)
![[TF] Architecture - Computational Graphs [TF] Architecture - Computational Graphs](https://pic2.zhimg.com/v2-00f3681ce908c326bca72be62f2dcb01_r.png)
![[TF] Architecture - Computational Graphs [TF] Architecture - Computational Graphs](https://pic3.zhimg.com/v2-547cfe1350de20e7e6bbf799ee925866_b.png)