Algoritmo online per deviazione assoluta media e set di dati di grandi dimensioni


16

Ho un piccolo problema che mi sta facendo impazzire. Devo scrivere una procedura per un processo di acquisizione online di una serie temporale multivariata. Ad ogni intervallo di tempo (ad esempio 1 secondo), ottengo un nuovo campione, che è fondamentalmente un vettore a virgola mobile di dimensione N. L'operazione che devo fare è un po 'complicata:

  1. Per ogni nuovo campione, calcolo i percentuali per quel campione (normalizzando il vettore in modo che gli elementi si sommino a 1).

  2. Calcolo il vettore dei percentuali medi allo stesso modo, ma usando i valori passati.

  3. Per ciascun valore passato, calcolo la deviazione assoluta del vettore dei percentuali relativa a quel campione con il vettore dei percentuali medi globali calcolato al passaggio 2. In questo modo, la deviazione assoluta è sempre un numero compreso tra 0 (quando il vettore è uguale alla media vettore) e 2 (quando è totalmente diverso).

  4. Usando la media delle deviazioni per tutti i campioni precedenti, ho calcolato la deviazione assoluta media, che è di nuovo un numero compreso tra 0 e 2.

  5. Uso la deviazione assoluta media per rilevare se un nuovo campione è compatibile con gli altri campioni (confrontando la sua deviazione assoluta con la deviazione assoluta media dell'intero set calcolata al passaggio 4).

Poiché ogni volta che un nuovo campione viene raccolto, la media globale cambia (e quindi cambia anche la deviazione assoluta media), c'è un modo per calcolare questo valore senza scansionare più volte l'intero set di dati? (una volta per calcolare le percentuali medie globali e una volta per raccogliere le deviazioni assolute). Ok, so che è assolutamente facile calcolare le medie globali senza scansionare l'intero set, dal momento che devo solo usare un vettore temporaneo per memorizzare la somma di ogni dimensione, ma per quanto riguarda la deviazione assoluta media? Il suo calcolo include ilabs() operatore, quindi ho bisogno di accedere a tutti i dati passati!

Grazie per l'aiuto.

Risposte:


6

Se si può accettare qualche imprecisione, questo problema può essere risolto facilmente binning conta. Cioè, scegliere un numero largeish (dire M = 1000 ), quindi inizializzare alcuni bidoni interi B i , j per i = 1 ... M e j = 1 ... N , dove N è la dimensione vettore, come zero. Poi, quando si vede il k esima osservazione di un vettore percentuale, incremento B i , j se il j -esimo elemento di questo vettore è tra (MM=1000Bi,ji=1Mj=1NNkBi,jj e i / M , eseguendo il ciclo sopra glielementi N del vettore. (Suppongo che i tuoi vettori di input non siano negativi, quindi quando calcoli i tuoi "percentuali", i vettori sono nell'intervallo [ 0 , 1 ] .)(i1)/Mio/MN[0,1]

In qualsiasi momento, è possibile stimare il vettore medio dai bin e la deviazione assoluta media. Dopo aver osservato tali vettori, il j ° elemento della media è stimato da ˉ X j = 1Kje ilj-esimo elemento della deviazione media assoluta è stimata da1

X¯j=1Kii1/2MBi,j,
j
1Ki|Xj¯i1/2M|Bi,j

modifica : questo è un caso specifico di un approccio più generale in cui si sta costruendo una stima della densità empirica. Questo potrebbe essere fatto con polinomi, spline, ecc., Ma l'approccio di binning è il più semplice da descrivere e implementare.


Caspita, approccio davvero interessante. Non lo sapevo e lo terrò a mente. Sfortunatamente, in questo caso non funzionerà, dal momento che ho requisiti veramente restrittivi dal punto di vista dell'utilizzo della memoria, quindi M dovrebbe essere davvero piccolo e immagino che ci sarebbe troppa perdita di precisione.
gianluca,

@gianluca: sembra che tu abbia 1. molti dati, 2. risorse di memoria limitate, 3. requisiti di alta precisione. Posso capire perché questo problema ti sta spaventando! Forse, come menzionato da @kwak, puoi calcolare qualche altra misura di diffusione: MAD, IQR, deviazione standard. Tutti questi hanno approcci che potrebbero funzionare per il tuo problema.
Shabbychef,

gianluca:> Dacci un'idea più quantitativa della dimensione della memoria, degli array e della precisione che desideri. Tuttavia, potrebbe darsi che alla tua domanda venga data una risposta migliore @ StackOverflow.
user603

4

Ho usato il seguente approccio in passato per calcolare la deviazione di assoluzione in modo moderatamente efficiente (nota, questo è un approccio programmatore, non uno statistico, quindi indubbiamente potrebbero esserci trucchi intelligenti come quello di Shabbychef che potrebbero essere più efficienti).

ATTENZIONE: questo non è un algoritmo online. Richiede O(n)memoria. Inoltre, ha prestazioni nel peggiore dei casiO(n) , per insiemi di dati come [1, -2, 4, -8, 16, -32, ...](cioè lo stesso del ricalcolo completo). [1]

Tuttavia, poiché funziona ancora bene in molti casi d'uso, potrebbe valere la pena pubblicare qui. Ad esempio, al fine di calcolare la devianza assoluta di 10000 casuali numeri tra -100 e 100 all'arrivo di ogni articolo, il mio algoritmo impiega meno di un secondo, mentre il ricalcolo completo richiede oltre 17 secondi (sulla mia macchina, varierà per macchina e in base ai dati di input). Tuttavia, è necessario mantenere l'intero vettore in memoria, il che può costituire un vincolo per alcuni usi. Lo schema dell'algoritmo è il seguente:

  1. Invece di avere un singolo vettore per memorizzare le misurazioni passate, usa tre code di priorità ordinate (qualcosa come un heap min / max). Queste tre liste suddividono l'input in tre: elementi maggiori della media, elementi minori della media e elementi uguali alla media.
  2. (Quasi) ogni volta che aggiungi un oggetto la media cambia, quindi dobbiamo ripartizionare. La cosa cruciale è la natura ordinata delle partizioni, il che significa che invece di scansionare tutti gli elementi dell'elenco per ripararli, dobbiamo solo leggere quegli elementi che stiamo spostando. Mentre nel peggiore dei casi ciò richiederà ancoraO(n) operazioni di spostamento, per molti casi d'uso non è così.
  3. Usando una contabilità intelligente, possiamo assicurarci che la devianza sia calcolata correttamente in ogni momento, durante il ripartizionamento e quando si aggiungono nuovi elementi.

Di seguito è riportato un codice di esempio, in python. Si noti che consente solo di aggiungere elementi all'elenco, non rimossi. Questo potrebbe essere facilmente aggiunto, ma al momento in cui ho scritto questo non ne avevo bisogno. Piuttosto che implementare personalmente le code prioritarie, ho usato la lista ordinata dall'eccellente pacchetto blist di Daniel Stutzbach , che usa B + Tree internamente .

Considera questo codice concesso in licenza con la licenza MIT . Non è stato significativamente ottimizzato o perfezionato, ma ha funzionato per me in passato. Le nuove versioni saranno disponibili qui . Fammi sapere se hai domande o trova qualche bug.

from blist import sortedlist
import operator

class deviance_list:
    def __init__(self):
        self.mean =  0.0
        self._old_mean = 0.0
        self._sum =  0L
        self._n =  0  #n items
        # items greater than the mean
        self._toplist =  sortedlist()
        # items less than the mean
        self._bottomlist = sortedlist(key = operator.neg)
        # Since all items in the "eq list" have the same value (self.mean) we don't need
        # to maintain an eq list, only a count
        self._eqlistlen = 0

        self._top_deviance =  0
        self._bottom_deviance =  0

    @property
    def absolute_deviance(self):
        return self._top_deviance + self._bottom_deviance

    def append(self,  n):
        # Update summary stats
        self._sum += n
        self._n +=  1
        self._old_mean =  self.mean
        self.mean =  self._sum /  float(self._n)

        # Move existing things around
        going_up = self.mean > self._old_mean
        self._rebalance(going_up)

        # Add new item to appropriate list
        if n >  self.mean:
            self._toplist.add(n)
            self._top_deviance +=  n -  self.mean
        elif n == self.mean: 
            self._eqlistlen += 1
        else:
            self._bottomlist.add(n)
            self._bottom_deviance += self.mean -  n


    def _move_eqs(self,  going_up):
        if going_up:
            self._bottomlist.update([self._old_mean] *  self._eqlistlen)
            self._bottom_deviance += (self.mean - self._old_mean) * self._eqlistlen
            self._eqlistlen = 0
        else:
            self._toplist.update([self._old_mean] *  self._eqlistlen)
            self._top_deviance += (self._old_mean - self.mean) * self._eqlistlen
            self._eqlistlen = 0


    def _rebalance(self, going_up):
        move_count,  eq_move_count = 0, 0
        if going_up:
            # increase the bottom deviance of the items already in the bottomlist
            if self.mean !=  self._old_mean:
                self._bottom_deviance += len(self._bottomlist) *  (self.mean -  self._old_mean)
                self._move_eqs(going_up)


            # transfer items from top to bottom (or eq) list, and change the deviances
            for n in iter(self._toplist):
                if n < self.mean:
                    self._top_deviance -= n -  self._old_mean
                    self._bottom_deviance += (self.mean -  n)
                    # we increment movecount and move them after the list
                    # has finished iterating so we don't modify the list during iteration
                    move_count +=  1
                elif n == self.mean:
                    self._top_deviance -= n -  self._old_mean
                    self._eqlistlen += 1
                    eq_move_count +=  1
                else:
                    break
            for _ in xrange(0,  move_count):
                self._bottomlist.add(self._toplist.pop(0))
            for _ in xrange(0,  eq_move_count):
                self._toplist.pop(0)

            # decrease the top deviance of the items remain in the toplist
            self._top_deviance -= len(self._toplist) *  (self.mean -  self._old_mean)
        else:
            if self.mean !=  self._old_mean:
                self._top_deviance += len(self._toplist) *  (self._old_mean -  self.mean)
                self._move_eqs(going_up)
            for n in iter(self._bottomlist): 
                if n > self.mean:
                    self._bottom_deviance -= self._old_mean -  n
                    self._top_deviance += n -  self.mean
                    move_count += 1
                elif n == self.mean:
                    self._bottom_deviance -= self._old_mean -  n
                    self._eqlistlen += 1
                    eq_move_count +=  1
                else:
                    break
            for _ in xrange(0,  move_count):
                    self._toplist.add(self._bottomlist.pop(0))
            for _ in xrange(0,  eq_move_count):
                self._bottomlist.pop(0)

            # decrease the bottom deviance of the items remain in the bottomlist
            self._bottom_deviance -= len(self._bottomlist) *  (self._old_mean -  self.mean)


if __name__ ==  "__main__":
    import random
    dv =  deviance_list()
    # Test against some random data,  and calculate result manually (nb. slowly) to ensure correctness
    rands = [random.randint(-100,  100) for _ in range(0,  1000)]
    ns = []
    for n in rands: 
        dv.append(n)
        ns.append(n)
        print("added:%4d,  mean:%3.2f,  oldmean:%3.2f,  mean ad:%3.2f" %
              (n, dv.mean,  dv._old_mean,  dv.absolute_deviance / dv.mean))
        assert sum(ns) == dv._sum,  "Sums not equal!"
        assert len(ns) == dv._n,  "Counts not equal!"
        m = sum(ns) / float(len(ns))
        assert m == dv.mean,  "Means not equal!"
        real_abs_dev = sum([abs(m - x) for x in ns])
        # Due to floating point imprecision, we check if the difference between the
        # two ways of calculating the asb. dev. is small rather than checking equality
        assert abs(real_abs_dev - dv.absolute_deviance) < 0.01, (
            "Absolute deviances not equal. Real:%.2f,  calc:%.2f" %  (real_abs_dev,  dv.absolute_deviance))

[1] Se i sintomi persistono, consultare il medico.


2
Mi manca qualcosa: se devi "mantenere in memoria l'intero vettore", come si qualifica un algoritmo "online" ??
whuber

@whuber No, non manca qualcosa, suppongo che non sia un algoritmo online. Richiede O(n)memoria e nel peggiore dei casi richiede O (n) tempo per ogni elemento aggiunto. Tuttavia, nei dati normalmente distribuiti (e probabilmente in altre distribuzioni) funziona in modo abbastanza efficiente.
fmark

3

XXXss2/π


Questa è un'idea interessante. Potresti integrarlo, forse, con il rilevamento online di valori anomali e utilizzarli per modificare il preventivo man mano che procedi.
whuber

Probabilmente potresti usare il metodo di Welford per calcolare la deviazione standard online che ho documentato nella mia seconda risposta.
fmark

1
Si dovrebbe tuttavia notare che in questo modo si potrebbe perdere la solidità di stimatori come MAD esplicito, che a volte guidano verso la sua scelta rispetto a alternative più semplici.
Quarzo,

2

MAD (x) è solo due calcoli mediani simultanei, ognuno dei quali può essere effettuato online tramite l' algoritmo binmediano .

È possibile trovare la carta associata così come codice C e FORTRAN on-line qui .

(questo è solo l'uso di un trucco intelligente in aggiunta al trucco intelligente di Shabbychef, per risparmiare memoria).

Addendum:

Esistono numerosi metodi multi-pass precedenti per il calcolo dei quantili. Un approccio popolare è quello di mantenere / aggiornare un serbatoio di dimensioni deterministiche di osservazioni scelte casualmente dal flusso e calcolare ricorsivamente i quantili (vedi questa recensione) su questo serbatoio. Questo (e relativo) approccio sono sostituiti da quello proposto sopra.


Potresti per favore dettagliare o fare riferimento alla relazione tra MAD e le due mediane?
Quarzo,

è davvero la formula del MAD: medio=1n|Xio-medio=1n| (quindi due mediane)
user603

Ehm, intendevo davvero se puoi spiegare in che modo questa relazione consente ai due mediani di essere simultanei; quelli sembrano dipendenti da me, dal momento che gli input per la mediana esterna possono tutti cambiare ad ogni campione aggiunto al calcolo interno. Come li eseguiresti in parallelo?
Quarzo,

Devo tornare al documento binmediano per i dettagli ... ma dato un valore calcolato della mediana (medio=1nXio) e un nuovo valore di Xn+1 l'algoritmo potrebbe calcolare medio=1n+1Xio molto più veloce di O(n) identificando il cestino in cui Xn+1appartiene. Non vedo come questa intuizione non possa essere generalizzata alla mediana esterna nel calcolo folle.
user603

1

Di seguito viene fornita un'approssimazione imprecisa, sebbene l'inaccuratezza dipenderà dalla distribuzione dei dati di input. È un algoritmo online, ma si avvicina solo alla devianza assoluta. Si basa su un noto algoritmo per il calcolo della varianza online, descritto da Welford negli anni '60. Il suo algoritmo, tradotto in R, assomiglia a:

M2 <- 0
mean <- 0
n <- 0

var.online <- function(x){
    n <<- n + 1
    diff <- x - mean
    mean <<- mean + diff / n
    M2 <<- M2 + diff * (x - mean)
    variance <- M2 / (n - 1)
    return(variance)
}

Si comporta in modo molto simile alla funzione di varianza integrata di R:

set.seed(2099)
n.testitems <- 1000
n.tests <- 100
differences <- rep(NA, n.tests)
for (i in 1:n.tests){
        # Reset counters
        M2 <- 0
        mean <- 0
        n <- 0

        xs <- rnorm(n.testitems)
        for (j in 1:n.testitems){
                v <- var.online(xs[j])
        }

        differences[i] <- abs(v - var(xs))

}
summary(differences)
     Min.   1st Qu.    Median      Mean   3rd Qu.      Max. 
0.000e+00 2.220e-16 4.996e-16 6.595e-16 9.992e-16 1.887e-15 

La modifica dell'algoritmo per calcolare la deviazione assoluta comporta semplicemente una sqrtchiamata aggiuntiva . Tuttavia, sqrtintroduce imprecisioni che si riflettono nel risultato:

absolute.deviance.online <- function(x){
    n <<- n + 1
    diff <- x - mean
    mean <<- mean + diff / n
    a.dev <<- a.dev + sqrt(diff * (x - mean))
    return(a.dev)
}

Gli errori, calcolati come sopra, sono molto maggiori rispetto al calcolo della varianza:

    Min.  1st Qu.   Median     Mean  3rd Qu.     Max. 
0.005126 0.364600 0.808000 0.958800 1.360000 3.312000 

Tuttavia, a seconda del caso d'uso, questa entità dell'errore potrebbe essere accettabile.

historgram of differences


Questo non dà la risposta esatta, per il seguente motivo: ΣioXioΣioXio. Stai calcolando il primo, mentre l'OP vuole il secondo.
Shabbychef,

Sono d'accordo che il metodo è inesatto. Tuttavia, non sono d'accordo con la tua diagnosi di inesattezza. Il metodo di Welford per il calcolo della varianza, che non contiene nemmeno un sqrt, presenta un errore simile. Tuttavia, man mano che nsi ingrandisce, error/ndiventa sempre più piccolo, sorprendentemente rapidamente.
fmark

Il metodo di Welford non ha sqrt perché sta calcolando la varianza, non la deviazione standard. Prendendo sqrt, sembra che stiate stimando la deviazione standard, non la deviazione assoluta media. mi sto perdendo qualcosa?
shabbychef,

@shabbychef Ogni iterazione di Welfords sta calcolando il contributo del nuovo punto dati alla deviazione assoluta, al quadrato. Quindi prendo la radice quadrata di ogni contributo al quadrato per tornare alla devianza assoluta. Si potrebbe notare, ad esempio, che prendo la radice quadrata del delta prima di aggiungerla alla somma della devianza, piuttosto che successivamente come nel caso della deviazione standard.
fmark

3
Vedo il problema; Welfords oscura il problema con questo metodo: viene utilizzata la stima online della media anziché la stima finale della media. Mentre il metodo di Welford è esatto (fino al roundoff) per la varianza, questo metodo non lo è. Il problema non è dovuto sqrtall'imprecisione. È perché utilizza la stima della media corrente. Per vedere quando si romperà, prova xs <- sort(rnorm(n.testitems)) Quando provo questo con il tuo codice (dopo averlo corretto per restituirlo a.dev / n), ottengo errori relativi nell'ordine del 9% -16%. Quindi questo metodo non è invariante di permutazione, che potrebbe causare il caos ...
shabbychef,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.