Attualmente sto usando i binding Python di GDAL per lavorare su set di dati raster abbastanza grandi (> 4 GB). Dal momento che caricarli in memoria in una sola volta non è una soluzione fattibile per me li leggo in blocchi più piccoli e faccio i calcoli pezzo dopo pezzo. Per evitare una nuova allocazione per ogni blocco letto sto usando l'argomento buf_obj
(here) per leggere i valori in un array NumPy preallocato. A un certo punto devo calcolare la media e la deviazione standard dell'intero raster. Naturalmente ho usato np.std
per il calcolo. Tuttavia, analizzando il consumo di memoria del mio programma, mi sono reso conto che con ogni chiamata di np.std
la memoria è stata allocata e rilasciata.Consumo di memoria della funzione NumPy per la deviazione standard
Un esempio di lavoro minima che illustra questo comportamento:
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
Una ricerca all'interno dell'albero sorgente di NumPy su GitHub rivelato che la funzione np.std
richiama internamente la funzione _var
da _methods.py
(here). A un certo punto, _var
calcola le deviazioni dalla media e le somma. Pertanto viene creata una copia temporanea dell'array di input. La funzione calcola essenzialmente la deviazione standard come segue:
mu = sum(arr)/len(arr)
tmp = arr - mu
tmp = tmp * tmp
sd = np.sum(tmp)/len(arr)
Mentre questo approccio è OK per array in input piccole è sicuramente modo di andare per quelli più grandi. Dato che sto usando piccoli blocchi di memoria come accennato prima, questa copia aggiuntiva non è un problema di gioco dal punto di vista della memoria nel mio programma. Tuttavia, ciò che mi infastidisce è che per ogni blocco viene fatta e rilasciata una nuova allocazione prima di leggere il blocco successivo.
C'è qualche altra funzione in NumPy o SciPy che utilizza un aggiornamento con consumo di memoria costante come l'algoritmo di Welford (Wikipedia) per un calcolo di un passaggio della media e deviazione standard?
Un altro modo per andare sarebbe implementare una versione personalizzata della funzione _var
con un argomento opzionale out
per un buffer preallocato (come gli ufunc NumPy). Con questo approccio la copia aggiuntiva non verrebbe eliminata, ma almeno il consumo di memoria sarebbe costante e il tempo di esecuzione per le allocazioni in ogni blocco verrà salvato.
MODIFICA: Testata l'implementazione Cython dell'algoritmo di Welford come suggerito da kezzos.
attuazione Cython (modificato da kezzos):
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))
attuazione NumPy (media e std può eventualmente essere calcolato in una funzione):
def vector_approach(a):
return np.min(a), np.max(a), np.mean(a), np.std(a, ddof=1)
risultati mediante un set di dati casuali (volte in millisecondi, meglio di 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 |
----------------------------------
Sembra il iterativo un pproach con Cython è più veloce con set di dati più piccoli e l'approccio del vettore NumPy (possibilmente SIMD accelerato) per set di dati più grandi con oltre 10000 elementi. Tutti i test sono stati condotti con Python 2.7.9 e NumPy versione 1.9.2.
Nota che nel caso reale alle funzioni superiori verrebbe utilizzato per calcolare le statistiche per un singolo blocco del raster.Le deviazioni standard e i mezzi per tutti i blocchi devono essere combinati con la metodologia suggerita in Wikipedia (here). Ha il vantaggio che non tutti gli elementi del raster devono essere riassunti e quindi evita il problema dell'overflow del float (almeno fino ad un certo punto).
Hai provato a utilizzare l'algoritmo di Welford (in puro python) sull'oggetto buffer? – kezzos
@kezzos Ho aggiornato la domanda con i risultati del test per una pura implementazione Python. L'implementazione Python è molto più lenta della versione di NumPy. – Stefan