2013-07-04 28 views
6

Di solito lavoro con enormi simulazioni. A volte, ho bisogno di calcolare il centro di massa dell'insieme di particelle. Ho notato che in molte situazioni, il valore medio restituito da numpy.mean() è sbagliato. Posso capire che è dovuto alla saturazione dell'accumulatore. Per evitare il problema, posso suddividere la somma su tutte le particelle in un piccolo insieme di particelle, ma è scomodo. Qualcuno ha idea di come risolvere questo problema in modo elegante?Valore medio numpy errato?

Solo per piking la vostra curiosità, il seguente esempio produrre qualcosa di simile a quello che osservo nel mio simulazioni:

import numpy as np 
a = np.ones((1024,1024), dtype=np.float32)*30504.00005 

se si controlla l'valori massimi e minimi, si ottiene:

a.max() 
30504.0 
a.min() 
30504.0 

tuttavia, il valore medio è:

a.mean() 
30687.236328125 

si può capire che qualcosa non va Qui. Questo non sta accadendo quando si usa dtype = np.float64, quindi dovrebbe essere bello risolvere il problema per precisione singola.

+0

Se una di queste risposte ha risolto il problema, è necessario accettarlo. – tacaswell

risposta

5

Questo non è un problema di NumPy, è un problema in virgola mobile. Lo stesso accade in C:

float acc = 0; 
for (int i = 0; i < 1024*1024; i++) { 
    acc += 30504.00005f; 
} 
acc /= (1024*1024); 
printf("%f\n", acc); // 30687.304688 

(Live demo)

Il problema è che in virgola mobile è limitata precisione; man mano che il valore dell'accumulatore cresce in relazione agli elementi aggiunti ad esso, la precisione relativa diminuisce.

Una soluzione è limitare la crescita relativa, costruendo un albero ad albero. Ecco un esempio in C (il mio Python non è abbastanza buono ...):

float sum(float *p, int n) { 
    if (n == 1) return *p; 
    for (int i = 0; i < n/2; i++) { 
     p[i] += p[i+n/2]; 
    } 
    return sum(p, n/2); 
} 

float x[1024*1024]; 
for (int i = 0; i < 1024*1024; i++) { 
    x[i] = 30504.00005f; 
} 

float acc = sum(x, 1024*1024); 

acc /= (1024*1024); 
printf("%f\n", acc); // 30504.000000 

(Live demo)

+0

Grazie Oli, so che non è un problema con Numpy. Penso che dovrebbe essere interessante avere una funzione che divida da sola l'accumulatore per evitare questo problema (implementato in numpy) – Alejandro

+0

@ Alejandro: Vedi risposta aggiornata. –

+0

Grazie Oli, mi piace il tuo approccio. È molto utile – Alejandro

2

È possibile chiamare np.mean con un argomento dtype parola chiave, che specifica il tipo di accumulatore (che per impostazione predefinita è dello stesso tipo dell'array per gli array a virgola mobile).

Quindi chiamando a.mean(dtype=np.float64) risolverà il vostro esempio di giocattoli, e, forse, il problema con gli array più grandi.

+0

Sì, è stato indicato nella domanda. np.float64 risolve il problema come dici tu. Ma è possibile risolvere il problema quando si calcola la media a mano senza cambiare dtype. Se si prendono piccoli sottoinsiemi di dati e si calcolano sommazioni parziali, si ottiene un risultato migliore anche con una precisione singola – Alejandro

+0

La cosa giusta da fare sarebbe utilizzare (metodo di Welford) [http://stackoverflow.com/questions/895929/how -do-i-define-the-standard-deviation-stddev-of-a-set-of-values ​​/ 897463 # 897463], o una variante simile, ma nulla di simile è implementato in numpy. La prossima cosa migliore per creare array di 'np.float64' è dire a' np.mean' di usare un accumulatore 'np.float64' usando la parola chiave' dtype'. – Jaime

0

rapido e sporco risposta

assert a.ndim == 2 
a.mean(axis=-1).mean() 

Questo dà il risultato previsto per il * 1024 matrice di 1024, ma naturalmente questo non sarà vero per gli array più grandi ...

Se calcolando la media sarà non essere un collo di bottiglia nel codice Vorrei implementarmi un algoritmo ad hoc in python: i dettagli dipendono comunque dalla struttura dei dati.

Se calcolare la media è un collo di bottiglia, allora un algoritmo di riduzione (parallelo) specializzato potrebbe risolvere il problema.

Modifica

Questo approccio può sembrare sciocco, ma di sicuro a mitigare il problema ed è quasi efficiente come .mean() stessa.

In [65]: a = np.ones((1024,1024), dtype=np.float32)*30504.00005 

In [66]: a.mean() 
Out[66]: 30687.236328125 

In [67]: a.mean(axis=-1).mean() 
Out[67]: 30504.0 

In [68]: %timeit a.mean() 
1000 loops, best of 3: 894 us per loop 

In [69]: %timeit a.mean(axis=-1).mean() 
1000 loops, best of 3: 906 us per loop 

Dare una risposta più sensata richiede qualche informazione in più sulle strutture di dati, è formati, e architeture bersaglio.

2

Si può parzialmente ovviare a questo utilizzando un built-in math.fsum, che rintraccia le somme parziali (i documenti contengono un link ad una ricetta prototipo AS):

>>> fsum(a.ravel())/(1024*1024) 
30504.0 

Per quanto ne sono a conoscenza , numpy non ha un analogo.

+0

+1 per la precisione, ma sulla mia macchina più di 100 volte più lento di 'a.mean()' o 'a.mean (axis = -1) .mean()'. –

+0

sicuro che sia, è puro pitone. E anche se questo genere di cose sta diventando intorpidito, c'è ancora un sacco di lavoro rispetto a sommare le cose. Ma la domanda, ovviamente, è se fare questo creerà un collo di bottiglia nel tuo codice reale --- hai menzionato "a volte" nel post originale :-). –

+0

'math.fsum' è implementato in C, la ricetta AS è solo un riferimento. Probabilmente il codice AS pitone è migliaia di volte più lento ... Dal momento che l'OP parla di problemi "enormi", anche se quella velocità era un problema, ma qui sono solo. Non c'è niente di sbagliato nella precisione del trading per la velocità e il minimo ingombro di memoria ... –

Problemi correlati