我有两个具有相同第一轴尺寸的二维数组。在python中,我想仅沿第二个轴对两个矩阵进行卷积。我想得到下面的
C
,而不计算沿第一个轴的卷积。
import numpy as np
import scipy.signal as sg
M, N, P = 4, 10, 20
A = np.random.randn(M, N)
B = np.random.randn(M, P)
C = sg.convolve(A, B, 'full')[(2*M-1)/2]
有没有快速的方法?
您可以使用
np.apply_along_axis
沿所需的轴应用 np.convolve
。以下是将 Boxcar 过滤器应用于二维数组的示例:
import numpy as np
a = np.arange(10)
a = np.vstack((a,a)).T
filt = np.ones(3)
np.apply_along_axis(lambda m: np.convolve(m, filt, mode='full'), axis=0, arr=a)
这是概括许多没有
axis
参数的函数的简单方法。
使用ndimage.convolve1d,您可以指定轴...
np.apply_along_axis
不会真正帮助你,因为你正在尝试迭代 two 数组。实际上,您必须使用循环,如此处所述。
现在,如果你的数组很小,循环就可以了,但如果 N 和 P 很大,那么你可能想使用 FFT 来进行卷积。
但是,您需要首先对数组进行适当的零填充,以便您的“完整”卷积具有预期的形状:
M, N, P = 4, 10, 20
A = np.random.randn(M, N)
B = np.random.randn(M, P)
A_ = np.zeros((M, N+P-1), dtype=A.dtype)
A_[:, :N] = A
B_ = np.zeros((M, N+P-1), dtype=B.dtype)
B_[:, :P] = B
A_fft = np.fft.fft(A_, axis=1)
B_fft = np.fft.fft(B_, axis=1)
C_fft = A_fft * B_fft
C = np.real(np.fft.ifft(C_fft))
# Test
C_test = np.zeros((M, N+P-1))
for i in range(M):
C_test[i, :] = np.convolve(A[i, :], B[i, :], 'full')
assert np.allclose(C, C_test)
对于 2D 数组,函数 scipy.signal.convolve2d 更快,scipy.signal.fftconvolve 甚至更快(取决于数组的维度):
这里是 N = 100000 的相同代码
import time
import numpy as np
import scipy.signal as sg
M, N, P = 10, 100000, 20
A = np.random.randn(M, N)
B = np.random.randn(M, P)
T1 = time.time()
C = sg.convolve(A, B, 'full')
print(time.time()-T1)
T1 = time.time()
C_2d = sg.convolve2d(A, B, 'full')
print(time.time()-T1)
T1 = time.time()
C_fft = sg.fftconvolve(A, B, 'full')
print(time.time()-T1)
>>> 12.3
>>> 2.1
>>> 0.6
由于使用的计算方法不同,答案都相同,但略有不同(例如,fft 与直接乘法,但我不知道 exaclty convolve2d 使用什么):
print(np.max(np.abs(C - C_2d)))
>>>7.81597009336e-14
print(np.max(np.abs(C - C_fft)))
>>>1.84741111298e-13
回复晚了,但值得发布以供参考。引用OP的评论:
A 中的每一行都被 B 中的相应行过滤。我可以 像这样实现它,只是想可能有更快的方法。
A 的大小约为 10 GB,我使用重叠相加。
import numpy as np
import scipy.signal as sg
M, N, P = 4, 10, 20
A = np.random.randn(M, N) # (4, 10)
B = np.random.randn(M, P) # (4, 20)
C = np.vstack([sg.convolve(a, b, 'full') for a, b in zip(A, B)])
>>> C.shape
(4, 29)
A 中的每一行与 B 中的每一行进行卷积,本质上是对 M 个一维数组/向量进行卷积。
可以使用 PyTorch 的 F.conv1d 来复制此操作。我们必须将
A
想象为长度为 10 的 4 通道一维信号。我们希望将 A
中的每个通道与长度为 20 的特定内核进行卷积。这是一种称为 深度卷积 的特殊情况,通常用于深度学习。
注意torch的卷积是作为互相关实现的,所以我们需要提前翻转B来进行实际的卷积。
import torch
import torch.nn.functional as F
@torch.no_grad()
def torch_conv(A, B):
M, N, P = A.shape[0], A.shape[1], B.shape[1]
C = F.conv1d(A, B[:, None, :], bias=None, stride=1, groups=M, padding=N+(P-1)//2)
return C.numpy()
# Convert A and B to torch tensors + flip B
X = torch.from_numpy(A) # (4, 10)
W = torch.from_numpy(np.fliplr(B).copy()) # (4, 20)
# Do grouped conv and get np array
Y = torch_conv(X, W)
>>> Y.shape
(4, 29)
>>> np.allclose(C, Y)
True
使用 torch 进行深度卷积的优点:
A
和 B
是非常大的矩阵,这确实可以加快速度。 (从OP的评论来看,情况似乎是这样:A的大小是10GB。)缺点:
scipy.signal.oaconvolve()
和 scipy.signal.fftconvolve()
都提供 axes
参数,它可以仅沿给定的一个或多个轴应用卷积。
这两个函数的行为与
scipy.signal.convolve()
非常相似(事实上,如果设置正确,convolve()
会在内部调用 fftconvolve()
)。特别是,这两个函数都提供与 mode
相同的 convolve()
参数来控制信号边界的处理。