Ich habe Mühe, genau zu verstehen, wie es einsum
funktioniert. Ich habe mir die Dokumentation und einige Beispiele angesehen, aber es scheint nicht zu bleiben.
Hier ist ein Beispiel, das wir im Unterricht besprochen haben:
C = np.einsum("ij,jk->ki", A, B)
für zwei Arrays A
undB
Ich denke, das würde dauern A^T * B
, aber ich bin mir nicht sicher (es braucht die Transponierung von einem von ihnen, oder?). Kann mich jemand genau durch das führen, was hier passiert (und im Allgemeinen bei der Verwendung einsum
)?
python
arrays
numpy
multidimensional-array
numpy-einsum
Lanzenstraße
quelle
quelle
(A * B)^T
oder gleichwertigB^T * A^T
.einsum
hier geschrieben . (Ich bin glücklich, die relevantesten Bits auf eine Antwort auf Stapelüberlauf zu übertragen, wenn dies nützlich ist).numpy
Dokumentation ist bei der Erläuterung der Details absolut unzureichend.*
nicht um eine Matrixmultiplikation handelt, sondern um eine elementweise Multiplikation. Achtung!Antworten:
(Hinweis: Diese Antwort ist auf einer kurzen basierende Blog - Post über die
einsum
ich vor einer Weile geschrieben.)Was macht
einsum
dasStellen Sie sich vor, wir haben zwei mehrdimensionale Arrays
A
undB
. Nehmen wir jetzt an, wir wollen ...A
mitB
in einer bestimmten Art und Weise neue Reihe von Produkten zu erzeugen; und dann vielleichtEs gibt eine gute Chance , dass
einsum
uns helfen wird , tun dies schneller und Speicher-effizient , dass Kombinationen der NumPy Funktionen wiemultiply
,sum
undtranspose
ermöglicht.Wie funktioniert das
einsum
?Hier ist ein einfaches (aber nicht ganz triviales) Beispiel. Nehmen Sie die folgenden zwei Arrays:
Wir werden multiplizieren
A
undB
elementweise und dann entlang der Zeilen des neuen Arrays summieren. In "normalem" NumPy würden wir schreiben:Hier richtet die Indizierungsoperation
A
die ersten Achsen der beiden Arrays so aus, dass die Multiplikation gesendet werden kann. Die Zeilen des Produktarrays werden dann summiert, um die Antwort zurückzugeben.Wenn wir
einsum
stattdessen verwenden wollten , könnten wir schreiben:Der Signatur - String
'i,ij->i'
ist hier der Schlüssel und braucht ein wenig zu erklären. Sie können sich das in zwei Hälften vorstellen. Auf der linken Seite (links von->
) haben wir die beiden Eingabearrays beschriftet. Rechts von->
haben wir das Array beschriftet, mit dem wir enden möchten.Folgendes passiert als nächstes:
A
hat eine Achse; wir haben es beschrifteti
. UndB
hat zwei Achsen; Wir haben Achse 0 alsi
und Achse 1 als bezeichnetj
.Indem wir die Beschriftung in beiden Eingabearrays wiederholen
i
, sagen wir,einsum
dass diese beiden Achsen miteinander multipliziert werden sollten. Mit anderen Worten, wir multiplizieren das ArrayA
mit jeder Spalte des ArraysB
, genau wie dies derA[:, np.newaxis] * B
Fall ist.Beachten Sie, dass
j
dies in unserer gewünschten Ausgabe nicht als Beschriftung angezeigt wird. Wir haben gerade verwendeti
(wir möchten mit einem 1D-Array enden). Durch das Weglassen des Etiketts, doch sagen wir summieren entlang dieser Achse. Mit anderen Worten, wir summieren die Zeilen der Produkte genau wie dies der Fall ist.einsum
.sum(axis=1)
Das ist im Grunde alles, was Sie wissen müssen, um zu verwenden
einsum
. Es hilft, ein wenig herumzuspielen; Wenn wir beide Beschriftungen in der Ausgabe belassen, erhalten'i,ij->ij'
wir ein 2D-Array von Produkten zurück (wieA[:, np.newaxis] * B
). Wenn wir keine Ausgabeetiketten sagen,'i,ij->
, erhalten wir eine einzelne Zahl zurück (genau wie beim Ausführen(A[:, np.newaxis] * B).sum()
).Das Tolle daran
einsum
jedoch, dass nicht zuerst eine temporäre Reihe von Produkten erstellt wird. es summiert nur die Produkte, wie es geht. Dies kann zu großen Einsparungen bei der Speichernutzung führen.Ein etwas größeres Beispiel
Um das Punktprodukt zu erklären, sind hier zwei neue Arrays:
Wir werden das Punktprodukt mit berechnen
np.einsum('ij,jk->ik', A, B)
. Hier ist ein Bild, das die Beschriftung desA
undB
und des Ausgabearrays zeigt, die wir von der Funktion erhalten:Sie können sehen, dass die Beschriftung
j
wiederholt wird. Dies bedeutet, dass wir die Zeilen vonA
mit den Spalten von multiplizierenB
. Darüber hinaus ist das Etikettj
nicht in der Ausgabe enthalten - wir fassen diese Produkte zusammen. Etiketteni
undk
werden für die Ausgabe aufbewahrt, sodass wir ein 2D-Array zurückerhalten.Es könnte noch deutlicher sein , dieses Ergebnis mit dem Array zu vergleichen , wo das Etikett
j
wird nicht summiert. Unten links sehen Sie das 3D-Array, das sich aus dem Schreiben ergibtnp.einsum('ij,jk->ijk', A, B)
(dh wir haben die Beschriftung beibehaltenj
):Summierachse
j
gibt das erwartete Punktprodukt an, das rechts gezeigt wird.Einige Übungen
Um mehr Gefühl dafür zu bekommen
einsum
, kann es nützlich sein, vertraute NumPy-Array-Operationen unter Verwendung der tiefgestellten Notation zu implementieren. Alles, was Kombinationen aus Multiplikations- und Summierachsen beinhaltet, kann mit geschrieben werdeneinsum
.A und B seien zwei 1D-Arrays gleicher Länge. Zum Beispiel
A = np.arange(10)
undB = np.arange(5, 15)
.Die Summe von
A
kann geschrieben werden:Elementweise Multiplikation
A * B
kann geschrieben werden:Das innere Produkt oder Punktprodukt
np.inner(A, B)
odernp.dot(A, B)
kann geschrieben werden:Das äußere Produkt
np.outer(A, B)
kann geschrieben werden:Für 2D-Arrays
C
undD
, vorausgesetzt, die Achsen sind kompatible Längen (beide haben dieselbe Länge oder eine von ihnen hat die Länge 1), hier einige Beispiele:Die Spur von
C
(Summe der Hauptdiagonale)np.trace(C)
kann geschrieben werden:Elementweise Multiplikation
C
und die transponierteD
,C * D.T
kann geschrieben werden:Das Multiplizieren jedes Elements
C
mit dem ArrayD
(um ein 4D-Array zuC[:, :, None, None] * D
erstellen ) kann wie folgt geschrieben werden:quelle
ij,jk
dies von selbst (ohne die Pfeile) funktionieren könnte, um die Matrixmultiplikation zu bilden. Aus Gründen der Übersichtlichkeit ist es jedoch am besten, die Pfeile und dann die Ausgabedimensionen zu platzieren. Es ist im Blog-Beitrag.A
es die Länge 3 hat, die gleiche Länge wie die Länge der Spalten inB
(während Zeilen mit derB
Länge 4 haben und nicht elementweise mit multipliziert werden könnenA
).->
Auswirkungen die Semantik beeinflusst: "Im impliziten Modus sind die ausgewählten Indizes wichtig, da die Achsen der Ausgabe alphabetisch neu angeordnet werden. Dies bedeutet, dassnp.einsum('ij', a)
ein 2D-Array nicht beeinflusst wird, während esnp.einsum('ji', a)
transponiert wird."Die Idee von zu erfassen
numpy.einsum()
ist sehr einfach, wenn Sie es intuitiv verstehen. Beginnen wir als Beispiel mit einer einfachen Beschreibung der Matrixmultiplikation .Um dies zu verwenden
numpy.einsum()
, müssen Sie lediglich die sogenannte Indexzeichenfolge als Argument übergeben, gefolgt von Ihren Eingabearrays .Angenommen, Sie haben zwei 2D - Arrays,
A
undB
, und Sie wollen Matrix - Multiplikation zu tun. Also tust du es:Hier entspricht die Indexzeichenfolge
ij
dem Array,A
während die Indexzeichenfolgejk
dem Array entsprichtB
. Das Wichtigste dabei ist auch, dass die Anzahl der Zeichen in jeder tiefgestellten Zeichenfolge mit den Abmessungen des Arrays übereinstimmen muss . (dh zwei Zeichen für 2D-Arrays, drei Zeichen für 3D-Arrays usw.) Wenn Sie die Zeichen zwischen tiefgestellten Zeichenfolgen (j
in unserem Fall) wiederholen , bedeutet dies, dass dieein
Summe entlang dieser Dimensionen erfolgen soll. Somit werden sie summenreduziert. (dh diese Dimension wird weg sein )Die Indexzeichenfolge danach
->
ist unser resultierendes Array. Wenn Sie es leer lassen, wird alles summiert und als Ergebnis ein Skalarwert zurückgegeben. Andernfalls hat das resultierende Array Dimensionen entsprechend der tiefgestellten Zeichenfolge . In unserem Beispiel wird es seinik
. Dies ist intuitiv, da wir wissen, dass für die Matrixmultiplikation die Anzahl der Spalten im ArrayA
mit der Anzahl der Zeilen im Array übereinstimmen muss,B
was hier geschieht (dh wir codieren dieses Wissen, indem wir das Zeichenj
in der tiefgestellten Zeichenfolge wiederholen ).Hier sind einige weitere Beispiele , die kurz und bündig die Verwendung / Leistungsfähigkeit bei der
np.einsum()
Implementierung einiger gängiger Tensor- oder nd-Array- Operationen veranschaulichen .Eingänge
1) Matrixmultiplikation (ähnlich wie
np.matmul(arr1, arr2)
)2) Elemente entlang der Hauptdiagonale extrahieren (ähnlich wie
np.diag(arr)
)3) Hadamard-Produkt (dh elementweises Produkt zweier Arrays) (ähnlich
arr1 * arr2
)4) Elementweises Quadrieren (ähnlich
np.square(arr)
oderarr ** 2
)5) Spur (dh Summe der Hauptdiagonalelemente) (ähnlich wie
np.trace(arr)
)6) Matrixtransponierung (ähnlich wie
np.transpose(arr)
)7) Äußeres Produkt (von Vektoren) (ähnlich
np.outer(vec1, vec2)
)8) Inneres Produkt (von Vektoren) (ähnlich
np.inner(vec1, vec2)
)9) Summe entlang der Achse 0 (ähnlich wie
np.sum(arr, axis=0)
)10) Summe entlang der Achse 1 (ähnlich wie
np.sum(arr, axis=1)
)11) Batch-Matrix-Multiplikation
12) Summe entlang der Achse 2 (ähnlich wie
np.sum(arr, axis=2)
)13) Summiere alle Elemente im Array (ähnlich wie
np.sum(arr)
)14) Summe über mehrere Achsen (dh Marginalisierung)
(ähnlich wie
np.sum(arr, axis=(axis0, axis1, axis2, axis3, axis4, axis6, axis7))
)15) Double Dot Produkte (ähnlich np.sum (hadamard Produkt) s 3 )
16) 2D- und 3D-Array-Multiplikation
Eine solche Multiplikation kann sehr nützlich sein, wenn Sie ein lineares Gleichungssystem ( Ax = b ) lösen, bei dem Sie das Ergebnis überprüfen möchten.
Im Gegenteil, wenn man
np.matmul()
für diese Überprüfung verwenden muss, müssen wir einigereshape
Operationen ausführen, um das gleiche Ergebnis zu erzielen:Bonus : Lesen Sie hier mehr Mathe: Einstein-Summation und definitiv hier: Tensor-Notation
quelle
Erstellen wir zwei Arrays mit unterschiedlichen, aber kompatiblen Abmessungen, um deren Zusammenspiel hervorzuheben
Ihre Berechnung verwendet einen 'Punkt' (Summe der Produkte) von a (2,3) mit a (3,4), um ein (4,2) -Array zu erzeugen.
i
ist das 1. Dim vonA
, das letzte vonC
;k
der letzte vonB
, 1. vonC
.j
wird durch die Summe 'verbraucht'.Dies ist das Gleiche wie
np.dot(A,B).T
- es ist die endgültige Ausgabe, die transponiert wird.Um mehr darüber zu
j
erfahren, was passiert , ändern Sie dieC
Indizes inijk
:Dies kann auch hergestellt werden mit:
Fügen Sie also eine
k
Dimension am EndeA
und eine am Anfangi
von hinzuB
, was zu einem (2,3,4) -Array führt.0 + 4 + 16 = 20
,9 + 28 + 55 = 92
Etc; Summierej
und transponiere, um das frühere Ergebnis zu erhalten:quelle
Ich fand NumPy: Die Tricks des Handels (Teil II) lehrreich
Beachten Sie, dass es drei Achsen gibt, i, j, k, und dass j wiederholt wird (auf der linken Seite).
i,j
stellen Zeilen und Spalten für dara
.j,k
fürb
.Um das Produkt zu berechnen und die
j
Achse auszurichten , müssen wir eine Achse hinzufügena
. (b
wird entlang (?) der ersten Achse ausgestrahlt)j
fehlt auf der rechten Seite, also summieren wir, überj
welcher die zweite Achse des 3x3x3-Arrays liegtSchließlich werden die Indizes auf der rechten Seite (alphabetisch) umgekehrt, sodass wir transponieren.
quelle
Beim Lesen von einsum-Gleichungen fand ich es am hilfreichsten, sie nur mental auf ihre imperativen Versionen reduzieren zu können.
Beginnen wir mit der folgenden (imposanten) Aussage:
Wenn wir zuerst die Interpunktion durcharbeiten, sehen wir, dass wir zwei durch Kommas getrennte 4-Buchstaben-Blobs haben -
bhwi
undbhwj
vor dem Pfeil und einen einzelnen 3-Buchstaben-Blobbij
danach. Daher erzeugt die Gleichung ein Tensor-Ergebnis vom Rang 3 aus zwei Tensoreingaben vom Rang 4.Lassen Sie nun jeden Buchstaben in jedem Blob den Namen einer Bereichsvariablen sein. Die Position, an der der Buchstabe im Blob erscheint, ist der Index der Achse, über die er sich in diesem Tensor erstreckt. Die imperative Summation, die jedes Element von C erzeugt, muss daher mit drei verschachtelten for-Schleifen beginnen, eine für jeden Index von C.
Sie haben also im Wesentlichen eine
for
Schleife für jeden Ausgabeindex von C. Wir lassen die Bereiche vorerst unbestimmt.Als nächstes schauen wir uns die linke Seite an - gibt es dort Bereichsvariablen, die nicht auf der rechten Seite erscheinen? In unserem Fall - ja
h
undw
. Fügen Sie eine innere Verschachtelung hinzufor
für jede solche Variable Schleife hinzu:In der innersten Schleife sind jetzt alle Indizes definiert, sodass wir die eigentliche Summierung schreiben können und die Übersetzung abgeschlossen ist:
Wenn Sie bisher dem Code folgen konnten, dann herzlichen Glückwunsch! Dies ist alles, was Sie brauchen, um einsum-Gleichungen lesen zu können. Beachten Sie insbesondere, wie die ursprüngliche Einsum-Formel der endgültigen Summationsanweisung im obigen Snippet zugeordnet ist. Die for-Schleifen und Bereichsgrenzen sind nur Flusen und diese abschließende Aussage ist alles, was Sie wirklich brauchen, um zu verstehen, was los ist.
Der Vollständigkeit halber sehen wir uns an, wie die Bereiche für jede Bereichsvariable bestimmt werden. Nun, der Bereich jeder Variablen ist einfach die Länge der Dimension (en), die sie indiziert. Wenn eine Variable mehr als eine Dimension in einem oder mehreren Tensoren indiziert, müssen natürlich die Längen jeder dieser Dimensionen gleich sein. Hier ist der obige Code mit den vollständigen Bereichen:
quelle