tf.assign, tf.while_loop的一些理解和坑

原文:https://blog.csdn.net/ziliwangmoe/article/details/81297548#commentBox

炎炎夏天,如果如果还开个满负荷运转的GPU机器训练模型。看着变幻莫测的误差下降曲线,真的有种在炼丹的感觉。

如果你还用TensorFlow,这种不可捉摸的感觉又添加几分,这里就用tf.assign, tf.while_loop两个函数来举例。

 
  1. aa=tf.Variable([0])

  2. bb=tf.assign(aa,1)

  3. cc=tf.assign(aa,2)

  4. with tf.session() as sess:

  5. sess.run([aa, bb, cc])

请问上面代码运行下来,aa, bb, cc的值分别是什么。

 
  1. aa=tf.Variable([0])

  2. cc=tf.assign(aa,1)

  3. bb=tf.assign(aa,2)

  4. with tf.session() as sess:

  5. sess.run([aa, bb, cc])

 
  1. aa=tf.Variable([0])

  2. cc=tf.assign(aa,1)

  3. bb=tf.assign(aa,2)

  4. with tf.session() as sess:

  5. sess.run([aa, cc, bb])

又问这个代码的结果呢?答案是aa,bb,cc的值要不都是1,要不都是2,其中规律非常复杂。为了理解这个现象,需要理解assign函数的本质。

assign函数只能对Variable(准确的说只能对类型为ref的结构)做操作。Variable其实是对应的内存的一块区域的地址,所以其dtype只能为xxx_ref(float32_ref, string_ref)。而assign的第一个参数要求必须传入一个ref类型的变量。

所以assign函数有两个输入,一个是地址,一个是值。操作内容就是把这个地址用传入的值填充。它的输出并不是必须的。但是TensorFlow作者基于使用的方便,把这个地址变量放入tensor中传了出去。

assign的输出的是tensor,而内容仍然是一个地址

这个事情一般情况都没什么问题,因为大部分函数在被传入一个 Variable时,会自动把Variable对应的值取出来,比如run()函数。还有所有Variable在经过运算后,都会变为值。但是tf.cast()不会把Variable变为tensor。

如果想把一个ref变为值最简单的办法是使用Identity函数。注意使用identity后得到的tensor和传入的Variable仍然指向同一内存。

 当知道assign的本质就是一个对某快内存的赋值操作后。我们可以很容易得到下面代码的结果:

 
  1. aa=tf.Variable([0])

  2. cc=tf.assign(aa,1)

  3. bb=tf.assign(aa,2)

  4. with tf.session() as sess:

  5. sess.run(aa)

  6. sess.run(bb)

  7. sess.run(cc)

sess.run(bb)就等于执行一次对aa对应内存的赋值。然后显示出这块内存的值。

 
  1. aa=tf.Variable([0])

  2. cc=tf.assign(aa,1)

  3. bb=tf.assign(aa,2)

  4. with tf.session() as sess:

  5. sess.run([aa, bb, cc])

但对于最开始的代码,就不好说了。计算cc和bb都需要aa的结果,但是cc和bb并没有依赖关系。所以最终你会发现aa, bb, cc的值是都会相同的,但是在1,2之间随机的。都相同因为其实他们都指向的同一内存。

各个操作在run()函数中被执行的顺序是按照需求关系逐步扩展开的。所以run结果的时候一定要分析依赖树。同一层的操作的执行顺序是允许有随机性的。要去掉这个随机性,就体现了tf.with_dependence的意义了。

要看看到aa,bb,cc的值得随机性,最好的办法是多次执行相同的run。

 
  1. sess.run([aa, bb, cc])

  2. sess.run([aa, bb, cc])

  3. sess.run([aa, bb, cc])

  4. sess.run([aa, bb, cc])

  5. sess.run([aa, bb, cc])

关于tf.while_loop,在loop的body中对Variable变量进行复制是最常见的方式,因为需要一个变量来存储每一次循环的结果。

 
  1. import tensorflow as tf

  2. t_var = tf.Variable(0.0)

  3. def cond(i, _):

  4. return tf.less(i, 6)

  5. def body(i, _):

  6. global t_var

  7. flow = tf.assign(t_var, tf.cast(i, tf.float32))

  8. return tf.add(i, 1), flow

  9. i, re = tf.while_loop(cond, body, [1, t_var])

  10. with tf.Session() as sess:

  11. tf.global_variables_initializer().run()

  12. print(sess.run([i, re]))

re的结果会是啥呢?答案是随机。是不是惊呆了。因为每个循环的flow都是依赖于t_var,也就是他们之间是平级了,那么这么多个assign的执行顺序也是不定的。 

 
  1. import tensorflow as tf

  2. t_var = tf.Variable(0)

  3. def cond(i, _):

  4. return tf.less(i, 6)

  5. def body(i, _):

  6. global t_var

  7. flow = tf.assign(t_var, i)

  8. return tf.add(i, 1), flow

  9. i, re = tf.while_loop(cond, body, [1, t_var])

  10. with tf.Session() as sess:

  11. tf.global_variables_initializer().run()

  12. print(sess.run([i, re]))

上面这个代码只是把t_var从float变为的int。然后居然就报错了,错误说某个操作不支持ref类型。悄悄告诉你我去年买了个表。也就是while_loop中float和int的处理规则也是不一样的。解决这个问题就是在flow后面再用下identity,把ref类型转为val类型。

flow=tf.identity(flow)

tf.while_loop的脾气不似一般的怪异。但tensorflow内部还是把循环转化为了矩阵运算,所以如果自己能显式的用矩阵运算代替while_loop的话,这样可以避免调入while_loop这么多坑里面。当然为了显示自己对TensorFlow的了如指掌得人除外。

其实研究这些特殊的例子不是说一定要咬文嚼字,而是可以通过这些边缘地带的特性来研究TensorFlow内部的机理。

Python的灵活性其实是一把双刃剑,因为其期望的结果可能性远远大于C++。所以很难用一本书,或者一个结构化的资料覆盖里面所有的规则。这就拉开了新手和老鸟之间的距离。所以说Python是一个需要大量实战经验的语言。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值