《深入了解TensorFlow》笔记——Chapter 3.1 编程范式:数据流图

本文介绍了编程范式的两种主要类型:声明式编程和命令式编程,重点讨论了声明式编程在TensorFlow中的应用。通过数据流图,TensorFlow实现了代码可读性强、引用透明和预编译优化能力的优势。数据流图中的节点代表操作,有向边定义了数据依赖,允许在执行时进行并行化和优化,提高计算效率。预编译优化包括无依赖逻辑并行化等,使得TensorFlow能有效地加速深度学习模型的训练。
摘要由CSDN通过智能技术生成

编程范式:数据流图

TF采用了更适合描述深度神经网络的声明式编程范式,并以数据流图作为核心抽象。基于声明式编程的数据流图具有以下优势:

  • 代码可读性强
  • 支持引用透明
  • 提供预编译优化能力

声明式编程和命令式编程

编程范式定义:编程语言或函数库为开发人员提供的程序实际基本风格和典型模式。

声明式编程与命令式编程是两种常见的编程方式,他们最大的区别在于:前者强调“做什么”,后者强调“怎么做”。

声明式编程:结构化、抽象化,用户不必纠结每个步骤的具体实现,而是通过下定义的方式描述期望达到的状态。
命令式编程:过程化、具体化,告诉机器怎么做,机器按照用户的只是一步步执行命令,并转换到最终的停止状态。

声明式编程比较接近人的思考模式,即程序中的变量代表数学抽象符号,而不是某一块内存地址。用户将计算过程抽象为函数表达式,将程序的输出定义为函数值。声明式程序按照用户定义的函数对输入数据进行表达式变换和计算,程序最终的输出仅依赖于用户的输入数据。计算过程既不受内部状态影响,也不受外部环境影响。

命令式编程起源于对汇编语言和机器指令的进一步抽象,本身带有明显的硬件思维特征。命令式程序具有内部状态,计算的过程就是状态转换的过程,改变状态的方式就是对存储器中的变量进行赋值操作。

声明式编程的优势

1. 代码可读性强

通常,声明式编程范式写出来的代码可读性更强,它以目标为导向,更接近数学公式或者人类思维方式。我们以Python计算Fibonacci数为例,说明两类programming paradigm的差别。
Fibonacci数定义: F 1 = 1 F_1 = 1 F1=1 F 2 = 1 F_2=1 F2=1 F n = F n − 1 + F n − 2 F_n = F_{n-1} + F_{n-2} Fn=Fn1+Fn2 ( n > 2 n > 2 n>2)
命令式编程范式如下:

def fibonacci(n):
	a, b = 1, 1
	for i in range(n)
		a, b = b, a+b
	return a

声明式编程范式如下:

	fib = lambda x : 1 if x<=2 else fib(x-1) + fib(x-2)

值得注意的是这段声明式代码涉及函数递归调用,性能非常差。对于Python解释器这种简单的runtime环境,递归调用的性能确实存在问题。但对于专门为声明式编程涉及的开发库和runtime环境,一般都有多种编译优化机制来处理递归展开等问题,可以自动改善代码性能。TF的数据流图Runtime框架也不例外。TF推荐使用声明式编程范式创建深度神经网络模型,通过调用丰富的内置抽象,可以让网络一浴设计并具有良好的层级结构。

2. 支持引用透明

一般命令式函数除了返回值以外,还可能对外部环境产生附加影响,例如修改了函数作用域以外的变量或输入参数。这会给程序设计带来不必要的麻烦,有可能引入难以查找的错误,并降低程序的可读性。引用透明(referetial transparency)的概念和上述问题相关,且受其影响。如果一个函数的语义与它的上下文无关,则称它是引用透明的。

引用透明的一个推论是:函数的调用语句可以被它的返回值取代,而不影响程序语义。

声明式编程没有内部状态,也不依赖外部环境;输出结果有输入数据唯一确定,与代码上下文无关。用户创建的模型就是一幅数据流图,图的拓扑结构由函数的组合关系所定义。每一个函数都是一个计算单元或者模块,对应着图中的一个节点。用户可以选择执行任意的模块组合,以得到不同模型结构的输出结果。同时,用户也可以反复执行同样的模型结构,通过输入不同的数据,得到不同的输出结果,以此实现神经网络训练等迭代计算逻辑。

引用透明的函数跟符合数学语言中对函数的定义,而不再像计算机语言中的原始函数定义——子程序。TF和类似的DL Library都内置了大量的数学函数,如代数计算、数组计算、卷积计算、神经网络及图像处理函数等。声明式编程对函数和其它数据类型一视同仁,一个函数本身能够以“闭包”的形式成为另一个函数的输入,而非像命令式编程那样,以函数的输入、输出参数的方式传输状态值。

3. 提供预编译优化能力

对于以数据流图为核心抽象的声明式编程范式,其runtime环境不像解释型命令式编程范式那样立刻执行代码,而是类似于编译型命令式编程语言的语法树生成过程,需要实现编译得到完整的数据流图,然后根据用户选择的子图,输入数据进行计算。因此,声明式编程能够实现多种预编译优化,包括无依赖逻辑并行化、无效逻辑溢出、公共逻辑提取、细粒度操作融合等。
我们以无依赖逻辑并行化为例,说明声明式编程如何提供预编译优化能力。定义模型为E=(A+B)*(C-D),伪代码如下:

A = Variable('A')
B = Variable('B')
C = Variable('C')
D = Variable('D')
E = MUL(ADD(A, B), SUB(C, D))
f = compile(E)
e = f(A=3, B=2, C=1, D = 2) # e = -1

下图描绘了执行预编译函数f=complie(E)后,程序获得数据流图。
数据流图
如果一个函数的输出是另一个函数的输入,则认为后者依赖前者。对应数据流图中一条前者指向后者的有向边。Runtime获取到整幅数据流图之后,能够清楚地理解各个输入、输出之间的依赖关系,故而容易找到可以并行结算的节点。本例中,A+B和C-D没有任何依赖关系,因此图上不存在两者之间的有向边,所以他们可以并行执行。然而,E必须等待A+B和C-D计算完之后,才能进行运算。通过预编译优化技术,TensorFlow可以在运行时有效提升算法并行度,加快程序的运行速度。

TensorFlow数据流图概念

数据流图:用数据节点和有向边描述数学运算的有向无环图。

数据流图中的节点通常代表各类操作(operation),具体包括数学运算、数据填充、结果输出和变量读写等操作,每个节点上的操作都需要分配到具体的device上(CPU OR GPU)执行。有向边描述了节点间的输入、输出关系。Tensor在图上Flow,就是TensorFlow名字的来源。

基于梯度下降法求解的ML问题,通常都可以分为前向图求值和后向图求梯度两个计算阶段。

前向图由用户编写代码完成,主要包括构建模型、定义目标函数、输入输出数据的维度和类型。
后向图由TF optimizer自动生成,主要功能就是计算梯度,用于更新模型参数。

  1. 节点
    前向图中的节点统一称为操作,根据功能可分为以下三类:

    数学函数或者表达式:例如MatMul, BiasAdd, Softmax等等,绝大多数节点都属于此类;
    存储模型参数的变量(variable):如上图中ReLu Layer的权重;
    占位符(placeholder):通常用来描述输入、输出数据的类型和形状等,便于用户利用数据的抽象结构直接定义模型。在数据流图执行时,占位符需要填充对应的数据。

    后向图中的节点同样分为三类:

    梯度值:反向传播计算出来的各层梯度;
    参数操作:根据梯度,更新各层网络参数;
    模型参数:更新后的模型参数,与前向图的模型参数一一对应;

  2. 有向边
    数据流图中的有向边用于定义操作之间的关系,主要分为两类:数据边和控制边。

    数据边,用于数据传输,绝大部分流动着张量的边都是此类。
    控制边,用于定义控制依赖,通过设定节点的前置依赖,决定相关节点的执行顺序。

    所有节点都通过数据边和控制变连接。入度为0的节点没有前置依赖,可以立即执行;入度非0的节点需要等待所有依赖节点执行结束后,方可执行。

  3. 执行原理
    在深度神经网络模型的数据流图上,各个节点的执行顺序并不完全依赖于代码定义的顺序,而是与节点之间的逻辑关系以及Runtime库的实现机制相关。
    数据流图上的节点执行顺序的实现参考了拓扑排序的设计思想。当用户使用TF执行制定数据流图式,其过程可以总结为4个步骤:
    (1)以节点名称作为关键字,入度作为值,创建一张散列表,并将次数据流图上的所有节点放入散列表;
    (2)为此数据流图创建一个可执行节点队列,将散列表中入度为零的节点压入该队列中,并从散列表中删除这些节点;
    (3)依次执行对立中的每个节点,执行成功后将此节点的输出指向的节点的入度值减1,更新散列表中对应节点的入度值;
    (4)重复步骤(2)和(3),直到可执行节点队列为空。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dongz__

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值