Repository: Theano/Theano

Issue:
Incorrect pool shape
There seems to be a problem with calculating the shape in theano.tensor.signal.pool.pool_2d. The result of the symbolic shape ((5, 5, 2, 2) below) isn't the same as either the actual shape of the output or the result of `Pool.out_shape` (both (1, 1, 1, 1)).

```
import theano
import theano.tensor as T
from theano.tensor.signal.pool import pool_2d
fX = theano.config.floatX
kwargs = dict(
    ds=(2, 3),
    st=(2, 2),
    ignore_border=True,
    padding=(1, 1),
)
pooled = pool_2d(T.constant(np.random.randn(1, 1, 1, 1).astype(fX)), **kwargs)
print pooled.shape.eval() # [5, 5, 2, 2]
print pooled.eval().shape # (1, 1, 1, 1)

from theano.tensor.signal.pool import Pool
print Pool.out_shape((1, 1, 1, 1), **kwargs) # [1, 1, 1, 1]
```



Discussion:
We fixed such a problem recently last week. Can you update Theano do the
dev version and try again:

http://deeplearning.net/software/theano/install.html#bleeding-edge-install-instructions

On Sun, May 29, 2016 at 5:13 PM, diogo149 notifications@github.com wrote:

> There seems to be a problem with calculating the shape in
> theano.tensor.signal.pool.pool_2d. The result of the symbolic shape ((5, 5,
> 2, 2) below) isn't the same as either the actual shape of the output or the
> result of Pool.out_shape (both (1, 1, 1, 1)).
> 
> import theano
> import theano.tensor as T
> from theano.tensor.signal.pool import pool_2d
> fX = theano.config.floatX
> kwargs = dict(
>     ds=(2, 3),
>     st=(2, 2),
>     ignore_border=True,
>     padding=(1, 1),
> )
> pooled = pool_2d(T.constant(np.random.randn(1, 1, 1, 1).astype(fX)), **kwargs)
> print pooled.shape.eval() # [5, 5, 2, 2]
> print pooled.eval().shape # (1, 1, 1, 1)
> 
> from theano.tensor.signal.pool import Pool
> print Pool.out_shape((1, 1, 1, 1), **kwargs) # [1, 1, 1, 1]
> 
> —
> You are receiving this because you are subscribed to this thread.
> Reply to this email directly, view it on GitHub
> https://github.com/Theano/Theano/issues/4560, or mute the thread
> https://github.com/notifications/unsubscribe/AALC-5BrH53QRAtWvrSpgA3Ob7QqNdQBks5qGgFcgaJpZM4IpYX8
> .

I believe it is occuring on the most recent version (commit hash: 0044349fdf4244c5b616994bf16ad2ff1ff8ce8a)

I just tried with 0044349 and it did work for me with a shared variable, but not a constant.
Running in DebugMode, I have the following error:

```
BadOptimization: BadOptimization Error 
  Variable: id 139708182581456 MakeVector{dtype='int64'}.0
  Op MakeVector{dtype='int64'}(TensorConstant{5}, TensorConstant{5}, TensorConstant{2}, TensorConstant{2})
  Value Type: <type 'numpy.ndarray'>
  Old Value shape, dtype, strides: (4,) int64 (8,)

  Old Value:  [1 1 1 1]
  New Value shape, dtype, strides: (4,) int64 (8,)

  New Value:  [5 5 2 2]
  Reason:  local_shape_to_shape_i
  Old Graph:
  Shape [id A] ''   
   |Pool{ds=(2, 3), ignore_border=True, st=(2, 2), padding=(1, 1), mode='max'} [id B] ''   
     |TensorConstant{(1, 1, 1, ..3008169912} [id C]

  New Graph:
  MakeVector{dtype='int64'} [id D] ''   
   |TensorConstant{5} [id E]
   |TensorConstant{5} [id E]
   |TensorConstant{2} [id F]
   |TensorConstant{2} [id G]
```

I'm quite confused, as I don't know where those numbers could come from in the first place, and why shape_i would use those.

I started investigating, will take another look tomorrow.


Resolving patch:
diff --git a/theano/tensor/basic.py b/theano/tensor/basic.py
--- a/theano/tensor/basic.py
+++ b/theano/tensor/basic.py
@@ -612,14 +612,14 @@ def get_scalar_constant_value(orig_v, elemwise=True,
             return numpy.asarray(v)
 
         if isinstance(v, numpy.ndarray):
-            return numpy_scalar(v)
+            return numpy_scalar(v).copy()
 
         if isinstance(v, Constant):
             if getattr(v.tag, 'unique_value', None) is not None:
                 data = v.tag.unique_value
             else:
                 data = v.data
-            return numpy_scalar(data)
+            return numpy_scalar(data).copy()
 
         if not only_process_constants and getattr(v, 'owner', None):
             if isinstance(v.owner.op, (Alloc, DimShuffle, Rebroadcast,
@@ -649,7 +649,7 @@ def get_scalar_constant_value(orig_v, elemwise=True,
                              for i in v.owner.inputs]
                     ret = [[None]]
                     v.owner.op.perform(v.owner, const, ret)
-                    return ret[0][0]
+                    return ret[0][0].copy()
             elif elemwise and isinstance(v.owner.op, Elemwise):
                 if isinstance(v.owner.op.scalar_op, scal.Second):
                     # We don't need both input to be constant for second
@@ -662,13 +662,13 @@ def get_scalar_constant_value(orig_v, elemwise=True,
                              for i in v.owner.inputs]
                     ret = [[None]]
                     v.owner.op.perform(v.owner, const, ret)
-                    return ret[0][0]
+                    return ret[0][0].copy()
             elif (isinstance(v.owner.op, theano.tensor.subtensor.Subtensor) and
                   v.ndim == 0):
                 if isinstance(v.owner.inputs[0], TensorConstant):
                     cdata = tuple(v.owner.op.get_constant_idx(v.owner.inputs))
                     try:
-                        return v.owner.inputs[0].data.__getitem__(cdata)
+                        return v.owner.inputs[0].data.__getitem__(cdata).copy()
                     except IndexError:
                         raise IndexError(
                             str(tuple(v.owner.op.idx_list)) +


Test patch:
diff --git a/theano/tensor/tests/test_basic.py b/theano/tensor/tests/test_basic.py
--- a/theano/tensor/tests/test_basic.py
+++ b/theano/tensor/tests/test_basic.py
@@ -48,7 +48,7 @@
         nonzero, flatnonzero, nonzero_values,
         stacklists, DimShuffle, hessian, ptp, power,
         swapaxes, choose, Choose, NoneConst, AllocEmpty,
-        isclose, allclose, mgrid, ogrid,
+        isclose, allclose, mgrid, ogrid, extract_constant,
         )
 
 from theano.tests import unittest_tools as utt
@@ -7026,12 +7026,21 @@ def test_second(self):
         s = theano.tensor.second(shp, c)
         assert get_scalar_constant_value(s) == c.data
 
+    def test_copy(self):
+        # Make sure we do not return the internal storage of a constant,
+        # so we cannot change the value of a constant by mistake.
+        c = theano.tensor.constant(3)
+        d = extract_constant(c)
+        d += 1
+        e = extract_constant(c)
+        self.assertTrue(e == 3, (c, d, e))
+
 
 class T_as_tensor_variable(unittest.TestCase):
     """
     We test that ticket #649 stay fixed.
     We should not allow as_tensor_variable to accept True or False
-    But it should upcast an ndrarray of bool to uint8
+    But it should upcast an ndarray of bool to uint8
     """
 
     def test_bool(self):