Frage Multiplizieren Sie Matrizen höherer Ordnung mit numpy


Ich habe dieses Spielzeugproblem erstellt, das mein viel größeres Problem widerspiegelt:

import numpy as np

ind = np.ones((3,2,4)) # shape=(3L, 2L, 4L)
dist = np.array([[0.1,0.3],[1,2],[0,1]]) # shape=(3L, 2L)

ans = np.array([np.dot(dist[i],ind[i]) for i in xrange(dist.shape[0])]) # shape=(3L, 4L)
print ans

""" prints:
   [[ 0.4  0.4  0.4  0.4]
    [ 3.   3.   3.   3. ]
    [ 1.   1.   1.   1. ]]
"""

Ich möchte es so schnell wie möglich machen, also benutze numpys Funktionen um zu berechnen ans sollte der beste Ansatz sein, da diese Operation schwer ist und meine Matrizen ziemlich groß sind.

ich sah dieser Beitrag, aber die Formen sind unterschiedlich und ich kann nicht verstehen, welche axes Ich sollte für dieses Problem verwenden. Aber ich bin mir sicher Tensordot sollte die Antwort haben. Irgendwelche Vorschläge?

EDIT: Ich habe akzeptiert @ Ajcrs Antwort, aber bitte lesen Sie meine eigene Antwort, es kann anderen helfen ...


10
2018-06-13 08:43


Ursprung


Antworten:


Du könntest benutzen np.einsum um die Operation durchzuführen, da es eine sehr sorgfältige Kontrolle darüber erlaubt, welche Achsen multipliziert werden und welche addiert werden:

>>> np.einsum('ijk,ij->ik', ind, dist)
array([[ 0.4,  0.4,  0.4,  0.4],
       [ 3. ,  3. ,  3. ,  3. ],
       [ 1. ,  1. ,  1. ,  1. ]])

Die Funktion multipliziert die Einträge in der ersten Achse von ind mit den Einträgen in der ersten Achse von dist (Index 'i'). Dito für die zweite Achse jedes Arrays (tiefgestellt 'j'). Anstatt ein 3D-Array zurückzugeben, teilen wir einsum mit, die Werte entlang der Achse zu summieren 'j' indem sie von den Ausgabeskripten weggelassen wird, wodurch ein 2D-Array zurückgegeben wird.


np.tensordot ist schwieriger auf dieses Problem anzuwenden. Es summiert automatisch die Produkte von Achsen. Aber wir wollen zwei Sätze von Produkten, aber nur zu summieren ein von ihnen.

Schreiben np.tensordot(ind, dist, axes=[1, 1]) (wie in der Antwort, die Sie verknüpft haben) berechnet die richtigen Werte für Sie, aber gibt ein 3D-Array mit Form zurück (3, 4, 3). Wenn Sie sich die Speicherkosten eines größeren Arrays leisten können, könnten Sie Folgendes verwenden:

np.tensordot(ind, dist, axes=[1, 1])[0].T

Dies gibt Ihnen das richtige Ergebnis, aber weil tensordot erstellt zuerst ein Array, das viel größer als nötig ist einsum scheint eine bessere Option zu sein.


14
2018-06-13 09:22



Folgend @ Ajcrs großartige AntwortIch wollte herausfinden, welche Methode am schnellsten ist, also habe ich sie benutzt timeit :

import timeit

setup_code = """
import numpy as np
i,j,k = (300,200,400)
ind = np.ones((i,j,k)) #shape=(3L, 2L, 4L)
dist = np.random.rand(i,j) #shape=(3L, 2L)
"""

basic ="np.array([np.dot(dist[l],ind[l]) for l in xrange(dist.shape[0])])"
einsum = "np.einsum('ijk,ij->ik', ind, dist)"
tensor= "np.tensordot(ind, dist, axes=[1, 1])[0].T"

print "tensor - total time:", min(timeit.repeat(stmt=tensor,setup=setup_code,number=10,repeat=3))
print "basic - total time:", min(timeit.repeat(stmt=basic,setup=setup_code,number=10,repeat=3))
print "einsum - total time:", min(timeit.repeat(stmt=einsum,setup=setup_code,number=10,repeat=3))

Die überraschenden Ergebnisse waren:

tensor - total time: 6.59519493952
basic - total time: 0.159871203461
einsum - total time: 0.263569731028

Also offensichtlich mit Tensordot war der falsche Weg, es zu tun (nicht zu erwähnen memory error in größeren Beispielen, wie @ajcr angegeben hat).

Da dieses Beispiel klein war, habe ich die Größe der Matrizen geändert i,j,k = (3000,200,400), drehte die Reihenfolge um, um sicher zu sein, dass es keinen Effekt hatte und richtete einen weiteren Test mit höheren Wiederholungszahlen ein:

print "einsum - total time:", min(timeit.repeat(stmt=einsum,setup=setup_code,number=50,repeat=3))
print "basic - total time:", min(timeit.repeat(stmt=basic,setup=setup_code,number=50,repeat=3))

Die Ergebnisse stimmten mit dem ersten Lauf überein:

einsum - total time: 13.3184077671
basic - total time: 8.44810031351

Allerdings testet eine andere Art von Größenwachstum - i,j,k = (30000,20,40) führte folgende Ergebnisse:

einsum - total time: 0.325594117768
basic - total time: 0.926416766397

In den Kommentaren finden Sie Erläuterungen zu diesen Ergebnissen.

Die Moral ist es, bei der Suche nach der schnellsten Lösung für ein spezifisches Problem, Daten zu erzeugen, die den ursprünglichen Daten möglichst ähnlich sind, und zwar in Form von Typen und Formen. In meinem Fall i ist viel kleiner als j,k und so blieb ich bei der hässlichen Version, die in diesem Fall auch die schnellste ist.


14
2018-06-13 13:07