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

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

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

aa=tf.Variable([0])
bb=tf.assign(aa,1)
cc=tf.assign(aa,2)
with tf.session() as sess:
    sess.run([aa, bb, cc])

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

aa=tf.Variable([0])
cc=tf.assign(aa,1)
bb=tf.assign(aa,2)
with tf.session() as sess:
    sess.run([aa, bb, cc])
aa=tf.Variable([0])
cc=tf.assign(aa,1)
bb=tf.assign(aa,2)
with tf.session() as sess:
    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的本质就是一个对某快内存的赋值操作后。我们可以很容易得到下面代码的结果:

aa=tf.Variable([0])
cc=tf.assign(aa,1)
bb=tf.assign(aa,2)
with tf.session() as sess:
    sess.run(aa)
    sess.run(bb)
    sess.run(cc)

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

aa=tf.Variable([0])
cc=tf.assign(aa,1)
bb=tf.assign(aa,2)
with tf.session() as sess:
    sess.run([aa, bb, cc])

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

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

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

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

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

import tensorflow as tf
t_var = tf.Variable(0.0)
def cond(i, _):
    return tf.less(i, 6)
def body(i, _):
    global t_var
    flow = tf.assign(t_var, tf.cast(i, tf.float32))
    return tf.add(i, 1), flow
i, re = tf.while_loop(cond, body, [1, t_var])
with tf.Session() as sess:
    tf.global_variables_initializer().run()
    print(sess.run([i, re]))

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

import tensorflow as tf
t_var = tf.Variable(0)
def cond(i, _):
    return tf.less(i, 6)
def body(i, _):
    global t_var
    flow = tf.assign(t_var, i)
    return tf.add(i, 1), flow
i, re = tf.while_loop(cond, body, [1, t_var])
with tf.Session() as sess:
    tf.global_variables_initializer().run()
    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是一个需要大量实战经验的语言。

你可能感兴趣的:(深度学习)