Ich verwende derzeit die Python-Bindungen von GDAL, um mit ziemlich großen Raster-Datensätzen (> 4 GB) zu arbeiten. Da das Laden in den Speicher auf einmal keine praktikable Lösung für mich ist, lese ich sie in kleinere Blöcke und führe die Berechnungen Stück für Stück durch. Um eine neue Zuordnung für jeden gelesenen Block zu vermeiden, verwende ich das buf_obj
Argument (here), um die Werte in ein vorbelegtes NumPy-Array einzulesen. An einem Punkt muss ich die mittlere und Standardabweichung des gesamten Rasters berechnen. Natürlich habe ich np.std
für die Berechnung verwendet. Durch das Profiling des Speicherverbrauchs meines Programms habe ich jedoch festgestellt, dass bei jedem Aufruf von np.std
zusätzlich Speicher zugewiesen und freigegeben wird.Speicherverbrauch der NumPy-Funktion für Standardabweichung
Ein Minimum Beispiel arbeitet, die dieses Verhalten zeigt:
In [1] import numpy as np
In [2] a = np.random.rand(20e6) # Approx. 150 MiB of memory
In [3] %memit np.mean(a)
peak memory: 187.30 MiB, increment: 0.48 MiB
In [4] %memit np.std(a)
peak memory: 340.24 MiB, increment: 152.91 MiB
Eine Suche ergab im Quellbaum von NumPy auf GitHub, dass die np.std
Funktion intern die _var
Funktion von _methods.py
ruft (here). An einem Punkt berechnet _var
die Abweichungen vom Mittelwert und summiert sie. Daher wird eine temporäre Kopie des Eingabearrays erstellt. Die Funktion berechnet im Wesentlichen die Standardabweichung wie folgt:
mu = sum(arr)/len(arr)
tmp = arr - mu
tmp = tmp * tmp
sd = np.sum(tmp)/len(arr)
Während dieser Ansatz OK für kleinere Eingabe-Arrays ist es definitiv keine Möglichkeit für größere zu gehen. Da ich, wie bereits erwähnt, kleinere Speicherblöcke benutze, ist diese zusätzliche Kopie in meinem Programm kein Speicherproblem. Was mich jedoch stört ist, dass für jeden Block eine neue Zuweisung gemacht und freigegeben wird, bevor der nächste Block gelesen wird.
Gibt es eine andere Funktion innerhalb von NumPy oder SciPy, die einen Ansatz mit konstantem Speicherverbrauch wie den Welford-Algorithmus (Wikipedia) für die Berechnung von Mittelwert und Standardabweichung verwendet?
Ein anderer Weg zu gehen wäre, eine benutzerdefinierte Version der _var
Funktion mit einem optionalen out
Argument für einen vorab zugeordneten Puffer (wie NumPy ufuncs) zu implementieren. Bei diesem Ansatz würde die zusätzliche Kopie nicht eliminiert werden, aber zumindest wäre der Speicherverbrauch konstant und die Laufzeit für die Zuweisungen in jedem Block wird gespeichert.
EDIT: Getestet die Cython-Implementierung des Welford-Algorithmus wie von Kezzos vorgeschlagen.
Cython Umsetzung (von kezzos modifiziert):
cimport cython
cimport numpy as np
from libc.math cimport sqrt
@cython.boundscheck(False)
def iterative_approach(np.ndarray[np.float32_t, ndim=1] a):
cdef long n = 0
cdef float mean = 0
cdef float M2 = 0
cdef long i
cdef float delta
cdef float a_min = 10000000 # Must be set to Inf and -Inf for real cases
cdef float a_max = -10000000
for i in range(len(a)):
n += 1
delta = a[i] - mean
mean += delta/n
M2 += delta * (a[i] - mean)
if a[i] < a_min:
a_min = a[i]
if a[i] > a_max:
a_max = a[i]
return a_min, a_max, mean, sqrt(M2/(n - 1))
NumPy Umsetzung (Mittelwert und Standard kann möglicherweise in einer Funktion berechnet werden):
def vector_approach(a):
return np.min(a), np.max(a), np.mean(a), np.std(a, ddof=1)
Prüfergebnisse eine Zufallsdatensatzes unter Verwendung von (Zeiten in Millisekunden, Best of 25):
----------------------------------
| Size | Iterative | Vector |
----------------------------------
| 1e2 | 0.00529 | 0.17149 |
| 1e3 | 0.02027 | 0.16856 |
| 1e4 | 0.17850 | 0.23069 |
| 1e5 | 1.93980 | 0.77727 |
| 1e6 | 18.78207 | 8.83245 |
| 1e7 | 180.04069 | 101.14722 |
| 1e8 | 1789.60228 | 1086.66737 |
----------------------------------
Es scheint wie die iterative a pproach mit Cython ist schneller mit kleineren Datensätzen und der NumPy Vektor (möglicherweise SIMD beschleunigt) Ansatz für größere Datensätze mit 10000 Elemente. Alle Tests wurden mit Python 2.7.9 und NumPy Version 1.9.2 durchgeführt.
Beachten Sie, dass im realen Fall zu den oberen Funktionen würden verwendet werden, um die Statistiken für einen einzelnen Block des Rasters zu berechnen.Die Standardabweichungen und Mittelwerte für alle Blöcke sind mit der in Wikipedia vorgeschlagenen Methode (here) zu kombinieren. Es hat den Vorteil, dass nicht alle Elemente des Rasters summiert werden müssen und somit das Float-Overflow-Problem (zumindest bis zu einem gewissen Punkt) vermieden wird.
Haben Sie versucht, den Welford-Algorithmus (in reinem Python) auf dem Pufferobjekt zu verwenden? – kezzos
@kezzos Ich habe die Frage mit Testergebnissen für eine reine Python-Implementierung aktualisiert. Die Python-Implementierung ist wesentlich langsamer als die NumPy-Version. – Stefan