Repository: Theano/Theano

Issue:
images2neibs with border_mode=half
Is there any way to perform `theano.tensor.nnet.neighbours.images2neibs` with the equivalent to `boreder_mad=half` of the standard 2D Convolution, without explicitly construing the padded image tensor?


Discussion:
We need someone to implement it. Currently it isn't.
Do you know who worked on the `Images2Neibs` op both in the nnet and gpuarray, as the C does not look too pretty and there is some interaction with PyObjects in C, but on the other hand the change should not be particularly difficult? I might be able to implement it, but I would need a pointer of which part of the code should correspond to the padding.
I forgot so worked on it. The GPU array version is just a port of the old
backend version. To know who worked on it, check git history on the files.

Le jeu. 11 mai 2017 19:20, Alexander Botev <notifications@github.com> a
écrit :

> Do you know who worked on the Images2Neibs op both in the nnet and
> gpuarray, as the C does not look too pretty and there is some interaction
> with PyObjects in C, but on the other hand the change should not be
> particularly difficult?
>
> —
> You are receiving this because you commented.
>
>
> Reply to this email directly, view it on GitHub
> <https://github.com/Theano/Theano/issues/5938#issuecomment-300966211>, or mute
> the thread
> <https://github.com/notifications/unsubscribe-auth/AALC-79P_3b9tuVn7o_M7DRjvjozgdIeks5r48HPgaJpZM4NYWdw>
> .
>

@nouiz So I have a question, since I've managed to get on the CPU working implementation, but I'm not sure a bit about performance:

In the numpy version the output array is initialized to empty. This means that for padding I have to fill in the 0s. However, if it is initialized to 0s you can skip these iterations. Would it make sense to initialize it to 0 and would the overhead of filling it with zeros be more (or any intuition on this)?
Similarly, the same consensus on the CPU (C) version and for the GPU?

If the case is to stick with empty init, what is the numpy C API to set more than one entry to 0? E.g. since when you are out of the top/bottom you get the next `d` entries to be 0 rather than looping. Currently, I use `memset`, but I don't know if there are case where this might fail.

@nouiz Could you tell me how I can run only the tests_neighoburhood.py and could someone review the code briefly. I used the manual padding + 'valid' to verify the code - I think that is reasonable.
You can run the code manually with `theano-nose .../theano/tensor/nnet/tests/test_neighbours.py` (or `nosetests` instead of `theano-nose`).

Resolving patch:
diff --git a/theano/gpuarray/neighbours.py b/theano/gpuarray/neighbours.py
--- a/theano/gpuarray/neighbours.py
+++ b/theano/gpuarray/neighbours.py
@@ -23,17 +23,20 @@ class GpuImages2Neibs(GpuKernelBase, Images2Neibs, Op):
 
     """
     def __init__(self, mode='valid'):
-        if mode not in ['valid', 'ignore_borders', 'wrap_centered']:
-            raise NotImplementedError("Only the mode valid, ignore_borders"
-                                      " and wrap_centered"
-                                      " have been implemented for the op"
-                                      " GpuImages2Neibs")
+        if mode not in ['valid', 'half', 'full',
+                        'ignore_borders', 'wrap_centered']:
+            raise NotImplementedError("Only the mode valid, half, full, "
+                                      "ignore_borders and wrap_centered have "
+                                      "been implemented for GpuImages2Neibs")
         self.mode = mode
 
-    def make_node(self, ten4, neib_shape, neib_step):
+    def make_node(self, ten4, neib_shape, neib_step=None):
         ten4 = as_gpuarray_variable(ten4, infer_context_name(ten4))
         neib_shape = T.as_tensor_variable(neib_shape)
-        neib_step = T.as_tensor_variable(neib_step)
+        if neib_step is None:
+            neib_step = neib_shape
+        else:
+            neib_step = T.as_tensor_variable(neib_step)
 
         assert ten4.ndim == 4
         assert neib_shape.ndim == 1
@@ -50,7 +53,7 @@ def get_params(self, node):
         return node.inputs[0].type.context
 
     def c_code_cache_version(self):
-        return (11,)
+        return (12,)
 
     def c_headers(self):
         return ['<numpy_compat.h>', '<gpuarray/types.h>']
@@ -85,8 +88,8 @@ def gpu_kernels(self, node, nodename):
             GLOBAL_MEM %(type_z)s * global_out, const ga_size offset_out
         )
         {
-            const ga_int wrap_centered_idx_shift_x = c/2;
-            const ga_int wrap_centered_idx_shift_y = d/2;
+            const ga_int wrap_centered_half_idx_shift_x = c/2;
+            const ga_int wrap_centered_half_idx_shift_y = d/2;
             global_ten4 = (GLOBAL_MEM const %(type_ten4)s *)(((GLOBAL_MEM char *)global_ten4)+offset_ten4);
             global_out = (GLOBAL_MEM %(type_z)s *)(((GLOBAL_MEM char *)global_out)+offset_out);
 
@@ -111,31 +114,42 @@ def gpu_kernels(self, node, nodename):
                             {
                                 ga_int ten4_2 = i + a * step_x;
                                 if("%(mode)s"=="wrap_centered"){
-                                    ten4_2 -= wrap_centered_idx_shift_x;
+                                    ten4_2 -= wrap_centered_half_idx_shift_x;
                                     if ( ten4_2 < 0 )
                                         ten4_2 += height;
                                     else if (ten4_2 >= height)
                                         ten4_2 -= height;
+                                } else if ("%(mode)s"=="half"){
+                                    ten4_2 -= wrap_centered_half_idx_shift_x;
+                                } else if ("%(mode)s"=="full"){
+                                    ten4_2 -= c - 1;
                                 }
                                 ga_int j = LID_0;  // loop over d
                                 {
                                     ga_int ten4_3 = j + b * step_y;
                                     if("%(mode)s"=="wrap_centered"){
-                                        ten4_3 -= wrap_centered_idx_shift_y;
+                                        ten4_3 -= wrap_centered_half_idx_shift_y;
                                         if ( ten4_3 < 0 )
                                             ten4_3 += width;
                                         else if (ten4_3 >= width)
                                             ten4_3 -= width;
+                                    } else if ("%(mode)s"=="half"){
+                                        ten4_3 -= wrap_centered_half_idx_shift_y;
+                                    } else if ("%(mode)s"=="full"){
+                                        ten4_3 -= d - 1;
                                     }
 
-                                    ga_int ten4_idx = stride3*ten4_3 +
-                                                   stride2*ten4_2 +
-                                                   stride1*s + stride0*n;
-
                                     ga_int z_col = j + d * i;
                                     ga_int z_idx = z_col * out_s1 +
                                                 z_row * out_s0;
-                                    global_out[z_idx] = global_ten4[ten4_idx];
+                                    if(ten4_2 < 0 || ten4_2 >= height || ten4_3 < 0 || ten4_3 >= width){
+                                        global_out[z_idx] = 0;
+                                    } else {
+                                        ga_int ten4_idx = stride3*ten4_3 +
+                                                       stride2*ten4_2 +
+                                                       stride1*s + stride0*n;
+                                        global_out[z_idx] = global_ten4[ten4_idx];
+                                    }
                                 }
                             }
             }
@@ -172,8 +186,8 @@ def gpu_kernels(self, node, nodename):
             GLOBAL_MEM %(type_z)s * global_out, const ga_size offset_out
         )
         {
-            const ga_int wrap_centered_idx_shift_x = c/2;
-            const ga_int wrap_centered_idx_shift_y = d/2;
+            const ga_int wrap_centered_half_idx_shift_x = c/2;
+            const ga_int wrap_centered_half_idx_shift_y = d/2;
             global_ten4 = (GLOBAL_MEM const %(type_ten4)s *)(((GLOBAL_MEM char *)global_ten4)+offset_ten4);
             global_out = (GLOBAL_MEM %(type_z)s *)(((GLOBAL_MEM char *)global_out)+offset_out);
 
@@ -199,32 +213,43 @@ def gpu_kernels(self, node, nodename):
                             {
                                 ga_int ten4_2 = i + a * step_x;
                                 if("%(mode)s"=="wrap_centered"){
-                                    ten4_2 -= wrap_centered_idx_shift_x;
+                                    ten4_2 -= wrap_centered_half_idx_shift_x;
                                     if ( ten4_2 < 0 )
                                         ten4_2 += height;
                                     else if (ten4_2 >= height)
                                         ten4_2 -= height;
+                                } else if ("%(mode)s"=="half"){
+                                    ten4_2 -= wrap_centered_half_idx_shift_x;
+                                } else if ("%(mode)s"=="full"){
+                                    ten4_2 -= c - 1;
                                 }
                                 // loop over d
                                 for (ga_int j = LID_0; j < d; j+=LDIM_0)
                                 {
                                     ga_int ten4_3 = j + b * step_y;
                                     if("%(mode)s"=="wrap_centered"){
-                                        ten4_3 -= wrap_centered_idx_shift_y;
+                                        ten4_3 -= wrap_centered_half_idx_shift_y;
                                         if ( ten4_3 < 0 )
                                             ten4_3 += width;
                                         else if (ten4_3 >= width)
                                             ten4_3 -= width;
+                                    } else if ("%(mode)s"=="half"){
+                                        ten4_3 -= wrap_centered_half_idx_shift_y;
+                                    } else if ("%(mode)s"=="full"){
+                                        ten4_3 -= d - 1;
                                     }
 
-                                    ga_int ten4_idx = stride3*ten4_3 +
-                                                   stride2*ten4_2 +
-                                                   stride1*s + stride0*n;
-
                                     ga_int z_col = j + d * i;
                                     ga_int z_idx = z_col * out_s1 +
                                                 z_row * out_s0;
-                                    global_out[z_idx] = global_ten4[ten4_idx];
+                                    if(ten4_2 < 0 || ten4_2 >= height || ten4_3 < 0 || ten4_3 >= width){
+                                        global_out[z_idx] = 0;
+                                    } else {
+                                        ga_int ten4_idx = stride3*ten4_3 +
+                                                       stride2*ten4_2 +
+                                                       stride1*s + stride0*n;
+                                        global_out[z_idx] = global_ten4[ten4_idx];
+                                    }
                                 }
                             }
             }
@@ -313,6 +338,22 @@ def c_code(self, node, name, inp, out, sub):
             const npy_intp step_y = (npy_intp) *(npy_%(dtype_neib_step)s*)
                                          PyArray_GETPTR1(%(neib_step)s, 1);
 
+            if (step_x <=0 || step_y <=0)
+            {
+                PyErr_Format(PyExc_ValueError,
+                             "neib_step wrong step ; values <= 0. Got %%lld %%lld.",
+                             (long long) step_x, (long long) step_y);
+                %(fail)s;
+            }
+
+            if (c <=0 || d <=0)
+            {
+                PyErr_Format(PyExc_ValueError,
+                             "neib_shape values <= 0. Got %%lld %%lld.",
+                             (long long)c, (long long)d);
+                %(fail)s;
+            }
+
             if ( "%(mode)s" == "wrap_centered") {
                 if (c%%2!=1 || d%%2!=1){
                     PyErr_Format(PyExc_TypeError,
@@ -367,6 +408,56 @@ def c_code(self, node, name, inp, out, sub):
                 grid_c = 1+(((PyGpuArray_DIMS(%(ten4)s))[2]-c)/step_x);
                 //number of patch in width
                 grid_d = 1+(((PyGpuArray_DIMS(%(ten4)s))[3]-d)/step_y);
+            }else if ( "%(mode)s" == "half") {
+                if ( ((PyGpuArray_DIMS(%(ten4)s))[2] < c) ||
+                     ((((PyGpuArray_DIMS(%(ten4)s))[2]-(c%%2)) %% step_x)!=0))
+                {
+                    PyErr_Format(PyExc_TypeError, "GpuImages2Neibs:"
+                                 " neib_shape[0]=%%d, neib_step[0]=%%d and"
+                                 " ten4.shape[2]=%%d not consistent",
+                                 c, step_x,
+                                 PyGpuArray_DIMS(%(ten4)s)[2]);
+                    %(fail)s;
+                }
+                if ( ((PyGpuArray_DIMS(%(ten4)s))[3] < d) ||
+                     ((((PyGpuArray_DIMS(%(ten4)s))[3]-(d%%2)) %% step_y)!=0))
+                {
+                    PyErr_Format(PyExc_TypeError, "GpuImages2Neibs:"
+                                 " neib_shape[1]=%%d, neib_step[1]=%%d and"
+                                 " ten4.shape[3]=%%d not consistent",
+                                 d, step_y,
+                                 PyGpuArray_DIMS(%(ten4)s)[3]);
+                    %(fail)s;
+                }
+                //number of patch in height
+                grid_c = 1+(((PyGpuArray_DIMS(%(ten4)s))[2]-(c%%2))/step_x);
+                //number of patch in width
+                grid_d = 1+(((PyGpuArray_DIMS(%(ten4)s))[3]-(d%%2))/step_y);
+            }else if ( "%(mode)s" == "full") {
+                if ( ((PyGpuArray_DIMS(%(ten4)s))[2] < c) ||
+                 ( (((PyGpuArray_DIMS(%(ten4)s))[2]+c-2) %% step_x)!=0))
+                {
+                    PyErr_Format(PyExc_TypeError,
+                                 "neib_shape[0]=%%ld, neib_step[0]=%%ld and"
+                                 " ten4.shape[2]=%%ld not consistent",
+                                 (long int)c, (long int)step_x,
+                                 (long int)(PyGpuArray_DIMS(%(ten4)s)[2]));
+                    %(fail)s;
+                }
+                if ( ((PyGpuArray_DIMS(%(ten4)s))[3] < d) ||
+                     ( (((PyGpuArray_DIMS(%(ten4)s))[3]+d-2) %% step_y)!=0))
+                {
+                    PyErr_Format(PyExc_TypeError,
+                                 "neib_shape[1]=%%ld, neib_step[1]=%%ld and"
+                                 " ten4.shape[3]=%%ld not consistent",
+                                 (long int)d, (long int)step_y,
+                                 (long int)(PyGpuArray_DIMS(%(ten4)s)[3]));
+                    %(fail)s;
+                }
+                //number of patch in height
+                grid_c = 1+(((PyGpuArray_DIMS(%(ten4)s))[2]+c-2)/step_x);
+                //number of patch in width
+                grid_d = 1+(((PyGpuArray_DIMS(%(ten4)s))[3]+d-2)/step_y);
             }else{
                 PyErr_Format(PyExc_TypeError,
                              "GpuImages2Neibs:: unknown mode '%(mode)s'");
@@ -485,5 +576,5 @@ def perform(self, node, inp, out, ctx):
 @op_lifter([Images2Neibs])
 @register_opt2([Images2Neibs], 'fast_compile')
 def local_gpua_images2neibs(op, context_name, inputs, outputs):
-    if op.mode in ['valid', 'ignore_borders', 'wrap_centered']:
+    if op.mode in ['valid', 'half', 'full', 'ignore_borders', 'wrap_centered']:
         return GpuImages2Neibs(op.mode)
diff --git a/theano/tensor/nnet/neighbours.py b/theano/tensor/nnet/neighbours.py
--- a/theano/tensor/nnet/neighbours.py
+++ b/theano/tensor/nnet/neighbours.py
@@ -24,6 +24,12 @@ class Images2Neibs(Op):
         - 'valid' :
             Requires an input that is a multiple of the pooling factor
             (in each direction).
+        - 'half' :
+            Equivalent to 'valid' if we pre-pad with zeros the input on
+            each side by (neib_shape[0]//2, neib_shape[1]//2)
+        - 'full' :
+            Equivalent to 'valid' if we pre-pad with zeros the input on
+            each side by (neib_shape[0] - 1, neib_shape[1] - 1)
         - 'ignore_borders' :
             Same as valid, but will ignore the borders if the shape(s)
             of the input is not a multiple of the pooling factor(s).
@@ -35,10 +41,11 @@ class Images2Neibs(Op):
     __props__ = ("mode",)
 
     def __init__(self, mode='valid'):
-        if mode not in ['valid', 'wrap_centered', 'ignore_borders']:
-            raise NotImplementedError("Only the mode valid, ignore_borders"
-                                      " and wrap_centered have been"
-                                      " implemented for the op Images2Neibs")
+        if mode not in ['valid', 'half', 'full',
+                        'wrap_centered', 'ignore_borders']:
+            raise NotImplementedError("Only the mode valid, half, full, "
+                                      "ignore_borders and wrap_centered have "
+                                      "been implemented for Images2Neibs")
         self.mode = mode
 
     def __str__(self):
@@ -152,7 +159,7 @@ def pos2map(pidx, pgz, prior_result, neib_shape, neib_step):
                 grad_undefined(self, 2, neib_step)]
 
     def c_code_cache_version(self):
-        return (7,)
+        return (8,)
 
     def perform(self, node, inp, out_):
         ten4, neib_shape, neib_step = inp
@@ -198,7 +205,6 @@ def CEIL_INTDIV(a, b):
                     (c, d, ten4.shape[2], ten4.shape[3]))
             grid_c = CEIL_INTDIV(ten4.shape[2], step_x)
             grid_d = CEIL_INTDIV(ten4.shape[3], step_y)
-
         elif mode == "valid":
             if (ten4.shape[2] < c) or (((ten4.shape[2] - c) % step_x) != 0):
                 raise TypeError(
@@ -219,9 +225,48 @@ def CEIL_INTDIV(a, b):
             grid_c = 1 + ((ten4.shape[2] - c) // step_x)
             # number of patch in width
             grid_d = 1 + ((ten4.shape[3] - d) // step_y)
+        elif mode == "half":
+            # This is equivalent to 'valid' with padding (c // 2, d // 2) on both sides
+            # Thus the expanded image will have size (h + 2 * (c // 2), w + 2 * (d // 2))
+            # Plugging these in the equation for 'valid' we get
+            # h + 2 * (c // 2) - c  = h - (c % 2)
+            # w + 2 * (d // 2) - c  = w - (d % 2)
+            if (ten4.shape[2] < c) or (((ten4.shape[2] - (c % 2)) % step_x) != 0):
+                raise TypeError(
+                    "neib_shape[0]=%d, neib_step[0]=%d and"
+                    " ten4.shape[2]=%d not consistent" %
+                    (c, step_x, ten4.shape[2]))
+            if (ten4.shape[3] < d) or (((ten4.shape[3] - (d % 2)) % step_y) != 0):
+                raise TypeError(
+                    "neib_shape[1]=%d, neib_step[1]=%d and"
+                    " ten4.shape[3]=%d not consistent" %
+                    (d, step_y, ten4.shape[3]))
+            # number of patch in height
+            grid_c = 1 + ((ten4.shape[2] - (c % 2)) // step_x)
+            # number of patch in width
+            grid_d = 1 + ((ten4.shape[3] - (d % 2)) // step_y)
+        elif mode == "full":
+            # This is equivalent to 'valid' with padding (c - 1, d - 1) on both sides
+            # Thus the expanded image will have size (h + 2 * (c - 1), w + 2 * (d - 1))
+            # Plugging these in the equation for 'valid' we get
+            # h + 2 * (c - 1) - c  = h + c - 2
+            # w + 2 * (d - 1) - c  = w + d - 2
+            if (ten4.shape[2] < c) or (((ten4.shape[2] + c - 2) % step_x) != 0):
+                raise TypeError(
+                    "neib_shape[0]=%d, neib_step[0]=%d and"
+                    " ten4.shape[2]=%d not consistent" %
+                    (c, step_x, ten4.shape[2]))
+            if (ten4.shape[3] < d) or (((ten4.shape[3] + d - 2) % step_y) != 0):
+                raise TypeError(
+                    "neib_shape[1]=%d, neib_step[1]=%d and"
+                    " ten4.shape[3]=%d not consistent" %
+                    (d, step_y, ten4.shape[3]))
+            # number of patch in height
+            grid_c = 1 + ((ten4.shape[2] + c - 2) // step_x)
+            # number of patch in width
+            grid_d = 1 + ((ten4.shape[3] + d - 2) // step_y)
         else:
             raise TypeError("Images2Neibs: unknow mode '%s'" % mode)
-
         z_dim0 = grid_c * grid_d * ten4.shape[1] * ten4.shape[0]
         z_dim1 = c * d
         z[0] = np.empty((z_dim0, z_dim1), dtype=node.outputs[0].dtype)
@@ -231,8 +276,8 @@ def CEIL_INTDIV(a, b):
         height = ten4.shape[2]
         width = ten4.shape[3]
 
-        wrap_centered_idx_shift_x = c // 2
-        wrap_centered_idx_shift_y = d // 2
+        wrap_centered_half_idx_shift_x = c // 2
+        wrap_centered_half_idx_shift_y = d // 2
         for n in range(nb_batch):
             for s in range(nb_stack):
                 # loop over the number of patch in height
@@ -243,22 +288,35 @@ def CEIL_INTDIV(a, b):
                         for i in range(c):
                             ten4_2 = i + a * step_x
                             if mode == "wrap_centered":
-                                ten4_2 -= wrap_centered_idx_shift_x
+                                ten4_2 -= wrap_centered_half_idx_shift_x
                                 if ten4_2 < 0:
                                     ten4_2 += height
                                 elif ten4_2 >= height:
                                     ten4_2 -= height
-                            for j in range(d):
-                                ten4_3 = j + b * step_y
-                                if mode == "wrap_centered":
-                                    ten4_3 -= wrap_centered_idx_shift_y
-                                    if ten4_3 < 0:
-                                        ten4_3 += width
-                                    elif ten4_3 >= width:
-                                        ten4_3 -= width
-                                z_col = j + d * i
-
-                                z[0][z_row, z_col] = ten4[n, s, ten4_2, ten4_3]
+                            elif mode == "half":
+                                ten4_2 -= wrap_centered_half_idx_shift_x
+                            elif mode == "full":
+                                ten4_2 -= c - 1
+                            if ten4_2 < 0 or ten4_2 >= height:
+                                z[0][z_row, d * i: d * i + d] = 0
+                            else:
+                                for j in range(d):
+                                    ten4_3 = j + b * step_y
+                                    if mode == "wrap_centered":
+                                        ten4_3 -= wrap_centered_half_idx_shift_y
+                                        if ten4_3 < 0:
+                                            ten4_3 += width
+                                        elif ten4_3 >= width:
+                                            ten4_3 -= width
+                                    elif mode == "half":
+                                        ten4_3 -= wrap_centered_half_idx_shift_y
+                                    elif mode == "full":
+                                        ten4_3 -= d - 1
+                                    z_col = j + d * i
+                                    if ten4_3 < 0 or ten4_3 >= width:
+                                        z[0][z_row, z_col] = 0
+                                    else:
+                                        z[0][z_row, z_col] = ten4[n, s, ten4_2, ten4_3]
 
     def infer_shape(self, node, input_shape):
         in_shape = input_shape[0]
@@ -273,6 +331,14 @@ def infer_shape(self, node, input_shape):
         elif self.mode == 'ignore_borders':
             grid_c = 1 + ((in_shape[2] - c) // step_x)
             grid_d = 1 + ((in_shape[3] - d) // step_y)
+        elif self.mode == 'half':
+            grid_c = 1 + ((in_shape[2] - (c % 2)) // step_x)
+            grid_d = 1 + ((in_shape[3] - (d % 2)) // step_y)
+        elif self.mode == 'full':
+            grid_c = 1 + ((in_shape[2] + c - 2) // step_x)
+            grid_d = 1 + ((in_shape[3] + d - 2) // step_y)
+        else:
+            raise TypeError("Images2Neibs: unknow mode '%s'" % self.mode)
         z_dim0 = grid_c * grid_d * in_shape[1] * in_shape[0]
         z_dim1 = c * d
         return [(z_dim0, z_dim1)]
@@ -394,7 +460,57 @@ def c_code(self, node, name, inp, out, sub):
             grid_c = 1+(((PyArray_DIMS(%(ten4)s))[2]-c)/step_x);
             //number of patch in width
             grid_d = 1+(((PyArray_DIMS(%(ten4)s))[3]-d)/step_y);
-        }else{
+        }else if ( "%(mode)s" == "half") {
+            if ( ((PyArray_DIMS(%(ten4)s))[2] < c) ||
+                 ( (((PyArray_DIMS(%(ten4)s))[2]-(c%%2)) %% step_x)!=0))
+            {
+                PyErr_Format(PyExc_TypeError,
+                             "neib_shape[0]=%%ld, neib_step[0]=%%ld and"
+                             " ten4.shape[2]=%%ld not consistent",
+                             (long int)c, (long int)step_x,
+                             (long int)(PyArray_DIMS(%(ten4)s)[2]));
+                %(fail)s;
+            }
+            if ( ((PyArray_DIMS(%(ten4)s))[3] < d) ||
+                 ( (((PyArray_DIMS(%(ten4)s))[3]-(d%%2)) %% step_y)!=0))
+            {
+                PyErr_Format(PyExc_TypeError,
+                             "neib_shape[1]=%%ld, neib_step[1]=%%ld and"
+                             " ten4.shape[3]=%%ld not consistent",
+                             (long int)d, (long int)step_y,
+                             (long int)(PyArray_DIMS(%(ten4)s)[3]));
+                %(fail)s;
+            }
+            //number of patch in height
+            grid_c = 1+(((PyArray_DIMS(%(ten4)s))[2]-(c%%2))/step_x);
+            //number of patch in width
+            grid_d = 1+(((PyArray_DIMS(%(ten4)s))[3]-(d%%2))/step_y);
+        }else if ( "%(mode)s" == "full") {
+            if ( ((PyArray_DIMS(%(ten4)s))[2] < c) ||
+                 ( (((PyArray_DIMS(%(ten4)s))[2]+c-2) %% step_x)!=0))
+            {
+                PyErr_Format(PyExc_TypeError,
+                             "neib_shape[0]=%%ld, neib_step[0]=%%ld and"
+                             " ten4.shape[2]=%%ld not consistent",
+                             (long int)c, (long int)step_x,
+                             (long int)(PyArray_DIMS(%(ten4)s)[2]));
+                %(fail)s;
+            }
+            if ( ((PyArray_DIMS(%(ten4)s))[3] < d) ||
+                 ( (((PyArray_DIMS(%(ten4)s))[3]+d-2) %% step_y)!=0))
+            {
+                PyErr_Format(PyExc_TypeError,
+                             "neib_shape[1]=%%ld, neib_step[1]=%%ld and"
+                             " ten4.shape[3]=%%ld not consistent",
+                             (long int)d, (long int)step_y,
+                             (long int)(PyArray_DIMS(%(ten4)s)[3]));
+                %(fail)s;
+            }
+            //number of patch in height
+            grid_c = 1+(((PyArray_DIMS(%(ten4)s))[2]+c-2)/step_x);
+            //number of patch in width
+            grid_d = 1+(((PyArray_DIMS(%(ten4)s))[3]+d-2)/step_y);
+        }else {
             PyErr_Format(PyExc_TypeError,
                          "Images2Neibs: unknow mode '%(mode)s'");
             %(fail)s;
@@ -444,8 +560,8 @@ def c_code(self, node, name, inp, out, sub):
         const npy_intp step_x = (npy_intp) *(dtype_%(neib_step)s*) PyArray_GETPTR1(%(neib_step)s, 0);
         const npy_intp step_y = (npy_intp) *(dtype_%(neib_step)s*) PyArray_GETPTR1(%(neib_step)s, 1);
 
-        const int wrap_centered_idx_shift_x = c/2;
-        const int wrap_centered_idx_shift_y = d/2;
+        const int wrap_centered_half_idx_shift_x = c/2;
+        const int wrap_centered_half_idx_shift_y = d/2;
         // Oh this is messed up...
         for (int n = 0; n < nb_batch; n++)              // loop over batches
             for (int s = 0; s < nb_stack; s++)          // loop over stacks
@@ -457,27 +573,38 @@ def c_code(self, node, name, inp, out, sub):
                         {
                             int ten4_2 = i + a * step_x;
                             if ( "%(mode)s" == "wrap_centered" ){
-                                ten4_2 -= wrap_centered_idx_shift_x;
+                                ten4_2 -= wrap_centered_half_idx_shift_x;
                                 if ( ten4_2 < 0 ) ten4_2 += height;
                                 else if (ten4_2 >= height) ten4_2 -= height;
+                            } else if ( "%(mode)s" == "half" ){
+                                ten4_2 -= wrap_centered_half_idx_shift_x;
+                            } else if ( "%(mode)s" == "full" ){
+                                ten4_2 -= c - 1;
                             }
-                            for (int j = 0; j < d; j++)  // loop over d
-                            {
-
-                                int ten4_3 = j + b * step_y;
-                                if ( "%(mode)s" == "wrap_centered" ){
-                                    ten4_3 -= wrap_centered_idx_shift_y;
-                                    if ( ten4_3 < 0 ) ten4_3 += width;
-                                    else if (ten4_3 >= width) ten4_3 -= width;
+                            if (ten4_2 < 0 | ten4_2 >= height) {
+                                dtype_%(z)s* curr_z = (dtype_%(z)s*) PyArray_GETPTR2(%(z)s, z_row, d * i);
+                                memset(curr_z, 0, d*sizeof(*curr_z));
+                            } else {
+                                for (int j = 0; j < d; j++)  // loop over d
+                                {
+                                    int ten4_3 = j + b * step_y;
+                                    if ( "%(mode)s" == "wrap_centered" ){
+                                        ten4_3 -= wrap_centered_half_idx_shift_y;
+                                        if ( ten4_3 < 0 ) ten4_3 += width;
+                                        else if (ten4_3 >= width) ten4_3 -= width;
+                                    } else if ( "%(mode)s" == "half" ){
+                                        ten4_3 -= wrap_centered_half_idx_shift_y;
+                                    } else if ( "%(mode)s" == "full" ){
+                                        ten4_3 -= d - 1;
+                                    }
+                                    int z_col = j + d * i;
+                                    dtype_%(z)s* curr_z = (dtype_%(z)s*) PyArray_GETPTR2(%(z)s, z_row, z_col);
+                                    if (ten4_3 < 0 | ten4_3 >= width) {
+                                        *curr_z = 0;
+                                    } else {
+                                        *curr_z = *( (dtype_%(ten4)s*) PyArray_GETPTR4(%(ten4)s, n, s, ten4_2, ten4_3));
+                                    }
                                 }
-                                int z_col = j + d * i;
-
-                                dtype_%(z)s* curr_z = (dtype_%(z)s*) PyArray_GETPTR2(%(z)s, z_row, z_col);
-                                *curr_z = *( (dtype_%(ten4)s*) PyArray_GETPTR4(%(ten4)s, n, s, ten4_2, ten4_3));
-
-                                //printf("\\n(%%i,%%i,%%i,%%i) --> (%%i,%%i)",
-                                //       n, s, ten4_2, ten4_3, z_row, z_col);
-                                //printf("%%f ", *curr_z);
                             }
                         }
                     }
@@ -513,10 +640,16 @@ def images2neibs(ten4, neib_shape, neib_step=None, mode='valid'):
         By default it is equal to `neib_shape` in other words, the patches are
         disjoint. When the step is greater than `neib_shape`, some elements are
         omitted. When None, this is the same as neib_shape (patch are disjoint).
-    mode : {'valid', 'ignore_borders', 'wrap_centered'}
+    mode : {'valid', 'ignore_borders', 'wrap_centered', 'half'}
         ``valid``
             Requires an input that is a multiple of the
             pooling factor (in each direction).
+        ``half``
+            Equivalent to 'valid' if we pre-pad with zeros the input on
+            each side by (neib_shape[0]//2, neib_shape[1]//2)
+        ``full``
+            Equivalent to 'valid' if we pre-pad with zeros the input on
+            each side by (neib_shape[0] - 1, neib_shape[1] - 1)
         ``ignore_borders``
             Same as valid, but will ignore the borders if the shape(s) of
             the input is not a multiple of the pooling factor(s).


Test patch:
diff --git a/theano/tensor/nnet/tests/test_neighbours.py b/theano/tensor/nnet/tests/test_neighbours.py
--- a/theano/tensor/nnet/tests/test_neighbours.py
+++ b/theano/tensor/nnet/tests/test_neighbours.py
@@ -94,7 +94,7 @@ def test_neibs_manual_step(self):
         shape = (2, 3, 5, 5)
         for dtype in self.dtypes:
             images = shared(np.asarray(np.arange(np.prod(
-                            shape)).reshape(shape), dtype=dtype))
+                shape)).reshape(shape), dtype=dtype))
             neib_shape = T.as_tensor_variable((3, 3))
             neib_step = T.as_tensor_variable((2, 2))
             for border in ['valid', 'ignore_borders']:
@@ -213,7 +213,7 @@ def test_neibs_wrap_centered_step_manual(self):
             for dtype in self.dtypes:
 
                 images = shared(np.asarray(np.arange(np.prod(
-                                shape)).reshape(shape), dtype=dtype))
+                    shape)).reshape(shape), dtype=dtype))
                 neib_shape = T.as_tensor_variable(neib_shape)
                 neib_step = T.as_tensor_variable(neib_step)
                 expected = np.asarray(expected)
@@ -236,13 +236,64 @@ def test_neibs_wrap_centered_step_manual(self):
                 # TODO: why this is commented?
                 # assert numpy.allclose(images.get_value(borrow=True), g())
 
+    def test_neibs_half_step_by_valid(self):
+        neib_shapes = ((3, 3), (3, 5), (5, 3))
+        for shp_idx, (shape, neib_step) in enumerate([
+            [(7, 8, 5, 5), (1, 1)],
+            [(7, 8, 5, 5), (2, 2)],
+            [(7, 8, 5, 5), (4, 4)],
+            [(7, 8, 5, 5), (1, 4)],
+            [(7, 8, 5, 5), (4, 1)],
+            [(80, 90, 5, 5), (1, 2)],
+            [(1025, 9, 5, 5), (2, 1)],
+            [(1, 1, 5, 1037), (2, 4)],
+            [(1, 1, 1045, 5), (4, 2)]]
+        ):
+            for neib_shape in neib_shapes:
+                for dtype in self.dtypes:
+                    x = theano.shared(np.random.randn(*shape).astype(dtype))
+                    extra = (neib_shape[0] // 2, neib_shape[1] // 2)
+                    padded_shape = (x.shape[0], x.shape[1], x.shape[2] + 2 * extra[0], x.shape[3] + 2 * extra[1])
+                    padded_x = T.zeros(padded_shape)
+                    padded_x = T.set_subtensor(padded_x[:, :, extra[0]:-extra[0], extra[1]:-extra[1]], x)
+                    x_using_valid = images2neibs(padded_x, neib_shape, neib_step, mode="valid")
+                    x_using_half = images2neibs(x, neib_shape, neib_step, mode="half")
+                    close = T.allclose(x_using_valid, x_using_half)
+                    f = theano.function([], close, mode=self.mode)
+                    assert f()
+
+    def test_neibs_full_step_by_valid(self):
+        for shp_idx, (shape, neib_step, neib_shapes) in enumerate([
+            [(7, 8, 5, 5), (1, 1), ((3, 3), (3, 5), (5, 3))],
+            [(7, 8, 5, 5), (2, 2), ((3, 3), (3, 5), (5, 3))],
+            [(7, 8, 6, 6), (3, 3), ((2, 2), (2, 5), (5, 2))],
+            [(7, 8, 6, 6), (1, 3), ((2, 2), (2, 5), (5, 2))],
+            [(7, 8, 6, 6), (3, 1), ((2, 2), (2, 5), (5, 2))],
+            [(80, 90, 5, 5), (1, 2), ((3, 3), (3, 5), (5, 3))],
+            [(1025, 9, 5, 5), (2, 1), ((3, 3), (3, 5), (5, 3))],
+            [(1, 1, 11, 1037), (2, 3), ((3, 3), (5, 3))],
+            [(1, 1, 1043, 11), (3, 2), ((3, 3), (3, 5))]]
+        ):
+            for neib_shape in neib_shapes:
+                for dtype in self.dtypes:
+                    x = theano.shared(np.random.randn(*shape).astype(dtype))
+                    extra = (neib_shape[0] - 1, neib_shape[1] - 1)
+                    padded_shape = (x.shape[0], x.shape[1], x.shape[2] + 2 * extra[0], x.shape[3] + 2 * extra[1])
+                    padded_x = T.zeros(padded_shape)
+                    padded_x = T.set_subtensor(padded_x[:, :, extra[0]:-extra[0], extra[1]:-extra[1]], x)
+                    x_using_valid = images2neibs(padded_x, neib_shape, neib_step, mode="valid")
+                    x_using_full = images2neibs(x, neib_shape, neib_step, mode="full")
+                    close = T.allclose(x_using_valid, x_using_full)
+                    f = theano.function([], close, mode=self.mode)
+                    assert f()
+
     def test_neibs_bad_shape_wrap_centered(self):
         shape = (2, 3, 10, 10)
 
         for dtype in self.dtypes:
             images = shared(np.arange(
                 np.prod(shape), dtype=dtype
-                ).reshape(shape))
+            ).reshape(shape))
 
             for neib_shape in [(3, 2), (2, 3)]:
                 neib_shape = T.as_tensor_variable(neib_shape)
@@ -281,6 +332,28 @@ def fn(images):
         self.assertRaises(TypeError, unittest_tools.verify_grad,
                           fn, [images_val], mode=self.mode)
 
+    def test_grad_half(self):
+        # It is not implemented for now. So test that we raise an error.
+        shape = (2, 3, 6, 6)
+        images_val = np.random.rand(*shape).astype('float32')
+
+        def fn(images):
+            return images2neibs(images, (3, 3), mode='half')
+
+        self.assertRaises(TypeError, unittest_tools.verify_grad,
+                          fn, [images_val], mode=self.mode)
+
+    def test_grad_full(self):
+        # It is not implemented for now. So test that we raise an error.
+        shape = (2, 3, 6, 6)
+        images_val = np.random.rand(*shape).astype('float32')
+
+        def fn(images):
+            return images2neibs(images, (3, 3), mode='full')
+
+        self.assertRaises(TypeError, unittest_tools.verify_grad,
+                          fn, [images_val], mode=self.mode)
+
     def test_grad_valid(self):
         shape = (2, 3, 6, 6)
         images_val = np.random.rand(*shape).astype('float32')
@@ -330,15 +403,33 @@ def test_neibs_valid_with_inconsistent_borders(self):
         images_val = np.arange(np.prod(shape),
                                dtype='float32').reshape(shape)
 
-        def fn(images):
-            return T.sum(T.sqr(images2neibs(images, (2, 2), mode='valid')),
-                         axis=[0, 1])
-
         f = theano.function([images],
                             T.sqr(images2neibs(images, (2, 2), mode='valid')),
                             mode=self.mode)
         self.assertRaises(TypeError, f, images_val)
 
+    def test_neibs_half_with_inconsistent_borders(self):
+        shape = (2, 3, 5, 5)
+        images = T.dtensor4()
+        images_val = np.arange(np.prod(shape),
+                               dtype='float32').reshape(shape)
+
+        f = theano.function([images],
+                            T.sqr(images2neibs(images, (2, 2), mode='half')),
+                            mode=self.mode)
+        self.assertRaises(TypeError, f, images_val)
+
+    def test_neibs_full_with_inconsistent_borders(self):
+        shape = (2, 3, 5, 5)
+        images = T.dtensor4()
+        images_val = np.arange(np.prod(shape),
+                               dtype='float32').reshape(shape)
+
+        f = theano.function([images],
+                            T.sqr(images2neibs(images, (2, 2), mode='full')),
+                            mode=self.mode)
+        self.assertRaises(TypeError, f, images_val)
+
     def test_can_not_infer_nb_dim(self):
         # Was reported in gh-5613. Test that we do not crash
         # or that we crash in a few other case found while
@@ -346,7 +437,7 @@ def test_can_not_infer_nb_dim(self):
 
         img = T.tensor4('img')
         patches = T.nnet.neighbours.images2neibs(img, [16, 16])
-        extractPatches = theano.function([img], patches)
+        extractPatches = theano.function([img], patches, mode=self.mode)
 
         patsRecovery = T.matrix('patsRecovery')
         original_size = T.ivector('original_size')
@@ -354,7 +445,8 @@ def test_can_not_infer_nb_dim(self):
         for mode in ['valid', 'ignore_borders']:
             out = neibs2images(patsRecovery, (16, 16),
                                original_size, mode=mode)
-            f = theano.function([patsRecovery, original_size], out)
+            f = theano.function([patsRecovery, original_size], out,
+                                mode=self.mode)
 
             im_val = np.ones((1, 3, 320, 320), dtype=np.float32)
             neibs = extractPatches(im_val)
@@ -392,6 +484,32 @@ def speed_neibs_wrap_centered(self):
         for i in range(1000):
             f()
 
+    def speed_neibs_half(self):
+        shape = (100, 40, 18, 18)
+        images = shared(np.arange(np.prod(shape),
+                                  dtype='float32').reshape(shape))
+        neib_shape = T.as_tensor_variable((3, 3))
+
+        f = function([],
+                     images2neibs(images, neib_shape, mode="half"),
+                     mode=self.mode)
+
+        for i in range(1000):
+            f()
+
+    def speed_neibs_full(self):
+        shape = (100, 40, 18, 18)
+        images = shared(np.arange(np.prod(shape),
+                                  dtype='float32').reshape(shape))
+        neib_shape = T.as_tensor_variable((3, 3))
+
+        f = function([],
+                     images2neibs(images, neib_shape, mode="full"),
+                     mode=self.mode)
+
+        for i in range(1000):
+            f()
+
     def test_infer_shape(self):
         shape = (100, 40, 6, 3)
         images = np.ones(shape).astype('float32')
@@ -431,6 +549,24 @@ def test_infer_shape(self):
             [x], [images2neibs(
                 x, neib_shape=(3, 3), mode='wrap_centered')],
             [images], Images2Neibs)
+        shape = (100, 40, 6, 4)
+        images = np.ones(shape).astype('float32')
+        x = T.ftensor4()
+        self._compile_and_check(
+            [x], [images2neibs(x, neib_shape=(2, 1), mode='half')],
+            [images], Images2Neibs)
+        self._compile_and_check(
+            [x], [images2neibs(x, neib_shape=(2, 3), mode='half')],
+            [images], Images2Neibs)
+        shape = (100, 40, 6, 5)
+        images = np.ones(shape).astype('float32')
+        x = T.ftensor4()
+        self._compile_and_check(
+            [x], [images2neibs(x, neib_shape=(2, 1), mode='full')],
+            [images], Images2Neibs)
+        self._compile_and_check(
+            [x], [images2neibs(x, neib_shape=(2, 3), mode='full')],
+            [images], Images2Neibs)
 
 if __name__ == '__main__':
     unittest.main()