Was ist der effizienteste Weg, um eine Funktion über ein Numpy-Array abzubilden? Ich habe es in meinem aktuellen Projekt folgendermaßen gemacht:
import numpy as np
x = np.array([1, 2, 3, 4, 5])
# Obtain array of square of each element in x
squarer = lambda t: t ** 2
squares = np.array([squarer(xi) for xi in x])
Dies scheint jedoch wahrscheinlich sehr ineffizient zu sein, da ich ein Listenverständnis verwende, um das neue Array als Python-Liste zu erstellen, bevor ich es wieder in ein Numpy-Array konvertiere.
Können wir es besser machen?
python
performance
numpy
Ryan
quelle
quelle
squarer(x)
?x = np.array([1, 2, 3, 4, 5]); x**2
WerkeAntworten:
Ich habe alle vorgeschlagenen Methoden plus
np.array(map(f, x))
mitperfplot
(einem kleinen Projekt von mir) getestet .Wenn die Funktion, die Sie bereits vektorisieren möchten, vektorisiert ist (wie im
x**2
Beispiel im ursprünglichen Beitrag), ist die Verwendung viel schneller als alles andere (beachten Sie die Protokollskala):Wenn Sie tatsächlich eine Vektorisierung benötigen, spielt es keine Rolle, welche Variante Sie verwenden.
Code zur Reproduktion der Diagramme:
quelle
f(x)
von Ihrer Verschwörung ausgelassen zu haben . Es ist möglicherweise nicht für jeden anwendbarf
, aber es ist hier anwendbar und es ist leicht die schnellste Lösung, wenn zutreffend.vf = np.vectorize(f); y = vf(x)
für kurze Eingaben gewinnt.pip install -U perfplot
) wird die folgende Meldung angezeigt :AttributeError: 'module' object has no attribute 'save'
Beim Einfügen des Beispielcodes.Wie wäre es mit
numpy.vectorize
.quelle
The vectorize function is provided primarily for convenience, not for performance. The implementation is essentially a for loop.
In anderen Fragen stellte ich fest, dass diesvectorize
die Iterationsgeschwindigkeit des Benutzers verdoppeln könnte. Die eigentliche Beschleunigung liegt jedoch bei echtennumpy
Array-Operationen.squarer(x)
würde schon arbeiten für Nicht-1d - Arrays.vectorize
hat nur wirklich einen Vorteil gegenüber einem Listenverständnis (wie dem in der Frage), nicht übersquarer(x)
.TL; DR
Wie von @ user2357112 angegeben , ist eine "direkte" Methode zum Anwenden der Funktion immer die schnellste und einfachste Möglichkeit, eine Funktion über Numpy-Arrays abzubilden:
Vermeiden
np.vectorize
Sie im Allgemeinen , da es nicht gut funktioniert und eine Reihe von Problemen aufweist (oder hatte) . Wenn Sie andere Datentypen verarbeiten, möchten Sie möglicherweise die anderen unten gezeigten Methoden untersuchen.Methodenvergleich
Im Folgenden finden Sie einige einfache Tests zum Vergleichen von drei Methoden zum Zuordnen einer Funktion. Dieses Beispiel wird mit Python 3.6 und NumPy 1.15.4 verwendet. Zunächst die Setup-Funktionen zum Testen:
Testen mit fünf Elementen (sortiert vom schnellsten zum langsamsten):
Mit Hunderten von Elementen:
Und mit Tausenden von Array-Elementen oder mehr:
Verschiedene Versionen von Python / NumPy und die Compiler-Optimierung führen zu unterschiedlichen Ergebnissen. Führen Sie daher einen ähnlichen Test für Ihre Umgebung durch.
quelle
count
Argument und einen Generatorausdruck verwenden,np.fromiter
ist dies erheblich schneller.'np.fromiter((f(xi) for xi in x), x.dtype, count=len(x))'
f(x)
, die alles andere um eine Größenordnung übertrifft .f
2 Variablen vorhanden sind und das Array 2D ist?Es gibt Numexpr , Numba und Cython . Das Ziel dieser Antwort ist es, diese Möglichkeiten zu berücksichtigen.
Aber lassen Sie uns zuerst das Offensichtliche sagen: Egal wie Sie eine Python-Funktion einem Numpy-Array zuordnen, es bleibt eine Python-Funktion, das heißt für jede Auswertung:
Float
. B. a ).Welche Maschinerie verwendet wird, um das Array tatsächlich zu durchlaufen, spielt aufgrund des oben erwähnten Overheads keine große Rolle - sie bleibt viel langsamer als die Verwendung der integrierten Funktionalität von numpy.
Schauen wir uns das folgende Beispiel an:
np.vectorize
wird als Vertreter der reinen Python-Funktionsklasse von Ansätzen ausgewählt. Mitperfplot
(siehe Code im Anhang dieser Antwort) erhalten wir folgende Laufzeiten:Wir können sehen, dass der Numpy-Ansatz 10x-100x schneller ist als die reine Python-Version. Der Leistungsabfall bei größeren Array-Größen ist wahrscheinlich darauf zurückzuführen, dass Daten nicht mehr in den Cache passen.
Erwähnenswert ist auch, dass
vectorize
auch viel Speicher benötigt wird, so dass die Speichernutzung häufig der Flaschenhals ist (siehe verwandte SO-Frage ). Beachten Sie auch, dass in der Dokumentation von numpy angegeben istnp.vectorize
, dass es "in erster Linie der Einfachheit halber und nicht der Leistung dient".Wenn Leistung gewünscht wird, sollten andere Tools verwendet werden. Neben dem Schreiben einer C-Erweiterung von Grund auf gibt es folgende Möglichkeiten:
Man hört oft, dass die Numpy-Performance so gut ist wie es nur geht, weil es reines C unter der Haube ist. Dennoch gibt es viel Raum für Verbesserungen!
Die vektorisierte Numpy-Version verwendet viel zusätzlichen Speicher und Speicherzugriffe. Die Numexp-Bibliothek versucht, die Numpy-Arrays zu kacheln und so eine bessere Cache-Auslastung zu erzielen:
Führt zu folgendem Vergleich:
Ich kann nicht alles in der obigen Darstellung erklären: Wir können am Anfang einen größeren Overhead für die numexpr-Bibliothek sehen, aber da der Cache besser genutzt wird, ist er für größere Arrays etwa zehnmal schneller!
Ein anderer Ansatz besteht darin, die Funktion zu kompilieren und so einen echten UFunc mit reinem C zu erhalten. Dies ist Numbas Ansatz:
Es ist zehnmal schneller als der ursprüngliche Numpy-Ansatz:
Die Aufgabe ist jedoch peinlich parallelisierbar, sodass wir sie auch verwenden könnten
prange
, um die Schleife parallel zu berechnen:Wie erwartet ist die Parallelfunktion bei kleineren Eingängen langsamer, bei größeren jedoch schneller (fast Faktor 2):
Während sich numba auf die Optimierung von Operationen mit numpy-Arrays spezialisiert hat, ist Cython ein allgemeineres Werkzeug. Es ist komplizierter, die gleiche Leistung wie bei numba zu extrahieren - oft liegt es an llvm (numba) gegenüber dem lokalen Compiler (gcc / MSVC):
Cython führt zu etwas langsameren Funktionen:
Fazit
Offensichtlich beweist das Testen nur für eine Funktion nichts. Man sollte auch bedenken, dass für das gewählte Funktionsbeispiel die Bandbreite des Speichers der Flaschenhals für Größen größer als 10 ^ 5 Elemente war - daher hatten wir in dieser Region die gleiche Leistung für numba, numexpr und cython.
Letztendlich hängt die endgültige Antwort von der Art der Funktion, der Hardware, der Python-Verteilung und anderen Faktoren ab. Zum Beispiel Anaconda-Distribution verwendet Intels VML für Funktionen numpy ist und damit übertrifft numba (es sei denn , es SVML verwendet, finden Sie diese SO-post ) leicht für transzendente Funktionen wie
exp
,sin
,cos
und ähnlich - siehe zum Beispiel die folgenden SO-Post .Aufgrund dieser Untersuchung und meiner bisherigen Erfahrungen würde ich jedoch feststellen, dass Numba das einfachste Werkzeug mit der besten Leistung zu sein scheint, solange keine transzendentalen Funktionen beteiligt sind.
Laufzeit mit Perfplot-Paket zeichnen :
quelle
Arithmetische Operationen auf Arrays werden automatisch elementweise angewendet, mit effizienten Schleifen auf C-Ebene, die den gesamten Interpreter-Overhead vermeiden, der für eine Schleife oder ein Verständnis auf Python-Ebene gelten würde.
Die meisten Funktionen, die Sie elementweise auf ein NumPy-Array anwenden möchten, funktionieren nur, einige müssen jedoch möglicherweise geändert werden. Funktioniert beispielsweise
if
nicht elementweise. Sie möchten diese konvertieren, um Konstrukte wie die folgenden zu verwendennumpy.where
:wird
quelle
Ich glaube, in einer neueren Version (ich verwende 1.13) von numpy können Sie die Funktion einfach aufrufen, indem Sie das numpy-Array an die Funktion übergeben, die Sie für den Skalartyp geschrieben haben. Es wendet den Funktionsaufruf automatisch auf jedes Element über das numpy-Array an und gibt Sie zurück ein weiteres numpy Array
quelle
**
Operator, der die Berechnung auf jedes Element t von anwendett
. Das ist gewöhnliche Numpy. Das Einwickeln inlambda
macht nichts extra.In vielen Fällen ist numpy.apply_along_axis die beste Wahl. Es erhöht die Leistung um etwa das 100-fache im Vergleich zu den anderen Ansätzen - und zwar nicht nur für triviale Testfunktionen, sondern auch für komplexere Funktionszusammensetzungen aus Numpy und Scipy.
Wenn ich die Methode hinzufüge:
Zum Perfplot-Code erhalte ich folgende Ergebnisse:
quelle
Es scheint, dass niemand eine eingebaute Fabrikmethode zur Herstellung
ufunc
in Numpy-Verpackungen erwähnt hat,np.frompyfunc
die ich erneut getestetnp.vectorize
und um etwa 20 bis 30% übertroffen habe. Natürlich funktioniert es gut wie vorgeschriebener C-Code oder sogarnumba
(was ich nicht getestet habe), aber es kann eine bessere Alternative sein alsnp.vectorize
Ich habe auch größere Proben getestet und die Verbesserung ist proportional. Siehe auch die Dokumentation hier
quelle
Wie in diesem Beitrag erwähnt , verwenden Sie einfach Generatorausdrücke wie folgt:
quelle
Alle obigen Antworten lassen sich gut vergleichen, aber wenn Sie eine benutzerdefinierte Funktion für die Zuordnung verwenden müssen und dies auch tun, müssen
numpy.ndarray
Sie die Form des Arrays beibehalten.Ich habe nur zwei verglichen, aber es wird die Form von behalten
ndarray
. Ich habe das Array mit 1 Million Einträgen zum Vergleich verwendet. Hier verwende ich die quadratische Funktion, die ebenfalls in Numpy eingebaut ist und eine große Leistungssteigerung aufweist, da Sie, da etwas benötigt wurde, eine Funktion Ihrer Wahl verwenden können.Ausgabe
Hier können Sie deutlich sehen
numpy.fromiter
, dass die Arbeit in Anbetracht des einfachen Ansatzes großartig ist. Wenn eine eingebaute Funktion verfügbar ist, verwenden Sie diese bitte.quelle
Verwenden
numpy.fromfunction(function, shape, **kwargs)
Siehe " https://docs.scipy.org/doc/numpy/reference/generated/numpy.fromfunction.html ".
quelle