Repository: Theano/Theano

Issue:
CorrMM: upcast inputs?
When `CorrMM` receives inputs of mixed types (single and double precision), it fails at runtime:

``` python
import theano
import theano.tensor as T
x = T.ftensor4()
y = T.dtensor4()
z = T.nnet.corr.CorrMM()(x, y)
fn = theano.function([x, y], z)
fn(np.random.randn(2,3,4,5).astype(np.float32), np.random.randn(1,2,3,4))
```

This fails with `ValueError: CorrMM received weight with wrong type.`, from here: https://github.com/Theano/Theano/blob/a668c6c/theano/tensor/nnet/corr_gemm.c#L130
`CorrMM`, `CorrMM_gradWeights` and `CorrMM_gradInputs` should probably upcast their input tensors to a common datatype in `make_node`, or at least check for matching types early instead of failing at runtime.

Problem reported at https://groups.google.com/d/topic/lasagne-users/I5u9RHXTHmI/discussion.



Discussion:

Resolving patch:
diff --git a/theano/tensor/nnet/corr.py b/theano/tensor/nnet/corr.py
--- a/theano/tensor/nnet/corr.py
+++ b/theano/tensor/nnet/corr.py
@@ -75,6 +75,14 @@ def __str__(self):
             str(self.subsample),
             str(self.filter_dilation))
 
+    @staticmethod
+    def as_common_dtype(in1, in2):
+        """
+        Upcast input variables if neccesary.
+        """
+        dtype = theano.scalar.upcast(in1.dtype, in2.dtype)
+        return in1.astype(dtype), in2.astype(dtype)
+
     def c_support_code(self):
         return blas_header_text()
 
@@ -383,6 +391,7 @@ def __init__(self, border_mode="valid", subsample=(1, 1),
     def make_node(self, img, kern):
         img = as_tensor_variable(img)
         kern = as_tensor_variable(kern)
+        img, kern = self.as_common_dtype(img, kern)
         if img.type.ndim != 4:
             raise TypeError('img must be 4D tensor')
         if kern.type.ndim != 4:
@@ -445,6 +454,7 @@ def __init__(self, border_mode="valid", subsample=(1, 1),
     def make_node(self, img, topgrad, shape=None):
         img = as_tensor_variable(img)
         topgrad = as_tensor_variable(topgrad)
+        img, topgrad = self.as_common_dtype(img, topgrad)
         if img.type.ndim != 4:
             raise TypeError('img must be 4D tensor')
         if topgrad.type.ndim != 4:
@@ -546,6 +556,7 @@ def __init__(self, border_mode="valid", subsample=(1, 1), filter_dilation=(1, 1)
     def make_node(self, kern, topgrad, shape=None):
         kern = as_tensor_variable(kern)
         topgrad = as_tensor_variable(topgrad)
+        kern, topgrad = self.as_common_dtype(kern, topgrad)
         if kern.type.ndim != 4:
             raise TypeError('kern must be 4D tensor')
         if topgrad.type.ndim != 4:


Test patch:
diff --git a/theano/tensor/nnet/tests/test_corr.py b/theano/tensor/nnet/tests/test_corr.py
--- a/theano/tensor/nnet/tests/test_corr.py
+++ b/theano/tensor/nnet/tests/test_corr.py
@@ -2,6 +2,7 @@
 
 from nose.plugins.skip import SkipTest
 from nose.plugins.attrib import attr
+from nose.tools import assert_equals
 import numpy
 from six import integer_types
 
@@ -259,6 +260,32 @@ def test_wrong_input(self):
         self.assertRaises(Exception, self.validate, (3, 2, 8, 8), (4, 2, 5, 5),
                           'valid', input=T.dtensor3())
 
+    def test_dtype_upcast(self):
+        """
+        Checks dtype upcast for CorrMM methods.
+        """
+        def rand(shape, dtype='float64'):
+            r = numpy.asarray(numpy.random.rand(*shape), dtype=dtype)
+            return r * 2 - 1
+
+        ops = [corr.CorrMM, corr.CorrMM_gradWeights, corr.CorrMM_gradInputs]
+        a_shapes = [[4, 5, 6, 3], [1, 5, 6, 3], [1, 5, 6, 3]]
+        b_shapes = [[7, 5, 3, 2], [1, 5, 3, 1], [7, 1, 3, 1]]
+        dtypes = ['float32', 'float64']
+
+        for op, a_shape, b_shape in zip(ops, a_shapes, b_shapes):
+            for a_dtype in dtypes:
+                for b_dtype in dtypes:
+                    c_dtype = theano.scalar.upcast(a_dtype, b_dtype)
+                    a_tens = T.tensor4(dtype=a_dtype)
+                    b_tens = T.tensor4(dtype=b_dtype)
+                    a_tens_val = rand(a_shape, dtype=a_dtype)
+                    b_tens_val = rand(b_shape, dtype=b_dtype)
+
+                    c_tens = op()(a_tens, b_tens)
+                    f = theano.function([a_tens, b_tens], c_tens, mode=self.mode)
+                    assert_equals(f(a_tens_val, b_tens_val).dtype, c_dtype)
+
     @attr('slow')
     def test_infer_shape_forward(self):
         if theano.config.mode == "FAST_COMPILE":