2013-07-04 19 views
6

Zazwyczaj pracuję z ogromnymi symulacjami. Czasami muszę obliczyć środek masy zestawu cząsteczek. Zauważyłem, że w wielu sytuacjach średnia wartość zwracana przez numpy.mean() jest niepoprawna. Mogę zrozumieć, że jest to spowodowane nasyceniem akumulatora. Aby uniknąć tego problemu, mogę podzielić sumowanie na wszystkie cząstki w małym zestawie cząsteczek, ale jest niewygodne. Czy ktoś ma pomysł, jak rozwiązać ten problem w elegancki sposób?Niepoprawna wartość średnia?

Tylko dla piking się swoją ciekawość, następujący przykład produkować coś podobnego do tego, co obserwujemy w moich symulacji:

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

sprawdzając max i wartości min, otrzymasz:

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

jednak wartość średnia wynosi:

a.mean() 
30687.236328125 

można dowiedzieć się, że coś jest nie tak tutaj. Nie dzieje się tak podczas używania dtype = np.float64, więc powinno być fajnie rozwiązać problem z pojedynczą precyzją.

+0

Jeśli którakolwiek z tych odpowiedzi rozwiązała Twój problem, powinieneś ją zaakceptować. – tacaswell

Odpowiedz

5

To nie jest problem NumPy, jest to problem zmiennoprzecinkowy. To samo ma miejsce w 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)

Problemem jest to, że zmiennoprzecinkowy nie ograniczają dokładność; gdy wartość akumulatora rośnie w stosunku do elementów dodawanych do niego, względna precyzja spada.

Jednym z rozwiązań jest ograniczenie względnego wzrostu poprzez utworzenie drzewa sumującego. Oto przykład w języku C (mój Python nie jest wystarczająco dobre ...):

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

Dzięki Oli, wiem, że to nie jest problem z numpy. Myślę, że powinno być interesująco mieć funkcję, która sama rozdzieliła akumulator, aby uniknąć tego problemu (zaimplementowanego w numpy). – Alejandro

+0

@Alejandro: Zobacz zaktualizowaną odpowiedź. –

+0

Dzięki Oli, podoba mi się twoje podejście. Jest bardzo użyteczny – Alejandro

2

Można zadzwonić np.mean z argumentem dtype słów kluczowych, które określa rodzaj akumulatora (która domyślnie jest tego samego typu, co tablica dla tablic zmiennoprzecinkowych).

Wywołanie numeru a.mean(dtype=np.float64) rozwiąże Twój przykład zabawek, a może Twój problem z większymi tablicami.

+0

Tak, zostało to stwierdzone w pytaniu. np.float64 rozwiązuje problem, jak mówisz. Ale możliwe jest rozwiązanie problemu przy obliczaniu średniej ręcznie bez zmiany dtype. Jeśli weźmiesz małe podzbiory danych i obliczysz częściowe sumy, uzyskasz lepszy wynik nawet z pojedynczą precyzją. – Alejandro

+0

Właściwą rzeczą byłoby zastosowanie (metoda Welforda) [http://stackoverflow.com/questions/895929/how -do-i-określ-standardowe-odchylenie-stddev-zestawu wartości/897463 # 897463] lub podobny wariant, ale nic takiego nie jest zaimplementowane w numpy. Następną najlepszą rzeczą do zrobienia tablic 'np.float64' jest poinformowanie' np.mean' o użyciu akumulatora 'np.float64' za pomocą słowa kluczowego' dtype'. – Jaime

0

Szybki i brudny odpowiedź

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

To daje oczekiwanego rezultatu do 1024 * 1024 matrycy, ale oczywiście to nie będzie prawdziwa dla większych tablic ...

przypadku obliczania średniej będzie Nie wąskie gardło w twoim kodzie Wdałbym sobie algorytm ad-hoc w pythonie: szczegóły zależą jednak od twojej struktury danych.

Jeśli obliczanie średniej jest wąskim gardłem, wówczas może pomóc rozwiązać pewien wyspecjalizowany (równoległy) algorytm redukcji.

Edit

Takie podejście może wydawać się głupie, ale na pewno złagodzić ten problem i jest prawie tak samo skuteczne jak .mean().

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 

Udzielenie rozsądniejszej odpowiedzi wymaga więcej informacji o strukturach danych, rozmiarach i architekturze docelowej.

2

można częściowo zaradzić za pomocą wbudowanego math.fsum, który tropi sum częściowych (docs zawierać link do prototypu receptury AS):

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

O ile jestem świadomy , numpy nie ma analog.

+0

+1 dla dokładności, ale na mojej maszynie ponad 100 razy wolniej niż "a.mean()" lub "a.mean (axis = -1) .mean()'. –

+0

Na pewno jest, to czysty python. I nawet jeśli tego rodzaju rzeczy stają się coraz bardziej nieprzyzwoite, wciąż jest sporo pracy w porównaniu do zwykłego podsumowania. Ale pytanie brzmi oczywiście: czy to spowoduje wąskie gardło w twoim prawdziwym kodzie --- wspomniałeś "czasami" w oryginalnym wpisie :-). –

+0

'math.fsum' jest zaimplementowany w C, AS jest tylko referencją. Prawdopodobnie kod Pythona AS jest tysiąc razy wolniejszy ... Ponieważ OP mówi o "wielkich" problemach, chociaż ta szybkość była problemem, ale tutaj jestem sam. Nie ma nic złego w dokładności handlu dla szybkości i małej ilości miejsca w pamięci ... –

Powiązane problemy