#5482·Theano

Inconsistent behaviour of given kw in theano.function

Author: lamblinCreated Feb 2, 2017Updated Jan 26, 2025
LabelsBugInterface

In the following code, when trying to replace a node by one of its descendants (that is already in the graph), the substitution is not done in a consistent way when using function(..., given={...}).

Using clone(..., replace={...}) is consistent, when there is only one substitution to make.

python
>>> import theano
>>> from theano import function
>>> import theano.tensor as T
>>> 
a = T.iscalar()
>>> b = a + 1
>>> c = b + 2
>>> d = b + 3
>>> e = c + d
>>> f = e + 1

Normal run:

python
>>> ff = function([a], [a, b, c, d, e, f])
>>> ff(1)
[array(1, dtype=int32),
 array(2, dtype=int32),
 array(4, dtype=int32),
 array(5, dtype=int32),
 array(9, dtype=int32),
 array(10, dtype=int32)]


Replacing b with c does so in the definition of d, but not of c (c uses the old b, d uses the new one):

python
>>> ff = function([a], [a, b, c, d, e, f], givens={b: c})
>>> ff(1)
[array(1, dtype=int32),
 array(4, dtype=int32),
 array(4, dtype=int32),
 array(7, dtype=int32),
 array(11, dtype=int32),
 array(12, dtype=int32)]

Conversely, replacing b with d does so in the definition of c:

python
>>> ff = function([a], [a, b, c, d, e, f], givens={b: d})
>>> ff(1)
[array(1, dtype=int32),
 array(5, dtype=int32),
 array(7, dtype=int32),
 array(5, dtype=int32),
 array(12, dtype=int32),
 array(13, dtype=int32)]

Explicitly rebuilding the expression b + 2 works:

python
>>> ff = function([a], [a, b, c, d, e, f], givens={b: b+2})
>>> ff(1)
[array(1, dtype=int32),
 array(4, dtype=int32),
 array(6, dtype=int32),
 array(7, dtype=int32),
 array(13, dtype=int32),
 array(14, dtype=int32)]

As well as using theano.clone:

python
>>> ff = function([a], theano.clone([a, b, c, d, e, f], replace={b: b+2}))
>>> ff(1)
[array(1, dtype=int32),
 array(4, dtype=int32),
 array(6, dtype=int32),
 array(7, dtype=int32),
 array(13, dtype=int32),
 array(14, dtype=int32)]

A possible solution would be to detect when the replacement is (or depends on) a child of the replaced node that is already in the original graph. For instance {b: c} or {b: c - 2} would trigger that detection, but {b: b + 2} or b: theano.clone(c) would not.

If such a dependency is detected, I think we should raise an exception. The error message should mention explicit cloning as a solution.