TensorFlowFold深度探究 Blocks for Composition.docx
- 文档编号:30633710
- 上传时间:2023-08-18
- 格式:DOCX
- 页数:29
- 大小:122.70KB
TensorFlowFold深度探究 Blocks for Composition.docx
《TensorFlowFold深度探究 Blocks for Composition.docx》由会员分享,可在线阅读,更多相关《TensorFlowFold深度探究 Blocks for Composition.docx(29页珍藏版)》请在冰豆网上搜索。
TensorFlowFold深度探究BlocksforComposition
【TensorFlow_Fold】深度探究BlocksforComposition
0xFF未完工说明
Composition某种意义上可以说是基本、核心的一部分,打算一点点慢慢写,无奈Src有点多,我想继续边看边思考,敬请原谅~o(∩_∩)o~
【当前进度89%】
0x00前言
想写点东西试试,结果接下来就老老实实躺在了Pipeline上;
决定学跑之前先学爬,老老实实啃一下源码和官方文档,虽然官方还在一点点更新,不少地方还是空白的,不过先动起来多敲点试试看,老等着别人喂饭多不好呀(对呀我也觉得连前言都要重复我真的好懒呀23333);
TFF的基本单位之一是Block,看了下Markdown的结构,个人打算按照这个顺序来看看:
->Blocksforinput->(Done)♪(^∀^●)ノ
->Blocksforcomposition
->Blocksfortensors
->Blocksforsequences
->Otherblocks
->Primitiveblocks
->Blockcomposition
虽说……列完表就有想弃坑的冲动,不过毕竟万事开头难嘛,一个一个来,那下一件事就来看看Blocks4Composition好了。
Reference
BlocksforInput官方文档出处
Blocks的官方解释及示例Markdown
Source:
TensorFlow_Fold/PythonBlocksAPI
SourceCode:
Github/blocks.py
AboutBlock_Info()
因为在探究block,所以时不时看看block的类型以及输入输出是必要的,代码中会常使用block_info()函数来查询与验证。
如果没有特殊说明,block_info()函数指代TFF官方代码里的block描述函数,如下:
#Functionfordescribeblocks
defblock_info(block):
print("%s:
%s->%s"%(block,block.input_type,block.output_type))
Abouttimes_scalar_block()
这一次要学的大约都是块和块,啊不对block和block之间的关系,所以为了偷懒先事先准备一个block放着,后面直接以这个block来举例说明。
这个block是以time_scalar_block(n)函数接受传参,传入的值即输出的block的倍率,如time_scalar_block(3)则返回一个令输入值乘以三输出的block(严格来说这个其实应该是个fn,毕竟是个td.Function()…)。
importfunctools
deftimes_scalar_block(n):
returntd.Function(functools.partial(tf.multiply,n))
0x01td.Composition()
说明文档
classtd.Composition
Acompositionofblocks,whichareconnectedinaDAG.
td.Composition.init(children=None,name=None)
td.Composition.connect(a,b)
Connectatotheinputofb.
Theargumentacanbeeither:
Ablock,inwhichcasetheoutputofaisfedintotheinputofb.
Thei^thoutputofablock,obtainedfroma[i].
Atupleorlistofblocksorblockoutputs.
Atd.CompositionblockallowstheinputsandoutputsofitschildrentobewiredtogetherinanarbitraryDAG.
Testing
说到Composition,我就开始头疼了,虽说就是个连接操作……但是越是这种最基础的东西,底层越麻烦。
哇哦,这些test写的真的好……感觉在纸上随便画画就理解不少了,oh,我突然有个好点子,反正连接使用的地方那么广,测试code怕是也特别多,要不然这一节直接用blocks_test里的代码来讲吧~
入门篇:
PyObject连接
deftest_pyobject_composition(self):
block=tdb.AllOf(tdb.Identity(),tdb.Identity())
self.assertBuildsConst(('foo','foo'),block,'foo')
block=(tdb.AllOf(tdb.Identity(),tdb.Identity())>>
(tdb.Scalar(),tdb.Scalar()))
self.assertBuilds((42.0,42.0),block,42,max_depth=None)
既然决定了要带大家看代码的形式来理解,那第一个函数就多说一点,讲一下这些看上去高大上的东西的意思好啦。
这里的tdb指的是td,好的抬走下一个;
self.assertBuilds(a,block,b,[others...])
这个是一个测试函数,可以理解为“我把b扔进block里,输出的是a吗?
”,鉴于这是一个验证函数正确性的code,所以我们可以默认这个文件中的这个函数另一个角度实际是告知我们“你们把b作为输入传给block,输出的是a”;
以这一段为例:
block为
td.AllOf(td.Identity(),td.Identity())>>(td.Scalar(),td.Scalar())
在这个block中,只有一个>>,
左侧为:
td.AllOf(td.Identity(),td.Identity())
右侧为:
(td.Scalar(),td.Scalar())
td.AllOf也是当前节会介绍的东西,简单来说的话就是,对于其中的每一个都做一件事(微歧义,后面会说明):
#Ablockthatrunsallofitschildren(conceptually)inparallel.
AllOf().eval(inp)=>None
AllOf(a).eval(inp)=>(a.eval(inp),)
AllOf(a,b,c).eval(inp)=>(a.eval(inp),b.eval(inp),c.eval(inp))
详细的请拉到本篇较后面的位置寻找或者直接Ctrl+F寻找td.AllOf()
Bytheway,这里的td.Identity()之前没有介绍过,贴一条一句话解释:
classtd.Identity
Ablockthatmerelyreturnsitsinput.
于是这一段test_pyobject_composition则是:
对于Python_Object的Composition操作测试,是否可以简单地将PyObject类型使用>>连接成为pipeline,将输入的值复制为2份,传给一个包含两个td.Scalar的tuple,示例中,输入42,输出(42.0,42.0)。
基础篇:
Function连接
deftest_function_composition(self):
sc=tdb.Scalar()
fn1=times_scalar_block(2.0)
c=sc>>fn1
self.assertBuilds(42.,c,21)
deftest_function_composition_with_block(self):
c=tdb.Composition()
withc.scope():
scalar=tdb.Scalar().reads(c.input)
c.output.reads(times_scalar_block(2.0).reads(scalar))
self.assertBuilds(42.,c,21)
上述均是目标为function的连接,所以放在一起看一看~
首先,times_scalar_block文首已经提及过啦,你们肯定都记……好吧不见得你们还记得,再说一次:
importfunctools
deftimes_scalar_block(n):
returntd.Function(functools.partial(tf.multiply,n))
首先解释一下c.scope(),简而言之,就是说c(c=td.Composition())是一个里面有很多小block的大block,“在c里面,我们有blablabla”就是withc.scope():
的意思了。
我们需要知道的是,等我们构建完毕之后,c.input是c的输入,同时也是c中与c.input相连的block的输入,同理,与c.output相连的输出即c的输出。
再解释一下.read(),对于一个block而言,blockX.read(blockA)与c.connect(blockA,blockX)是同样的效果,并且.read()函数仅能在scope的范围中使用。
我们可以在scope中使用.connect()或是.read()来为blocks连边,从而构建一个DAG(有向无环图)的计算图。
以这两段代码为例,可以看作效果是相同的,都是将输入值传给td.Scalar(),然后连到乘以二的Block上,最后连向输出。
可以仔细观察一下这些操作,是block连接的基础或者说基本,不同的是后者使用td.Composition来创建block,并在scope中进行的连接操作。
同样是Function连接,下面讲一下使用lambda快速构造Function:
deftest_function_tuple_in_out(self):
#f(a,(b,c)):
=((c,a),b)
b=((tdb.Vector
(1),(tdb.Vector
(2),tdb.Vector(3)))>>
tdb.Function(lambdax,y:
((y[1],x),y[0])))
self.assertBuilds((([4.,5.,6.],[1.]),[2.,3.]),b,
([1],([2,3],[4,5,6])))
deftest_function_raises(self):
self.assertRaisesWithLiteralMatch(
TypeError,'tf_fnisnotcallable:
42',tdb.Function,42)
td.Function.__init__(tf_fn,name=None,infer_output_type=True)
关于td.Function(),严格上来说应该属于Blocksfortensors的内容,在这里我们只需要知道:
这个函数可以把传入的函数转化为block的形式,特别地,传入的函数需要是TITO(TensorIn,TensorOut)的,infer_output_type参数与占位符有关,本篇不予多加评论。
(我写到这里才领悟过来,啊原来这个test是用来测试td.Function()的呀,算了不管了等我写到那一篇再贴过去,再说了就算只是为了描述Function连接,多举个例子总是好的)
在这个例子中,我们可以看到,输入(a,(b,c)),输出((a,b),c),这个block的实现只需要简单的td.Function(lambdax,y:
((y[1],x),y[0])))就可以了。
而这里需要尤其注意!
td.Function与td.InputTransform不同,后者输入输出都是PyObject,前者输入输出都是TensorType,在这里非常容易遇见TypeError哦!
小贴士:
为了防止被海量的TypeError烦的一拳打穿电脑,推荐每写一个block,用block_info看一眼输入输出的数据类型,类型对上了再连接。
进阶篇:
运算图分叉(Diamond形运算图的连接)
deftest_composition_diamond(self):
sc=tdb.Scalar()
fn1=times_scalar_block(2.0)
fn2=times_scalar_block(3.0)
fn3=tdb.Function(tf.add)
#out=in*2+in*3
c=tdb.Composition([sc,fn1,fn2,fn3])
c.connect(c.input,sc)
c.connect(sc,fn1)
c.connect(sc,fn2)
c.connect((fn1,fn2),fn3)
c.connect(fn3,c.output)
self.assertBuilds(25.,c,5,max_depth=2)
deftest_composition_diamond_with_block(self):
#out=in*2+in*3
c=tdb.Composition()
withc.scope():
scalar=tdb.Scalar().reads(c.input)
fn1=times_scalar_block(2.0).reads(scalar)
fn2=times_scalar_block(3.0).reads(scalar)
c.output.reads(tdb.Function(tf.add).reads(fn1,fn2))
self.assertBuilds(25.,c,5,max_depth=2)
deftest_composition_nested(self):
fn1=times_scalar_block(2.0)
fn2=times_scalar_block(3.0)
c=tdb.Composition([fn1,fn2])
c.connect(c.input,fn1)
c.connect(c.input,fn2)
c.connect((fn1,fn2),c.output)
c2=tdb.Scalar()>>c>>tdb.Function(tf.add)
self.assertBuilds(5.0,c2,1.0,max_depth=2
为了实现一个有类似Diamond分叉的计算图,其艰难困苦程度一言以蔽之:
“TypeError:
二货点你给我走远点我不想再看到你!
”
调试许久,最终方案A&B如下:
cd=td.Composition()
withcd.scope():
fn1=(td.GetItem(0)).reads(cd.input)
fn2=(td.GetItem
(1)).reads(cd.input)
h1=(td.InputTransform(lookup)>>td.Vector
(2)).reads(fn1)
h2=(td.InputTransform(lookup)>>td.Vector
(2)).reads(fn2)
cd.output.reads(td.Concat().reads(h1,h2))
block_info(cd)#=>
None->TensorType((4,),'float32')
cd.eval([1,2,'a',4])#=>array([2.,1.,3.,2.],dtype=float32)
cd=td.Composition()
withcd.scope():
fn1=td.GetItem(0)
fn2=td.GetItem
(1)
h1=td.InputTransform(lookup)>>td.Vector
(2)
h2=td.InputTransform(lookup)>>td.Vector
(2)
cc=td.Concat()
cd2.connect(cd2.input,fn1)
cd2.connect(cd2.input,fn2)
cd2.connect(fn1,h1)
cd2.connect(fn2,h2)
cd2.connect((h1,h2),cc)
cd2.connect(cc,cd2.output)
block_info(cd)#=>
None->TensorType((4,),'float32')
cd.eval([1,2,'a',4])#=>array([2.,1.,3.,2.],dtype=float32)
进阶篇:
scope嵌套
deftest_composition_nested_with_block(self):
c1=tdb.Composition()
withc1.scope():
scalar=tdb.Scalar().reads(c1.input)
c2=tdb.Composition().reads(scalar)
withc2.scope():
fn1=times_scalar_block(2.0).reads(c2.input)
fn2=times_scalar_block(3.0).reads(c2.input)
c2.output.reads(fn1,fn2)
c1.output.reads(tdb.Function(tf.add).reads(c2))
self.assertBuilds(5.0,c1,1.0,max_depth=2)
番外篇:
运算图拓扑
对于一个顺序七零八落的td.Composition([fn4,fn3,fn0,fn2,fn1])
我们按照如下的顺序将他们连接起来:
input->fn0
fn0->fn1
fn0->fn2
fn2->fn3
fn1&fn3->fn4
fn4->output
deftest_composition_toposort(self):
fn0=tdb.Scalar()
fn1=times_scalar_block(2.0)
fn2=times_scalar_block(3.0)
fn3=times_scalar_block(1.0)
fn4=tdb.Function(tf.add)
c=tdb.Composition([fn4,fn3,fn0,fn2,fn1])
c.connect(c.input,fn0)
c.connect(fn0,fn1)
c.connect(fn0,fn2)
c.connect(fn2,fn3)
c.connect((fn1,fn3),fn4)
c.connect(fn4,c.output)
self.assertBuilds(5.0,c,1.0,max_depth=3)
deftest_composition_toposort_output(self):
block=tdb.Composition()
withblock.scope():
s=tdb.Scalar('int32').reads(block.input)
block.output.reads(s,s)
self.assertBuildsConst((3,3),block,3)
不用在意其原先的先后顺序,Composition会自动按照他们之间的联系得出这个DAG的流从哪里开始一步一步向下走,先前我在一则文章中看到:
给图中的每一个节点(操作)标注一个深度,所有没有任何依赖的节点标注为深度0,依赖的节点深度最大为d的节点的深度标注为d+1;在图中插入pass-through(直通)的操作,使得第d+1层只依赖于第d层;将同一深度涉及相同操作的节点合并到一起,方便并行计算;将同一深度的计算结果按Tensor类型(包括Tensor的形状和数值类型)有序拼接在一起;将输入原始计算图中的每条边标记上(深度,数据类型,序号),对应它们可以获取上一层计算结果的位置。
对于一批不同结构的计算图,我们可以把它们看做不连通的大图同样处理。
上面算法的第三步会将这批图中同一深度的相同操作进行合并,方便并行计算。
说完图的构建,我们再说说怎么执行:
算法在每次迭代中执行一个深度的计算,使用tf.while_loop从深度0一直执行到最大深度。
在每一个深度中,tf.gather根据上面第五步的标记为各个Operation获取当前深度各条输入边的Tensor,如果某个Operation没有获取到任何Tensor,说明当前深度这个Operation不需要执行计算。
Operation执行完后tf.concat将相同Tensor类型的计算结果拼接在一起,提供给下一个深度的计算。
上面这一幅图来着官方论文,左边是DynamicBatching为二叉TreeRNN构建的通用计算图。
右边是一颗简单的语法解析树。
通用计算图中有两种Tensor,代表单词的编码整数、词向量/hidden向量的128维向量。
Operation也只有两个一个词向量查表操作(embedlookup)和一个RNN的Cell。
图中gather和concat之间的直连表示直通(pass-through)操作。
右边的语法解析树可以分为三层计算被执行:
第一层,将1、3、5通过词向量查表操作,输出3个128维的词向量;第二层,1和3对应的词向量通过RNNCell输出一个128维的隐含层向量,5对应的词向量直通输出;第三层,上一层计算的隐含层向量和5对应的词向量通过RNNCell,输出一个128维的隐含层向量。
计算完毕。
后传篇:
连接过程中需要注意的错误
TypeError
最常见的错误,输入输出记得要统一数据类型呀……
话虽这么说,我也在这里头疼了真的很久,到现在还没绕明白,等我哪天弄明白了,写个Fold的Type解说章好了……
PyObject
TensorType
TupleType
SequenceType(2017Apr.21:
又出现一种Type,烦躁不已瞬间爆炸,特此记录)
de
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- TensorFlowFold深度探究 Blocks for Composition TensorFlowFold 深度 探究