我需要计算值的标准偏差存储在循环缓冲区中。最终算法将在资源受限的设备上运行,因此我希望它尽可能轻量级。天真的方法是每次推入新值时重新评估整个缓冲区的标准偏差,但它会非常慢。理想情况下,我想要一种在推入新值时动态更新标准差当前值的算法。
Wikipedia reports some techniques for rapid calculation,但它们可以在流上使用:在我的情况下,当推入新值时,应该计算标准偏差,好像最后一个已经弹出的值永远不存在。
tl; dr:如何以最小的计算量计算循环缓冲区的标准偏差?
答案 0 :(得分:8)
标准差可表示为:
stddev = sqrt(1/N * SUM(x[i]^2) - 1/N^2 * SUM(x[i])^2)
对于未校正的样品标准偏差。对于校正的样本标准差,可以写:
stddev = sqrt(1/(N-1) * SUM(x[i]^2) - 1/(N^2-N) * SUM(x[i])^2)
如果你保留两个累加器,一个用于SUM(x[i]^2)
,一个用于SUM(x[i])
,那么对于每个新值来说这些都是微不足道的(减去最旧值的影响,并添加效果)最新的价值)。 N
当然是缓冲区的长度。
当然,如果你是在浮点运算,那么你可能会遇到舍入错误((x + y) - x != y
,一般而言)。我认为没有任何简单的解决办法。
答案 1 :(得分:2)
为集合保留三个数字:值的计数,值的总和以及值的平方和。为方便起见,我们将这些称为k,sn和sn2。
如果我认为你暗示,新值总是替换循环队列中的旧值,那么计数可能是常量。或者也许可能有一个不完整的队列。无论哪种方式:
每次向队列添加值时,向计数中添加一个值,将此值添加到总和中,并将此值的平方值添加到平方和中。也就是说,如果添加新值“n”,则k = k + 1,sn = sn + n,sn2 = sn2 + n ^ 2.
每次从队列中删除一个值时,从计数中减去一个,从总和中减去该值,并从平方和中减去该值的平方。也就是说,如果删除值“n”,则k = k-1,sn = sn-n,sn2 = sn2-n ^ 2.
然后,您可以在每次更改后轻松重新计算标准差,而无需重新计算所有内容。
请注意,这意味着您必须能够在真正删除之前“捕获”某个值。
注意:我怀疑这是我的袖珍计算器的用法,因为它具有转储sum(n)和sum(n ^ 2)的功能,我可以“删除”我从未添加的值,就像我可以说添加2和4到集合,然后删除3,它说没关系。所以我认为它没有列出一个清单:它必须保持计数和总和。
答案 2 :(得分:1)
最简单的方法是反转Knuth的公式(来自Wikipedia article方差计算:
M k-1 = M k - (x k - M k )/(k- 1)
S k-1 = S k - (x k - M k-1 )(x k - M k )
但请注意,浮点错误将累积在整个运行上!这意味着你的均值和方差数会趋于漂移,这种方法实际上不能用作准确的在线方法。
一个稳定的在线方法,每个样本采用O(log N)操作(其中N是队列中元素的数量)将使用统计项的二进制合并树,使用维基百科文章中的“并行合并”公式如下(以C ++形式):
struct Statistic {
int k;
Element M;
Element S;
Statistic(Element x)
: k(1)
, M(x)
, S(0)
{}
Statistic(Statistic a, Statistic b)
: k(a.k + b.k)
, M(a.M*a.k + b.M*b.k)/float(k)
, S(a.S + b.S + (a.M-b.M)*(a.M-b.M)*(a.k*b.k/float(k)))
{}
};
对于稳定的O(log N)在线算法,维护上述统计的平衡二叉树,叶子代表各个元素;根将产生所需的在线统计数据。在更新元素时(以旋转缓冲区样式),将执行O(log N)操作以将每个更新从叶传播到根。