Warum ist Looping Beat Indexierung hier?

Vor ein paar Jahren hat jemand auf Active State Rezepte für Vergleichszwecke, drei Python / NumPy-Funktionen veröffentlicht; Jeder von ihnen akzeptiert die gleichen Argumente und gab das gleiche Ergebnis, eine Distanzmatrix .

Zwei davon wurden aus veröffentlichten Quellen entnommen; Sie sind beide – oder sie scheinen mir zu sein – idiomatischer numpy code. Die Wiederholungsberechnungen, die erforderlich sind, um eine Distanzmatrix zu erzeugen, werden durch die elegante Index-Syntax von numpy gesteuert. Hier ist einer von ihnen:

from numpy.matlib import repmat, repeat def calcDistanceMatrixFastEuclidean(points): numPoints = len(points) distMat = sqrt(sum((repmat(points, numPoints, 1) - repeat(points, numPoints, axis=0))**2, axis=1)) return distMat.reshape((numPoints,numPoints)) 

Die dritte schuf die Distanzmatrix mit einer einzigen Schleife (die offensichtlich viel Schleife ist, da eine Distanzmatrix von nur 1.000 2D Punkten eine Million Einträge hat). Auf den ersten Blick sah diese Funktion mir wie der Code, den ich geschrieben habe, als ich NumPy lernte und ich würde NumPy-Code schreiben, indem ich zuerst Python-Code schreibe und ihn dann Zeile für Zeile übersetzte.

Einige Monate nach dem Active State Post wurden Ergebnisse von Performance-Tests, die die drei verglichen, in einem Thread auf der NumPy-Mailingliste veröffentlicht und diskutiert.

Die Funktion mit der Schleife hat die beiden anderen deutlich übertroffen :

 from numpy import mat, zeros, newaxis def calcDistanceMatrixFastEuclidean2(nDimPoints): nDimPoints = array(nDimPoints) n,m = nDimPoints.shape delta = zeros((n,n),'d') for d in xrange(m): data = nDimPoints[:,d] delta += (data - data[:,newaxis])**2 return sqrt(delta) 

Ein Teilnehmer am Thread (Keir Mierle) bot einen Grund an, warum dies wahr sein könnte:

Der Grund, dass ich vermute, dass dies schneller sein wird, ist, dass es eine bessere Lokalität, komplett beenden eine Berechnung auf einem relativ kleinen Arbeitssatz vor dem Umzug auf die nächste. Die Einsteiger müssen das potentiell große MxN-Array wiederholt in den Prozessor ziehen.

Auf dem eigenen Konto des Plakats ist seine Bemerkung nur ein Verdacht, und es scheint nicht, daß es weiter diskutiert wurde.

Irgendwelche anderen Gedanken darüber, wie diese Ergebnisse zu berücksichtigen?

Insbesondere gibt es eine nützliche Regel – in Bezug auf, wenn zu schleifen und wann zu indexieren -, die aus diesem Beispiel als Anleitung zum Schreiben von numpy Code extrahiert werden können?

Für diejenigen, die nicht mit NumPy vertraut sind oder die den Code nicht angesehen haben, basiert dieser Vergleich nicht auf einem Randfall – es wäre sicherlich nicht so interessant für mich, wenn es so wäre. Stattdessen handelt es sich bei diesem Vergleich um eine Funktion, die eine gemeinsame Aufgabe in der Matrixberechnung ausführt (dh ein Ergebnis-Array bei zwei Vorgängern zu erstellen); Darüber hinaus besteht jede Funktion wiederum aus den gebräuchlichsten neunten Einbauten.

2 Solutions collect form web for “Warum ist Looping Beat Indexierung hier?”

TL; DR Der zweite Code oben ist nur Schleife über die Anzahl der Dimensionen der Punkte (3 mal durch die for-Schleife für 3D-Punkte), so dass die Schleife ist nicht viel da. Die echte Beschleunigung in der zweiten Code oben ist, dass es besser nutzt die Macht der Numpy zu vermeiden, dass einige zusätzliche Matrizen bei der Suche nach den Unterschieden zwischen Punkten. Das reduziert den eingesetzten Speicher und den Rechenaufwand.

calcDistanceMatrixFastEuclidean2 Erklärung Ich denke, dass die Funktion calcDistanceMatrixFastEuclidean2 Sie mit ihrer Schleife täuscht. Es ist nur Schleife über die Anzahl der Dimensionen der Punkte. Bei 1D-Punkten läuft die Schleife nur einmal, für 2D, zweimal und für 3D dreimal. Das ist wirklich nicht viel Schleife überhaupt.

Lasst uns den Code ein wenig analysieren, um zu sehen, warum der eine schneller ist als der andere. calcDistanceMatrixFastEuclidean Ich rufe fast1 und calcDistanceMatrixFastEuclidean2 wird fast2 .

fast1 basiert auf dem Matlab-Weg, Dinge zu tun, wie sich aus der repmap Funktion ergibt. Die repmap Funktion erzeugt in diesem Fall ein Array, bei dem nur die Originaldaten immer wieder wiederholt werden. Allerdings, wenn man sich den Code für die Funktion, ist es sehr ineffizient. Es verwendet viele Numpy-Funktionen (3 Reshape s und 2 repeat ), um dies zu tun. Die repeat wird auch verwendet, um ein Array zu erstellen, das die ursprünglichen Daten enthält, wobei jedes Datenelement mehrmals wiederholt wird. Wenn unsere Eingangsdaten [1,2,3] dann subtrahieren wir [1,2,3,1,2,3,1,2,3] von [1,1,1,2,2,2,3,3,3] . Numpy musste eine Menge zusätzlicher Matrizen zwischen dem Ausführen von Numpys C-Code erstellen, der vermieden werden könnte.

fast2 nutzt mehr von Numpys schwerem Heben, ohne so viele Matrizen zwischen Numpy-Anrufen zu erzeugen. fast2 Schleifen durch jede Dimension der Punkte, tut die Subtraktion und hält eine laufende Summe der quadratischen Unterschiede zwischen jeder Dimension. Erst am Ende ist die Quadratwurzel fertig. So weit, das klingt vielleicht nicht so effizient wie fast1 , aber fast2 vermeidet es, das repmat Zeug zu machen, indem du Numpys Indexierung benutzt. Schauen wir uns den 1D-Fall zur Vereinfachung an. fast2 macht ein 1D-Array der Daten und subtrahiert es von einem 2D (N x 1) Array der Daten. Dies schafft die Differenzmatrix zwischen jedem Punkt und allen anderen Punkten, ohne repmat zu verwenden und zu repeat und dadurch repmat dass viele zusätzliche Arrays entstehen. Hier liegt der wirkliche Geschwindigkeitsunterschied meiner Meinung nach. fast1 schafft eine Menge Extra zwischen Matrizen (und sie werden aufwendig rechnerisch erstellt), um die Unterschiede zwischen den Punkten zu finden, während fast2 besser die Kraft von Numpy benutzt, um diese zu vermeiden.

Übrigens, hier ist ein bisschen schnellere Version von fast2 :

 def calcDistanceMatrixFastEuclidean3(nDimPoints): nDimPoints = array(nDimPoints) n,m = nDimPoints.shape data = nDimPoints[:,0] delta = (data - data[:,newaxis])**2 for d in xrange(1,m): data = nDimPoints[:,d] delta += (data - data[:,newaxis])**2 return sqrt(delta) 

Der Unterschied ist, dass wir nicht mehr Delta als Nullenmatrix erzeugen.

dis zum spaß:

dis.dis(calcDistanceMatrixFastEuclidean)

  2 0 LOAD_GLOBAL 0 (len) 3 LOAD_FAST 0 (points) 6 CALL_FUNCTION 1 9 STORE_FAST 1 (numPoints) 3 12 LOAD_GLOBAL 1 (sqrt) 15 LOAD_GLOBAL 2 (sum) 18 LOAD_GLOBAL 3 (repmat) 21 LOAD_FAST 0 (points) 24 LOAD_FAST 1 (numPoints) 27 LOAD_CONST 1 (1) 30 CALL_FUNCTION 3 4 33 LOAD_GLOBAL 4 (repeat) 36 LOAD_FAST 0 (points) 39 LOAD_FAST 1 (numPoints) 42 LOAD_CONST 2 ('axis') 45 LOAD_CONST 3 (0) 48 CALL_FUNCTION 258 51 BINARY_SUBTRACT 52 LOAD_CONST 4 (2) 55 BINARY_POWER 56 LOAD_CONST 2 ('axis') 59 LOAD_CONST 1 (1) 62 CALL_FUNCTION 257 65 CALL_FUNCTION 1 68 STORE_FAST 2 (distMat) 5 71 LOAD_FAST 2 (distMat) 74 LOAD_ATTR 5 (reshape) 77 LOAD_FAST 1 (numPoints) 80 LOAD_FAST 1 (numPoints) 83 BUILD_TUPLE 2 86 CALL_FUNCTION 1 89 RETURN_VALUE 

dis.dis(calcDistanceMatrixFastEuclidean2)

  2 0 LOAD_GLOBAL 0 (array) 3 LOAD_FAST 0 (nDimPoints) 6 CALL_FUNCTION 1 9 STORE_FAST 0 (nDimPoints) 3 12 LOAD_FAST 0 (nDimPoints) 15 LOAD_ATTR 1 (shape) 18 UNPACK_SEQUENCE 2 21 STORE_FAST 1 (n) 24 STORE_FAST 2 (m) 4 27 LOAD_GLOBAL 2 (zeros) 30 LOAD_FAST 1 (n) 33 LOAD_FAST 1 (n) 36 BUILD_TUPLE 2 39 LOAD_CONST 1 ('d') 42 CALL_FUNCTION 2 45 STORE_FAST 3 (delta) 5 48 SETUP_LOOP 76 (to 127) 51 LOAD_GLOBAL 3 (xrange) 54 LOAD_FAST 2 (m) 57 CALL_FUNCTION 1 60 GET_ITER >> 61 FOR_ITER 62 (to 126) 64 STORE_FAST 4 (d) 6 67 LOAD_FAST 0 (nDimPoints) 70 LOAD_CONST 0 (None) 73 LOAD_CONST 0 (None) 76 BUILD_SLICE 2 79 LOAD_FAST 4 (d) 82 BUILD_TUPLE 2 85 BINARY_SUBSCR 86 STORE_FAST 5 (data) 7 89 LOAD_FAST 3 (delta) 92 LOAD_FAST 5 (data) 95 LOAD_FAST 5 (data) 98 LOAD_CONST 0 (None) 101 LOAD_CONST 0 (None) 104 BUILD_SLICE 2 107 LOAD_GLOBAL 4 (newaxis) 110 BUILD_TUPLE 2 113 BINARY_SUBSCR 114 BINARY_SUBTRACT 115 LOAD_CONST 2 (2) 118 BINARY_POWER 119 INPLACE_ADD 120 STORE_FAST 3 (delta) 123 JUMP_ABSOLUTE 61 >> 126 POP_BLOCK 8 >> 127 LOAD_GLOBAL 5 (sqrt) 130 LOAD_FAST 3 (delta) 133 CALL_FUNCTION 1 136 RETURN_VALUE 

Ich bin kein Experte auf dem, aber es scheint, als müssten Sie mehr auf die Funktionen, die die erste ruft zu wissen, warum sie eine Weile dauern. Es gibt auch ein Performance-Profiler-Tool mit Python, cProfile .

  • Kosten der Listenfunktionen in Python
  • Warum lädt SQLAlchemy-Objekte über das ORM 5-8x langsamer als die Zeilen über einen rohen MySQLdb-Cursor?
  • Ist es besser, eine Ausnahme oder einen Rückkehrcode in Python zu verwenden?
  • Überrascht über gute Rekursionsleistung in Python
  • Setzen Sie die Zeile von csr_matrix
  • Gute Graphen-Traversal-Algorithmus
  • High Performance Server - was soll ich verwenden?
  • Unterschiede zwischen "d = dict ()" und "d = {}"
  • Effizienter Algorithmus zur Auswertung eines 1-d-Arrays von Funktionen auf gleichem 1d-Numpy-Array
  • Schnelle python numpy wo Funktionalität?
  • Effizienter Weg zum Loop?
  • Python ist die beste Programmiersprache der Welt.