【TF/Guide笔记】 04. Graphs and functions
这一章才明确的进入计算图的部分,前面使用的都还是立即执行的代码,但我理解,前面使用的eager execution应该是计算图的一种特化,本质上是一样的。
当然对于从脚本语言这个方向先理解的人,把思路转换为计算图可能需要点儿时间,所以这一章的开头做了大量说明,我基本上就略过了。
比较关键的是tf自己说的在图上可以做什么优化:
第一个constant folding我还特意查了一下,这是个源自编译器的概念,就是把结果与输入无关的东西先算出来之后固定下来,毕竟一个计算图大概率是要迭代运行的,所以可以提前保留一些固定结果,当然有经验的用户大概也不会写出这种代码。
第二个关于图的拆分,在Variable那一节的笔记里,我当时认为tf的图是按顺序执行的,现在看来他们也做了op之间的并行执行,不过这里也说了,互相独立的计算才可以被分开执行,我觉得两个使用了相同输入tensor的op在tf里不一定能被判定为independent,所以输入只读的方式应该是能更细的拆分计算图的,虽然这个优化极少产生实际作用。
最后一个比较关键,说的也比较模糊,有一些数学表达式是可以合并的,比如我们写y=xw+b,通常声明会在这里连接出两个op,一个乘法一个加法,但如果有一个op是使用三个输入,实现了xw+b这个操作的话,肯定会比操作两次更快,虽然框架不可能覆盖的了所有复杂情况。然而万一他有这种细化操作(比如是cuda提供的),那么把乘法和加法两个op合并成一个三输入op就可以提速,我推测它干的就是这样一个事。以前我们也提出过要做,但是我觉得这个也许能带来提升,但需要框架对计算图做大量的特判,我们那种就几个人会用的小工具,不如显示的直接调用三输入的op来的更实际,所以一直就没做。
从计算图的定义来看,语法上跟我们以前大差不差,就是拿个函数包一下,虽然这里强调了与tf 1.0的语法产生了很大区别,但我相信这种包函数的方式无非是内部帮你声明了一个全局session,然后后面的op在定义时默认走了全局变量的那个session罢了,底层肯定是没有大改动的(看c++那个示例就知道了)。
由于tf支持图上的控制分支,也就是有专门的if op,所以如果你想要构造这样的图,自然就需要用if op。以前我们没想清楚条件和循环怎么在图上体现,所以一般这种if条件就特化在了op里面,相当于把多条件下的输出合并。tf作为大公司当然要支持各种情况了,按我理解他的这个autograph模块,就是帮你把用户随意写的py代码转换成计算图的,比如大部分人不会知道判断的地方要写 if tf.greater(x, 0),人家肯定直观的写 if x > 0,autograph就会帮你自动把这类代码转换。不过我觉得对于一部分高级用户来说,他可能更喜欢自己掌控全部计算过程的方式,不喜欢有这种无法预知的不透明过程,对于大公司来说肯定可以做到尽善尽美,我们要是去做这种事情那就是纯粹添乱了。
Seeing the speed-up这个例子多少有点糊弄人,其实它做的事情就是算了一个矩阵的100次方,只不过是用循环实现的。声明函数里,可以理解为每一个for里面的result都不再是最初那个result变量了,而是新生成的下游节点,于是这张图是由100个乘法顺序连接起来的。
而tf所谓的加速,大概率是将这100个连乘做了第三条优化,合并成了更简洁的计算式,也就是x^100,cuda内部自然是做了快速幂的,证据就是,如果把for去掉,function里直接写x**y,eager明显比graph快很多。
当然并不是graph没用,对于大量的数据来说,肯定是graph更好,只是tf又想展示特性,又不能给文档整太复杂,就弄了这么个tricky的例子。
至于他所谓的多态,我们以前的function是在调用的时候产生图,然后外面再去多次执行,而tf这里可以重复调用function,内部会记录下来造过的图,当输入格式不匹配的时候就重新造一个,让外界没有感知。本来我认为就是把我们那种模式包装的更人性化了一些,直到看见最后关于tracing的解释才发现并不一样。
所谓tracing应该就是构造图,以及剪枝合并等等优化这个过程。
从tf特别解释了tracing对效率的影响来看,我大概理解他是怎么做数据流的了。我们以前在实现的时候,完整跑完一个计算图是要走完全数据的,数据流是跑计算图的必要条件,所以一个图在声明到运行完,跑完的是一整份数据的训练。
但是tf.function应该针对的是一个mini batch的数据,这张图跑完一次之后就是算出了一次梯度,至于流式数据是要在function外面再去包一层,这也是为什么function做成了重复调用时只有第一次生成计算图。
包括文档里也强调了,如果你的训练跑的特别慢,也许是哪里写的不好导致了重复tracing,也就是每个mini batch都跑了一遍tracing,因为只有mini batch级的重复构图才能产生大幅拖慢训练的影响。