【问题标题】:Can cond support TF ops with side effects?cond 可以支持有副作用的 TF 操作吗?
【发布时间】:2016-01-21 18:07:23
【问题描述】:

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。另一方面,这段代码 sn-p 确实有效,唯一的区别是我添加了新的操作每次调用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

不知道为什么每次创建新操作都有效,而另一种情况则无效,但我显然不想添加节点,因为图表最终会变得太大。

【问题讨论】:

    标签: tensorflow


    【解决方案1】:

    您的第二个版本——assign_add()assign_sub() 操作在传递给 cond() 的 lambda 中创建——是正确的方法。幸运的是,在调用 cond() 期间,这两个 lambda 表达式中的每一个都只计算一次,因此您的图形不会无限制地增长。

    cond() 的基本作用如下:

    1. 创建一个Switch 节点,该节点仅将其输入转发到两个输出之一,具体取决于pred 的值。我们将输出称为pred_truepred_false。 (它们与pred 具有相同的值,但这并不重要,因为它永远不会被直接评估。)

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

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

    4. 将来自两个 lambda 的返回值列表压缩在一起,并为每一个创建一个 Merge 节点。 Merge 节点接受两个输入,其中预计只会产生一个,并将其转发到其输出。

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

    这意味着您可以运行您的第二个版本,并且满足于图表保持固定大小,无论您运行多少步。

    您的第一个版本不起作用的原因是,当捕获 Tensor(如您的示例中的 addersubtractor)时,会添加一个额外的 Switch 节点以强制执行张量的值仅转发到实际执行的分支。这是 TensorFlow 如何在其执行模型中结合前馈数据流和控制流的产物。结果是捕获的张量(在本例中为 assign_addassign_sub 的结果)将始终被评估,即使它们没有被使用,您也会看到它们的副作用。这是我们需要更好地记录的内容,as Michael says,我们将在未来使其更有用。

    【讨论】:

      【解决方案2】:

      第二种情况有效,因为您在 cond 中添加了操作:这会导致它们有条件地执行。

      第一种情况就好比说:

      adder = (count += 1)
      subtractor = (count -= 2)
      if (cond) { adder } else { subtractor }
      

      由于加法器和减法器在条件之外,它们总是被执行。

      第二种情况更像是说

      if (cond) { adder = (count += 1) } else { subtractor = (count -= 2) }
      

      在这种情况下,它符合您的预期。

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

      【讨论】:

        猜你喜欢
        • 2016-07-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-04-07
        • 2015-11-21
        • 1970-01-01
        相关资源
        最近更新 更多