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