Ich habe zwei Numpy-Arrays, die die x- und y-Achse eines Gitters definieren. Beispielsweise:
x = numpy.array([1,2,3])
y = numpy.array([4,5])
Ich möchte das kartesische Produkt dieser Arrays generieren, um Folgendes zu generieren:
array([[1,4],[2,4],[3,4],[1,5],[2,5],[3,5]])
In gewisser Weise ist das nicht besonders ineffizient, da ich dies viele Male in einer Schleife tun muss. Ich gehe davon aus, dass das Konvertieren in eine Python-Liste und das Verwenden itertools.product
und Zurück in ein Numpy-Array nicht die effizienteste Form ist.
python
numpy
cartesian-product
Reich
quelle
quelle
Antworten:
Eine allgemeine Lösung zur Berechnung des kartesischen Produkts von N Arrays finden Sie unter Verwenden von numpy zum Erstellen eines Arrays aller Kombinationen von zwei Arrays.
quelle
meshgrid
+dstack
-Ansatz ist zwar in einigen Fällen schneller, kann jedoch zu Fehlern führen, wenn Sie erwarten, dass das kartesische Produkt für Arrays derselben Größe in derselben Reihenfolge erstellt wird.meshgrid
+dstack
. Könnten Sie ein Beispiel posten?Ein kanonischer
cartesian_product
(fast)Es gibt viele Ansätze für dieses Problem mit unterschiedlichen Eigenschaften. Einige sind schneller als andere, andere sind allgemeiner. Nach vielen Tests und Optimierungen habe ich festgestellt, dass die folgende Funktion, die eine n-Dimension berechnet
cartesian_product
, für viele Eingaben schneller ist als die meisten anderen. Für ein paar Ansätze, die etwas komplexer sind, aber in vielen Fällen sogar etwas schneller, siehe die Antwort von Paul Panzer .Angesichts dieser Antwort ist dies nicht mehr die schnellste Implementierung des kartesischen Produkts
numpy
, die mir bekannt ist. Ich denke jedoch, dass seine Einfachheit es weiterhin zu einem nützlichen Maßstab für zukünftige Verbesserungen machen wird:Es ist erwähnenswert, dass diese Funktion auf
ix_
ungewöhnliche Weise verwendet wird. Während die dokumentierte Verwendung von darinix_
besteht, Indizes in einem Array zu generieren , kommt es nur so vor, dass Arrays mit derselben Form für die Broadcast-Zuweisung verwendet werden können. Vielen Dank an mgilson , der mich dazu inspiriert hat,ix_
diesen Weg zu versuchen , und an unutbu , der einige äußerst hilfreiche Rückmeldungen zu dieser Antwort gegeben hat, einschließlich des Vorschlags zur Verwendungnumpy.result_type
.Bemerkenswerte Alternativen
Es ist manchmal schneller, zusammenhängende Speicherblöcke in Fortran-Reihenfolge zu schreiben. Das ist die Basis dieser Alternative,
cartesian_product_transpose
die sich auf einigen Hardwarekomponenten als schneller erwiesen hat alscartesian_product
(siehe unten). Die Antwort von Paul Panzer, die dasselbe Prinzip verwendet, ist jedoch noch schneller. Trotzdem füge ich dies hier für interessierte Leser ein:Nachdem ich Panzers Ansatz verstanden hatte, schrieb ich eine neue Version, die fast so schnell ist wie seine und fast so einfach wie
cartesian_product
:Dies scheint einen zeitlich konstanten Overhead zu haben, der es bei kleinen Eingaben langsamer laufen lässt als bei Panzer. Bei größeren Eingaben funktioniert es in allen von mir durchgeführten Tests genauso gut wie seine schnellste Implementierung (
cartesian_product_transpose_pp
).In den folgenden Abschnitten füge ich einige Tests anderer Alternativen hinzu. Diese sind jetzt etwas veraltet, aber anstatt doppelte Anstrengungen zu unternehmen, habe ich beschlossen, sie aus historischem Interesse hier zu lassen. Aktuelle Tests finden Sie in Panzers Antwort sowie in der von Nico Schlömer .
Tests gegen Alternativen
Hier finden Sie eine Reihe von Tests, die die Leistungssteigerung zeigen, die einige dieser Funktionen im Vergleich zu einer Reihe von Alternativen bieten. Alle hier gezeigten Tests wurden auf einem Quad-Core-Computer unter Mac OS 10.12.5, Python 3.6.1 und
numpy
1.12.1 durchgeführt. Es ist bekannt, dass Variationen von Hardware und Software zu unterschiedlichen Ergebnissen führen, so YMMV. Führen Sie diese Tests selbst durch, um sicherzugehen!Definitionen:
Testergebnisse:
In allen Fällen ist
cartesian_product
die am Anfang dieser Antwort definierte Antwort am schnellsten.Für Funktionen, die eine beliebige Anzahl von Eingabearrays akzeptieren, lohnt es sich, die Leistung auch dann zu überprüfen
len(arrays) > 2
. (Bis ich feststellen kann, warumcartesian_product_recursive
in diesem Fall ein Fehler auftritt, habe ich ihn aus diesen Tests entfernt.)Wie diese Tests zeigen,
cartesian_product
bleibt es wettbewerbsfähig, bis die Anzahl der Eingabearrays über (ungefähr) vier steigt. Danachcartesian_product_transpose
hat eine leichte Kante.Es sollte wiederholt werden, dass Benutzer mit anderer Hardware und Betriebssystemen möglicherweise andere Ergebnisse sehen. Unutbu-Berichte zeigen beispielsweise die folgenden Ergebnisse für diese Tests mit Ubuntu 14.04, Python 3.4.3 und
numpy
1.14.0.dev0 + b7050a9:Im Folgenden gehe ich auf einige Details zu früheren Tests ein, die ich in diesem Sinne durchgeführt habe. Die relative Leistung dieser Ansätze hat sich im Laufe der Zeit für verschiedene Hardware und verschiedene Versionen von Python und geändert
numpy
. Es ist zwar nicht sofort nützlich für Benutzer, die aktuelle Versionen von verwendennumpy
, zeigt jedoch, wie sich die Dinge seit der ersten Version dieser Antwort geändert haben.Eine einfache Alternative:
meshgrid
+dstack
Die aktuell akzeptierte Antwort verwendet
tile
undrepeat
sendet zwei Arrays zusammen. Aber diemeshgrid
Funktion macht praktisch das Gleiche. Hier ist die Ausgabe vontile
undrepeat
bevor sie zur Transponierung übergeben wird:Und hier ist die Ausgabe von
meshgrid
:Wie Sie sehen können, ist es fast identisch. Wir müssen nur das Ergebnis umformen, um genau das gleiche Ergebnis zu erzielen.
Anstatt an dieser Stelle eine Umformung vorzunehmen, könnten wir die Ausgabe von
meshgrid
an übergebendstack
und anschließend umformen, was einige Arbeit spart:Entgegen der Behauptung in diesem Kommentar habe ich keine Beweise dafür gesehen, dass unterschiedliche Eingaben unterschiedlich geformte Ausgaben erzeugen, und wie oben gezeigt, tun sie sehr ähnliche Dinge, so dass es ziemlich seltsam wäre, wenn sie dies tun würden. Bitte lassen Sie mich wissen, wenn Sie ein Gegenbeispiel finden.
Testen
meshgrid
+dstack
gegenrepeat
+transpose
Die relative Leistung dieser beiden Ansätze hat sich im Laufe der Zeit geändert. In einer früheren Version von Python (2.7) war das Ergebnis mit
meshgrid
+dstack
bei kleinen Eingaben deutlich schneller. (Beachten Sie, dass diese Tests aus einer alten Version dieser Antwort stammen.) Definitionen:Bei mäßig großen Eingaben sah ich eine deutliche Beschleunigung. Ich habe diese Tests jedoch mit neueren Versionen von Python (3.6.1) und
numpy
(1.12.1) auf einem neueren Computer wiederholt. Die beiden Ansätze sind jetzt fast identisch.Alter Test
Neuer Test
Wie immer YMMV, aber dies deutet darauf hin, dass diese in neueren Versionen von Python und Numpy austauschbar sind.
Verallgemeinerte Produktfunktionen
Im Allgemeinen können wir erwarten, dass die Verwendung integrierter Funktionen für kleine Eingaben schneller ist, während für große Eingaben eine speziell entwickelte Funktion möglicherweise schneller ist. Weiterhin für ein verallgemeinertes n-dimensionales Produkt
tile
undrepeat
wird nicht helfen, weil sie keine klaren höherdimensionalen Analoga haben. Es lohnt sich also, auch das Verhalten von speziell entwickelten Funktionen zu untersuchen.Die meisten relevanten Tests erscheinen am Anfang dieser Antwort, aber hier sind einige der Tests, die mit früheren Versionen von Python und
numpy
zum Vergleich durchgeführt wurden.Die
cartesian
in einer anderen Antwort definierte Funktion wurde für größere Eingaben verwendet. (Es ist das gleiche wie die Funktion aufgerufencartesian_product_recursive
oben.) Um zu vergleichen ,cartesian
zudstack_prodct
verwenden wir nur zwei Dimensionen.Auch hier zeigte der alte Test einen signifikanten Unterschied, während der neue Test fast keinen zeigt.
Alter Test
Neuer Test
Nach wie vor
dstack_product
schlägtcartesian
in kleineren Maßstäben.Neuer Test ( redundanter alter Test nicht gezeigt )
Diese Unterscheidungen sind meiner Meinung nach interessant und es lohnt sich, sie aufzuzeichnen. aber sie sind am Ende akademisch. Wie die Tests am Anfang dieser Antwort gezeigt haben, sind alle diese Versionen fast immer langsamer als
cartesian_product
am Anfang dieser Antwort definiert - was selbst etwas langsamer ist als die schnellsten Implementierungen unter den Antworten auf diese Frage.quelle
dtype=object
inarr = np.empty( )
würde die Verwendung verschiedener Typen im Produkt ermöglichen, zarrays = [np.array([1,2,3]), ['str1', 'str2']]
.cartesian_product_tranpose
schneller finden alscartesian_product
je nach Betriebssystem, Python oder Numpy-Version. Zum Beispiel auf Ubuntu 14.04, python3.4.3, numpy 1.14.0.dev0 + b7050a9,%timeit cartesian_product_transpose(x500,y500)
Erträge ,1000 loops, best of 3: 682 µs per loop
während die%timeit cartesian_product(x500,y500)
Renditen1000 loops, best of 3: 1.55 ms per loop
. Ich finde auch,cartesian_product_transpose
dass es schneller sein kann, wennlen(arrays) > 2
.cartesian_product
gibt ein Array von Gleitkommazahlen dtype währendcartesian_product_transpose
gibt ein Array von derselben dtype wie der erste (ausgestrahlt) -Array. Die Möglichkeit, dtype bei der Arbeit mit ganzzahligen Arrays beizubehalten, kann für Benutzer ein Grund sein, dies zu bevorzugencartesian_product_transpose
.dtype = np.find_common_type([arr.dtype for arr in arrays], [])
der Gedanke , dass so etwas verwendet werden könnte, um den gemeinsamen d-Typ aller Arrays zu finden, anstatt den Benutzer zu zwingen, das Array zu platzieren, das den d-Typ zuerst steuert.Sie können in Python einfach ein normales Listenverständnis durchführen
was sollte dir geben
quelle
Ich war auch daran interessiert und habe einen kleinen Leistungsvergleich durchgeführt, vielleicht etwas klarer als in @ senderles Antwort.
Für zwei Arrays (der klassische Fall):
Für vier Arrays:
(Beachten Sie, dass die Länge der Arrays hier nur ein paar Dutzend Einträge beträgt.)
Code zur Reproduktion der Diagramme:
quelle
Aufbauend auf @ senderles beispielhafter Basisarbeit habe ich zwei Versionen entwickelt - eine für C und eine für Fortran-Layouts - die oft etwas schneller sind.
cartesian_product_transpose_pp
ist - im Gegensatz zu @ senderle'scartesian_product_transpose
, das eine völlig andere Strategie verwendet - eine Version davoncartesion_product
, die das günstigere Transponierungsspeicherlayout + einige sehr geringfügige Optimierungen verwendet.cartesian_product_pp
bleibt beim ursprünglichen Speicherlayout. Was es schnell macht, ist die Verwendung von zusammenhängendem Kopieren. Aneinandergrenzende Kopien sind so viel schneller, dass das Kopieren eines vollständigen Speicherblocks, obwohl nur ein Teil davon gültige Daten enthält, dem Kopieren nur der gültigen Bits vorzuziehen ist.Einige Perfplots. Ich habe separate Layouts für C- und Fortran-Layouts erstellt, da dies IMO unterschiedliche Aufgaben sind.
Namen, die mit 'pp' enden, sind meine Ansätze.
1) viele winzige Faktoren (jeweils 2 Elemente)
2) viele kleine Faktoren (jeweils 4 Elemente)
3) drei Faktoren gleicher Länge
4) zwei Faktoren gleicher Länge
Code (für jeden Plot müssen separate Läufe durchgeführt werden, da ich nicht herausfinden konnte, wie er zurückgesetzt werden soll; außerdem muss er entsprechend bearbeitet / kommentiert werden):
quelle
arrays
in cartesian_product_transpose_pp (Arrays) eine bestimmte Größe überschreitet,MemoryError
tritt dies auf. In dieser Situation möchte ich, dass diese Funktion kleinere Ergebnisblöcke liefert. Ich habe eine Frage zu diesem Thema gestellt. Können Sie meine Frage beantworten? Vielen Dank.Seit Oktober 2017 verfügt numpy über eine generische
np.stack
Funktion, die einen Achsenparameter übernimmt. Mit ihm können wir ein "verallgemeinertes kartesisches Produkt" unter Verwendung der "dstack and meshgrid" -Technik haben:Hinweis zum
axis=-1
Parameter. Dies ist die letzte (innerste) Achse im Ergebnis. Es ist gleichbedeutend mit der Verwendungaxis=ndim
.Ein weiterer Kommentar: Da kartesische Produkte sehr schnell in die Luft jagen, sollten wir die Werte im laufenden Betrieb verwenden und verwenden , es sei denn, wir müssen das Array aus irgendeinem Grund im Speicher realisieren. Wenn das Produkt sehr groß ist
itertools
.quelle
Ich habe eine Weile @kennytm answer verwendet , aber als ich versuchte, dasselbe in TensorFlow zu tun, stellte ich fest, dass TensorFlow kein Äquivalent zu hat
numpy.repeat()
. Nach ein wenig Experimentieren habe ich eine allgemeinere Lösung für beliebige Punktvektoren gefunden.Für numpy:
und für TensorFlow:
quelle
Das Scikit- Lernpaket bietet eine schnelle Implementierung genau dieser:
Beachten Sie, dass sich die Konvention dieser Implementierung von der gewünschten unterscheidet, wenn Sie sich um die Reihenfolge der Ausgabe kümmern. Für Ihre genaue Bestellung können Sie tun
quelle
Im Allgemeinen können Sie diese Methode verwenden, wenn Sie zwei 2d-Numpy-Arrays a und b haben und jede Zeile von a mit jeder Zeile von b verketten möchten (ein kartesisches Produkt aus Zeilen, ähnlich einem Join in einer Datenbank) ::
quelle
Am schnellsten können Sie entweder einen Generatorausdruck mit der Kartenfunktion kombinieren:
Ausgaben (tatsächlich wird die gesamte resultierende Liste gedruckt):
oder mithilfe eines doppelten Generatorausdrucks:
Ausgaben (ganze Liste gedruckt):
Beachten Sie, dass der größte Teil der Rechenzeit in den Druckbefehl fließt. Die Generatorberechnungen sind ansonsten recht effizient. Ohne Druck sind die Berechnungszeiten:
für Generatorausdruck + Kartenfunktion und:
für den Doppelgeneratorausdruck.
Wenn Sie tatsächlich das tatsächliche Produkt jedes der Koordinatenpaare berechnen möchten, lösen Sie es am schnellsten als Numpy-Matrix-Produkt:
Ausgänge:
und ohne Drucken (in diesem Fall spart es nicht viel, da nur ein winziges Stück der Matrix tatsächlich ausgedruckt wird):
quelle
foo = a[:,None]*b
schneller. Wenn Sie Ihre Timing-Methode ohne verwendenprint(foo)
, sind es 0,001103 s gegenüber 0,002225 s. Mit timeit sind es 304 μs gegenüber 1,6 ms. Matrix ist bekanntermaßen langsamer als ndarray, daher habe ich Ihren Code mit np.array ausprobiert, aber er ist immer noch langsamer (1,57 ms) als Broadcasting.Dies kann auch einfach mit der Methode itertools.product erfolgen
Ergebnis: Array ([
[1, 4],
[1, 5],
[2, 4],
[2, 5],
[3, 4],
[3, 5]], dtype = int32)
Ausführungszeit: 0,000155 s
quelle
In dem speziellen Fall, in dem Sie einfache Vorgänge ausführen müssen, z. B. das Hinzufügen für jedes Paar, können Sie eine zusätzliche Dimension einführen und den Rundfunk die Aufgabe erledigen lassen:
Ich bin mir nicht sicher, ob es einen ähnlichen Weg gibt, um die Paare tatsächlich selbst zu bekommen.
quelle
dtype
istfloat
kann man tun ,(a[:, None, None] + 1j * b[None, :, None]).view(float)
was überraschend schnell.