Repository: Theano/Theano

Issue:
DeprecationWarning from numpy in sparse/
This line gets it: https://github.com/Theano/Theano/blob/master/theano/sparse/basic.py#L898

Simple pdb tracing reveals that in one case, grad is of shape (64,) and gout_data is also of shape (64,), but self.kmap is a list of length 36.  So there is something fishy there.

However the tests currently pass so I'm not sure what exactly is wrong.



Discussion:
@abergeron From the tests, i can see only `ComplexWarning` and `SparseEfficiencyWarning`s . How do i reproduce this error ? 

kmap is old stuff not used, not well tested and not well documented. I
tried to understand what it was doing years ago and I'm still not sure why
this is needed.

So I would vote to get rid of the kmap stuff (make a warning if someone try
to use it) and remove associated tests.

Objection?

On Wed, Jan 20, 2016 at 2:46 PM, Ramana Subramanyam <
notifications@github.com> wrote:

> @abergeron https://github.com/abergeron From the tests, i can see only
> ComplexWarning and SparseEfficiencyWarnings . How do i reproduce this
> error ?
> 
> —
> Reply to this email directly or view it on GitHub
> https://github.com/Theano/Theano/issues/3889#issuecomment-173337809.


Resolving patch:
diff --git a/theano/sparse/basic.py b/theano/sparse/basic.py
--- a/theano/sparse/basic.py
+++ b/theano/sparse/basic.py
@@ -93,20 +93,6 @@ def _is_dense(x):
     return isinstance(x, numpy.ndarray)
 
 
-def _kmap_eq(a, b):
-    if a is None and b is None:
-        return True
-    if a is None or b is None:
-        return False
-    return numpy.all(a == b)
-
-
-def _kmap_hash(a):
-    if a is None:
-        return 12345
-    return hash(numpy.str(a))
-
-
 # Wrapper type
 def as_sparse_variable(x, name=None):
     """
@@ -517,9 +503,9 @@ class CSMProperties(gof.Op):
 
     # we don't return a view of the shape, we create a new ndarray from the
     # shape tuple.
+    __props__ = ()
     view_map = {0: [0], 1: [0], 2: [0]}
 
-    kmap = None
     """
     Indexing to speficied what part of the data parameter
     should be use to construct the sparse matrix.
@@ -527,18 +513,8 @@ class CSMProperties(gof.Op):
     """
 
     def __init__(self, kmap=None):
-        self.kmap = kmap
-
-    def __eq__(self, other):
-        return type(self) == type(other) and _kmap_eq(self.kmap, other.kmap)
-
-    def __hash__(self):
-        return 8234 ^ hash(type(self)) ^ _kmap_hash(self.kmap)
-
-    def __str__(self):
-        return "%s{%s}" % (
-            self.__class__.__name__,
-            self.kmap)
+        if kmap is not None:
+            raise Exception("Do not use kmap, it is removed")
 
     def make_node(self, csm):
         csm = as_sparse_variable(csm)
@@ -551,14 +527,10 @@ def make_node(self, csm):
 
     def perform(self, node, inputs, out):
         (csm,) = inputs
-        if self.kmap is None:
-            out[0][0] = csm.data
-        else:
-            out[0][0] = csm.data[self.kmap]
+        out[0][0] = csm.data
         if str(csm.data.dtype) == 'int32':
             out[0][0] = theano._asarray(out[0][0], dtype='int32')
         # backport
-        # out[0][0] = csm.data if self.kmap is None else csm.data[self.kmap]
         out[1][0] = theano._asarray(csm.indices, dtype='int32')
         out[2][0] = theano._asarray(csm.indptr, dtype='int32')
         out[3][0] = theano._asarray(csm.shape, dtype='int32')
@@ -638,14 +610,12 @@ def csm_shape(csm):
 
 class CSM(gof.Op):
     # See doc in instance of this Op or function after this class definition.
-    kmap = None
     """
     Indexing to speficied what part of the data parameter
     should be used to construct the sparse matrix.
 
     """
-
-    _hashval = None
+    __props__ = ('format',)
     """
     Pre-computed hash value, defined by __init__.
 
@@ -655,32 +625,11 @@ def __init__(self, format, kmap=None):
         if format not in ('csr', 'csc'):
             raise ValueError("format must be one of: 'csr', 'csc'", format)
         self.format = format
-
-        # for efficiency, if remap does nothing, then do not apply it
-        if kmap is not None and all(kmap == numpy.arange(numpy.size(kmap))):
-            kmap = None
-
-        self.kmap = kmap
-
-        if not isinstance(self.kmap, numpy.ndarray):
-            # should view the other inputs too, but viewing multiple
-            # inputs is not currently supported by the destroyhandler
-            self.view_map = {0: [0]}
-
-        self._hashval = (hash(type(self)) ^ hash(self.format) ^
-                         _kmap_hash(self.kmap))
-
-    def __eq__(self, other):
-        return (type(other) is CSM and other.format == self.format and
-                _kmap_eq(self.kmap, other.kmap))
-
-    def __hash__(self):
-        return self._hashval
-
-    def __str__(self):
-        if self.kmap is not None:
-            return "%s{%s}" % (self.__class__.__name__, str(self.kmap))
-        return self.__class__.__name__
+        if kmap is not None:
+            raise Exception("Do not use kmap, it is removed")
+        # should view the other inputs too, but viewing multiple
+        # inputs is not currently supported by the destroyhandler
+        self.view_map = {0: [0]}
 
     def make_node(self, data, indices, indptr, shape):
         data = tensor.as_tensor_variable(data)
@@ -726,18 +675,14 @@ def perform(self, node, inputs, outputs):
         # for efficiency, if remap does nothing, then do not apply it
         (data, indices, indptr, shape) = inputs
         (out,) = outputs
-        if self.kmap is not None:
-            data = data[self.kmap]
 
         if len(shape) != 2:
             raise ValueError('Shape should be an array of length 2')
-        if (data.shape != indices.shape and numpy.size(data) !=
-                numpy.size(self.kmap)):
+        if data.shape != indices.shape:
             errmsg = ('Data (shape ' + repr(data.shape) +
                       ' must have the same number of elements ' +
                       'as indices (shape' + repr(indices.shape) +
-                      ') or elements as kmap (' +
-                      repr(numpy.size(self.kmap)) + ')')
+                      ')')
             raise ValueError(errmsg)
         if self.format == 'csc':
             out[0] = scipy.sparse.csc_matrix((data, indices.copy(),
@@ -757,17 +702,13 @@ def grad(self, inputs, gout):
         (g_out,) = gout
         g_data, g_indices, g_indptr, g_shape = csm_properties(g_out)
         # unpack the data vector and wrap it as a 1d TensorType
-        g_data = csm_grad(self.kmap)(x_data, x_indices, x_indptr, x_shape,
-                                     g_data, g_indices, g_indptr, g_shape)
+        g_data = csm_grad()(x_data, x_indices, x_indptr, x_shape,
+                            g_data, g_indices, g_indptr, g_shape)
         return [g_data, DisconnectedType()(), DisconnectedType()(), DisconnectedType()()]
 
     def infer_shape(self, node, shapes):
-        if self.kmap is None:
-            # node.inputs[3] is of lenght as we only support sparse matrix.
-            return [(node.inputs[3][0], node.inputs[3][1])]
-        else:
-            raise theano.tensor.basic.ShapeError("case not implemented")
-
+        # node.inputs[3] is of lenght as we only support sparse matrix.
+        return [(node.inputs[3][0], node.inputs[3][1])]
 
 CSC = CSM('csc')
 """
@@ -844,25 +785,16 @@ class CSMGrad(gof.op.Op):
     # 2. The elements in the sparse dimension are not guaranteed to be sorted.
     # Therefore, the input data vector may have a different order than the
     # gradient data vector.
+    __props__ = ()
 
     def __init__(self, kmap=None):
-        self.kmap = kmap
+        if kmap is not None:
+            raise Exception("Do not use kmap, it is removed")
         # This class always allocate a new output.
         # I keep this here to help GD understand what this kmap think is.
         # if self.kmap is None:
         #    self.view_map = {0: [1]}
 
-    def __eq__(self, other):
-        return type(self) == type(other) and _kmap_eq(self.kmap, other.kmap)
-
-    def __hash__(self):
-        return 82345 ^ hash(type(self)) ^ _kmap_hash(self.kmap)
-
-    def __str__(self):
-        return "%s{%s}" % (
-            self.__class__.__name__,
-            self.kmap)
-
     def make_node(self, x_data, x_indices, x_indptr, x_shape,
                   g_data, g_indices, g_indptr, g_shape):
         gout_data = g_data.type()
@@ -891,18 +823,11 @@ def perform(self, node, inputs, outputs):
             for j_ptr in range(g_indptr[i], g_indptr[i + 1]):
                 g_row[g_indices[j_ptr]] = 0
 
-        if self.kmap is None:
-            g_out[0] = gout_data
-        else:
-            grad = numpy.zeros_like(x_data)
-            grad[self.kmap] = gout_data
-            g_out[0] = grad
+        g_out[0] = gout_data
 
     def infer_shape(self, node, shapes):
-        if self.kmap is None:
-            return [shapes[1]]
-        else:
-            return [shapes[0]]
+        return [shapes[1]]
+
 csm_grad = CSMGrad
 
 
diff --git a/theano/sparse/sandbox/sp.py b/theano/sparse/sandbox/sp.py
--- a/theano/sparse/sandbox/sp.py
+++ b/theano/sparse/sandbox/sp.py
@@ -43,12 +43,6 @@ class ConvolutionIndices(Op):
     """
     __props__ = ()
 
-    @staticmethod
-    def sparse_eval(inshp, kshp, nkern, strides=(1, 1), mode='valid'):
-        (dx, dy) = strides
-        return convolution_indices.evaluate(inshp, kshp, (dx, dy),
-                                            nkern, mode=mode, ws=False)
-
     @staticmethod
     def conv_eval(inshp, kshp, strides=(1, 1), mode='valid'):
         (dx, dy) = strides
@@ -73,7 +67,7 @@ def evaluate(inshp, kshp, strides=(1, 1), nkern=1, mode='valid', ws=True):
         :param mode: 'valid' generates output only when kernel and
                      image overlap overlap fully. Convolution obtained
                      by zero-padding the input
-        :param ws: True if weight sharing, false otherwise
+        :param ws: must be always True
         :param (dx,dy): offset parameter. In the case of no weight sharing,
                         gives the pixel offset between two receptive fields.
                         With weight sharing gives the offset between the
@@ -83,6 +77,9 @@ def evaluate(inshp, kshp, strides=(1, 1), nkern=1, mode='valid', ws=True):
         :returns: the structure of a sparse matrix, and the logical dimensions
                   of the image which will be the result of filtering.
         """
+        if not ws:
+            raise Exception("ws is obsolete and it must be always True")
+
         (dx, dy) = strides
         N = numpy
 
@@ -267,75 +264,6 @@ def perform(self, node, inputs, outputs):
 convolution_indices = ConvolutionIndices()
 
 
-def applySparseFilter(kerns, kshp, nkern, images, imgshp,
-                      step=(1, 1), bias=None, mode='valid'):
-    """
-    "images" is assumed to be a matrix of shape batch_size x img_size,
-    where the second dimension represents each image in raster order
-
-    Output feature map will have shape:
-
-    .. code-block:: python
-
-       batch_size x number of kernels * output_size
-
-    .. note::
-
-        IMPORTANT: note that this means that each feature map is
-        contiguous in memory.
-
-        The memory layout will therefore be:
-        [ <feature_map_0> <feature_map_1> ... <feature_map_n>],
-        where <feature_map> represents a "feature map" in raster order
-
-    Note that the concept of feature map doesn't really apply to
-    sparse filters without weight sharing. Basically, nkern=1 will
-    generate one output img/feature map, nkern=2 a second feature map,
-    etc.
-
-    kerns is a 1D tensor, and assume to be of shape:
-
-    .. code-block:: python
-
-       nkern * N.prod(outshp) x N.prod(kshp)
-
-    Each filter is applied seperately to consecutive output pixels.
-
-    :param kerns: nkern*outsize*ksize vector containing kernels
-    :param kshp: tuple containing actual dimensions of kernel (not symbolic)
-    :param nkern: number of kernels to apply at each pixel in the
-                  input image.  nkern=1 will apply a single unique
-                  filter for each input pixel.
-    :param images: bsize x imgsize matrix containing images on which
-                   to apply filters
-    :param imgshp: tuple containing actual image dimensions (not symbolic)
-    :param step: determines number of pixels between adjacent receptive fields
-                 (tuple containing dx,dy values)
-    :param mode: 'full', 'valid' see CSM.evaluate function for details
-    :return: out1, symbolic result
-    :return: out2, logical shape of the output img (nkern,height,width)
-             (after dot product, not of the sparse matrix!)
-    """
-
-    # inshp contains either 2 entries (height,width) or 3 (nfeatures,h,w)
-    # in the first case, default nfeatures to 1
-    if numpy.size(imgshp) == 2:
-        imgshp = (1,) + imgshp
-
-    # construct indices and index pointers for sparse matrix
-    indices, indptr, spmat_shape, sptype, outshp, kmap = \
-        convolution_indices.sparse_eval(imgshp, kshp, nkern, step, mode)
-
-    # build a sparse weight matrix
-    sparsew = theano.sparse.CSM(sptype, kmap)(kerns, indices,
-                                              indptr, spmat_shape)
-    output = sparse.structured_dot(sparsew, images.T).T
-    if bias is not None:
-        output += bias
-
-    return output, numpy.hstack((nkern, outshp))
-
-
 def convolve(kerns, kshp, nkern, images, imgshp, step=(1, 1), bias=None,
              mode='valid', flatten=True):
     """Convolution implementation by sparse matrix multiplication.


Test patch:
diff --git a/theano/sparse/sandbox/test_sp.py b/theano/sparse/sandbox/test_sp.py
--- a/theano/sparse/sandbox/test_sp.py
+++ b/theano/sparse/sandbox/test_sp.py
@@ -130,133 +130,6 @@ def test_convolution(self):
 
         # profmode.print_summary()
 
-    @attr('slow')
-    def test_sparse(self):
-
-#        print '\n\n*************************************************'
-#        print '           TEST SPARSE'
-#        print '*************************************************'
-
-        # fixed parameters
-        bsize = 10     # batch size
-        imshp = (8, 8)
-        kshp = (5, 5)
-        nkern = 1  # per output pixel
-        ssizes = ((1, 1), (2, 2))
-        convmodes = ('full', 'valid',)
-
-        # symbolic stuff
-        bias = tensor.dvector()
-        kerns = tensor.dvector()
-        input = tensor.dmatrix()
-        rng = numpy.random.RandomState(3423489)
-
-        import theano.gof as gof
-
-        for mode in (None,):
-            ntot, ttot = 0, 0
-            for conv_mode in convmodes:
-                for ss in ssizes:
-
-                    output, outshp = sp.applySparseFilter(kerns, kshp,\
-                            nkern, input, imshp, ss, bias=bias, mode=conv_mode)
-                    f = function([kerns, bias, input], output, mode=mode)
-
-                    # build actual input images
-                    img2d = numpy.arange(bsize*numpy.prod(imshp)).reshape((bsize,)+imshp)
-                    img1d = img2d.reshape(bsize, -1)
-                    zeropad_img = numpy.zeros((bsize,\
-                                           img2d.shape[1]+2*(kshp[0]-1),\
-                                           img2d.shape[2]+2*(kshp[1]-1)))
-                    zeropad_img[:, kshp[0]-1:kshp[0]-1+img2d.shape[1],
-                                   kshp[1]-1:kshp[1]-1+img2d.shape[2]] = img2d
-
-                    # build kernel matrix -- flatten it for theano stuff
-                    filters = numpy.arange(numpy.prod(outshp)*numpy.prod(kshp)).\
-                                reshape(nkern, numpy.prod(outshp[1:]), numpy.prod(kshp))
-                    spfilt = filters.flatten()
-                    biasvals = numpy.arange(numpy.prod(outshp))
-
-                    # compute output by hand
-                    ntime1 = time.time()
-                    refout = numpy.zeros((bsize, nkern, outshp[1], outshp[2]))
-                    patch = numpy.zeros((kshp[0], kshp[1]))
-                    for b in xrange(bsize):
-                        for k in xrange(nkern):
-                            pixi = 0  # pixel index in raster order
-                            for j in xrange(outshp[1]):
-                                for i in xrange(outshp[2]):
-                                    n = j * ss[0]
-                                    m = i * ss[1]
-                                    patch = zeropad_img[b, n:n+kshp[0], m:m+kshp[1]]
-                                    refout[b, k, j, i] = numpy.dot(filters[k, pixi, :],\
-                                                            patch.flatten())
-                                    pixi += 1
-                    refout = refout.reshape(bsize, -1) + biasvals
-                    ntot += time.time() - ntime1
-
-                    # need to flatten images
-                    ttime1 = time.time()
-                    out1 = f(spfilt, biasvals, img1d)
-                    ttot += time.time() - ttime1
-
-                    temp = refout - out1
-                    assert (temp < 1e-10).all()
-
-                    # test downward propagation
-                    vis = tensor.grad(0.5*tensor.sqr(output).sum(), input)
-                    downprop = function([kerns, output], vis)
-                    temp1 = time.time()
-                    for zz in range(100):
-                        visval = downprop(spfilt, out1)
-                    indices, indptr, spmat_shape, sptype, outshp, kmap = \
-                            sp.convolution_indices.sparse_eval(imshp, kshp, nkern, ss, conv_mode)
-                    spmat = sparse.csc_matrix((spfilt[kmap], indices, indptr), spmat_shape)
-                    visref = numpy.dot(out1, spmat.todense())
-                    assert numpy.all(visref == visval), (visref, visval)
-
-#            print '**** Sparse Profiling Results (',mode,') ****'
-#            print 'Numpy processing time: ', ntot
-#            print 'Theano processing time: ', ttot
-        # profmode.print_summary()
-
-    @attr('slow')
-    def test_multilayer_sparse(self):
-        # fixed parameters
-        bsize = 10     # batch size
-        imshp = (5, 5)
-        kshp = ((3, 3), (2, 2))
-        nkerns = (10, 20)  # per output pixel
-        ssizes = ((1, 1), (2, 2))
-        convmodes = ('full', 'valid',)
-
-        # symbolic stuff
-        kerns = [tensor.dvector(), tensor.dvector()]
-        input = tensor.dmatrix()
-        rng = numpy.random.RandomState(3423489)
-
-        # build actual input images
-        img2d = numpy.arange(bsize*numpy.prod(imshp)).reshape((bsize,)+imshp)
-        img1d = img2d.reshape(bsize, -1)
-
-        for mode in ('FAST_COMPILE', 'FAST_RUN'):
-            for conv_mode in convmodes:
-                for ss in ssizes:
-
-                    l1hid, l1outshp = sp.applySparseFilter(kerns[0], kshp[0],\
-                            nkerns[0], input, imshp, ss, mode=conv_mode)
-                    l2hid, l2outshp = sp.applySparseFilter(kerns[1], kshp[1],\
-                            nkerns[1], l1hid, l1outshp, ss, mode=conv_mode)
-
-                    l1propup = function([kerns[0], input], l1hid, mode=mode)
-                    l2propup = function([kerns[1], l1hid], l2hid, mode=mode)
-
-                    # actual values
-                    l1kernvals = numpy.arange(numpy.prod(l1outshp)*numpy.prod(kshp[0]))
-                    l2kernvals = numpy.arange(numpy.prod(l2outshp)*numpy.prod(kshp[1])*nkerns[0])
-                    l1hidval = l1propup(l1kernvals, img1d)
-                    l2hidval = l2propup(l2kernvals, l1hidval)
-
     # this doesn't compare the output of anything... but I manually verified that the patches
     # are properly generated
     def test_multilayer_conv(self):
@@ -335,35 +208,6 @@ def mp(input):
                 return output
             utt.verify_grad(mp, [imval.reshape(imval.shape[0], -1)])
 
-    def test_CSMGrad(self):
-        imshp = (3, 3)
-        nkern = 1  # per output pixel
-        kshp = (2, 2)
-        #ssizes = ((1,1),(2,2))
-        ssizes = ((1, 1),)
-        #convmodes = ('full','valid',)
-        convmodes = ('full',)
-
-        kerns = tensor.dvector()
-        indices = tensor.ivector()
-        indptr = tensor.ivector()
-        spmat_shape = tensor.ivector()
-
-        for mode in ['FAST_COMPILE', 'FAST_RUN']:
-            for conv_mode in convmodes:
-                for ss in ssizes:
-                    indvals, indptrvals, spshapevals, sptype, outshp, kmap = \
-                            sp.convolution_indices.sparse_eval(imshp, kshp, nkern, ss, conv_mode)
-                    kvals = numpy.random.random(nkern*numpy.prod(kshp)*numpy.prod(outshp)).flatten()
-
-                    def d(kerns):
-                        return theano.sparse.dense_from_sparse(
-                                theano.sparse.CSM(sptype, kmap)(
-                                    kerns, indvals, indptrvals, spshapevals))
-
-                    # symbolic stuff
-                    utt.verify_grad(d, [kvals])
-
 
 if __name__ == '__main__':
     if 0: