Forループを使用して2D配列のストライド畳み込みを実装しようとしました、つまり
arr = np.array([[2,3,7,4,6,2,9],
[6,6,9,8,7,4,3],
[3,4,8,3,8,9,7],
[7,8,3,6,6,3,4],
[4,2,1,8,3,4,6],
[3,2,4,1,9,8,3],
[0,1,3,9,2,1,4]])
arr2 = np.array([[3,4,4],
[1,0,2],
[-1,0,3]])
def stride_conv(arr1,arr2,s,p):
beg = 0
end = arr2.shape[0]
final = []
for i in range(0,arr1.shape[0]-1,s):
k = []
for j in range(0,arr1.shape[0]-1,s):
k.append(np.sum(arr1[beg+i : end+i, beg+j:end+j] * (arr2)))
final.append(k)
return np.array(final)
stride_conv(arr,arr2,2,0)
これは3 * 3配列になります:
array([[ 91, 100, 88],
[ 69, 91, 117],
[ 44, 72, 74]])
同じことをするためのnumpy関数またはscipy関数はありますか?私のアプローチはそれほど良くありません。これをベクトル化するにはどうすればよいですか?
2番目の配列とのたたみ込みに十分な長さを持たないパディング引数と後続ウィンドウを無視して、ここにnp.lib.stride_tricks.as_strided
を使用した1つの方法があります-
def strided4D(arr,arr2,s):
strided = np.lib.stride_tricks.as_strided
s0,s1 = arr.strides
m1,n1 = arr.shape
m2,n2 = arr2.shape
out_shp = (1+(m1-m2)//s, m2, 1+(n1-n2)//s, n2)
return strided(arr, shape=out_shp, strides=(s*s0,s*s1,s0,s1))
def stride_conv_strided(arr,arr2,s):
arr4D = strided4D(arr,arr2,s=s)
return np.tensordot(arr4D, arr2, axes=((2,3),(0,1)))
または、scikit-imageビルトイン view_as_windows
を使用して、これらのウィンドウelegantlyを取得することもできます。 -
from skimage.util.shape import view_as_windows
def strided4D_v2(arr,arr2,s):
return view_as_windows(arr, arr2.shape, step=s)
私のアプローチはジェイソンのアプローチに似ていますが、インデックスを使用しています。
def strideConv(arr, arr2, s):
return signal.convolve2d(arr, arr2[::-1, ::-1], mode='valid')[::s, ::s]
カーネルを逆にする必要があることに注意してください。詳細については、ディスカッション here および here を参照してください。それ以外の場合は signal.correlate2d
。
例:
>>> strideConv(arr, arr2, 1)
array([[ 91, 80, 100, 84, 88],
[ 99, 106, 126, 92, 77],
[ 69, 98, 91, 93, 117],
[ 80, 79, 87, 93, 61],
[ 44, 72, 72, 63, 74]])
>>> strideConv(arr, arr2, 2)
array([[ 91, 100, 88],
[ 69, 91, 117],
[ 44, 72, 74]])
次のように、「有効な」fft畳み込みを実行して、ストライドされた場所でそれらの結果のみを抽出できると思います。
def strideConv(arr,arr2,s):
cc=scipy.signal.fftconvolve(arr,arr2[::-1,::-1],mode='valid')
idx=(np.arange(0,cc.shape[1],s), np.arange(0,cc.shape[0],s))
xidx,yidx=np.meshgrid(*idx)
return cc[yidx,xidx]
これは他の人の答えと同じ結果になります。しかし、これはカーネルサイズが奇数の場合にのみ機能すると思います。
また、カーネルをarr2[::-1,::-1]
で反転させて、他のカーネルとの整合性を保つために、状況によってはカーネルを省略したい場合があります。
UPDATE:
現在、numpyとscipyのみを使用して2Dまたは3Dの畳み込みを実行する方法はいくつかあります。さまざまなサイズのデータでどちらが高速かを比較するために、いくつか比較することを考えました。これがトピック外と見なされないことを願っています。
方法1:FFT畳み込み(scipy.signal.fftconvolve
を使用):
def padArray(var,pad,method=1):
if method==1:
var_pad=numpy.zeros(Tuple(2*pad+numpy.array(var.shape[:2]))+var.shape[2:])
var_pad[pad:-pad,pad:-pad]=var
else:
var_pad=numpy.pad(var,([pad,pad],[pad,pad])+([0,0],)*(numpy.ndim(var)-2),
mode='constant',constant_values=0)
return var_pad
def conv3D(var,kernel,stride=1,pad=0,pad_method=1):
'''3D convolution using scipy.signal.convolve.
'''
var_ndim=numpy.ndim(var)
kernel_ndim=numpy.ndim(kernel)
stride=int(stride)
if var_ndim<2 or var_ndim>3 or kernel_ndim<2 or kernel_ndim>3:
raise Exception("<var> and <kernel> dimension should be in 2 or 3.")
if var_ndim==2 and kernel_ndim==3:
raise Exception("<kernel> dimension > <var>.")
if var_ndim==3 and kernel_ndim==2:
kernel=numpy.repeat(kernel[:,:,None],var.shape[2],axis=2)
if pad>0:
var_pad=padArray(var,pad,pad_method)
else:
var_pad=var
conv=fftconvolve(var_pad,kernel,mode='valid')
if stride>1:
conv=conv[::stride,::stride,...]
return conv
方法2:特別なコンバージョン( this anwser を参照):
def conv3D2(var,kernel,stride=1,pad=0):
'''3D convolution by sub-matrix summing.
'''
var_ndim=numpy.ndim(var)
ny,nx=var.shape[:2]
ky,kx=kernel.shape[:2]
result=0
if pad>0:
var_pad=padArray(var,pad,1)
else:
var_pad=var
for ii in range(ky*kx):
yi,xi=divmod(ii,kx)
slabii=var_pad[yi:2*pad+ny-ky+yi+1:1, xi:2*pad+nx-kx+xi+1:1,...]*kernel[yi,xi]
if var_ndim==3:
slabii=slabii.sum(axis=-1)
result+=slabii
if stride>1:
result=result[::stride,::stride,...]
return result
方法3:Divakarによって提案された通りビューコンバージョン:
def asStride(arr,sub_shape,stride):
'''Get a strided sub-matrices view of an ndarray.
<arr>: ndarray of rank 2.
<sub_shape>: Tuple of length 2, window size: (ny, nx).
<stride>: int, stride of windows.
Return <subs>: strided window view.
See also skimage.util.shape.view_as_windows()
'''
s0,s1=arr.strides[:2]
m1,n1=arr.shape[:2]
m2,n2=sub_shape[:2]
view_shape=(1+(m1-m2)//stride,1+(n1-n2)//stride,m2,n2)+arr.shape[2:]
strides=(stride*s0,stride*s1,s0,s1)+arr.strides[2:]
subs=numpy.lib.stride_tricks.as_strided(arr,view_shape,strides=strides)
return subs
def conv3D3(var,kernel,stride=1,pad=0):
'''3D convolution by strided view.
'''
var_ndim=numpy.ndim(var)
kernel_ndim=numpy.ndim(kernel)
if var_ndim<2 or var_ndim>3 or kernel_ndim<2 or kernel_ndim>3:
raise Exception("<var> and <kernel> dimension should be in 2 or 3.")
if var_ndim==2 and kernel_ndim==3:
raise Exception("<kernel> dimension > <var>.")
if var_ndim==3 and kernel_ndim==2:
kernel=numpy.repeat(kernel[:,:,None],var.shape[2],axis=2)
if pad>0:
var_pad=padArray(var,pad,1)
else:
var_pad=var
view=asStride(var_pad,kernel.shape,stride)
#return numpy.tensordot(aa,kernel,axes=((2,3),(0,1)))
if numpy.ndim(kernel)==2:
conv=numpy.sum(view*kernel,axis=(2,3))
else:
conv=numpy.sum(view*kernel,axis=(2,3,4))
return conv
私は3セットの比較を行いました:
したがって、「FFTコンバージョン」は一般的に最速です。 「特別なコンバージョン」と「ストライドビューコンバージョン」は、カーネルサイズが大きくなると遅くなりますが、入力データのサイズに近づくと再び小さくなります。最後のサブプロットは最速の方法を示しているので、紫色の大きな三角形がFFTが勝者であることを示していますが、左側に細い緑色の列があることに注意してください(おそらく小さすぎて表示されません)。非常に小さいカーネル(約5x5より小さい)に有利です。カーネルサイズが入力に近づくと、「stride-view conv」が最も速くなります(対角線を参照)。
比較2:3Dデータの畳み込み。
設定:pad = 0、stride = 2、input dimension = nxnx5
、kernel shape = fxfx5
。
カーネルサイズが入力の中央にある場合、「特殊変換」と「ストライドビュー変換」の計算をスキップしました。基本的に、「特別な変換」は現在何の利点も示していません。「Stride-view」は、小さいカーネルと大きいカーネルの両方でFFTより高速です。
もう1つ注意点として、サイズが350を超えると、「Stride-view conv」のメモリ使用量がかなりピークに達します。
比較3:ストライドが大きい3Dデータのたたみ込み。
セットアップ:パッド= 0、ストライド= 5、入力ディメンション= nxnx10
、カーネル形状= fxfx10
。
今回は「Special Conv」を省略しました。面積が大きい場合、「Stride-view conv」はFFTを上回り、最後のサブプロットは、差が100%に近づいていることを示しています。おそらく、ストライドが上がるにつれて、FFTアプローチでは無駄な数値が増えるため、「ストライドビュー」は、小さいカーネルと大きいカーネルでより多くの利点を得ます。
O(N ^ d(log N)^ d)fftベースのアプローチを次に示します。アイデアは、ストライドを法とするすべてのオフセットで、両方のオペランドをストライド間隔グリッドにチョップし、対応するオフセットのグリッド間で従来のFFT畳み込みを行い、結果を点ごとに合計することです。それは少しインデックスが重いですが、私は助けられないことを恐れています:
import numpy as np
from numpy.fft import fftn, ifftn
def strided_conv_2d(x, y, strides):
s, t = strides
# consensus dtype
cdt = (x[0, 0, ...] + y[0, 0, ...]).dtype
xi, xj = x.shape
yi, yj = y.shape
# round up modulo strides
xk, xl, yk, yl = map(lambda a, b: -a//b * -b, (xi,xj,yi,yj), (s,t,s,t))
# zero pad to avoid circular convolution
xp, yp = (np.zeros((xk+yk, xl+yl), dtype=cdt) for i in range(2))
xp[:xi, :xj] = x
yp[:yi, :yj] = y
# fold out strides
xp = xp.reshape((xk+yk)//s, s, (xl+yl)//t, t)
yp = yp.reshape((xk+yk)//s, s, (xl+yl)//t, t)
# do conventional fft convolution
xf = fftn(xp, axes=(0, 2))
yf = fftn(yp, axes=(0, 2))
result = ifftn(xf * yf.conj(), axes=(0, 2)).sum(axis=(1, 3))
# restore dtype
if cdt in (int, np.int_, np.int64, np.int32):
result = result.real.round()
return result.astype(cdt)
arr = np.array([[2,3,7,4,6,2,9],
[6,6,9,8,7,4,3],
[3,4,8,3,8,9,7],
[7,8,3,6,6,3,4],
[4,2,1,8,3,4,6],
[3,2,4,1,9,8,3],
[0,1,3,9,2,1,4]])
arr2 = np.array([[3,4,4],
[1,0,2],
[-1,0,3]])
print(strided_conv_2d(arr, arr2, (2, 2)))
結果:
[[ 91 100 88 23 0 29]
[ 69 91 117 19 0 38]
[ 44 72 74 17 0 22]
[ 16 53 26 12 0 0]
[ 0 0 0 0 0 0]
[ 19 11 21 -9 0 6]]