我试图找到一种方法来计算移动累积平均数,而不存储迄今为止收到的计数和总数据。
我想出了两个算法,但都需要存储计数:
这些方法的问题是,计数越来越大,导致在结果的平均精度损失。
第一个方法使用旧的计数器和下一个计数器,它们之间明显是1。这让我想到,也许有一种方法可以消除计数,但不幸的是,我还没有找到它。不过,它确实让我进一步了解了第二个方法,但仍然存在 count。
有可能吗,还是我只是在寻找不可能的事?
你可以简单地做:
double approxRollingAverage (double avg, double new_sample) { avg -= avg / N; avg += new_sample / N; return avg; }
其中 N是您希望平均超过的样本数。 请注意,这种近似等效于指数移动平均值。 参见: 用 C + + 计算滚动/移动平均值
N
New average = old average * (n-1)/n + new value /n
这里假设计数只被一个值改变,如果被 M 值改变,那么:
new average = old average * (n-len(M))/n + (sum of values in M)/n).
这是数学公式(我相信最有效的一个) ,相信你可以做进一步的代码自己
从 博客运行样本方差计算,其中平均值也使用 Welford 的方法计算:
可惜我们不能上传 SVG 图像。
一个使用 javascript 的例子,作为比较:
Https://jsfiddle.net/drzaus/lxsa4rpz/
function calcNormalAvg(list) { // sum(list) / len(list) return list.reduce(function(a, b) { return a + b; }) / list.length; } function calcRunningAvg(previousAverage, currentNumber, index) { // [ avg' * (n-1) + x ] / n return ( previousAverage * (index - 1) + currentNumber ) / index; }
(function(){ // populate base list var list = []; function getSeedNumber() { return Math.random()*100; } for(var i = 0; i < 50; i++) list.push( getSeedNumber() ); // our calculation functions, for comparison function calcNormalAvg(list) { // sum(list) / len(list) return list.reduce(function(a, b) { return a + b; }) / list.length; } function calcRunningAvg(previousAverage, currentNumber, index) { // [ avg' * (n-1) + x ] / n return ( previousAverage * (index - 1) + currentNumber ) / index; } function calcMovingAvg(accumulator, new_value, alpha) { return (alpha * new_value) + (1.0 - alpha) * accumulator; } // start our baseline var baseAvg = calcNormalAvg(list); var runningAvg = baseAvg, movingAvg = baseAvg; console.log('base avg: %d', baseAvg); var okay = true; // table of output, cleaner console view var results = []; // add 10 more numbers to the list and compare calculations for(var n = list.length, i = 0; i < 10; i++, n++) { var newNumber = getSeedNumber(); runningAvg = calcRunningAvg(runningAvg, newNumber, n+1); movingAvg = calcMovingAvg(movingAvg, newNumber, 1/(n+1)); list.push(newNumber); baseAvg = calcNormalAvg(list); // assert and inspect console.log('added [%d] to list at pos %d, running avg = %d vs. regular avg = %d (%s), vs. moving avg = %d (%s)' , newNumber, list.length, runningAvg, baseAvg, runningAvg == baseAvg, movingAvg, movingAvg == baseAvg ) results.push( {x: newNumber, n:list.length, regular: baseAvg, running: runningAvg, moving: movingAvg, eqRun: baseAvg == runningAvg, eqMov: baseAvg == movingAvg } ); if(runningAvg != baseAvg) console.warn('Fail!'); okay = okay && (runningAvg == baseAvg); } console.log('Everything matched for running avg? %s', okay); if(console.table) console.table(results); })();
Flip 的答案在计算上比 Muis 的更加一致。
使用双倍数字格式,你可以看到梅斯方法的四舍五入问题:
在进行除法和减法运算时,前一个存储值中会出现一个舍入,并对其进行更改。
然而,Flip 方法保留了存储值并减少了除法的数量,从而减少了舍入,并使传播到存储值的错误最小化。如果有要添加的东西,只添加将带来舍入(当 N 很大时,没有要添加的东西)
当你得到一个大值的平均值时,这些变化是显著的,它们的平均值趋于零。
我用一个电子表格程序向您展示结果:
首先,研究结果如下:
A 和 B 列分别是 n 和 X _ n 值。
C 列是 Flip 方法,D 列是 Muis 方法,结果存储在均值中。E 列对应于计算中使用的介质值。
下一个是显示偶数平均值的图表:
正如你所看到的,这两种方法有很大的不同。
这里还有另一个答案提供评论如何 梅斯,阿卜杜拉 · 阿吉尔和 翻转的答案是 从数学上来说都是一样的,除了写不同。
当然,我们有 José Manuel Ramos的分析,解释舍入错误对每个错误的影响略有不同,但这取决于实现,并且会根据每个答案应用于代码的方式而改变。
在 梅斯的 N k0的 k和 k1的 n。k1没有完全解释 n应该是什么,但是 N和 k的不同之处在于 N是“ k3”,而 k是采样值的计数。(尽管我怀疑拨打 N k4是否准确。)
k
n
现在我们来看看下面的答案。它本质上和其他的 指数加权移动平均数是一样的,所以如果你在寻找替代品,就停在这里。
最初:
average = 0 counter = 0
对于每个值:
counter += 1 average = average + (value - average) / min(counter, FACTOR)
区别在于 min(counter, FACTOR)部分,这和说 min(Flip's k, Muis's N)是一样的。
min(counter, FACTOR)
min(Flip's k, Muis's N)
FACTOR是一个常数,它影响平均“赶上”最新趋势的速度。数量越少越快。(在 1,它不再是平均值,而只是成为最新值。)
FACTOR
1
这个答案需要运行计数器 counter。如果有问题,可以用 FACTOR代替 min(counter, FACTOR),把它变成 梅斯的答案。这样做的问题是,无论 average初始化为什么,移动平均值都会受到影响。如果初始化为 0,那么这个零可能需要很长时间才能达到平均值。
counter
average
0
在 Java8中:
LongSummaryStatistics movingAverage = new LongSummaryStatistics(); movingAverage.accept(new data); ... average = movingAverage.getAverage();
你还有 IntSummaryStatistics,DoubleSummaryStatistics..。
IntSummaryStatistics
DoubleSummaryStatistics
基于以上答案的一个简洁的 Python 解决方案:
class RunningAverage(): def __init__(self): self.average = 0 self.n = 0 def __call__(self, new_value): self.n += 1 self.average = (self.average * (self.n-1) + new_value) / self.n def __float__(self): return self.average def __repr__(self): return "average: " + str(self.average)
用途:
x = RunningAverage() x(0) x(2) x(4) print(x)