Skip to content

Commit 582dba0

Browse files
authored
Merge pull request #84 from lucasb-eyer/backward-conv-fix
Fix backwards-conv work in general (pad/stride/...)
2 parents 67cf8af + b63f4b9 commit 582dba0

File tree

2 files changed

+68
-10
lines changed

2 files changed

+68
-10
lines changed

DeepFried2/layers/BackwardsConvolutionCUDNN.py

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,24 @@
88

99
class BackwardsConvolutionCUDNN(df.Module):
1010
def __init__(self, nchan_in, nchan_out, filter_size, stride=1, border=0, mode='cross', init=df.init.xavier(), bias=df.init.const(0)):
11-
# mode='cross' is the default in Lasagne[1], Torch[2], matConvNet[3], Caffee[4].
12-
#
13-
# 1: https://github.com/Lasagne/Lasagne/blob/63d44a0d/lasagne/layers/dnn.py#L299
14-
# 2: https://github.com/soumith/cudnn.torch/blob/840f0228/SpatialConvolution.lua#L83
15-
# 3: https://github.com/vlfeat/matconvnet/blob/b7dd9c96/matlab/src/bits/impl/nnconv_cudnn.cu#L133
16-
# 4: https://github.com/BVLC/caffe/blob/50ab52cb/include/caffe/util/cudnn.hpp#L104
11+
"""
12+
This is the backwards path through a convolution, sometimes is also
13+
referred to as transposed convolution and (wrongly) deconvolution.
14+
15+
This is usually used for upsampling an image. If you want the exact
16+
counterpart to another convolution earlier part of your model, consider
17+
using the `backward` function with that convolution instead.
18+
19+
- `nchan_in`: number of channels in the input.
20+
- `nchan_out`: number of filters and thus channels in the output.
21+
- `filter_size`: 2D or 3D tuple describing the filter size.
22+
- `stride`: the stride "dilates" the output, i.e. makes it larger.
23+
- `border`: The counterpart to `border` in forward convolution. This
24+
effectively crops the output, as opposed to padding it.
25+
- `mode`: `'cross'` or `'conv'`, see forward convolution documentation.
26+
- `init`: initializer for the weights/filters.
27+
- `bias`: initializer for the bias, or `None` or `False`.
28+
"""
1729
df.Module.__init__(self)
1830
self.nchan_in = nchan_in
1931
self.nchan_out = nchan_out
@@ -39,14 +51,15 @@ def __init__(self, nchan_in, nchan_out, filter_size, stride=1, border=0, mode='c
3951

4052

4153
def symb_forward(self, symb_input):
42-
""" creates dummy forward conv and uses its gradient as backwards pass """
43-
""" This code is mostly taken from https://github.com/Newmu/dcgan_code/blob/master/lib/ops.py """
54+
# Calls directly into CUDNN's gradient methods to insert a backward-conv Op.
55+
# This code is originally taken from https://github.com/Newmu/dcgan_code/blob/master/lib/ops.py
56+
# and extended to more complex scenarios (stride, border)
4457
img = gpu_contiguous(symb_input)
4558
kerns = gpu_contiguous(self.W.param)
4659

47-
alloc_shape = (img.shape[0], kerns.shape[1]) + tuple(i*d for i,d in zip(img.shape[2:],self.stride))
48-
desc = dnn.GpuDnnConvDesc(border_mode=self.border, subsample=self.stride, conv_mode=self.mode)(gpu_alloc_empty(*alloc_shape).shape, kerns.shape)
60+
alloc_shape = (img.shape[0], self.nchan_out) + tuple((i-1)*s - 2*b + f for i,s,b,f in zip(img.shape[2:], self.stride, self.border, self.filter_size))
4961
out = gpu_alloc_empty(*alloc_shape)
62+
desc = dnn.GpuDnnConvDesc(border_mode=self.border, subsample=self.stride, conv_mode=self.mode)(out.shape, kerns.shape)
5063
grad = dnn.GpuDnnConv3dGradI if symb_input.ndim == 5 else dnn.GpuDnnConvGradI
5164
conv_output = grad()(kerns, img, out, desc)
5265

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#!/usr/bin/env python3
2+
3+
import DeepFried2 as df
4+
5+
import unittest
6+
import numpy as np
7+
8+
class TestBackwardsConvolutionCUDNN(unittest.TestCase):
9+
10+
def testFwdBwd(self):
11+
# Let's try fuzz-testing for that one, I hear good things about it!!
12+
13+
randint = np.random.randint
14+
15+
for _ in range(100):
16+
B = randint(1, 10)
17+
cin = randint(1, 10)
18+
cout = randint(1, 10)
19+
20+
# Test both 2D and 3D
21+
ndim = randint(2,3)
22+
23+
fs = tuple(randint(1, 11, size=ndim))
24+
25+
# Image should be >= filter size in all dimensions.
26+
ims = tuple(fs + randint(0, 10, size=ndim))
27+
28+
stride = tuple(randint(1, 4, size=ndim))
29+
border = tuple(randint(0, 5, size=ndim))
30+
31+
# We can only test those cases where no border gets "lost" during
32+
# forward conv using this strategy, as otherwise the result is smaller.
33+
# I could come up with the formula of the output shape in other cases,
34+
# but why bother if brute-force trying is just as good?
35+
# A little over a third of trials pass this test.
36+
if not all(((i+2*b) - f) % s == 0 for f,i,s,b in zip(fs, ims, stride, border)):
37+
continue
38+
39+
X = np.random.randn(B, cin, *ims).astype(df.floatX)
40+
net = df.Sequential(
41+
df.SpatialConvolutionCUDNN(cin, cout, fs, stride, border),
42+
df.BackwardsConvolutionCUDNN(cout, cin, fs, stride, border)
43+
)
44+
Y = net.forward(X)
45+
self.assertEqual(Y.shape, X.shape, "Setup: B={B},cin={cin},cout={cout},ndim={ndim},fs={fs},ims={ims},stride={stride},border={border}".format(**locals()))

0 commit comments

Comments
 (0)