Der schnellste Weg, um ein numpy numerisches Array zu vergrößern

80

Bedarf:

  • Ich muss ein beliebig großes Array aus Daten vergrößern.
  • Ich kann die Größe (ungefähr 100-200) erraten, ohne zu garantieren, dass das Array jedes Mal passt
  • Sobald es auf seine endgültige Größe angewachsen ist, muss ich numerische Berechnungen durchführen, daher würde ich es vorziehen, irgendwann zu einem 2-D-Numpy-Array zu gelangen.
  • Geschwindigkeit ist entscheidend. Beispielsweise wird für eine von 300 Dateien die update () -Methode 45 Millionen Mal aufgerufen (dauert ungefähr 150 Sekunden), und die finalize () -Methode wird 500.000 Mal aufgerufen (dauert insgesamt 106 Sekunden) ... insgesamt 250 Sekunden oder so.

Hier ist mein Code:

def __init__(self):
    self.data = []

def update(self, row):
    self.data.append(row)

def finalize(self):
    dx = np.array(self.data)

Andere Dinge, die ich versucht habe, sind der folgende Code ... aber das ist waaaaay langsamer.

def class A:
    def __init__(self):
        self.data = np.array([])

    def update(self, row):
        np.append(self.data, row)

    def finalize(self):
        dx = np.reshape(self.data, size=(self.data.shape[0]/5, 5))

Hier ist ein Schema, wie dies genannt wird:

for i in range(500000):
    ax = A()
    for j in range(200):
         ax.update([1,2,3,4,5])
    ax.finalize()
    # some processing on ax
Fodon
quelle
2
Muss es ein numpy Array sein, bevor es fertig ist? Wenn nicht, verwenden Sie eine Liste von Listen und konvertieren Sie sie, wenn Sie fertig sind.
Andrew Jaffe
1
@AndrewJaffe Stimmen Listenlisten mit der Speichereffizienz von numpy überein?
AturSams

Antworten:

96

Ich habe ein paar verschiedene Dinge ausprobiert, mit Timing.

import numpy as np
  1. Die Methode, die Sie als langsam erwähnen: (32.094 Sekunden)

    class A:
    
        def __init__(self):
            self.data = np.array([])
    
        def update(self, row):
            self.data = np.append(self.data, row)
    
        def finalize(self):
            return np.reshape(self.data, newshape=(self.data.shape[0]/5, 5))
    
  2. Regelmäßige alte Python-Liste: (0,308 Sekunden)

    class B:
    
        def __init__(self):
            self.data = []
    
        def update(self, row):
            for r in row:
                self.data.append(r)
    
        def finalize(self):
            return np.reshape(self.data, newshape=(len(self.data)/5, 5))
    
  3. Versuch, eine Arrayliste in Numpy zu implementieren: (0,362 Sekunden)

    class C:
    
        def __init__(self):
            self.data = np.zeros((100,))
            self.capacity = 100
            self.size = 0
    
        def update(self, row):
            for r in row:
                self.add(r)
    
        def add(self, x):
            if self.size == self.capacity:
                self.capacity *= 4
                newdata = np.zeros((self.capacity,))
                newdata[:self.size] = self.data
                self.data = newdata
    
            self.data[self.size] = x
            self.size += 1
    
        def finalize(self):
            data = self.data[:self.size]
            return np.reshape(data, newshape=(len(data)/5, 5))
    

Und so habe ich es geplant:

x = C()
for i in xrange(100000):
    x.update([i])

Es sieht also so aus, als wären normale alte Python-Listen ziemlich gut;)

Owen
quelle
1
Ich denke, der Vergleich ist klarer mit 60 Millionen Updates und 500.000 abgeschlossenen Anrufen. In diesem Beispiel haben Sie anscheinend nicht finalize aufgerufen.
Fodon
1
@fodon Ich habe tatsächlich finalize aufgerufen - einmal pro Lauf (also denke ich nicht wirklich eine große Auswirkung). Aber das lässt mich denken, dass ich vielleicht falsch verstanden habe, wie Ihre Daten wachsen: Wenn Sie bei einem Update 60 Millionen Daten erhalten, dachte ich, dies würde mindestens 60 Millionen Daten für die nächste Finalisierung liefern?
Owen
@Owen 60M und 500K bedeuten 60 Millionen bzw. 500.000 Anrufe an updateund finalize. Siehe mein überarbeitetes Timing, das ein 100: 1-Verhältnis von updatezufinalize
Prashant Kumar
Ich habe die Frage mit einem kurzen Skript aktualisiert (das möglicherweise nicht syntaktisch korrekt ist), um eine Vorstellung davon zu geben, wie dies funktioniert.
Fodon
3
Beachten Sie, dass die dritte Option überlegen ist, wenn Ihnen der Speicher ausgeht. Die zweite Option erfordert viel Speicher. Der Grund dafür ist, dass Pythons Listen Arrays von Verweisen auf Werte sind, während NumPys Arrays tatsächliche Arrays von Werten sind.
Fabianius
20

np.append () kopiert jedes Mal alle Daten im Array, aber list vergrößert die Kapazität um einen Faktor (1.125). Liste ist schnell, aber die Speichernutzung ist größer als Array. Sie können das Array-Modul der Python-Standardbibliothek verwenden, wenn Sie sich für den Speicher interessieren.

Hier ist eine Diskussion zu diesem Thema:

So erstellen Sie ein dynamisches Array

HYRY
quelle
2
Gibt es eine Möglichkeit, den Faktor zu ändern, um den die Liste wächst?
Fodon
1
Der Zeitaufwand für np.append () nimmt exponentiell mit der Anzahl der Elemente zu.
Uhr ZHONG
1
^ linear (dh die akkumulierte Gesamtzeit ist quadratisch), nicht exponentiell.
user1111929
15

Unter Verwendung der Klassendeklarationen in Owens Beitrag finden Sie hier ein überarbeitetes Timing mit einigen Auswirkungen des Finalisierens.

Kurz gesagt, ich finde Klasse C, um eine Implementierung bereitzustellen, die über 60x schneller ist als die Methode im ursprünglichen Beitrag. (Entschuldigung für die Textwand)

Die Datei, die ich verwendet habe:

#!/usr/bin/python
import cProfile
import numpy as np

# ... class declarations here ...

def test_class(f):
    x = f()
    for i in xrange(100000):
        x.update([i])
    for i in xrange(1000):
        x.finalize()

for x in 'ABC':
    cProfile.run('test_class(%s)' % x)

Nun die resultierenden Timings:

EIN:

     903005 function calls in 16.049 seconds

Ordered by: standard name

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
     1    0.000    0.000   16.049   16.049 <string>:1(<module>)
100000    0.139    0.000    1.888    0.000 fromnumeric.py:1043(ravel)
  1000    0.001    0.000    0.003    0.000 fromnumeric.py:107(reshape)
100000    0.322    0.000   14.424    0.000 function_base.py:3466(append)
100000    0.102    0.000    1.623    0.000 numeric.py:216(asarray)
100000    0.121    0.000    0.298    0.000 numeric.py:286(asanyarray)
  1000    0.002    0.000    0.004    0.000 test.py:12(finalize)
     1    0.146    0.146   16.049   16.049 test.py:50(test_class)
     1    0.000    0.000    0.000    0.000 test.py:6(__init__)
100000    1.475    0.000   15.899    0.000 test.py:9(update)
     1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
100000    0.126    0.000    0.126    0.000 {method 'ravel' of 'numpy.ndarray' objects}
  1000    0.002    0.000    0.002    0.000 {method 'reshape' of 'numpy.ndarray' objects}
200001    1.698    0.000    1.698    0.000 {numpy.core.multiarray.array}
100000   11.915    0.000   11.915    0.000 {numpy.core.multiarray.concatenate}

B:

     208004 function calls in 16.885 seconds

Ordered by: standard name

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
     1    0.001    0.001   16.885   16.885 <string>:1(<module>)
  1000    0.025    0.000   16.508    0.017 fromnumeric.py:107(reshape)
  1000    0.013    0.000   16.483    0.016 fromnumeric.py:32(_wrapit)
  1000    0.007    0.000   16.445    0.016 numeric.py:216(asarray)
     1    0.000    0.000    0.000    0.000 test.py:16(__init__)
100000    0.068    0.000    0.080    0.000 test.py:19(update)
  1000    0.012    0.000   16.520    0.017 test.py:23(finalize)
     1    0.284    0.284   16.883   16.883 test.py:50(test_class)
  1000    0.005    0.000    0.005    0.000 {getattr}
  1000    0.001    0.000    0.001    0.000 {len}
100000    0.012    0.000    0.012    0.000 {method 'append' of 'list' objects}
     1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
  1000    0.020    0.000    0.020    0.000 {method 'reshape' of 'numpy.ndarray' objects}
  1000   16.438    0.016   16.438    0.016 {numpy.core.multiarray.array}

C:

     204010 function calls in 0.244 seconds

Ordered by: standard name

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
     1    0.000    0.000    0.244    0.244 <string>:1(<module>)
  1000    0.001    0.000    0.003    0.000 fromnumeric.py:107(reshape)
     1    0.000    0.000    0.000    0.000 test.py:27(__init__)
100000    0.082    0.000    0.170    0.000 test.py:32(update)
100000    0.087    0.000    0.088    0.000 test.py:36(add)
  1000    0.002    0.000    0.005    0.000 test.py:46(finalize)
     1    0.068    0.068    0.243    0.243 test.py:50(test_class)
  1000    0.000    0.000    0.000    0.000 {len}
     1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
  1000    0.002    0.000    0.002    0.000 {method 'reshape' of 'numpy.ndarray' objects}
     6    0.001    0.000    0.001    0.000 {numpy.core.multiarray.zeros}

Klasse A wird durch die Aktualisierungen zerstört, Klasse B wird durch die Finalisierungen zerstört. Klasse C ist gegenüber beiden robust.

Prashant Kumar
quelle
Das Update wird ein Mal durchgeführt, dann wird finalize einmal aufgerufen. Dieser gesamte Vorgang wird m-mal ausgeführt (andernfalls müssen keine Daten finalisiert werden). Auch im Vergleich zum ursprünglichen Beitrag ... meinen Sie die erste (array.append + numpy-Konvertierung) oder (numpy.append + reshape)?
Fodon
1
cProfile. Es ist der erste Import und die letzte Zeile, die in meinem Code-Snippet aufgerufen wird.
Prashant Kumar
5

Es gibt einen großen Leistungsunterschied in der Funktion, die Sie für die Finalisierung verwenden. Betrachten Sie den folgenden Code:

N=100000
nruns=5

a=[]
for i in range(N):
    a.append(np.zeros(1000))

print "start"

b=[]
for i in range(nruns):
    s=time()
    c=np.vstack(a)
    b.append((time()-s))
print "Timing version vstack ",np.mean(b)

b=[]
for i in range(nruns):
    s=time()
    c1=np.reshape(a,(N,1000))
    b.append((time()-s))

print "Timing version reshape ",np.mean(b)

b=[]
for i in range(nruns):
    s=time()
    c2=np.concatenate(a,axis=0).reshape(-1,1000)
    b.append((time()-s))

print "Timing version concatenate ",np.mean(b)

print c.shape,c2.shape
assert (c==c2).all()
assert (c==c1).all()

Die Verwendung von Verkettung scheint doppelt so schnell wie die erste Version und mehr als zehnmal schneller als die zweite Version zu sein.

Timing version vstack  1.5774928093
Timing version reshape  9.67419199944
Timing version concatenate  0.669512557983
Luca Fiaschi
quelle
1

Wenn Sie die Leistung mit Listenoperationen verbessern möchten, schauen Sie sich die Blist-Bibliothek an. Es ist eine optimierte Implementierung von Python-Listen und anderen Strukturen.

Ich habe es noch nicht bewertet, aber die Ergebnisse auf ihrer Seite scheinen vielversprechend.

joaonrb
quelle