Skip to content

Commit 555c7f7

Browse files
ryVijay Vasudevan
authored andcommitted
Fix conv2d with kernel 1x1 stride > 1 (#1868)
Fixes #889
1 parent bc75c6f commit 555c7f7

7 files changed

Lines changed: 91 additions & 44 deletions

File tree

tensorflow/core/kernels/conv_ops.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,7 @@ struct LaunchConvOp<GPUDevice, T> {
272272
if (use_cudnn) {
273273
Tensor input = input_param;
274274
if (filter.dim_size(0) == 1 && filter.dim_size(1) == 1 &&
275+
row_stride == 1 && col_stride == 1 &&
275276
data_format == FORMAT_NHWC) {
276277
// 1x1 filter, so call cublas directly.
277278
const uint64 m =

tensorflow/core/kernels/eigen_spatial_convolutions_test.cc

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,55 @@ TEST(EigenSpatialConvolutionsTest, StridedSpatialConvolution) {
405405
}
406406
}
407407

408+
409+
TEST(EigenSpatialConvolutionsTest, KernelSmallerThanStride) {
410+
const int input_depth = 2;
411+
const int input_rows = 3;
412+
const int input_cols = 3;
413+
const int num_batches = 5;
414+
const int output_depth = 6;
415+
const int patch_rows = 1;
416+
const int patch_cols = 1;
417+
const int output_rows = 2;
418+
const int output_cols = 2;
419+
420+
Tensor<float, 4> input(input_depth, input_rows, input_cols, num_batches);
421+
Tensor<float, 4> kernel(output_depth, input_depth, patch_rows, patch_cols);
422+
Tensor<float, 4> result(output_depth, output_rows, output_cols, num_batches);
423+
input = input.constant(11.0f) + input.random();
424+
kernel = kernel.constant(2.0f) + kernel.random();
425+
result.setRandom();
426+
427+
// Apply a spatial convolution using a 1x1 kernel, valid padding, and a stride
428+
// of 2.
429+
int stride = 2;
430+
result = SpatialConvolution(input, kernel, stride, stride, PADDING_VALID);
431+
432+
EXPECT_EQ(result.dimension(0), output_depth);
433+
EXPECT_EQ(result.dimension(1), output_rows);
434+
EXPECT_EQ(result.dimension(2), output_cols);
435+
EXPECT_EQ(result.dimension(3), num_batches);
436+
437+
for (int b = 0; b < num_batches; ++b) {
438+
for (int od = 0; od < output_depth; ++od) {
439+
for (int i = 0; i < output_rows; ++i) {
440+
for (int j = 0; j < output_cols; ++j) {
441+
float expected = 0.0f;
442+
for (int c = 0; c < patch_cols; ++c) {
443+
for (int r = 0; r < patch_rows; ++r) {
444+
for (int id = 0; id < input_depth; ++id) {
445+
expected += input(id, r + stride * i, c + stride * j, b) *
446+
kernel(od, id, r, c);
447+
}
448+
}
449+
}
450+
EigenApprox(result(od, i, j, b), expected);
451+
}
452+
}
453+
}
454+
}
455+
}
456+
408457
TEST(EigenSpatialConvolutionsTest, StridedSpatialConvolutionRowMajor) {
409458
const int input_depth = 10;
410459
const int input_rows = 5;

tensorflow/core/kernels/ops_util.cc

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,6 @@ Status Get2dOutputSizeVerbose(const int in_height, const int in_width,
3838
int row_stride, int col_stride, Padding padding,
3939
int* new_height, int* new_width, int* pad_top,
4040
int* pad_bottom, int* pad_left, int* pad_right) {
41-
// Cannot have strides larger than the patch size.
42-
if (row_stride > filter_height || col_stride > filter_width) {
43-
return errors::InvalidArgument(
44-
"stride must be less than or equal to kernel size");
45-
}
4641
switch (padding) {
4742
case Padding::VALID:
4843
*new_height = ceil((in_height - filter_height + 1.f) /

tensorflow/core/kernels/ops_util_test.cc

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -129,12 +129,6 @@ class OpsUtilTest : public ::testing::Test {
129129
}
130130
};
131131

132-
// Test stride > ksize fails with INVALID_ARGUMENT.
133-
TEST_F(OpsUtilTest, Get2dOutputSizeInvalidTest) {
134-
padding_struct pad_struct = {{3, 3, 1, 2, 2, 2, SAME}, {3, 3, 1, 1, 1, 1}};
135-
VerifyGet2dOutputSizeBoundaries(pad_struct, error::INVALID_ARGUMENT);
136-
}
137-
138132
TEST_F(OpsUtilTest, Get2dOutputSizeNegativeSizeTest) {
139133
padding_struct pad_struct = {{1, 1, 3, 3, 1, 1, VALID}, {-1, -1, 0, 0, 0, 0}};
140134
VerifyGet2dOutputSizeBoundaries(pad_struct, error::INVALID_ARGUMENT);

tensorflow/python/kernel_tests/conv_ops_test.py

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,20 @@ def testConv2D2x2FilterStride1x2(self):
319319
strides=[1, 2], padding="VALID",
320320
expected=expected_output)
321321

322+
def testConv2DKernelSmallerThanStrideValid(self):
323+
expected_output = [65, 95, 275, 305]
324+
self._VerifyValues(tensor_in_sizes=[1, 7, 7, 1],
325+
filter_in_sizes=[2, 2, 1, 1],
326+
strides=[3, 3], padding="VALID",
327+
expected=expected_output)
328+
329+
def testConv2DKernelSmallerThanStrideSame(self):
330+
expected_output = [1, 3, 7, 9]
331+
self._VerifyValues(tensor_in_sizes=[1, 3, 3, 1],
332+
filter_in_sizes=[1, 1, 1, 1],
333+
strides=[2, 2], padding="SAME",
334+
expected=expected_output)
335+
322336
# Testing for backprops
323337
def _RunAndVerifyBackpropInput(self, input_sizes, filter_sizes, output_sizes,
324338
strides, padding, expected, data_format,
@@ -862,21 +876,6 @@ def testShapeFunctionEdgeCases(self):
862876
shape=[21, 20, 3, 2]),
863877
strides=[1, 1, 1, 1], padding="SAME")
864878

865-
# Stride larger than filter.
866-
with self.assertRaisesRegexp(ValueError,
867-
"stride must be less than or equal to filter"):
868-
tf.nn.conv2d(tf.placeholder(tf.float32,
869-
shape=[32, 20, 20, 3]),
870-
tf.placeholder(tf.float32,
871-
shape=[4, 5, 3, 2]),
872-
strides=[1, 5, 5, 1], padding="SAME")
873-
with self.assertRaisesRegexp(ValueError,
874-
"stride must be less than or equal to filter"):
875-
tf.nn.conv2d(tf.placeholder(tf.float32,
876-
shape=[32, 20, 20, 3]),
877-
tf.placeholder(tf.float32,
878-
shape=[5, 4, 3, 2]),
879-
strides=[1, 5, 5, 1], padding="SAME")
880879

881880

882881
# This is only a very simple test. More comprehensive tests live in

tensorflow/python/kernel_tests/pooling_ops_test.py

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,33 @@ def testDepthwiseMaxPool2x2DepthWindow3(self):
370370
expected=[3.0, 6.0, 9.0, 12.0, 15.0, 18.0, 21.0, 24.0],
371371
use_gpu=False)
372372

373+
def testKernelSmallerThanStride(self):
374+
for use_gpu in [True, False]:
375+
self._VerifyValues(tf.nn.max_pool, input_sizes=[1, 3, 3, 1],
376+
ksize=[1, 1, 1, 1], strides=[1, 2, 2, 1],
377+
padding="SAME",
378+
expected=[1, 3, 7, 9],
379+
use_gpu=use_gpu)
380+
381+
self._VerifyValues(tf.nn.max_pool, input_sizes=[1, 7, 7, 1],
382+
ksize=[1, 2, 2, 1], strides=[1, 3, 3, 1],
383+
padding="VALID",
384+
expected=[9, 12, 30, 33],
385+
use_gpu=use_gpu)
386+
387+
self._VerifyValues(tf.nn.avg_pool, input_sizes=[1, 3, 3, 1],
388+
ksize=[1, 1, 1, 1], strides=[1, 2, 2, 1],
389+
padding="SAME",
390+
expected=[1, 3, 7, 9],
391+
use_gpu=use_gpu)
392+
393+
self._VerifyValues(tf.nn.avg_pool, input_sizes=[1, 7, 7, 1],
394+
ksize=[1, 2, 2, 1], strides=[1, 3, 3, 1],
395+
padding="VALID",
396+
expected=[5, 8, 26, 29],
397+
use_gpu=use_gpu)
398+
399+
373400
def _testDepthwiseMaxPoolInvalidConfig(self, in_size, ksize, strides,
374401
error_msg, use_gpu=False):
375402
t = tf.constant(1.0, shape=in_size)
@@ -885,20 +912,6 @@ def testShapeFunctionEdgeCases(self):
885912
shape=[32, 20, 20, 3]),
886913
ksize=[1, 21, 20, 1], strides=[1, 1, 1, 1], padding="SAME")
887914

888-
# Stride larger than filter.
889-
for pool_func in [tf.nn.max_pool, tf.nn.avg_pool,
890-
tf.nn.max_pool_with_argmax]:
891-
with self.assertRaisesRegexp(
892-
ValueError, "stride must be less than or equal to filter"):
893-
pool_func(tf.placeholder(tf.float32,
894-
shape=[32, 20, 20, 3]),
895-
ksize=[1, 5, 3, 1], strides=[1, 5, 5, 1], padding="SAME")
896-
with self.assertRaisesRegexp(
897-
ValueError, "stride must be less than or equal to filter"):
898-
pool_func(tf.placeholder(tf.float32,
899-
shape=[32, 20, 20, 3]),
900-
ksize=[1, 3, 5, 1], strides=[1, 5, 5, 1], padding="SAME")
901-
902915

903916
def GetMaxPoolFwdTest(input_size, filter_size, strides, padding):
904917
def Test(self):

tensorflow/python/ops/common_shapes.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -149,10 +149,6 @@ def get2d_conv_output_size(input_height, input_width, filter_height,
149149
"filter must not be larger than the input: "
150150
"Filter: [%sx%s] Input: [%sx%s]"
151151
% (filter_height, filter_width, input_height, input_width))
152-
if row_stride > filter_height or col_stride > filter_width:
153-
raise ValueError("stride must be less than or equal to filter size",
154-
"stride: [%sx%s] filter: [%sx%s]"
155-
% (row_stride, col_stride, filter_height, filter_width))
156152

157153
# Compute number of rows in the output, based on the padding.
158154
if input_height.value is None or filter_height.value is None:

0 commit comments

Comments
 (0)