TF(2): 核心概念

TF的核心是围绕Graph展开的,简而言之,就是Tensor沿着Graph传递闭包完成Flow的过程。所以在介绍Graph之前需要讲述一下符号编程、计算流图、梯度计算、控制流的概念。

张量(Tensor)


  • 名字就是TensorFlow,直观来看,就是张量的流动。张量(tensor),即任意维度的数据,一维、二维、三维、四维等数据统称为张量。而张量的流动则是指保持计算节点不变,让数据进行流动。这样的设计是针对连接式的机器学习算法。连接式的机器学习算法可以把算法表达成一张图,张量从图中从前到后走一遍就完成了前向运算;而残差从后往前走一遍,就完成了后向传播。
  • 官网中对“Tensors”的解释是可以看做一个数组。其实Tensor就是流图中的,数据要“流”过边,就需要适合这个”边”, 或者说”管道”大小. 比如这个边只能有维数为5的数据通过, 那么这个Tensor就是5维度固定. 这样设计的好处是, 维数固定, 运行效率就高. 而且, 在整个流图中, 也有复用的可能.
  • 在数学上,Matrix表示二维线性映射,Tensor表示多维线性映射,Tensor是对Matrix的泛化,可以表示1-dim、2-dim、N-dim的高维空间。
  • Tensor在高维空间数学运算比Matrix计算复杂,计算量也非常大,加速张量并行运算是TF优先考虑的问题,如add, contract, slice, reshape, reduce, shuffle等运算。
  • TF中Tensor的维数描述为数值是0阶向量是1阶,矩阵是2阶,以此类推,可以表示n阶高维数据。
  • 上图中,Tensor主要包含两个变量m_datam_dimension,m_data保存了Tensor的数据块,T是泛化的数据类型,m_dimensions保存了Tensor的维度信息。
  • Eigen:Tensor的成员变量很简单,却支持非常多的基本运算,Eigen::Tensor主要包含了
    1. 一元运算(Unary),如sqrt、square、exp、abs等。

    2. 二元运算(Binary),如add,sub,mul,div等

    3. 选择运算(Selection),即if / else条件运算

    4. 归纳运算(Reduce),如reduce_sum, reduce_mean等

    5. 几何运算(Geometry),如reshape,slice,shuffle,chip,reverse,pad,concatenate,extract_patches,extract_image_patches等

    6. 张量积(Contract)和卷积运算(Convolve)是重点运算

算子(operation)


在TF的实现中,机器学习算法被表达成图中的节点是算子(operation),节点会有0到多个输出,下图是TF实现的一些算子。每个算子都会有属性,所有的属性都在建立图的时候被确定下来,比如,最常用的属性是为了支持多态,比如加法算子既能支持float32,又能支持int32计算。

  • 标量运算:Add、Sub、Mul、Div、Exp、Log、Greater、Less、Equal
  • 向量运算:Concat、Slice、Split、Constant、Rank、Shape、Shuffle
  • 矩阵运算:MatMul、MatrixInverse、MatrixDeterminant
  • 带状态的运算:Variable、Assign、AssignAdd
  • 神经网络组件:SoftMax、Sigmoid、ReLU、Convolution2D、MaxPooling
  • 存储、恢复:Save、Restore
  • 队列及同步运算:Enqueue、Dequeue、MutexAcquire、MutexRelease
  • 控制流:Merge、Switch、Enter、Leave、NextIteration

核(kernel)


  • kernel是operation在某种设备上的具体实现。TF的库通过注册机制来定义op和kernel,所以可以通过链接一个其他的库来进行kernel和op的扩展。
  • 在 c++ 中实现 op:op 的实现称之为 kernel ,它是op 的一个具体实现。对于不同的输入输出类型或者 架构(CPUs,GPUs)可以有不同的 kernel 实现 。(参见: https://blog.csdn.net/u012436149/article/details/73737299/
  • TensorFlow的运行时包含200多个标准的OP,包括数值计算,多维数组操作,控制流,状态管理等。每一个OP根据设备类型都会存在一个优化了的Kernel实现。在运行时根据本地设备的类型,为OP选择特定的Kernel实现,完成该OP的计算。
  • 其中,大多数Kernel基于Eigen::Tensor实现。Eigen::Tensor是一个使用C++模板技术,为多核CPU/GPU生成高效的并发代码。但是,TensorFlow也可以灵活地直接使用cuDNN实现更高效的Kernel。

  • 此外,TensorFlow实现了矢量化技术,使得在移动设备,及其满足高吞吐量,以数据为中心的应用需求,实现更高效的推理。

  • 如果对于复合OP的子计算过程很难表示,或执行效率低下,TensorFlow甚至支持更高效的Kernle实现的注册,其扩展性表现相当优越。

边(edge)


TF的图中的边分为两种:

  • 正常边,正常边上可以流动数据,即正常边就是tensor
  • 特殊边,又称作控制依赖,(control dependencies)
    • 没有数据从特殊边上流动,但是特殊边却可以控制节点之间的依赖关系,在特殊边的起始节点完成运算之前,特殊边的结束节点不会被执行。
    • 也不仅仅非得有依赖关系才可以用特殊边,还可以有其他用法,比如为了控制内存的时候,可以让两个实际上并没有前后依赖关系的运算分开执行。
    • 特殊边可以在client端被直接使用

会话(Session)


  • 客户端使用会话来和TF系统交互,一般的模式是,建立会话,此时会生成一张空图;在会话中添加节点和边,形成一张图,然后执行。
  • 会话中运行一些流图, 一个会话中流图变量和另一个会话中流图变量是隔离的. 这种通过对话跑流图的方式, 清晰地隔离变量, 利用已有的CPU, GPU等资源(Tensorflow内部有一套placement算法, 安排哪些节点操作应该跑在哪些CPU或GPU上).

 变量(Variables)


  • 机器学习算法都会有参数,而参数的状态是需要保存的。而参数是在图中有其固定的位置的,不能像普通数据那样正常流动。因而,TF中将Variables实现为一个特殊的算子,该算子会返回它所保存的可变tensor的句柄

符号编程(symbolic style programs)


  • 编程模式通常分为命令式编程(imperative style programs)和符号式编程(symbolic style programs)
  • 命令式编程容易理解和调试,命令语句基本没有优化,按原有逻辑执行。符号式编程涉及较多的嵌入和优化,不容易理解和调试,但运行速度有同比提升
  • 这两种编程模式在实际中都有应用,Torch是典型的命令式风格,caffe、theano、mxnet和Tensorflow都使用了符号式编程。其中caffe、mxnet采用了两种编程模式混合的方法,而Tensorflow是完全采用了符号式编程,Theano和Tensorflow的编程模式更相近。
  • 命令式编程是常见的编程模式,编程语言如python/C++都采用命令式编程。命令式编程明确输入变量,并根据程序逻辑逐步运算,这种模式非常在调试程序时进行单步跟踪,分析中间变量。举例来说,设A=10, B=10,如下图计算逻辑,第一步计算得出C=100,第二步计算得出D=101,输出结果D=101。
  • 符号式编程将计算过程抽象为计算图,计算流图可以方便的描述计算过程,所有输入节点、运算节点、输出节点均符号化处理。计算图通过建立输入节点到输出节点的传递闭包,从输入节点出发,沿着传递闭包完成数值计算和数据流动,直到达到输出节点。这个过程经过计算图优化,以数据(计算)流方式完成,节省内存空间使用,计算速度快,但不适合程序调试,通常不用于编程语言中。举上面的例子,先根据计算逻辑编写符号式程序并生成计算图
  • 其中A和B是输入符号变量,C和D是运算符号变量,compile函数生成计算图F,如图6符号编程的正向计算图
  • 最后得到A=10, B=10时变量D的值,这里D可以复用C的内存空间,省去了中间变量的空间存储。

流图执行模型


  • Tensorflow在执行时需要考虑到效率问题, 特别是多机器, 多CPU,GPU设备的情况.多机器, 多设备的执行组件如下图:
  • 当我们写程序在一个Session中run一个流图. Tensorflow做的是, 从客户端client发一个run的请求给master端, 最后master端发送告诉哪些worker应该跑那些cpu和gpu. 这里的组件: client, master, worker 都是软件层面的. cpu, gpu是硬件层面的.
  • 在实际的硬件传输值的过程中, TensorFlow内部还会自动添加一些节点操作,如下图:
  • 实际场景中TensorFlow会添加send和recv操作节点. 用来在设备间加一层缓存机制. 当然, (a)是最原始的流图, 也可以使用, 但是不如(b)和(c)来的高效.

实现(Implementation)


  • TF的实现分为了单机实现分布式实现,在分布式实现中,需要实现的是对client,master,worker process不在同一台机器上时的支持。如下图:
  • client:客户端执行session run与master相连
  • master:用来与客户端交互,同时调度任务;
  • worker:与多个硬件设备(device)相连,并管理他们

 运行模式:

  • 单机模式:计算图会按依赖关系被顺序执行。当一个节点的所有前置节点执行完时(依赖数为0),这个节点就会被加入ready queue以等待执行;同时,它后置的所有节点依赖数减1,这就是标准的计算拓扑方式。

  • 分布式模式:设计了一套节点(node)分配设备策略。 通过计算一个代价模型,估算每一个节点的输入、输出tensor的大小和所需的时间。代价模型由人工经验制定也可由实际运算测量得到。策略确定后,计算图会被划分成许多子图,使用同一设备且相邻的节点会被划分到同一个子图。

Partial Execution(局部执行)


  • TF支持部分执行,对于多输出的TF图,可能用户只想获取一个输出,此时可以指定需要的输入(feed)和输出(fetch)值。然后TF会自动计算该输出的依赖,然后只计算必要的部分。如下图,指定b为输入,f为输出,那么此时d、e是不会被计算的。

Control Flow(控制流)


  • 虽然TF的图已经有了很强的表达能力,但还不够,还需要控制流的表达,比如已经实现的Switch、Merge、Enter、Leave和NextIteration等一系列控制算子。

  • 在编程语言中,if…else…是最常见的逻辑控制,在TF的数据流中也可以通过这种方式控制数据流向。接口函数如下,pred为判别表达式,fn1和fn2为运算表达式。当pred为true是,执行fn1操作;当pred为false时,执行fn2操作。
  • TF还可以协调多个数据流,在存在依赖节点的场景下非常有用,例如节点B要读取模型参数θ更新后的值,而节点A负责更新参数θ,则节点B必须等节点A完成后才能执行,否则读取的参数θ为更新前的数值,这时需要一个运算控制器。接口函数如下,tf.control_dependencies函数可以控制多个数据流执行完成后才能执行接下来的操作,通常与tf.group函数结合使用。
  • TF使用和MIT Token-Tagged machine相似的表示系统,将循环的每次迭代标记为一个tag,迭代的执行状态标记为一个frame,但迭代所需的数据准备好的时候,就可以开始计算,从而多个迭代可以同时执行。

  • 对于分布式系统来说,控制循环的状态是一个大问题。而TF使用图重写的方式来实现它,在图切分的时候,添加一个小的状态机来监控迭代的开始和结束,

  • 而对于有梯度计算的图来说,在有控制流的情况下,需要记录各种状态,比如对于if算子,需要记录哪个分支被运行了;而对于循环,需要记录循环了几次。

Input Operations(输入操作)


  • 为Input也构建了一个Node,来管理数据从硬盘到内存的过程。往往需要提前将数据读入进来以减少内存瓶颈。

Queues(队列)


  • TF实现了一个队列来支持异步操作,EnQueue可以阻塞直到队列中的空间足够;DeQueue也可以阻塞直到队列中一系列的要求得到满足。队列有两个典型应用:

    1. 读入数据,数据在队列中,这样可以达到数据处理和数据载入的并行

    2. 梯度的累加,让梯度存储在队列中,直到队列中的梯度积累到一定的数值,这样可以达到多个mini-batch组成一个大的batch

    3. 句子的聚合,对LSTM中的输入句子按长度来进行聚合,统一计算以提高效率。

  • 除了FIFO队列外,TF还实现了一个shuffle队列。

Containers(容器)


  • 普通的Cotainer可以长期的存储可变状态,但Container不止于此,用Container,甚至不同的会话中的图之间也可以通过Container来共享状态。

Lossy Compression


  • 在数据传输过程中,为了加快传输效率,往往需要对精度进行压缩。在TF中,传输之前将32bit的float转变为16float,在传输完之后再转回来,转回来时用0补齐。

数据并行训练( Data Parallel Training)


  •  
  • 通过数据并行的方式来提升模型的效率,比如,假如每次模型的mini-batch是1000个样本,那么,切成10份,每份100个,然后将模型复制10份,每份都将梯度传到参数服务器上。
  • 数据切分也分为同步和异步两种方式,同步的切分是等待每个独立的model传上来的梯度都到齐后再进行更新,这样和一个大的batch没有区别。异步的切分则是不用等待,每个独立的模型的参数更新直接更新。

模型并行训练( Model Parallel Training)


  • 还可以对模型进行切分,让模型的不同部分执行在不同的设备上,这样可以一个迭代的样本可以在不同的设备上同时执行。如上图所示的LSTM模型

TF与其他平台的区别于联系:


  • 支持符号推导,如Theano和Chainer
  • 使用C++写内核,从而跨平台部署很方便,如Caffe,
  • 支持跨设备计算,让用户高层表达模型,如Adam和Distbelief,但比Adam和DistBelief优越的是,更有弹性,支持更多的模型。
  • 相对于Adam、DistBelief和Parameter Server,TF把参数节点化,更新操作也是图中的一个节点。而Adam等三个会有一个独立的Parameter Server。
  • Halide拥有和TF相似的中间表达但却有更高级的语义表示,在并行方面优化的更多,但却是单机的,TF有意向向此方向发展,将Halide的东西添加到TF中来。

其他还有很多像TF那样支持数据流图的分布式平台,比如:

  • Dryad、Flume、CIEL(数据流调度算法从此借鉴而来)
  • Naind(分布式架构非常像)
  • Spark、Naind(在内存足够的情况下,TF和他们一样运行的很好)
  • Dandelion(跨设备)
  • TF的迭代是混合的方法:多个数据流但却一次执行,同时共享状态。多个数据流通过变量来共享,或使用图中的同步机制,比如队列。TF还支持图内的迭代,这是CIEL和Naiad的混合,简单来说,像CIEL一样每个节点只有当准备好之后才会启动;但为了效率整张图则表示为一个静态的循环的图,像Naiad一样。

猜你喜欢

转载自www.cnblogs.com/tgzhu/p/9282901.html