对于实时应用,找到信号平均值和标准偏差的理想方法是什么。我希望能够在信号在一定时间内偏离平均值超过3个标准偏差时触发控制器。
我假设专用DSP可以很容易地做到这一点,但是是否有可能不需要那么复杂的“捷径”?
对于实时应用,找到信号平均值和标准偏差的理想方法是什么。我希望能够在信号在一定时间内偏离平均值超过3个标准偏差时触发控制器。
我假设专用DSP可以很容易地做到这一点,但是是否有可能不需要那么复杂的“捷径”?
Answers:
Jason R的答案有一个缺陷,这一点在Knuth的“计算机编程艺术”一卷中进行了讨论。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
只是恶作剧。
编辑:刚刚注意到Belisarius引用Wikipedia的评论,但确实提到了Knuth算法。
对于实时应用,找到信号平均值和标准偏差的理想方法是什么。我希望能够在信号在一定时间内偏离平均值超过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);
这里是确定移动平均的有效长度的常数。下面的“分析”中介绍了如何选择a。
上面表示为命令性程序的内容也可以描述为信号流程图:
分析
上面的算法计算,其中x i是样本i的输入,而y i是输出(即,平均值的估计)。这是一个简单的单极IIR滤波器。通过z变换,我们发现传递函数H (z )= a。
将IIR滤波器压缩到自己的块中,现在该图如下所示:
参考文献
0 < a < 1
。如果您的系统具有采样tmie,T
并且您想要一个平均时间常数tau
,则选择a = 1 - exp (2*pi*T/tau)
。
z=1
(DC)转换H(z) = a/(1-(1-a)/z)
,你会得到1
我以前在嵌入式处理应用程序中使用过的一种方法是维护感兴趣信号的和和平方和的累加器:
或者您可以使用:
取决于您喜欢哪种标准偏差估算方法。这些方程式基于方差的定义:
我过去曾经成功使用过这些方法(尽管我只关注方差估计,而不关注标准偏差),但是如果要进行求和,则必须注意用于保存累加器的数值类型长时间 你不想溢出。
编辑:除了以上关于溢出的注释之外,应该注意的是,这在浮点算术中实现时不是一种数字健壮的算法,可能会导致估计统计信息中的大错误。在这种情况下,请查看Jason S的答案以寻求更好的方法。
与上面的首选答案(Jason S.)类似,并且也从取自Knut的公式(第2卷,第232页)中得出,也可以导出一个公式来替换值,即一步删除并添加值。根据我的测试,替换提供了比两步删除/添加版本更好的精度。
下面的代码是在Java中,mean
并s
得到更新(“全局”的成员变量),同样m
与s
上述Jason的岗位。该值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的回答在一个重要方面有所不同:Jason的方法计算整个信号的std dev和均值(因为y = 0),而Nibot的计算是“运行中”的计算,即,它权衡比来自样本的样本更强的最近样本遥远的过去。
由于该应用程序似乎需要std dev和均值作为时间的函数,因此Nibot的方法可能更合适(针对此特定应用程序)。但是,真正棘手的部分是正确设置时间加权部分。Nibot的示例使用一个简单的单极点滤波器。
低通滤波器的选择可以由您对信号的了解以及估计所需的时间分辨率来指导。较低的截止频率和较高的阶数将导致精度更高,但响应时间更慢。
为了使事情更加复杂,在线性域中应用了一个滤波器,在平方域中应用了另一个。平方会显着改变信号的频谱内容,因此您可能需要在平方域中使用其他滤波器。
这是一个有关如何估计均值,均方根和标准差作为时间函数的示例。
%% 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);
。