Implementing the adjoint operator of a convolutional layer

Hello everybody,

that’s my first post, so please be kind!

I am currently trying to implement the adjoint operation of a convolutional layer (in 2D and 3D).
More precisely, let c be a convoltutional layer with a pre-defined kernel, e.g. a 3x3 kernel.
What is the easiest and most efficient way of implementing the adjoint operation of c, i.e. c*?
Note that I am NOT looking for a transposed convolution, but I really need the adoint operator of c, i.e. it has to fulfill
<cx,y> = <x,c*y> for any x and y.

Thank you very much in advance for any help!
Cheers,
Andreas

I’m not sure, if you mean the Hermitian adjoint, but if so I assume you would like to inverse the convolution?
If so, then note that inverse filtering is often not a good idea, if it doesn’t account for the additional error signal in the input:

(f * g) + e = h

If you know the type of noise (e.g. Gaussian) you could use a Wiener deconvolution.

For a general use case you could use the FFT and reverse the (noise free) convolution via:

F = H / G

and apply the inverse FFT again.

exactly, i need the hermitian adjoint of the convolution-operation c, say c^H The reason is that I am considering a functional F(x) = 1/2*||cx -y||_2^2, where c is just a simple conv.layer with no activation function. Considering the derivative of F with respect to x I get c^H(cx -y). Thus, I need the adjoint operator of c. Since a conv. can in general be associated with a linear operator (i.e. a matrix), if I had the matrix as it was, it would be easy, cause I would just use A^H. But I would like to be able to express the operation A^H by means of the same function which I am using in the implementation, i.e conv2d or conv3d. I guess using FFT in that case does not help, since in the end I end up with c^H(cx -y) anyway.
But thanks again :slight_smile:

Hello Andreas!

ConvTranspose2d should do what you want (at least in the plain-vanilla,
2d case – I didn’t check 3d or various stride / padding combinations).

Here is an example script:

import torch
torch.__version__
torch.random.manual_seed (2020)

cnva = torch.nn.Conv2d (1, 1, 3, padding = 1, bias = False)
cnvt = torch.nn.ConvTranspose2d (1, 1, 3, padding = 1, bias = False)
with torch.no_grad():
    _ = cnvt.weight.copy_ (cnva.weight)

s = torch.randn ((1, 1, 7, 7))
t = torch.randn ((1, 1, 7, 7))
(cnva (s) * t).sum()
(s * cnvt (t)).sum()

Note the use of the same weight matrix for ConvTranspose2d as for
Conv2d.

And here is its output:

>>> import torch
>>> torch.__version__
'1.6.0'
>>> torch.random.manual_seed (2020)
<torch._C.Generator object at 0x7efc1f81c6f0>
>>>
>>> cnva = torch.nn.Conv2d (1, 1, 3, padding = 1, bias = False)
>>> cnvt = torch.nn.ConvTranspose2d (1, 1, 3, padding = 1, bias = False)
>>> with torch.no_grad():
...     _ = cnvt.weight.copy_ (cnva.weight)
...
>>> s = torch.randn ((1, 1, 7, 7))
>>> t = torch.randn ((1, 1, 7, 7))
>>> (cnva (s) * t).sum()
tensor(3.5478, grad_fn=<SumBackward0>)
>>> (s * cnvt (t)).sum()
tensor(3.5478, grad_fn=<SumBackward0>)

To understand the adjoint, think of a 2d 100x100 image as being a
vector of length 1,000,000. The 2d convolution can be represented
(inefficiently) as a 1,000,000 x 1,000,000 matrix with a particular
banded structure. Because it’s real, its adjoint is just its transpose.

ConvTranspose2d represents (algorithmically) the appropriate
banded structure for that transposed 1,000,000 x 1,000,000 matrix.

(If pytorch were to support convolutions with complex weights, you
would presumably use the complex conjugate of Conv2d’s weights
for ConvTranspose2d.)

Good luck.

K. Frank

Hello,
perfect, this looks like what I was looking for. Exactly, I am aware of the interpretation of the conv as an application of a matrix to a vector. I knew that for 2D, one typically only has to rotate the filter to obtain the tranposed operation. But since for 3D there are multiple possibilities to rotate the filter, I was wondering if there was a function which does this without me having to take care of how exactly to manipulate the filter depending on the dimensionality of the problem.
Thanks a lot again! :slight_smile:

Hello Andreas!

Did you try this scheme with Conv3d? (I didn’t.) Did it work?

Best.

K. Frank

Hi Frank,
yes, it worked fo 3D as well. :slight_smile: Thanks again!

Best,

Andreas