Permalink
Browse files

Fix conv2d with kernel 1x1 stride > 1 (#1868)

Fixes #889
  • Loading branch information...
ry authored and vrv committed Apr 15, 2016
1 parent bc75c6f commit 555c7f7c4108ae91601dce2e2dea1ad1158d5977
@@ -272,6 +272,7 @@ struct LaunchConvOp<GPUDevice, T> {
if (use_cudnn) {
Tensor input = input_param;
if (filter.dim_size(0) == 1 && filter.dim_size(1) == 1 &&
+ row_stride == 1 && col_stride == 1 &&
data_format == FORMAT_NHWC) {
// 1x1 filter, so call cublas directly.
const uint64 m =
@@ -405,6 +405,55 @@ TEST(EigenSpatialConvolutionsTest, StridedSpatialConvolution) {
}
}
+
+TEST(EigenSpatialConvolutionsTest, KernelSmallerThanStride) {
+ const int input_depth = 2;
+ const int input_rows = 3;
+ const int input_cols = 3;
+ const int num_batches = 5;
+ const int output_depth = 6;
+ const int patch_rows = 1;
+ const int patch_cols = 1;
+ const int output_rows = 2;
+ const int output_cols = 2;
+
+ Tensor<float, 4> input(input_depth, input_rows, input_cols, num_batches);
+ Tensor<float, 4> kernel(output_depth, input_depth, patch_rows, patch_cols);
+ Tensor<float, 4> result(output_depth, output_rows, output_cols, num_batches);
+ input = input.constant(11.0f) + input.random();
+ kernel = kernel.constant(2.0f) + kernel.random();
+ result.setRandom();
+
+ // Apply a spatial convolution using a 1x1 kernel, valid padding, and a stride
+ // of 2.
+ int stride = 2;
+ result = SpatialConvolution(input, kernel, stride, stride, PADDING_VALID);
+
+ EXPECT_EQ(result.dimension(0), output_depth);
+ EXPECT_EQ(result.dimension(1), output_rows);
+ EXPECT_EQ(result.dimension(2), output_cols);
+ EXPECT_EQ(result.dimension(3), num_batches);
+
+ for (int b = 0; b < num_batches; ++b) {
+ for (int od = 0; od < output_depth; ++od) {
+ for (int i = 0; i < output_rows; ++i) {
+ for (int j = 0; j < output_cols; ++j) {
+ float expected = 0.0f;
+ for (int c = 0; c < patch_cols; ++c) {
+ for (int r = 0; r < patch_rows; ++r) {
+ for (int id = 0; id < input_depth; ++id) {
+ expected += input(id, r + stride * i, c + stride * j, b) *
+ kernel(od, id, r, c);
+ }
+ }
+ }
+ EigenApprox(result(od, i, j, b), expected);
+ }
+ }
+ }
+ }
+}
+
TEST(EigenSpatialConvolutionsTest, StridedSpatialConvolutionRowMajor) {
const int input_depth = 10;
const int input_rows = 5;
@@ -38,11 +38,6 @@ Status Get2dOutputSizeVerbose(const int in_height, const int in_width,
int row_stride, int col_stride, Padding padding,
int* new_height, int* new_width, int* pad_top,
int* pad_bottom, int* pad_left, int* pad_right) {
- // Cannot have strides larger than the patch size.
- if (row_stride > filter_height || col_stride > filter_width) {
- return errors::InvalidArgument(
- "stride must be less than or equal to kernel size");
- }
switch (padding) {
case Padding::VALID:
*new_height = ceil((in_height - filter_height + 1.f) /
@@ -129,12 +129,6 @@ class OpsUtilTest : public ::testing::Test {
}
};
-// Test stride > ksize fails with INVALID_ARGUMENT.
-TEST_F(OpsUtilTest, Get2dOutputSizeInvalidTest) {
- padding_struct pad_struct = {{3, 3, 1, 2, 2, 2, SAME}, {3, 3, 1, 1, 1, 1}};
- VerifyGet2dOutputSizeBoundaries(pad_struct, error::INVALID_ARGUMENT);
-}
-
TEST_F(OpsUtilTest, Get2dOutputSizeNegativeSizeTest) {
padding_struct pad_struct = {{1, 1, 3, 3, 1, 1, VALID}, {-1, -1, 0, 0, 0, 0}};
VerifyGet2dOutputSizeBoundaries(pad_struct, error::INVALID_ARGUMENT);
@@ -319,6 +319,20 @@ def testConv2D2x2FilterStride1x2(self):
strides=[1, 2], padding="VALID",
expected=expected_output)
+ def testConv2DKernelSmallerThanStrideValid(self):
+ expected_output = [65, 95, 275, 305]
+ self._VerifyValues(tensor_in_sizes=[1, 7, 7, 1],
+ filter_in_sizes=[2, 2, 1, 1],
+ strides=[3, 3], padding="VALID",
+ expected=expected_output)
+
+ def testConv2DKernelSmallerThanStrideSame(self):
+ expected_output = [1, 3, 7, 9]
+ self._VerifyValues(tensor_in_sizes=[1, 3, 3, 1],
+ filter_in_sizes=[1, 1, 1, 1],
+ strides=[2, 2], padding="SAME",
+ expected=expected_output)
+
# Testing for backprops
def _RunAndVerifyBackpropInput(self, input_sizes, filter_sizes, output_sizes,
strides, padding, expected, data_format,
@@ -862,21 +876,6 @@ def testShapeFunctionEdgeCases(self):
shape=[21, 20, 3, 2]),
strides=[1, 1, 1, 1], padding="SAME")
- # Stride larger than filter.
- with self.assertRaisesRegexp(ValueError,
- "stride must be less than or equal to filter"):
- tf.nn.conv2d(tf.placeholder(tf.float32,
- shape=[32, 20, 20, 3]),
- tf.placeholder(tf.float32,
- shape=[4, 5, 3, 2]),
- strides=[1, 5, 5, 1], padding="SAME")
- with self.assertRaisesRegexp(ValueError,
- "stride must be less than or equal to filter"):
- tf.nn.conv2d(tf.placeholder(tf.float32,
- shape=[32, 20, 20, 3]),
- tf.placeholder(tf.float32,
- shape=[5, 4, 3, 2]),
- strides=[1, 5, 5, 1], padding="SAME")
# This is only a very simple test. More comprehensive tests live in
@@ -370,6 +370,33 @@ def testDepthwiseMaxPool2x2DepthWindow3(self):
expected=[3.0, 6.0, 9.0, 12.0, 15.0, 18.0, 21.0, 24.0],
use_gpu=False)
+ def testKernelSmallerThanStride(self):
+ for use_gpu in [True, False]:
+ self._VerifyValues(tf.nn.max_pool, input_sizes=[1, 3, 3, 1],
+ ksize=[1, 1, 1, 1], strides=[1, 2, 2, 1],
+ padding="SAME",
+ expected=[1, 3, 7, 9],
+ use_gpu=use_gpu)
+
+ self._VerifyValues(tf.nn.max_pool, input_sizes=[1, 7, 7, 1],
+ ksize=[1, 2, 2, 1], strides=[1, 3, 3, 1],
+ padding="VALID",
+ expected=[9, 12, 30, 33],
+ use_gpu=use_gpu)
+
+ self._VerifyValues(tf.nn.avg_pool, input_sizes=[1, 3, 3, 1],
+ ksize=[1, 1, 1, 1], strides=[1, 2, 2, 1],
+ padding="SAME",
+ expected=[1, 3, 7, 9],
+ use_gpu=use_gpu)
+
+ self._VerifyValues(tf.nn.avg_pool, input_sizes=[1, 7, 7, 1],
+ ksize=[1, 2, 2, 1], strides=[1, 3, 3, 1],
+ padding="VALID",
+ expected=[5, 8, 26, 29],
+ use_gpu=use_gpu)
+
+
def _testDepthwiseMaxPoolInvalidConfig(self, in_size, ksize, strides,
error_msg, use_gpu=False):
t = tf.constant(1.0, shape=in_size)
@@ -885,20 +912,6 @@ def testShapeFunctionEdgeCases(self):
shape=[32, 20, 20, 3]),
ksize=[1, 21, 20, 1], strides=[1, 1, 1, 1], padding="SAME")
- # Stride larger than filter.
- for pool_func in [tf.nn.max_pool, tf.nn.avg_pool,
- tf.nn.max_pool_with_argmax]:
- with self.assertRaisesRegexp(
- ValueError, "stride must be less than or equal to filter"):
- pool_func(tf.placeholder(tf.float32,
- shape=[32, 20, 20, 3]),
- ksize=[1, 5, 3, 1], strides=[1, 5, 5, 1], padding="SAME")
- with self.assertRaisesRegexp(
- ValueError, "stride must be less than or equal to filter"):
- pool_func(tf.placeholder(tf.float32,
- shape=[32, 20, 20, 3]),
- ksize=[1, 3, 5, 1], strides=[1, 5, 5, 1], padding="SAME")
-
def GetMaxPoolFwdTest(input_size, filter_size, strides, padding):
def Test(self):
@@ -149,10 +149,6 @@ def get2d_conv_output_size(input_height, input_width, filter_height,
"filter must not be larger than the input: "
"Filter: [%sx%s] Input: [%sx%s]"
% (filter_height, filter_width, input_height, input_width))
- if row_stride > filter_height or col_stride > filter_width:
- raise ValueError("stride must be less than or equal to filter size",
- "stride: [%sx%s] filter: [%sx%s]"
- % (row_stride, col_stride, filter_height, filter_width))
# Compute number of rows in the output, based on the padding.
if input_height.value is None or filter_height.value is None:

0 comments on commit 555c7f7

Please sign in to comment.