tf.cond
的(源代码)文档不清楚评估谓词时要执行的函数是否会产生副作用。我做了一些测试但是我得到了相互矛盾的结果。例如,下面的代码不起作用:
import tensorflow as tf
from tensorflow.python.ops import control_flow_ops
pred = tf.placeholder(tf.bool, [])
count = tf.Variable(0)
adder = count.assign_add(1)
subtractor = count.assign_sub(2)
my_op = control_flow_ops.cond(pred, lambda: adder, lambda: subtractor)
sess = tf.InteractiveSession()
tf.initialize_all_variables().run()
my_op.eval(feed_dict={pred: True})
count.eval() # returns -1
my_op.eval(feed_dict={pred: False})
count.eval() # returns -2
即。无论谓词评估的是什么值,两个函数都会运行,因此最终结果是减1。另一方面,此代码片段确实有效,唯一的区别是我向图表中添加了新的操作每次调用my_op
时:
pred = tf.placeholder(tf.bool, [])
count = tf.Variable(0)
my_op = control_flow_ops.cond(pred, lambda:count.assign_add(1), lambda:count.assign_sub(2))
sess = tf.InteractiveSession()
tf.initialize_all_variables().run()
my_op.eval(feed_dict={pred: False})
count.eval() # returns -2
my_op.eval(feed_dict={pred: True})
count.eval() # returns -1
不确定为什么每次创建新的操作都有效,而另一种情况没有,但我显然不会添加节点,因为图形最终会变得太大。
答案 0 :(得分:11)
你的第二个版本 - assign_add()
和assign_sub()
ops在传递给cond()
的lambda中创建 - 是正确的方法。幸运的是,在调用cond()
期间,两个lambdas中的每一个仅被评估一次,因此您的图形将不会无限制地增长。
基本上cond()
的作用如下:
创建一个Switch
节点,根据pred
的值,将其输入转发给两个输出中的一个。让我们调用输出pred_true
和pred_false
。 (它们具有与pred
相同的值,但这并不重要,因为永远不会直接评估它。)
构建与if_true
lambda对应的子图,其中所有节点都对pred_true
具有控制依赖性。
构建与if_false
lambda对应的子图,其中所有节点都对pred_false
具有控制依赖性。
将两个lambda中的返回值列表压缩在一起,并为每个lambda创建一个Merge
节点。 Merge
节点有两个输入,其中只有一个输出,并将其转发到输出。
返回作为Merge
节点输出的张量。
这意味着您可以运行第二个版本,并且无论您运行了多少步骤,都可以确保图表保持固定大小。
您的第一个版本不起作用的原因是,当抓取Tensor
时(例如您的adder
或subtractor
),另外{{1}添加节点以强制执行张量值仅转发到实际执行的分支的逻辑。这是TensorFlow如何在其执行模型中组合前馈数据流和控制流的工件。结果是,捕获的张量(在这种情况下是Switch
和assign_add
的结果)将始终被评估,即使它们未被使用,您也会看到它们副作用。这是我们需要更好地记录的内容,as Michael says,我们将来会更加有用。
答案 1 :(得分:9)
第二种情况有效,因为你在cond中添加了ops:这会导致它们有条件地执行。
第一种情况类似于说:
chrome.tabs.onRemoved.addListener(function(tabid, removed) {
alert("tab closed")
})
chrome.windows.onRemoved.addListener(function(windowid) {
alert("window closed")
})
由于加法器和减法器在条件之外,因此它们总是被执行。
第二种情况更像是说
adder = (count += 1)
subtractor = (count -= 2)
if (cond) { adder } else { subtractor }
在这种情况下,符合您的预期。
我们意识到副作用和(有些)懒惰评估之间的相互作用令人困惑,我们有一个中期目标,即使事情更加统一。但是现在要理解的重要一点是我们不进行真正的懒惰评估:条件获取对在任一分支中使用的条件之外定义的每个数量的依赖。