仅沿一个轴进行卷积

问题描述 投票:0回答:6

我有两个具有相同第一轴尺寸的二维数组。在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]

有没有快速的方法?

numpy signal-processing scipy linear-algebra convolution
6个回答
27
投票

您可以使用

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
参数的函数的简单方法。


10
投票

使用ndimage.convolve1d,您可以指定轴...


5
投票

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)

3
投票

对于 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

1
投票

回复晚了,但值得发布以供参考。引用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 个一维数组/向量进行卷积。

无循环+ CUDA 支持版本

可以使用 PyTorch 的 F.conv1d 来复制此操作。我们必须将

A
想象为长度为 104 通道一维信号。我们希望将
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 进行深度卷积的优点:

  • 无循环!
  • 上述解决方案也可以在 CUDA/GPU 上运行,如果
    A
    B
    是非常大的矩阵,这确实可以加快速度。 (从OP的评论来看,情况似乎是这样:A的大小是10GB。)

缺点:

  • 从数组转换为张量的开销(应该可以忽略不计)
  • 操作前需翻转B一次

0
投票

scipy.signal.oaconvolve()
scipy.signal.fftconvolve()
都提供
axes
参数,它可以仅沿给定的一个或多个轴应用卷积。

这两个函数的行为与

scipy.signal.convolve()
非常相似(事实上,如果设置正确,
convolve()
会在内部调用
fftconvolve()
)。特别是,这两个函数都提供与
mode
相同的
convolve()
参数来控制信号边界的处理。

© www.soinside.com 2019 - 2024. All rights reserved.