web-dev-qa-db-ja.com

非常に巨大なデータセットの平均値と標準偏差

非結合データセットの平均値と標準偏差を計算するアルゴリズムがあるかどうか疑問に思っています。

たとえば、電流などの測定値を監視しています。すべての履歴データの平均値が欲しいです。新しい値が来るたびに、平均と標準偏差を更新しますか?データが大きすぎて保存できないため、データを保存せずにオンザフライで平均と標準偏差を更新できることを願っています。

データが保存されていても、標準的な方法(d1 + ... + dn)/ nは機能せず、合計がデータ表現を吹き飛ばします。

Sum(d1/n + d2/n + ... d3/n)については、nが大きい場合、エラーが大きすぎて累積されます。また、この場合、nはバインドされていません。

データの数は間違いなく無限であり、いつでも、値を更新する必要があります。

そのためのアルゴリズムがあるかどうか誰かが知っていますか?

21
Alfred Zhong

[質問は変わりましたか?たぶん私は最初だけを読みます。私はより良い返信をするために更新/編集しました:]

私が知っている完璧な解決策(一定のメモリ内)はありませんが、さまざまなアプローチをとることができます。

まず、基本的な計算では、すべての値の合計(sum_x)、それらの二乗の合計(sum_x2)、および総数(n)のみが必要です。次に:

mean = sum_x / n
stdev = sqrt( sum_x2/n - mean^2 )

そしてこれらすべての値(sum_xsum_x2n)はストリームから更新できます。

問題は(あなたが言うように)オーバーフローや制限された精度を扱っていることです。これは、sum_x2が大きすぎて内部表現に単一の2乗値の大きさの値が含まれていない場合に浮動小数点を考慮するとわかります。

問題を回避する簡単な方法は、正確な算術演算を使用することですが、次第に遅くなります(O(log(n))メモリ)。

役立つもう1つの方法は、値を正規化することです。値が通常Xであることがわかっている場合は、x - Xで計算を行って合計を小さくすることができます(当然、次にXを追加しますとにかく!)これは、精度が失われる時点を延期するのに役立ちます(また、ここで他の方法と組み合わせることができます(たとえば、ビニングするときは、前のビンの平均を使用できます)。これを段階的に行う方法については このアルゴリズム(knuthの方法))を参照してください

(小さな定数係数)を気にしない場合O(n)メモリコストrestartすべてのN値(例:million-よりスマートなのは、精度が低すぎる場合に検出することでこの値を適応させることです)、以前の平均と標準偏差を保存し、最終結果を結合します(つまり、平均は、最近の合計と古いビン値からの適切な加重値です)。

ビニングのアプローチは、おそらくmultiple levels(ビンのビニングを開始する)に一般化でき、O(log(n))メモリ使用量ですが、詳細はわかりません。

最後に、より実用的な解決策は、たとえば1000の値に対して最初のアプローチを実行してから、新しい合計を並行して開始することです。 2つの加重平均を表示し、次の1000の値の後で(それらの重みを徐々に減らしてから)古い合計をドロップし、新しいセットを開始できます。したがって、常に2セットの合計があり、それらの間に加重平均を表示すると、最新の1000個の値(のみ)を反映する連続データが得られます。場合によってはそれで十分だと思います(「最近の」データのみを対象としているため、正確な値ではありませんが、スムーズで代表的なものであり、固定量のメモリを使用しています)。

ps、後で私に起こった何か-実際には、これを「永久に」行うことは、とにかくあまり意味がありません。なぜなら、値が古いデータによって完全に支配されるようになるからです。古い値の重みを減らす「移動平均」を使用する方が良いでしょう。たとえば https://en.wikipedia.org/wiki/Moving_average を参照してください。ただし、一般的なstdevと同等のものは知りません。

19
andrew cooke

興味深い質問です。

それが少し簡単だからといって、最初に平均を議論しましょう。

あなたは現在の合計の丸め誤差について正しいです。データセットが十分に大きい場合は、精度が低下します。 likeでデータを事前にソートし、小さなデータを最初に合計します。もちろん、これはあなたの場合は不可能です。ただし、複数の累計を維持することで、ソートされたデータの利点のほとんどを達成できます。

CまたはC++スタイルの概念的な例:

const double max_small  =    0.001;
const double max_medium = 1000.0;

double total_small;
double total_medium;
double total_large;

while(true) {
    const double datum = get_datum(); // (Use here whatever function you use to get a datum.)
    if (!is_datum_valid()) break;
    if (abs(datum) <= max_small) total_small += datum;
    else if (abs(datum) <= max_medium) total_medium += datum;
    else total_large += datum;
}

double total = 0.0;
total += total_small;
total += total_medium;
total += total_large;

現実的なコードでは、おそらく3つ以上の合計を保持します。もちろん、データの2乗の合計も継続しますが、上記の例はその考えを伝えています。詳細を記入できます。

また、@ andrewcookeのアイデアを応用して、次のような方法でループを拡張できます。

while(true) {
    const double datum = get_datum();
    if (!is_datum_valid()) break;
    if (abs(datum) <= max_small) {
        total_small += datum;
        if (abs(total_small) > max_medium) {
            total_large += total_small;
            total_small = 0.0;
        }
    }
    else if (abs(datum) <= max_medium) total_medium += datum;
    else total_large += datum;
}

ここでも、詳細を入力できます。幸運を。

付録:標準偏差の実際の計算

平均の事前の知識なしに標準偏差を計算する方法に関して、ここのさまざまなコメントスレッドで良い質問が出されました。幸い、標準偏差を計算するトリックが知られています。トリックを説明する2ページのメモを用意しました here (PDF)。

いずれにしても、実行中の統計に標準偏差を含めるために必要なことは、データだけでなくデータの二乗も合計することです。そしてもちろん、上記のコードと同じパターンに従って、データ自体と同じように正方形を合計できます。

4
thb

番号。

(私は考えました:いいえ、しかし私は間違っていることが証明されています)。

あなたは合計と数を運ぶことができるので、

_sum(i)=500, count(i)=50, => avg:=10
next value = 20
sum=520, count=51 => avg:= 10.19
_

しかし、stddevをそのように構築することはできません。新しい平均に対するすべての値のデルタを生成し、それらを二乗する必要があります。その後、Nで除算します。 ただし、問題は、どのような値がそれらであるかです(数学的な観点から-物理学に近づかないでください!:))。通常の状況では、2000要素の後で値が変化するとは思いません。さもなければ、そもそもmeanとstddevを構築することは疑わしいかもしれません。

また、2000要素の場合、値をすばやく計算できるはずです。

たぶん、バッファを使用して、2000の値ごとに最後の2000の値のavgとstddevを常に計算できます。これが意味のあるデータかどうかは、決定する必要があります。


私たちはチャットでの議論をうまく続けることができません...

値下げについて詳しく説明していないためです。したがって、私の投稿を使用して自分の立場を明確にします。これは主にthbでのコメントに広がっていますが、アンドリューはstddevのスライド計算も信じているようです。

以下は、計算をわかりやすく、わかりやすくするための幅広い表です。列は次のとおりです。

  • i:実行中のインデックス。最初に値1〜3を計算し、次に値1〜5を計算します。
  • x(i)は、私が任意に選択したデータです。 3,4,5および4,6
  • 合計は、それらが合計するものにすぎません。興味深いのは、グループの最後の12と22です。注:3つの値と2つの値の合計ではなく、最初の3つと最初の5つの合計をとります。
  • 平均はちょうど12/3または22/5です。あなたが私と合計を知っている場合、平均はスライドして計算することができます。 sum(i+1) = (sum (i)+x(i))/i+1今のところ論争はありません。
  • Stddev。を計算するには、各値の差をavgに取り、それを二乗する必要があります(これにより、符号が失われ、そうしないと、差が無効になります-常に0になります)。 2番目の効果は、いくつかの大きな距離は、多くの小さな距離よりも大きなstddevにつながるということです。距離_(1,1,-1,-1)=> 4*1² = 4._対照的に:_(2,-2)=> 2² + -2² = 4+4 = 8_。最初の列は3つの値、2番目の列は5つの値(計算を続けるため)です。
  • 次の列(最後)²は二乗を行います。
  • まとめて
  • n-1で割る
  • 平方根を取る

spreadsheed with calculation (oocalc screenshot)

これはstddevを計算する有効な方法であることに同意できるかもしれません。ここで問題は、完全な3行目(x(3)= 5を除く)がわかっている場合の計算方法であり、シートに示されているように2つの個別の値(4、6)を取得しますが、(x( i)i = 1、2、3の場合.

私の主張は失敗しました:できます。

わかりました-数式を使用しようとしました。

ð²= 1 /(N-1)(合計(x²)-1/N(合計(x))²)

だから私が得る4つの値について

  • N = 5
  • sum(x)= 22
  • sum(x²)= 102

数式に挿入:

_ð² = 1/(N-1) (Sum (x<sub>i</sub>²) - 1/N (Sum (x<sub>i</sub>))²)
ð² = 1/4 (102 - 1/5 (22²))
ð² = 1/4 (102 - 1/5 (484))
ð² = 1/4 (102 - 96.8)
ð² = 1/4 (5.2)
ð² = 1.3
ð  = 1.140
_

私の結果は1.14で、あなたの結果は1.14ですしたがって、ショートカットがあります。非常に興味深い-私はまだ驚いています。

3
user unknown

実際、標準偏差を計算するときの小さなデータセットでも、平方和を計算しないにする必要があります。問題は致命的なキャンセルと呼ばれます(Wikipediaへのリンク)。

ウィキペディアには、この問題を回避するのに役立つ2つの記事もあります。

  • Kahan加算アルゴリズム 非常に小さな値を多数加算するとき(たとえば、すべてのx/n値を加算するとき)の系統誤差を回避するためのキャリーオーバーがあります。
  • 分散を計算するためのアルゴリズム 、特に「オンライン」バージョンは、大きなデータセットに適しています。 増分的に更新各観測の平均なので、値はデータのスケールに残ります!最初のオンラインアルゴリズムはまだsqaured-deviations-from-meanの合計を計算するため、分散にはより高次のバージョンを使用する必要がある場合があります。そのため、nが大きい場合、値の範囲が大きくなる可能性があります。高次バージョンのM2には、出力のスケールである平均二乗偏差からの平均が含まれている必要があります。

これはおそらく、単純な統計計算で最も一般的な問題の1つです。

平均が0付近に留まり、分散よりはるかに小さい場合、問題は発生しないことに注意してください。

したがって、これが以前に使用されたアルゴリズムであるかどうかはわかりませんが、とにかく提供します。間違った平均値で標準偏差を計算し、実際の平均値に基づいて修正するという考えから始めました。これは私が書いたものの写真です

0
user1585635