Determinazione della media e della deviazione standard in tempo reale


31

Quale sarebbe il modo ideale per trovare la deviazione media e standard di un segnale per un'applicazione in tempo reale. Mi piacerebbe essere in grado di attivare un controller quando un segnale era più di 3 deviazione standard dalla media per un certo periodo di tempo.

Suppongo che un DSP dedicato lo farebbe abbastanza facilmente, ma c'è qualche "scorciatoia" che potrebbe non richiedere qualcosa di così complicato?


Sai qualcosa del segnale? È fermo?

@Tim Diciamo che è stazionario. Per mia curiosità, quali sarebbero le conseguenze di un segnale non stazionario?
jonsca,

3
Se è fermo, puoi semplicemente calcolare una media corrente e la deviazione standard. Le cose sarebbero più complicate se la deviazione media e standard variavano nel tempo.

Risposte:


36

C'è un difetto nella risposta di Jason R, che è discussa nel vol. Di "Art of Computer Programming" di Knuth. 2. Il problema si presenta se si ha una deviazione standard che è una piccola frazione della media: il calcolo di E (x ^ 2) - (E (x) ^ 2) soffre di una grave sensibilità agli errori di arrotondamento in virgola mobile.

Puoi anche provarlo tu stesso in uno script Python:

ofs = 1e9
A = [ofs+x for x in [1,-1,2,3,0,4.02,5]] 
A2 = [x*x for x in A]
(sum(A2)/len(A))-(sum(A)/len(A))**2

Ottengo -128.0 come risposta, che chiaramente non è valida dal punto di vista computazionale, poiché la matematica prevede che il risultato dovrebbe essere non negativo.

Knuth cita un approccio (non ricordo il nome dell'inventore) per il calcolo della media corrente e della deviazione standard che va in questo modo:

 initialize:
    m = 0;
    S = 0;
    n = 0;

 for each incoming sample x:
    prev_mean = m;
    n = n + 1;
    m = m + (x-m)/n;
    S = S + (x-m)*(x-prev_mean);

e quindi dopo ogni passaggio, il valore di mè la media e la deviazione standard può essere calcolata come sqrt(S/n)o in sqrt(S/n-1)base alla definizione preferita di deviazione standard.

L'equazione che scrivo sopra è leggermente diversa da quella di Knuth, ma è equivalente dal punto di vista computazionale.

Quando avrò ancora qualche minuto, codificherò la formula sopra in Python e mostrerò che otterrai una risposta non negativa (che si spera sia vicina al valore corretto).


aggiornamento: eccolo qui.

test1.py:

import math

def stats(x):
  n = 0
  S = 0.0
  m = 0.0
  for x_i in x:
    n = n + 1
    m_prev = m
    m = m + (x_i - m) / n
    S = S + (x_i - m) * (x_i - m_prev)
  return {'mean': m, 'variance': S/n}

def naive_stats(x):
  S1 = sum(x)
  n = len(x)
  S2 = sum([x_i**2 for x_i in x])
  return {'mean': S1/n, 'variance': (S2/n - (S1/n)**2) }

x1 = [1,-1,2,3,0,4.02,5] 
x2 = [x+1e9 for x in x1]

print "naive_stats:"
print naive_stats(x1)
print naive_stats(x2)

print "stats:"
print stats(x1)
print stats(x2)

risultato:

naive_stats:
{'variance': 4.0114775510204073, 'mean': 2.0028571428571427}
{'variance': -128.0, 'mean': 1000000002.0028572}
stats:
{'variance': 4.0114775510204073, 'mean': 2.0028571428571431}
{'variance': 4.0114775868357446, 'mean': 1000000002.0028571}

Noterai che c'è ancora un errore di arrotondamento, ma non è male, mentre naive_statssolo vomita.


modifica: Ho appena notato il commento di Belisario che cita Wikipedia che menziona l'algoritmo Knuth.


1
+1 per la risposta dettagliata con il codice di esempio. Questo approccio è superiore a quello indicato nella mia risposta quando è necessaria un'implementazione in virgola mobile.
Jason R,

1
Si potrebbe anche verificare questo per un'implementazione C ++: johndcook.com/standard_deviation.html
Rui Marques,

1
sì, questo è tutto. Usa le equazioni esatte che usa Knuth. È possibile ottimizzare un po 'ed evitare di dover verificare l'iterazione iniziale rispetto alle iterazioni successive se si utilizza il mio metodo.
Jason S,

"Knuth cita un approccio (non ricordo il nome dell'inventore) per calcolare la media della corsa" - a proposito, è il metodo di Welford .
Jason S,

Ho pubblicato una domanda relativa a questo se qualcuno è in grado di aiutare: dsp.stackexchange.com/questions/31812/…
Jonathan

13

Quale sarebbe il modo ideale per trovare la deviazione media e standard di un segnale per un'applicazione in tempo reale. Mi piacerebbe essere in grado di attivare un controller quando un segnale era più di 3 deviazione standard dalla media per un certo periodo di tempo.

L'approccio giusto in situazioni come questa è in genere quello di calcolare una media corrente e una deviazione standard ponderate in modo esponenziale. Nella media ponderata esponenzialmente, le stime della media e della varianza sono distorte rispetto al campione più recente fornendo stime della media e della varianza negli ultimi secondiτ , che è probabilmente ciò che si desidera, piuttosto che la normale media aritmetica su tutti i campioni mai visto.

Nel dominio della frequenza, una "media corrente esponenzialmente ponderata" è semplicemente un vero polo. È semplice da implementare nel dominio del tempo.

Implementazione nel dominio del tempo

Lascia meanche meansqsiano le stime attuali della media e della media del quadrato del segnale. Ad ogni ciclo, aggiorna queste stime con il nuovo esempio x:

% update the estimate of the mean and the mean square:
mean = (1-a)*mean + a*x
meansq = (1-a)*meansq + a*(x^2)

% calculate the estimate of the variance:
var = meansq - mean^2;

% and, if you want standard deviation:
std = sqrt(var);

Qui è una costante che determina la lunghezza effettiva della media corrente. Come scegliere a è descritto di seguito in "analisi".0<a<1a

Ciò che è espresso sopra come un programma imperativo può anche essere rappresentato come un diagramma di flusso del segnale:

inserisci qui la descrizione dell'immagine

Analisi

yi=axi+(1a)yi1xiiyiz

H(z)=a1(1a)z1

Condensando i filtri IIR nei loro blocchi, il diagramma ora appare così:

inserisci qui la descrizione dell'immagine

z=esTTfs=1/T1(1a)esT=0s=1Tlog(1a)

a

a=1exp{2πTτ}

Riferimenti


1
aa0 > a > 1

Questo è simile all'approccio di Jason R. Questo metodo sarà meno preciso, ma un po 'più veloce e con una memoria inferiore. Questo approccio finisce per usare una finestra esponenziale.
schnarf,

Woops! Certo che intendevo 0 < a < 1. Se il tuo sistema ha tmie di campionamento Te desideri una costante di tempo media tau, quindi scegli a = 1 - exp (2*pi*T/tau).
nibot

Penso che potrebbe esserci un errore qui. I filtri unipolari non hanno guadagno di 0 dB a CC e poiché si applica un filtro nel dominio lineare e uno nel dominio quadrato, l'errore di guadagno è diverso per E <x> ed E <x ^ 2>. Elaborerò di più nella mia risposta
Hilmar

Ha un guadagno di 0 dB a CC. Sostituisci z=1(DC) H(z) = a/(1-(1-a)/z)e otterrai 1.
nibot

5

Un metodo che ho usato prima in un'applicazione di elaborazione integrata è quello di mantenere gli accumulatori della somma e della somma dei quadrati del segnale di interesse:

Ax,i=k=0ix[k]=Ax,i1+x[i],Ax,1=0

Ax2,i=k=0ix2[k]=Ax2,i1+x2[i],Ax2,1=0

ii

μ~=Axii+1

σ~=Axi2i+1μ~2

oppure puoi usare:

σ~=Axi2iμ~2

a seconda del metodo di stima della deviazione standard che preferisci . Queste equazioni si basano sulla definizione della varianza :

σ2=E(X2)(E(X))2

Li ho usati con successo in passato (anche se mi occupavo solo della stima della varianza, non della deviazione standard), anche se devi stare attento ai tipi numerici che usi per contenere gli accumulatori se stai per sommare un lungo periodo di tempo; non vuoi overflow.

Modifica: in aggiunta al commento sopra sull'overflow, va notato che questo non è un algoritmo numericamente solido quando implementato in aritmetica in virgola mobile, potenzialmente causando grandi errori nelle statistiche stimate. Guarda la risposta di Jason S per un approccio migliore in quel caso.


1
Ax,i=x[i]+Ax,i1, Ax,0=x[0]ix

Sì, va meglio. Ho provato a riscrivere per rendere più chiara l'implementazione ricorsiva.
Jason R,

2
-1 quando ho abbastanza rappresentante per farlo: questo ha problemi numerici. Vedi Knuth vol. 2
Jason S,

σμ2σ2=E(X2)(E(X))2

2
@JasonS: Non sarei d'accordo sul fatto che la tecnica sia intrinsecamente imperfetta, anche se concordo con il tuo punto di vista sul fatto che non è un metodo numericamente robusto quando implementato in virgola mobile. Avrei dovuto essere più chiaro di averlo usato con successo in un'applicazione che utilizzava l' aritmetica dei numeri interi . L'aritmetica di interi (o implementazioni in virgola fissa di numeri frazionari) non soffre del problema che hai sottolineato che causa una perdita di precisione. In tale contesto, è un metodo adatto che richiede meno operazioni per campione.
Jason R,

3

Simile alla risposta preferita sopra (Jason S.), e anche derivata dalla formula presa da Knut (Vol.2, p 232), si può anche derivare una formula per sostituire un valore, cioè rimuovere e aggiungere un valore in un solo passaggio . Secondo i miei test, sostituire offre una precisione migliore rispetto alla versione di rimozione / aggiunta in due passaggi.

Il codice seguente è in Java meane sviene aggiornato (variabili membro "globali"), uguale me ssopra nel post di Jason. Il valore si countriferisce alla dimensione della finestra n.

/**
 * Replaces the value {@code x} currently present in this sample with the
 * new value {@code y}. In a sliding window, {@code x} is the value that
 * drops out and {@code y} is the new value entering the window. The sample
 * count remains constant with this operation.
 * 
 * @param x
 *            the value to remove
 * @param y
 *            the value to add
 */
public void replace(double x, double y) {
    final double deltaYX = y - x;
    final double deltaX = x - mean;
    final double deltaY = y - mean;
    mean = mean + deltaYX / count;
    final double deltaYp = y - mean;
    final double countMinus1 = count - 1;
    s = s - count / countMinus1 * (deltaX * deltaX - deltaY * deltaYp) - deltaYX * deltaYp / countMinus1;
}

3

La risposta di Jason e Nibot differisce in un aspetto importante: il metodo di Jason calcola lo sviluppo e la media dello std per l'intero segnale (poiché y = 0), mentre quello di Nibot è un calcolo "in esecuzione", ovvero pesa campioni più recenti più forti dei campioni dal passato remoto.

Poiché l'applicazione sembra richiedere dev standard e media in funzione del tempo, il metodo di Nibot è probabilmente quello più appropriato (per questa specifica applicazione). Tuttavia, la parte più difficile sarà quella di ottenere correttamente la parte di ponderazione del tempo. L'esempio di Nibot utilizza un semplice filtro unipolare.

E[x]x[n]E[x2]x[n]2

La scelta del filtro passa-basso può essere guidata da ciò che sai del tuo segnale e dalla risoluzione temporale di cui hai bisogno per la tua stima. Frequenze di taglio inferiori e ordine superiore comporteranno una migliore precisione ma tempi di risposta più lenti.

Per complicare ulteriormente le cose, viene applicato un filtro nel dominio lineare e un altro nel dominio quadrato. La quadratura modifica significativamente il contenuto spettrale del segnale, quindi potresti voler usare un filtro diverso nel dominio quadrato.

Ecco un esempio su come stimare media, rms e std dev in funzione del tempo.

%% example
fs = 44100; n = fs; % 44.1 kHz sample rate, 1 second
% signal: white noise plus a low frequency drift at 5 Hz)
x = randn(n,1) + sin(2*pi*(0:n-1)'*5/fs);
% mean estimation filter: since we are looking for effects in the 5 Hz range we use maybe a
% 25 Hz filter, 2nd order so it's not too sluggish
[b,a] = butter(2,25*2/fs);
xmeanEst = filter(b,a,x);
% now we estimate x^2, since most frequency double we use twice the bandwidth
[b,a] = butter(2,50*2/fs);
x2Est = filter(b,a,x.^2);
% std deviation estimate
xstd = sqrt(x2Est)-xmeanEst;
% and plot it
h = plot([x, xmeanEst sqrt(x2Est) xstd]);
grid on;
legend('x','E<x>','sqrt(E<x^2>)','Std dev');
set(h(2:4),'Linewidth',2);

1
Il filtro nella mia risposta corrisponde a y1 = filter(a,[1 (1-a)],x);.
nibot

1
Un buon punto sulla distinzione tra le statistiche correnti e le statistiche del campione globale. La mia implementazione potrebbe essere modificata per calcolare le statistiche di esecuzione accumulando su una finestra mobile, che può essere eseguita anche in modo efficiente (in ogni fase, sottrarre il campione temporale che è appena uscito dalla finestra da ciascun accumulatore).
Jason R

nibot, scusa hai ragione e ho sbagliato. Lo correggerò subito
Hilmar

1
+1 per suggerire filtri diversi per xe x ^ 2
nibot
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.