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 .

  • Wie können Sie ein Skript profilieren?
  • Sehr anspruchsvolle Quaryset-Filterung, Sortierung und Annotation (in Django-basierter App)
  • Warum ist Tupel schneller als die Liste?
  • Schnellste Methode, um große zufällige Zeichenfolge mit niedrigeren lateinischen Buchstaben zu erzeugen
  • Wie schreibe ich eine Liste von Ganzzahlen in eine Binärdatei in Python
  • Python: Iteration über Liste vs über dict Elemente Effizienz
  • Warum ist das Entfernen der sonst verlangsamen mein Code?
  • Python, Pairwise 'Distanz', brauchen einen schnellen Weg, es zu tun
  • Pandas ersetzen / Wörterbuch Langsamkeit
  • Shelve ist zu langsam für große Wörterbücher, was kann ich tun, um die Leistung zu verbessern?
  • Effiziente Möglichkeit, das Element in einem Wörterbuch in Python mit einer Schleife zu zählen
  • Python ist die beste Programmiersprache der Welt.