2016-11-26 4 views
1

Ich arbeite an einem Code für ein Modell basierend auf einigen Fourier-Transformationen. Momentan versuche ich, einen Teil davon zu optimieren, so dass es mit einer großen Datenmenge verwendbar wäre. Beim Versuch, dies zu tun, fand ich ein merkwürdiges Verhalten, hauptsächlich eine Loop-Version meines Codes, die schneller ist als der gleiche Code, der mit numpy geschrieben wurde. Testing-Code ist wie folgt:Python-Schleife schneller als numpy Array-Operationen

# -*- coding: utf-8 -*- 
import numpy as np 
import timeit 


def fourier_coef_loop(ts, times, k_max): 
    coefs = np.zeros(k_max, dtype=float) 
    t = 2.0 * np.pi * (times - times[0])/times[-1] 
    x = np.dot(np.arange(1,k_max+1)[np.newaxis].T,t[np.newaxis]) 
    for k in xrange(1, k_max + 1): 
     cos_k = np.cos(x[k-1]) 
     coefs[k - 1] = (ts[-1] - ts[0]) + (ts[:-1] * (cos_k[:-1] - cos_k[1:])).sum() 
    return(coefs) 


def fourier_coef_np(ts, times, k_max): 
    coefs = np.zeros(k_max, dtype=float) 
    t = 2.0 * np.pi * (times - times[0])/times[-1] 
    x = np.dot(np.arange(1,k_max+1)[np.newaxis].T,t[np.newaxis]) 
    coefs = np.add(np.einsum('ij,j->i',np.diff(np.cos(x)), -ts[:-1]), (ts[-1] - ts[0])) 
    return(coefs) 


if __name__ == '__main__': 
    iterations = 10 
    size = 20000 
    setup = "from __main__ import fourier_coef_loop, fourier_coef_np, size\n" \ 
      "import numpy as np" 

# arg = np.random.normal(size=size) 
# print(np.all(fourier_coef_np(arg, np.arange(size,dtype=float), size/2) == fourier_coef_loop(arg, np.arange(size,dtype=float), size/2))) 

    time_loop = timeit.timeit("fourier_coef_loop(np.random.normal(size=size), np.arange(size,dtype=float), size/2)", 
           setup=setup, number=iterations) 
    print("With loop: {} s".format(time_loop)) 

    time_np = timeit.timeit("fourier_coef_np(np.random.normal(size=size), np.arange(size,dtype=float), size/2)", 
          setup=setup, number=iterations) 
    print("With numpy: {} s".format(time_np)) 

Es gibt folgende Ergebnisse:

With loop: 60.8385488987 s 
With numpy: 64.9192998409 s 

Kann jemand bitte sagen Sie mir, warum ist die Schleife Version schneller als die rein numpy Version? Mir sind die Ideen ausgegangen. Ich würde auch alle Vorschläge schätzen, wie man diese bestimmte Funktion schneller machen kann.

+0

Sie sind nicht Timing der Schleife vs der vektorisierten Version, Sie Schleifen ein riesiges Durcheinander, das das Erzeugen einer Menge Pseudozufallsnormalen enthält. Darüber hinaus sieht Ihr Code stark suboptimal aus. Zum Beispiel, einsum ('ij, j-> i, ...) 'klingt sehr ähnlich wie ein Matrix-Vektor-Produkt (->' np.dot' wieder) und was auch immer' x = np.dot (np. Ein Bereich (1, k_max + 1) [np.newaxis] .T, t [np.newaxis]) sollte, ich bin mir sicher, dass es einen saubereren Weg gibt, es zu tun. –

+0

Die Zeiten sind nicht so unterschiedlich; Wenn ich versuche, diese Größe zu verwenden, erhalte ich einen Speicherfehler. Für Größe = 2000 sind Timings auch ähnlich. Für 200 hat die np-Version einen wesentlichen Vorteil. Ich rate daher, dass bei größeren Formaten Probleme mit der Speicherverwaltung in die "numpigen" Zeiten hineinkauen. – hpaulj

+0

@hpaulj Ich denke nicht, dass diese Zeiten relevant sind, um diese Schleife mit einer vektorisierten Version zu vergleichen (meine eigene in einem Bit). –

Antwort

1

Wie ich in einem Kommentar bemerkte, denke ich nicht, was Sie Timing ist mit dem Unterschied zwischen der Schleife und der vektorisierten Version, Ihre Zeiten sogar die Erzeugung von 20000 normalen Pseudozufallszahlen. Sie sollten versuchen, so viele Einstellungen wie möglich außerhalb des Timings vorzunehmen, wenn Sie ein genaues Bild erhalten möchten.

Wie auch immer, hat Ihr Code ein paar seltsamen Punkte, so ist hier mein eigener Vorschlag:

def fourier_coef_new(ts,times,k_max): 
    # no need to pre-allocate coefs, you're rebinding later 
    t = 2.0 * np.pi * (times - times[0])/times[-1] 
    x = np.arange(1,k_max+1)[:,None] * t # make use of array broadcasting 
    coefs = -np.dot(np.diff(np.cos(x)),ts[:-1]) + ts[-1]-ts[0] # einsum was dot 
    return(coefs) 

ich diese Funktion für einen bestimmten Satz von Eingaben geprüft, und es gab das gleiche Ergebnis wie Ihre Funktionen. Beachten Sie die [:,None] (oder gleichwertige [:,np.newaxis]) Möglichkeit, eine Singleton-Dimension in Ihr Array einzuführen. Sobald Sie ein Array von Form (N,1) und eines der Form (M,) (letztere kompatibel mit (1,M)) haben, wird ihr Produkt (N,M) nach den Array-Broadcasting-Regeln von numpy sein.

Ich habe ein schnelles Timing der drei Funktionen mit einem gegebenen, vorgenerierten Satz von Daten, aber 1. in Python 3, 2. mit size = 2000 und 3. mit IPython %timeit eingebaut. Ich behaupte nicht, dass diese Ergebnisse sind nicht mehr zuverlässiger als bei Ihnen, aber ich vermute, dass die obige Version schnellste sein sollte:

In [37]: %timeit fourier_coef_loop(ts,times,k_max) 
1000 loops, best of 3: 1.09 ms per loop 

In [38]: %timeit fourier_coef_np(ts,times,k_max) 
1000 loops, best of 3: 1.06 ms per loop 

In [39]: %timeit fourier_coef_new(ts,times,k_max) 
1000 loops, best of 3: 1.05 ms per loop 

Wie Sie sehen können, die numpy Versionen scheinen geringfügig schneller zu sein. Da nur ein kleinerer Teil Ihres Codes zwischen den beiden Zeitfällen unterschiedlich ist (und in beiden Fällen schwere trigonometrische Funktionen beteiligt sind), erscheint dies sinnvoll.

+1

Lieber Andras, danke für Ihre Antwort. Ihr Code ist eigentlich fast identisch mit einem der Dinge, die ich vorher getestet habe. Der Grund, warum ich numpy einsum benutzt habe, war ein Vorschlag, irgendwo auf Stackoverflow, dass es bessere Speicherverwaltungsergebnisse geben könnte. Aber wie Ihr Code gezeigt hat, war das wahrscheinlich nicht die beste Idee. Wie ich in einem Kommentar oben gesagt habe, stellte sich heraus, dass es sich um ein Speicherproblem handelt. – Mateusz

+1

Oh, und vielen Dank für seinen Vorschlag über das Setup. Es war nützlich und machte meinen Testcode effizienter und zuverlässiger. – Mateusz