回答:
Jason Rの答えには欠陥があります。これは、Knuthの「コンピュータプログラミングの芸術」vol。2.平均のごく一部である標準偏差がある場合に問題が発生します。E(x ^ 2)-(E(x)^ 2)の計算は、浮動小数点の丸め誤差に対して深刻な影響を受けます。
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
答えとして-128.0が得られますが、数学的には結果が非負であるべきだと予測されているため、明らかに計算上有効ではありません。
Knuthは、次のような移動平均と標準偏差を計算するアプローチ(発明者の名前は覚えていません)を引用しています。
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);
そして、各ステップの後、の値m
は平均であり、標準偏差は、標準偏差の好みの定義として、sqrt(S/n)
またはそれにsqrt(S/n-1)
応じて計算できます。
上記の式は、Knuthの式とは少し異なりますが、計算上は同等です。
数分後、上記の式をPythonでコーディングし、否定でない答えが得られることを示します(正しい値に近いことを願っています)。
更新:ここにあります。
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)
結果:
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}
あなたはまだいくらかの丸め誤差があることに気付くでしょうが、それは悪いことではありませんが、naive_stats
ただ吐くだけです。
編集:Knuthアルゴリズムについて言及しているWikipediaを引用したBelisariusのコメントに気づいたところです。
リアルタイムアプリケーションの信号の平均と標準偏差を見つける理想的な方法は何でしょうか。信号が一定時間平均から3標準偏差以上外れたときにコントローラーをトリガーできるようにしたいと思います。
このような状況での正しいアプローチは、通常、指数関数的に加重された移動平均と標準偏差を計算することです。指数関数的に加重平均では、平均と分散の推定値は、あなたが平均と分散の推定値を与える最新のサンプルに偏っている最後のオーバー秒ではなく、すべての上に通常の算術平均よりも、あなたが望むものはおそらくあり、今まで見たサンプル。
周波数領域では、「指数関数的に重み付けされた移動平均」は、実際の極です。時間領域での実装は簡単です。
時間領域の実装
させるmean
およびmeansq
は、信号の二乗の平均および平均の現在の推定値です。すべてのサイクルで、これらの推定値を新しいサンプルで更新します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);
ここで、は、移動平均の有効な長さを決定する定数です。を選択方法については、「分析」で説明します。
上記の命令型プログラムとして表現されているものは、シグナルフロー図として表される場合もあります。
分析
上記のアルゴリズムは、計算しますここで、はサンプルの入力で、は出力(平均の推定値)です。これは、シンプルな単極IIRフィルターです。服用変換、我々は、伝達関数を見つける。
IIRフィルターを独自のブロックに凝縮すると、ダイアグラムは次のようになります。
連続領域に移動するには、置換ここで、はサンプル時間で、はサンプルレートです。解くと、連続システムの極がにあることがわかります。 T f s = 1 / T 1 − (1 − a )e − s T = 0 s = 1
選択:A = 1 - EXP { 2 π T
参照資料
0 > a > 1
0 < a < 1
。システムにサンプリングtmieがT
あり、平均化時定数が必要な場合はtau
、を選択しますa = 1 - exp (2*pi*T/tau)
。
z=1
(DC)に代入するH(z) = a/(1-(1-a)/z)
と、1が得られます。
以前に組み込み処理アプリケーションで使用した方法は、対象の信号の和と二乗和の累算器を維持することです。
また、上記の方程式で現在の時刻を追跡します(つまり、アキュムレータに追加したサンプルの数に注意してください)。次に、時間でのサンプル平均と標準偏差は次のとおりです。
または使用できます:
どちらの標準偏差推定方法を使用するかによります。これらの方程式は、分散の定義に基づいています。
過去にこれらを正常に使用しました(ただし、標準偏差ではなく分散推定のみに関係していました)。長期間; あなたはオーバーフローしたくない。
編集:上記のオーバーフローに関するコメントに加えて、これは浮動小数点演算で実装された場合、数値的に堅牢なアルゴリズムではなく、推定統計に大きなエラーを引き起こす可能性があることに注意する必要があります。その場合のより良いアプローチについては、ジェイソンSの答えを見てください。
上記の好ましい回答(Jason S.)と同様に、Knut(Vol.2、p 232)から得られた式から導き出された、値を置き換える式を導出することもできます。 。私のテストによると、replaceは2ステップの削除/追加バージョンよりも優れた精度を提供します。
以下のコードはJavaでmean
ありs
、更新されます(「グローバル」メンバー変数)。Jasonの投稿m
と同じs
です。値count
はウィンドウサイズを参照します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;
}
JasonとNibotの答えは1つの重要な側面で異なります。Jasonの方法は、信号全体のstd devと平均を計算します(y = 0であるため)。遠い過去。
アプリケーションは時間の関数としてstd devとmeanを必要とするため、Nibotの方法が(この特定のアプリケーションにとって)おそらくより適切な方法です。ただし、本当のトリッキーな部分は、時間の重み付け部分を正しくすることです。Nibotの例では、単純な単極フィルターを使用しています。
これを記述する適切な方法は、をフィルタリングすることで推定値を取得し、フィルタリングすることで推定値を取得することです。推定フィルターは通常、ローパスフィルターです。これらのフィルターは、0 Hzで0dBゲインになるようにスケーリングする必要があります。そうでなければ、一定のゲインエラーがあります。x [ n ] E [ x 2 ] x [ n ] 2
ローパスフィルターの選択は、信号について知っていることと、推定に必要な時間分解能によって決まります。カットオフ周波数を低くし、次数を高くすると、精度は向上しますが、応答時間が遅くなります。
さらに複雑にするために、1つのフィルターを線形領域に適用し、別のフィルターを2乗領域に適用します。2乗は信号のスペクトル成分を大幅に変更するため、2乗領域で別のフィルターを使用することもできます。
以下は、時間の関数として平均、rms、および標準偏差を推定する方法の例です。
%% 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);
y1 = filter(a,[1 (1-a)],x);
ます。