Repository: keras-team/keras

Issue:
border_mode="same" in AveragePooling2D with Theano
This currently raises a `NotImplementedError`. It looks to me that it would be fairly straightforward to implement. Are there any more complex issues that aren't obvious?

It looks like I can just make the following change to `pool2d` in `theano_backend.py`:

``` python
    if border_mode == 'same':
        ignore_border = True
        padding = (pool_size[0] - 2, pool_size[1] - 2)
    elif border_mode == 'valid':
        ignore_border = True
        padding = (0, 0)
    else:
        raise Exception('Invalid border mode: ' + str(border_mode))
    ...
```

I assume that if it was this easy, someone would have done it already, though.



Discussion:

Resolving patch:
diff --git a/keras/backend/theano_backend.py b/keras/backend/theano_backend.py
--- a/keras/backend/theano_backend.py
+++ b/keras/backend/theano_backend.py
@@ -650,10 +650,10 @@ def conv2d(x, kernel, strides=(1, 1), border_mode='valid', dim_ordering='th',
 def pool2d(x, pool_size, strides=(1, 1), border_mode='valid',
            dim_ordering='th', pool_mode='max'):
     if border_mode == 'same':
-        # TODO: add implementation for border_mode="same"
-        raise Exception('border_mode="same" not supported with Theano.')
+        w_pad = pool_size[0] - 2 if pool_size[0] % 2 == 1 else pool_size[0] - 1
+        h_pad = pool_size[1] - 2 if pool_size[1] % 2 == 1 else pool_size[1] - 1
+        padding = (w_pad, h_pad)
     elif border_mode == 'valid':
-        ignore_border = True
         padding = (0, 0)
     else:
         raise Exception('Invalid border mode: ' + str(border_mode))
@@ -666,17 +666,25 @@ def pool2d(x, pool_size, strides=(1, 1), border_mode='valid',
 
     if pool_mode == 'max':
         pool_out = downsample.max_pool_2d(x, ds=pool_size, st=strides,
-                                          ignore_border=ignore_border,
+                                          ignore_border=True,
                                           padding=padding,
                                           mode='max')
     elif pool_mode == 'avg':
         pool_out = downsample.max_pool_2d(x, ds=pool_size, st=strides,
-                                          ignore_border=ignore_border,
+                                          ignore_border=True,
                                           padding=padding,
                                           mode='average_exc_pad')
     else:
         raise Exception('Invalid pooling mode: ' + str(pool_mode))
 
+    if border_mode == 'same':
+        expected_width = (x.shape[2] + strides[0] - 1) // strides[0]
+        expected_height = (x.shape[3] + strides[1] - 1) // strides[1]
+
+        pool_out = pool_out[:, :,
+                            : expected_width,
+                            : expected_height]
+
     if dim_ordering == 'tf':
         pool_out = pool_out.dimshuffle((0, 2, 3, 1))
     return pool_out


Test patch:
diff --git a/tests/keras/layers/test_convolutional.py b/tests/keras/layers/test_convolutional.py
--- a/tests/keras/layers/test_convolutional.py
+++ b/tests/keras/layers/test_convolutional.py
@@ -191,17 +191,20 @@ def test_averagepooling_2d():
     stack_size = 7
     input_nb_row = 11
     input_nb_col = 12
-    pool_size = (3, 3)
 
     input = np.ones((nb_samples, stack_size, input_nb_row, input_nb_col))
-    for strides in [(1, 1), (2, 2)]:
-        layer = convolutional.AveragePooling2D(strides=strides,
-                                               border_mode='valid',
-                                               pool_size=pool_size)
-        layer.input = K.variable(input)
-        for train in [True, False]:
-            K.eval(layer.get_output(train))
-        layer.get_config()
+    for border_mode in ['valid', 'same']:
+        for pool_size in [(2, 2), (3, 3), (4, 4), (5, 5)]:
+            for strides in [(1, 1), (2, 2)]:
+                layer = convolutional.AveragePooling2D(strides=strides,
+                                                       border_mode=border_mode,
+                                                       pool_size=pool_size)
+                layer.input = K.variable(input)
+                for train in [True, False]:
+                    out = K.eval(layer.get_output(train))
+                    if border_mode == 'same' and strides == (1, 1):
+                        assert input.shape == out.shape
+                layer.get_config()
 
 
 def test_zero_padding_2d():