it-swarm.com.de

Rollender Varianz-Algorithmus

Ich versuche, einen effizienten, numerisch stabilen Algorithmus zu finden, um eine Rollvarianz zu berechnen (beispielsweise eine Varianz über ein Rollfenster von 20 Perioden). Ich kenne den Welford-Algorithmus , der effizient die laufende Varianz für einen Zahlenstrom berechnet (dies erfordert nur einen Durchlauf), bin jedoch nicht sicher, ob dies für ein Rollfenster angepasst werden kann. Ich möchte auch die Lösung, um die Genauigkeitsprobleme zu vermeiden, die oben in diesem Artikel von John D. Cook diskutiert wurden. Eine Lösung in jeder Sprache ist in Ordnung.

60
Abiel

Ich bin auch auf dieses Problem gestoßen. Es gibt einige großartige Beiträge bei der Berechnung der laufenden kumulativen Varianz, wie beispielsweise John Cookes Accurately computing running varance post und der Beitrag von Digital explorations, Python-Code zur Berechnung von Stichproben- und Populationsvarianzen, Kovarianz und Korrelation) Koeffizient. Ich konnte einfach keine finden, die an ein Rollfenster angepasst waren.

Der Standardabweichungen post von Sublumenal Messages war entscheidend, um die Rolling Window-Formel zum Laufen zu bringen. Jim nimmt die Potenzsumme der quadrierten Differenzen der Werte gegenüber dem Ansatz von Welford an, indem er die Summe der quadrierten Differenzen des Mittelwerts verwendet. Formel wie folgt: 

PSA heute = PSA (gestern) + (((x heute * x heute) - x gestern))/n

  • x = Wert in Ihrer Zeitreihe
  • n = Anzahl der Werte, die Sie bisher analysiert haben.

Um die Power Sum Average-Formel in eine Fenstersorte zu konvertieren, müssen Sie die Formel auf folgende Weise anpassen: 

PSA heute = PSA gestern + ((((x heute * x heute) - (x gestern * x gestern)/n 

  • x = Wert in Ihrer Zeitreihe
  • n = Anzahl der Werte, die Sie bisher analysiert haben.

Sie benötigen außerdem die Formel "Rolling Simple Moving Average":

SMA heute = SMA gestern + ((x heute - x heute - n)/n

  • x = Wert in Ihrer Zeitreihe
  • n = Zeitraum für Ihr Rollfenster.

Von dort können Sie die Rollende Populationsabweichung berechnen:

Bevölkerung Var heute = (PSA heute * n - n * SMA heute * SMA heute)/n

Oder die rollende Musterabweichung:

Sample Var heute = (PSA heute * n - n * SMA heute * SMA heute)/(n - 1)

Ich habe dieses Thema zusammen mit Beispiel-Python-Code in einem Blogbeitrag vor ein paar Jahren behandelt, Running Variance .

Hoffe das hilft.

Bitte beachten Sie: Ich habe Links zu allen Blogbeiträgen und mathematischen Formeln angegeben in Latex (Bilder) für diese Antwort. Aber aufgrund meines schlechten Rufes (< 10); Ich bin auf nur 2 Hyperlinks beschränkt und absolut keine Bilder. Es tut uns leid darüber. Hoffe, das nimmt den Inhalt nicht weg.

22
Mike Taylor

Ich habe mich mit dem gleichen Thema beschäftigt.

Der Mittelwert ist einfach iterativ zu berechnen, aber Sie müssen den gesamten Verlauf der Werte in einem Ringpuffer aufbewahren.

next_index = (index + 1) % window_size;    // oldest x value is at next_index, wrapping if necessary.

new_mean = mean + (x_new - xs[next_index])/window_size;

Ich habe den Welford-Algorithmus angepasst und er funktioniert für alle Werte, die ich getestet habe.

varSum = var_sum + (x_new - mean) * (x_new - new_mean) - (xs[next_index] - mean) * (xs[next_index] - new_mean);

xs[next_index] = x_new;
index = next_index;

Um die aktuelle Varianz zu erhalten, dividieren Sie varSum einfach durch die Fenstergröße: variance = varSum / window_size;

17
DanS

Wenn Sie Code gegenüber Wörtern bevorzugen (stark auf DanS-Beitrag basierend): http://calcandstuff.blogspot.se/2014/02/rolling-variance-calculation.html

public IEnumerable RollingSampleVariance(IEnumerable data, int sampleSize)
{
    double mean = 0;
    double accVar = 0;

    int n = 0;
    var queue = new Queue(sampleSize);

    foreach(var observation in data)
    {
        queue.Enqueue(observation);
        if (n < sampleSize)
        {
            // Calculating first variance
            n++;
            double delta = observation - mean;
            mean += delta / n;
            accVar += delta * (observation - mean);
        }
        else
        {
            // Adjusting variance
            double then = queue.Dequeue();
            double prevMean = mean;
            mean += (observation - then) / sampleSize;
            accVar += (observation - prevMean) * (observation - mean) - (then - prevMean) * (then - mean);
        }

        if (n == sampleSize)
            yield return accVar / (sampleSize - 1);
    }
}
7
Joachim

Hier ist ein Divide-and-Conquer-Ansatz, der O(log k)-Zeitaktualisierungen enthält, wobei k die Anzahl der Samples ist. Es sollte aus den gleichen Gründen relativ stabil sein, dass paarweise Summation und FFTs stabil sind, aber es ist etwas kompliziert und die Konstante ist nicht großartig.

Angenommen, wir haben eine Sequenz A der Länge m mit dem Mittelwert E(A) und der Varianz V(A) und eine Sequenz B der Länge n mit dem Mittelwert E(B) und der Varianz V(B). Sei C die Verkettung von A und B. Wir haben

p = m / (m + n)
q = n / (m + n)
E(C) = p * E(A) + q * E(B)
V(C) = p * (V(A) + (E(A) + E(C)) * (E(A) - E(C))) + q * (V(B) + (E(B) + E(C)) * (E(B) - E(C)))

Füllen Sie nun die Elemente in einen rot-schwarzen Baum, in dem jeder Knoten mit dem Mittelwert und der Varianz des untergeordneten Baums verziert ist. Einfügen rechts links löschen. (Da wir nur auf die Enden zugreifen, wird ein Splay-Tree möglicherweise O(1) amortisiert, aber ich schätze, Amortized ist ein Problem für Ihre Anwendung.) Wenn k zur Kompilierzeit bekannt ist, könnten Sie das wahrscheinlich abrollen innere Schleife im FFTW-Stil.

5
userOVER9000

Tatsächlich kann der Welfords-Algorithmus AFAICT leicht angepasst werden, um gewichtet Variance ..__ zu berechnen. Wenn Sie die Gewichte auf -1 setzen, sollten Sie Elemente effektiv löschen können. Ich habe die Berechnungen nicht überprüft, ob sie negative Gewichte zulassen, aber auf den ersten Blick sollte es!

Ich habe ein kleines Experiment mit ELKI durchgeführt:

void testSlidingWindowVariance() {
MeanVariance mv = new MeanVariance(); // ELKI implementation of weighted Welford!
MeanVariance mc = new MeanVariance(); // Control.

Random r = new Random();
double[] data = new double[1000];
for (int i = 0; i < data.length; i++) {
  data[i] = r.nextDouble();
}

// Pre-roll:
for (int i = 0; i < 10; i++) {
  mv.put(data[i]);
}
// Compare to window approach
for (int i = 10; i < data.length; i++) {
  mv.put(data[i-10], -1.); // Remove
  mv.put(data[i]);
  mc.reset(); // Reset statistics
  for (int j = i - 9; j <= i; j++) {
    mc.put(data[j]);
  }
  assertEquals("Variance does not agree.", mv.getSampleVariance(),
    mc.getSampleVariance(), 1e-14);
}
}

Ich habe eine Genauigkeit von ~ 14 Stellen im Vergleich zum genauen Zwei-Durchlauf-Algorithmus. Das ist ungefähr so ​​viel, wie man von Doppelungen erwarten kann. Beachten Sie, dass Welford does wegen der zusätzlichen Unterteilung mit Rechenaufwand verbunden ist - es dauert etwa doppelt so lange wie der genaue Zwei-Pass-Algorithmus. Wenn Ihre Fenstergröße klein ist, kann es viel sinnvoller sein, den Mittelwert neu zu berechnen und in einem zweiten Durchlauf die Varianz alle Zeit einzugeben.

Ich habe dieses Experiment als Komponententest zu ELKI hinzugefügt. Die vollständige Quelle finden Sie hier: http://elki.dbs.ifi.lmu.de/browser/elki/trunk/test/de/lmu/ifi/dbs /elki/math/TestSlidingVariance.Java Außerdem wird die genaue Varianz von zwei Durchgängen verglichen.

Bei verdrehten Datensätzen kann sich das Verhalten jedoch unterscheiden. Dieser Datensatz ist offensichtlich einheitlich verteilt; aber ich habe auch ein sortiertes Array ausprobiert und es hat funktioniert.

4
Erich Schubert

Ich weiß, dass diese Frage alt ist, aber falls jemand anderes interessiert, folgt der Python-Code. Es ist inspiriert von johndcook blog post, @ Joachim's, @ DanS 's Code und @Jaime Kommentaren. Der folgende Code enthält immer noch kleine Ungenauigkeiten für kleine Datenfenstergrößen. Genießen.

from __future__ import division
import collections
import math


class RunningStats:
    def __init__(self, WIN_SIZE=20):
        self.n = 0
        self.mean = 0
        self.run_var = 0
        self.WIN_SIZE = WIN_SIZE

        self.windows = collections.deque(maxlen=WIN_SIZE)

    def clear(self):
        self.n = 0
        self.windows.clear()

    def Push(self, x):

        self.windows.append(x)

        if self.n <= self.WIN_SIZE:
            # Calculating first variance
            self.n += 1
            delta = x - self.mean
            self.mean += delta / self.n
            self.run_var += delta * (x - self.mean)
        else:
            # Adjusting variance
            x_removed = self.windows.popleft()
            old_m = self.mean
            self.mean += (x - x_removed) / self.WIN_SIZE
            self.run_var += (x + x_removed - old_m - self.mean) * (x - x_removed)

    def get_mean(self):
        return self.mean if self.n else 0.0

    def get_var(self):
        return self.run_var / (self.WIN_SIZE - 1) if self.n > 1 else 0.0

    def get_std(self):
        return math.sqrt(self.get_var())

    def get_all(self):
        return list(self.windows)

    def __str__(self):
        return "Current window values: {}".format(list(self.windows))
2
ewerlopes

Für nur 20 Werte ist es trivial, die Methode here (ich habe nicht schnell gesagt) anzupassen.

Sie können einfach ein Array von 20 dieser RunningStat-Klassen aufnehmen.

Die ersten 20 Elemente des Streams sind etwas Besonderes. Sobald dies geschehen ist, ist es jedoch viel einfacher:

  • wenn ein neues Element eintrifft, löschen Sie die aktuelle RunningStat-Instanz, fügen Sie das Element allen 20 Instanzen hinzu und erhöhen Sie den "Zähler" (modulo 20), der die neue "volle" RunningStat-Instanz identifiziert
  • sie können jederzeit die aktuelle "full" -Instanz aufrufen, um Ihre laufende Variante zu erhalten.

Sie werden natürlich feststellen, dass dieser Ansatz nicht wirklich skalierbar ist ...

Sie können auch feststellen, dass die Zahlen, die wir behalten, eine gewisse Redudanz haben (wenn Sie die Klasse RunningStat full verwenden). Eine offensichtliche Verbesserung wäre, die 20 Leisten Mk und Sk direkt zu halten.

Ich kann mir keine bessere Formel über diesen speziellen Algorithmus vorstellen. Ich fürchte, dass die rekursive Formulierung uns die Hände bindet.

1
Matthieu M.

Ich freue mich darauf, dass ich mich als falsch erwiesen habe, aber ich glaube nicht, dass dies "schnell" geschehen kann. Das heißt, ein großer Teil der Berechnung verfolgt den EV über das Fenster, was leicht gemacht werden kann. 

Ich gehe mit der Frage: Sind Sie sicher, dass Sie brauchen eine Fensterfunktion? Wenn Sie nicht mit sehr großen Fenstern arbeiten, ist es wahrscheinlich besser, einen bekannten vordefinierten Algorithmus zu verwenden. 

1
Andrew White

Hier ist eine weitere O(log k)-Lösung: Finden Sie die ursprüngliche Folge der Quadrate, summieren Sie dann die Paare, dann die Quadrupel usw .. (Sie benötigen einen Puffer, um alle diese Informationen effizient finden zu können.) Addieren Sie dann die benötigten Werte um deine Antwort zu bekommen. Zum Beispiel:

|||||||||||||||||||||||||  // Squares
| | | | | | | | | | | | |  // Sum of squares for pairs
|   |   |   |   |   |   |  // Pairs of pairs
|       |       |       |  // (etc.)
|               |
   ^------------------^    // Want these 20, which you can get with
        |       |          // one...
    |   |       |   |      // two, three...
                    | |    // four...
   ||                      // five stored values.

Jetzt verwenden Sie Ihre Standardformel E (x ^ 2) -E (x) ^ 2 und Sie sind fertig. (Nicht, wenn Sie für kleine Zahlenmengen eine gute Stabilität benötigen; dies wurde unter der Annahme angenommen, dass nur die Anhäufung von Rollfehlern Probleme verursachte.)

Das Summieren von 20 Quadratzahlen ist auf den meisten Architekturen heutzutage sehr schnell. Wenn Sie mehr tun würden - etwa ein paar Hundert -, wäre eine effizientere Methode eindeutig besser. Aber ich bin nicht sicher, dass rohe Gewalt hier nicht der Weg ist.

1
Rex Kerr

Ich schätze, Ihre 20 Proben, Summe (X ^ 2 von 1..20) und Summe (X von 1..20), zu verfolgen und die beiden Summen bei jeder Iteration nacheinander neu zu berechnen, ist nicht effizient genug? Es ist möglich, die neue Varianz neu zu berechnen, ohne alle Samples zusammenzurechnen, zu quadrieren usw.

Wie in:

Sum(X^2 from 2..21) = Sum(X^2 from 1..20) - X_1^2 + X_21^2
Sum(X from 2..21) = Sum(X from 1..20) - X_1 + X_21
1
John

Dies ist nur eine kleine Ergänzung zu der hervorragenden Antwort von DanS. Die folgenden Gleichungen dienen dazu, die älteste Probe aus dem Fenster zu entfernen und den Mittelwert und die Varianz zu aktualisieren. Dies ist beispielsweise nützlich, wenn Sie kleinere Fenster in der Nähe des rechten Randes Ihres Eingabedatenstroms aufnehmen möchten (d. H. Einfach das älteste Fenster-Sample entfernen, ohne ein neues Sample hinzuzufügen).

window_size -= 1; % decrease window size by 1 sample
new_mean = prev_mean + (prev_mean - x_old) / window_size
varSum = varSum - (prev_mean - x_old) * (new_mean - x_old)

Hier ist x_old das älteste Muster in dem Fenster, das Sie entfernen möchten.

0
vibe