根据定义,离散卷积是关联的。但是当我试图在pytorch中验证这一点时,我找不到合理的结果。
关联定律是$ f *(g * \ psi)=(f * g)* \ psi $,所以我创建了三个以零为中心的离散函数(作为张量)并用适当的零填充卷积它们以使所有非零获得结果图中的元素。
import torch
import torch.nn as nn
def test_conv_compst():
# $\psi$
inputs = torch.randn((1,4,7,7))
# $g$
a = torch.randn((7, 4, 3, 3))
# $f$
b = torch.randn((3, 7, 3, 3))
int_1 = torch.conv2d(inputs, a, padding=2)
# results obtained by the first order
res_1 = torch.conv2d(int_1, b, padding=2)
comp_k = torch.conv2d(a.transpose(1, 0), b, padding=2).transpose(1, 0)
print(comp_k.shape)
# results obtained through the second order
res_2 = torch.conv2d(inputs, comp_k, padding=4)
print(res_1.shape)
print(res_2.shape)
print(torch.max(torch.abs(res_2-res_1)))
预期结果是两个结果的差异可以忽略不计。但它返回:
torch.Size([3, 4, 5, 5])
torch.Size([1, 3, 11, 11])
torch.Size([1, 3, 11, 11])
tensor(164.8044)
长话短说,这是因为批处理。 torch.conv2d
的第一个论点被解释为[batch, channel, height, width]
,第二个被解释为[out_channel, in_channel, height, width]
,输出被称为[batch, channel, height, width]
。因此,如果你调用conv2d(a, conv2d(b, c))
,你将b
的领先维度视为批处理,如果你调用conv2d(conv2d(a, b), c)
,则将其视为out_channels
。
话虽如此,我得到的印象是你在这里问数学,所以让我扩展一下。你的想法在理论上是正确的:卷积是线性算子,应该是关联的。但是,由于我们为它们提供内核而不是代表线性运算符的实际矩阵,因此需要在幕后进行一些“转换”,以便将内核正确地解释为矩阵。经典地,这可以通过构造相应的circulant matrices(边界条件除外)来完成。如果我们用a
,b
,c
和M
的循环矩阵创建算子表示内核,我们得到M(a) @ [M(b) @ M(c)] = [M(a) @ M(b)] @ M(c)
,其中@
表示矩阵 - 矩阵乘法。
卷积实现返回一个图像(矢量,内核,但你称之为),而不是相关的循环矩阵,这是非常冗余的,并且在大多数情况下不适合存储器。因此,我们还需要一些循环到矢量的算子V(matrix)
,它返回matrix
的第一列,因此是M
的逆。在抽象的数学术语中,像scipy.signal.convolve
这样的函数(实际上是correlate
,因为卷积需要额外翻转其中一个输入,为了清楚起见,我跳过)被实现为convolve = lambda a, b: V(M(a) @ M(b))
,因此
convolve(a, convolve(b, c)) =
= V(M(a) @ M(V[M(b) @ M(c)])
= V(M(a) @ M(b) @ M(c))
= V(M(V[M(a) @ M(b)]) @ M(c))
= convolve(convolve(a, b), c)
我希望我没有失去你,这只是通过利用V
是M
的逆和矩阵乘法的关联来移动括号的事实将一个转换为另一个。请注意,中间线基本上是“原始”ABC
。我们可以使用以下代码进行验证:
import numpy as np
import scipy.signal as sig
c2d = sig.convolve2d
a = np.random.randn(7, 7)
b = np.random.randn(3, 3)
c = np.random.randn(3, 3)
ab = c2d(a, b)
ab_c = c2d(ab, c)
bc = c2d(b, c)
a_bc = c2d(a, bc)
print((a_bc - ab_c).max())
PyTorch的问题在于它将第一个输入解释为[batch, channel, height, width]
,第二个输入解释为[out_channels, in_channels, height, width]
。这意味着“转换”运算符M
对于第一个参数和第二个参数是不同的。我们分别称他们为M
和N
。由于只有一个输出,只有一个V
,它可以是M
或N
的倒数,但不是两者(因为它们不同)。如果你重写上面的等式,注意区分M
和N
你会看到,根据你的选择V
是否反转了一个或另一个,你无法在第2行和第3行或第3行和第4行之间写出相等。
在实践中,还有channel
维度的附加问题,这在经典的卷积定义中并不存在,但是我的第一个猜测是它可以用两个操作数的单个提升算子M
处理,不像批处理。