cond可以支持带有副作用的TF操作吗?

时间:2016-01-21 18:07:23

标签: tensorflow

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

不确定为什么每次创建新的操作都有效,而另一种情况没有,但我显然不会添加节点,因为图形最终会变得太大。

2 个答案:

答案 0 :(得分:11)

你的第二个版本 - assign_add()assign_sub() ops在传递给cond()的lambda中创建 - 是正确的方法。幸运的是,在调用cond()期间,两个lambdas中的每一个仅被评估一次,因此您的图形将不会无限制地增长。

基本上cond()的作用如下:

  1. 创建一个Switch节点,根据pred的值,将其输入转发给两个输出中的一个。让我们调用输出pred_truepred_false。 (它们具有与pred相同的值,但这并不重要,因为永远不会直接评估它。)

  2. 构建与if_true lambda对应的子图,其中所有节点都对pred_true具有控制依赖性。

  3. 构建与if_false lambda对应的子图,其中所有节点都对pred_false具有控制依赖性。

  4. 将两个lambda中的返回值列表压缩在一起,并为每个lambda创建一个Merge节点。 Merge节点有两个输入,其中只有一个输出,并将其转发到输出。

  5. 返回作为Merge节点输出的张量。

  6. 这意味着您可以运行第二个版本,并且无论您运行了多少步骤,都可以确保图表保持固定大小。

    您的第一个版本不起作用的原因是,当抓取Tensor时(例如您的addersubtractor),另外{{1}添加节点以强制执行张量值仅转发到实际执行的分支的逻辑。这是TensorFlow如何在其执行模型中组合前馈数据流和控制流的工件。结果是,捕获的张量(在这种情况下是Switchassign_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 }
在这种情况下,

符合您的预期。

我们意识到副作用和(有些)懒惰评估之间的相互作用令人困惑,我们有一个中期目标,即使事情更加统一。但是现在要理解的重要一点是我们不进行真正的懒惰评估:条件获取对在任一分支中使用的条件之外定义的每个数量的依赖。