2015-08-15 8 views
7

Faltung entlang Z-Vektor eines 3d numpy-Arrays, dann andere Operationen auf den Ergebnissen, aber es ist langsam, wie es jetzt implementiert ist. Ist die for-Schleife, was mich hier verlangsamt oder ist die Faltung? Ich habe versucht, zu einem 1d Vektor umzuformen und die Faltung in 1 Durchlauf durchzuführen (wie ich in Matlab tat), ohne die for Schleife, aber es verbessert nicht Leistung. Meine Matlab-Version ist ungefähr 50% schneller als alles, was ich in Python finden kann. Relevanter Codeabschnitt:Beschleunigung für Schleife in Faltung für numpy 3D-Array?

convolved=np.zeros((y_lines,x_lines,z_depth)) 
for i in range(0, y_lines): 
    for j in range(0, x_lines): 
     convolved[i,j,:]= fftconvolve(data[i,j,:], Gauss) #80% of time here 
     result[i,j,:]= other_calculations(convolved[i,j,:]) #20% of time here 

Gibt es einen besseren Weg als eine for-Schleife? Von Cython gehört, aber ich habe jetzt begrenzte Erfahrung in Python, würde auf die einfachste Lösung zielen.

+0

Was ist 'Gauß'? Irgendeine Art von 1-D-Gauss-Kernel? Wenn ja, welche Größe relativ zu 'z_depth'? –

+0

Gaußscher Kern wird einmal vor der Schleife generiert. Daten sind 1d Vektor (z_depth) typischerweise um 1535 Elemente lang, mit 1D Gauss-Kernel der Länge typischerweise 79. Ich räumte ein paar der Overhead in fftconvolve, geht im Grunde nur direkt zu irfftn (rfftn (in1, fshape) * rfftn (in2, fshape), fshape) [fslice] .copy() – user4547612

Antwort

4

Die von Ihnen verwendete fftconvolve-Funktion stammt vermutlich von SciPy. Wenn ja, beachten Sie, dass N-dimensionale Arrays benötigt werden. So ein schneller Weg, um Ihre Faltung zu tun wäre, um das 3D-Kernel zu erzeugen, die in den x und y Dimensionen und macht eine 1d Gaußsche Faltung in z.

Einige Code und Timing-Ergebnisse sind unten nichts zu tun, entspricht. Auf meinem Rechner und mit einigen Spielzeug Daten, führte dies zu einer 10 × Speedup, wie Sie sehen können:

import numpy as np 
from scipy.signal import fftconvolve 
from scipy.ndimage.filters import gaussian_filter 

# use scipy filtering functions designed to apply kernels to isolate a 1d gaussian kernel 
kernel_base = np.ones(shape=(5)) 
kernel_1d = gaussian_filter(kernel_base, sigma=1, mode='constant') 
kernel_1d = kernel_1d/np.sum(kernel_1d) 

# make the 3d kernel that does gaussian convolution in z axis only 
kernel_3d = np.zeros(shape=(1, 1, 5,)) 
kernel_3d[0, 0, :] = kernel_1d 

# generate random data 
data = np.random.random(size=(50, 50, 50)) 

# define a function for loop based convolution for easy timeit invocation 
def convolve_with_loops(data): 
    nx, ny, nz = data.shape 
    convolved=np.zeros((nx, ny, nz)) 
    for i in range(0, nx): 
     for j in range(0, ny): 
      convolved[i,j,:]= fftconvolve(data[i, j, :], kernel_1d, mode='same') 
    return convolved 

# compute the convolution two diff. ways: with loops (first) or as a 3d convolution (2nd) 
convolved = convolve_with_loops(data) 
convolved_2 = fftconvolve(data, kernel_3d, mode='same') 

# raise an error unless the two computations return equivalent results 
assert np.all(np.isclose(convolved, convolved_2)) 

# time the two routes of the computation 
%timeit convolved = convolve_with_loops(data) 
%timeit convolved_2 = fftconvolve(data, kernel_3d, mode='same') 

timeit Ergebnisse:

10 loops, best of 3: 198 ms per loop 
100 loops, best of 3: 18.1 ms per loop 
+0

Versuchen Sie, Daten der Länge 64 zu generieren und sehen Sie, ob dies schneller geht. FFT ist in der Regel bei Zweierpotenzen wesentlich effizienter. – cxrodgers

+0

eine 3D-Version implementiert, ist es langsamer als das, was ich hatte: Zeit Falten: 5851,7 ms (Neue 3D-Version) Zeit Falten: 4093,4 ms (alte Version) – user4547612

+0

cxrodgers: fftconvolve verwendet def _next_regular (Ziel): zu Finden Sie die optimale Größe der Daten (hier 1620 für einen Vektor mit 1535 Elementen, Pad mit Nullen). – user4547612

1

Ich glaube, Sie haben bereits die source code der fftconvolve Funktion. Normalerweise verwendet es für reale Eingaben die Funktionen numpy.fft.rfftn und .irfftn, die N-dimensionale Transformationen berechnen. Da das Ziel mehrere 1-D-Transformationen zu tun ist, können Sie im Grunde fftconvolve wie folgt umschreiben (vereinfacht):

from scipy.signal.signaltools import _next_regular 

def fftconvolve_1d(in1, in2): 

    outlen = in1.shape[-1] + in2.shape[-1] - 1 
    n = _next_regular(outlen) 

    tr1 = np.fft.rfft(in1, n) 
    tr2 = np.fft.rfft(in2, n) 
    out = np.fft.irfft(tr1 * tr2, n) 

    return out[..., :outlen].copy() 

Und um das gewünschte Ergebnis berechnen:

result = fftconvolve_1d(data, Gauss) 

Das funktioniert, weil numpy.fft.rfft und .irfft (beachten Sie das Fehlen von n im Namen) über eine einzelne Achse des Eingabe-Array (die letzte Achse standardmäßig) zu transformieren. Das ist ungefähr 40% schneller als der OP-Code auf meinem System.


Weitere Beschleunigung kann durch Verwendung eines anderen FFT-Back-End erreicht werden.

Zum einen scheinen die Funktionen in scipy.fftpack etwas schneller als ihre Numpy-Äquivalente zu sein. Allerdings ist das Ausgabeformat der Scipy-Varianten ziemlich umständlich (siehe docs) und dies macht es schwierig, die Multiplikation durchzuführen.

Ein anderes mögliches Backend ist FFTW durch den PyFFTW-Wrapper. Nachteile sind, dass Transformationen eine langsame "Planungsphase" vorausgehen und dass die Eingaben 16-Byte-ausgerichtet sein müssen, um die beste Leistung zu erzielen. Dies wird in der pyFFTW tutorial ziemlich gut erklärt. Der resultierende Code könnte zum Beispiel sein:

from scipy.signal.signaltools import _next_regular 
import pyfftw 
pyfftw.interfaces.cache.enable() # Cache for the "planning" 
pyfftw.interfaces.cache.set_keepalive_time(1.0) 

def fftwconvolve_1d(in1, in2): 

    outlen = in1.shape[-1] + in2.shape[-1] - 1 
    n = _next_regular(outlen) 

    tr1 = pyfftw.interfaces.numpy_fft.rfft(in1, n) 
    tr2 = pyfftw.interfaces.numpy_fft.rfft(in2, n) 

    sh = np.broadcast(tr1, tr2).shape 
    dt = np.common_type(tr1, tr2) 
    pr = pyfftw.n_byte_align_empty(sh, 16, dt) 
    np.multiply(tr1, tr2, out=pr) 
    out = pyfftw.interfaces.numpy_fft.irfft(pr, n) 

    return out[..., :outlen].copy() 

mit ausgerichteten Ein- und Cache „Planung“ Ich sah in dem OP den Code eine Beschleunigung von fast 3x über.Speicherausrichtung can be easily checked durch Blick auf die Speicheradresse im ctypes.data Attribut eines Numpy-Array.

+0

Das Ersetzen von rfftn durch rfft verbesserte die Leistung um etwa 30%. Die pyfftw Methode jedoch nicht hilft: pyFFTW: 6,3 sec numpy rfft: 4,6 sec pyFFTW: 86,1 sec numpy rfft: 62,4 sec – user4547612