4

How can I make a Tensor Flow graph push an incrementing number to a queue?

I am just doing this for learning purposes, so I'd prefer if you kept it similar to what I'm doing (and correct what I'm doing wrong). This is my code:

import tensorflow as tf

# create queue
queue = tf.RandomShuffleQueue(capacity=10, min_after_dequeue=1, dtypes=tf.float32)

# create variables, and "add" operation
push_var = tf.Variable(initial_value=1.0, trainable=False)
add = push_var.assign_add(1)

# enqueue operation
push = queue.enqueue(add)

# dequeue operation
pop = queue.dequeue()

sess = tf.InteractiveSession()

tf.initialize_all_variables().run()

# add var to stack
sess.run(push) # push_var = 2 after ran
sess.run(push) # push_var = 3 after ran
sess.run(push) # push_var = 4 after ran
sess.run(push) # push_var = 5 after ran
sess.run(push) # push_var = 6 after ran
sess.run(push) # push_var = 7 after ran
sess.run(push) # push_var = 8 after ran

# pop variable (random shuffle)
print sess.run(pop)
print sess.run(pop)

sess.close()

Output:

8
8

I'm expecting it to be 2 random numbers between 2 and 8. Instead, it always is popping the current value of the variable.

Is this because instead of pushing the actual value of the variable I am instead pushing a pointer to the variable? Tensor Flow's documentation says assign_add returns

A Tensor that will hold the new value of this variable after the addition has completed.

Again, I'm trying to learn about Tensor Flow. I'd appreciate any learning resources (besides the TensorFlow website) if you have any! Thanks.

EDIT:

Changing push = queue.enqueue(add) to push = queue.enqueue(add + 0) results in expected behavior. Could someone explain this?

hetelek
  • 3,776
  • 5
  • 35
  • 56
  • Which version are you using? I tried in tf.11 and tf 12rc1, and I can't reproduce this behavior, I'm seeing dequeue produce different numbers when I try your code – Yaroslav Bulatov Dec 13 '16 at 20:06

2 Answers2

2

@David Wong is correct that the variable is just a reference to its underlying tensor. Even though you've pushed it 7 times, the 7 elements in the queue all point to the same underlying tensor. When pop is executed, the underlying tensor is referenced and returned.

Let me explain a little bit more. The assign_add(1) simply updates the referenced value, so it returns a reference. When you do push = queue.enqueue(add), it internally calls tf.convert_to_tensor(add) which would return a reference if its input is also a reference.

You can inspect the output of tf.convert_to_tensor(add) in the python shell:

In [2]: tf.convert_to_tensor(add)
Out[2]: <tf.Tensor 'AssignAdd:0' shape=() dtype=float32_ref>

The dtype=float32_ref indicates it is a reference.

As for add + 0, you can also inspect it in the ipython shell, which is equivalent to tf.add(add, 0):

In [3]: add+0
Out[3]: <tf.Tensor 'add:0' shape=() dtype=float32>

It is not a reference and has a parent node add = push_var.assign_add(1).

So the problem here is

1) a tensor would be evaluated when it is pushed to a queue, all its parent nodes would be evaluated as well.

In your case, the add + 0 is evaluated, so is its parent node add = push_assign_add(1) which incremented the referenced value by 1.

2) a reference is not evaluated when pushed to a queue. There are simply references in the queue. When they are popped and referenced, their actually tensor values are fetched.

In your case, all these references all point to the same tensor. So the pops all return 8.

yuefengz
  • 3,338
  • 1
  • 17
  • 24
  • Hm, I think queue.enqueue can only push concrete values of tensors, so it would force evaluation of the reference during run – Yaroslav Bulatov Dec 13 '16 at 20:36
  • @YaroslavBulatov would you mind pointing out where the evaluation happens? – yuefengz Dec 15 '16 at 07:39
  • When the `Enqueue` op is executed. It takes a Tensor and puts it on the queue, so the tensorflow runtime evaluates `assign_add` before running `enqueue` op. Evaluating `assign_add` gives variable value – Yaroslav Bulatov Dec 15 '16 at 17:15
  • Hm, I take it back, there's something reference-like going on, I assumed enqueue op took tensor by value rather than by reference, but it looks like the semantics of it are different depending on whether the tensor is on GPU or not -- http://stackoverflow.com/questions/41308515/force-copy-of-tensor-when-enqueuing – Yaroslav Bulatov Dec 24 '16 at 18:08
1

This is because your "add" variable is actually a reference to the push_var variable. Thus, when you push to queue, you are pushing a reference to the variable. Pushing "add+0" mean you are pushing a new tensor that contains the value of "add+0" with the value "add" at the time of the push, this is why it works.

David Wong
  • 748
  • 3
  • 6
  • Thank you. Do you have any idea of how to properly achieve this? – hetelek Nov 18 '16 at 04:02
  • 1
    Yeah I am not sure what the proper way is, but one way to achieve this without being super awkward is to use tmp = tf.add(push_var, 1) (which returns a new tensor), push tmp to the queue, and then push_var = tf.assign(tmp). – David Wong Nov 18 '16 at 04:11