如何迭代计算移动加权平均值,以使最后一个值最能加权?

问题描述 投票:13回答:7

我想实现一种迭代算法,该算法计算加权平均值。具体的权重定律无关紧要,但是对于最新的值,它应该接近1,对于最早的值,它应该接近0。

该算法应该是迭代的。即它不应该记住所有先前的值。它应该只知道一个最新值以及有关过去的任何汇总信息,例如平均值,总和,计数等的先前值。

有可能吗?

例如,以下算法可以是:

void iterate(double value) {
   sum *= 0.99;
   sum += value;
   count++;
   avg = sum / count;
}

它将使指数权重下降,这可能不好。有可能会逐步减轻体重吗?

编辑1

称重法的要求如下:

1)重量减少到过去2)我有一些平均或特征持续时间,因此,这个持续时间长的值比新持续时间的值要小得多3)我应该可以设置此持续时间

编辑2

我需要以下内容。假设v_i是值,其中v_1是第一个。还假设w_i是权重。但是w_0是最后一个。

所以,在第一个值出现之后,我有了第一个平均值

 a_1 = v_1 * w_0

[第二个值v_2到来之后,我应该具有平均值

 a_2 = v_1 * w_1 + v_2 * w_0

我应该拥有下一个值

 a_3 = v_1 * w_2 + v_2 * w_1 + v_3 * w_0

注意,当我沿着值序列移动时,体重曲线正在跟着我移动。

即每个值始终没有自己的权重。我的目标是减少过去的体重。

algorithm iteration weighted-average
7个回答
24
投票

[先介绍一下背景。如果我们保持一个正常的平均值,它将像这样:

average(a) = 11
average(a,b) = (average(a)+b)/2
average(a,b,c) = (average(a,b)*2 + c)/3
average(a,b,c,d) = (average(a,b,c)*3 + d)/4

您可以在这里看到,这是一个“在线”算法,我们只需要跟踪数据片段:1)平均值中的总数,以及2)平均值本身。然后我们可以将平均值除以总数,加上新的数字,然后除以新的总数

加权平均值略有不同。这取决于哪种加权平均值。例如,如果您定义:

weightedAverage(a,wa, b,wb, c,wc, ..., z,wz) = a*wa + b*wb + c*wc + ... + w*wz
 or
weightedAverage(elements, weights) = elements·weights

...然后,除了添加新元素*权重,您无需执行任何其他操作!但是,如果您定义的加权平均值类似于概率的期望值:

weightedAverage(elements,weights) = elements·weights / sum(weights)

...然后,您需要跟踪总重量。而不是除以元素总数,您除以总数weight,添加新元素* weight,然后除以新的总数weight] >>。

或者,您也不需要进行除法,如下所示:您可以仅跟踪封闭点或对象中的临时点积和总重量,并在屈服时对其进行除法(这有助于避免数值复合舍入误差引起的不准确)。

在python中为:

def makeAverager():
    dotProduct = 0
    totalWeight = 0

    def averager(newValue, weight):
        nonlocal dotProduct,totalWeight

        dotProduct += newValue*weight
        totalWeight += weight
        return dotProduct/totalWeight

    return averager

演示:

>>> averager = makeAverager()
>>> [averager(value,w) for value,w in [(100,0.2), (50,0.5), (100,0.1)]]
[100.0, 64.28571428571429, 68.75]
>>> averager(10,1.1)
34.73684210526316
>>> averager(10,1.1)
25.666666666666668
>>> averager(30,2.0)
27.4

> 但是我的任务是,每当新值到达时都重新计算平均值,并重新加权旧值。 –OP

即使使用非常简单的加权方案,您的任务几乎总是不可能的。

您正在使用O(1)存储器,通过变化的加权方案获得平均值。例如,对于一些几乎任意更改的权重序列,将{values·weights1(values+[newValue2])·weights2(values+[newValue2,newValue3])·weights3,...}作为新值传入。由于注入性,这是不可能的。将数字合并在一起后,您将丢失大量信息。例如,即使具有权重向量

,也无法恢复原始值向量,反之亦然。我只能想到两种情况,您可以从中摆脱出来:
  • 恒定权重,例如[2,2,2,... 2]:这等效于在线平均算法,您不希望这样做,因为不会对旧值进行“重新加权”。
  • 先前答案的相对权重不变。例如,您可以将权重设置为[8,4,2,1],并添加一个具有任意权重的新元素,例如...+[1],但是必须将所有先前的元素增加same乘积因子,例如[16,8,4,2]+[1]。因此,在每个步骤中,您都将添加新的任意权重以及过去的新的任意缩放比例,因此您具有2个自由度(如果需要保持点积归一化,则只有1个自由度)。您将获得的权重向量如下所示:
[w0]
[w0*(s1), w1]
[w0*(s1*s2), w1*(s2), w2]
[w0*(s1*s2*s3), w1*(s2*s3), w2*(s3), w3]
...

因此,您可以使任何看起来像这样的加权方案都可以工作(除非您需要通过权重之和对事物进行归一化,在这种情况下,您必须将新的平均值除以新的总和,然后通过保持仅O(1)内存)。仅将先前的平均值乘以新的s(这将隐式地将点积分布到权重中),然后对新的+w*newValue进行固定。

我认为您正在寻找这样的东西:

void iterate(double value) {
    count++;

    weight = max(0, 1 - (count / 1000));

    avg = ( avg * total_weight * (count - 1)  + weight * value) / (total_weight * (count - 1) + weight)
    total_weight += weight;
}

在这里,我假设您希望权重之和为1。只要您可以在将来不改变权重的情况下生成相对权重,就可以得到一个模仿此行为的解决方案。

即,假设您将权重定义为序列{s_0, s_1, s_2, ..., s_n, ...},并将输入定义为序列{i_0, i_1, i_2, ..., i_n}

请考虑以下形式:sum(s_0*i_0 + s_1*i_1 + s_2*i_2 + ... + s_n*i_n) / sum(s_0 + s_1 + s_2 + ... + s_n)。请注意,可以使用几个聚合计数器来逐步计算此值:

int counter = 0;
double numerator = 0;
double denominator = 0;

void addValue(double val)
{
    double weight = calculateWeightFromCounter(counter);
    numerator += weight * val;
    denominator += weight;
}

double getAverage()
{
    if (denominator == 0.0) return 0.0;
    return numerator / denominator;
}

当然,在这种情况下,calculateWeightFromCounter()不应生成总和为1的权重—此处的窍门是,我们通过除以权重之和来求平均值,以便最终,权重实际上似乎等于一个。

真正的窍门是您如何进行CalculationWeightFromCounter()。例如,您可以简单地返回计数器本身,但是请注意,最后的加权数不一定会接近计数器的总和,因此您可能无法获得所需的确切属性。 (很难说,因为如上所述,您留下了一个相当开放的问题。)

此时间太长,无法在评论中发布,但可能会有用。

假设您有:w_0*v_n + ... w_n*v_0(以下简称为w[0..n]*v[n..0]

然后下一步是:w_0*v_n1 + ... w_n1*v_0(简称为w[0..n1]*v[n1..0]

这意味着我们需要一种从w[1..n1]*v[n..0]来计算w[0..n]*v[n..0]的方法。

[z在某个位置x处,v[n..0]肯定是0, ..., 0, z, 0, ..., 0是可能的。

如果没有任何“额外”存储,则f(z*w(x))=z*w(x + 1),其中w(x)是位置x的权重。

重新排列方程式,w(x + 1) = f(z*w(x))/z。好吧,w(x + 1)对于常数x最好是常数,所以f(z*w(x))/z最好是常数。因此,f必须让z传播-即f(z*w(x)) = z*f(w(x))

但是这里又有一个问题。请注意,如果z(可以是任何数字)可以通过f传播,那么w(x)当然可以。所以f(z*w(x)) = w(x)*f(z)。因此f(w(x)) = w(x)/f(z)。但是对于常数xw(x)是常数,因此f(w(x))也最好是常数。 w(x)是常数,所以f(z)最好是常数,以便w(x)/f(z)是常数。因此,f(w(x)) = w(x)/c其中c是常数。

因此,f(x)=c*x其中c是常数,当x是权重值时。

所以w(x+1) = c*w(x)

即,每个权重是前一个的倍数。因此,权重的形式为w(x)=m*b^x

注意,这假设f仅有的信息是最后的合计值。请注意,除非您愿意存储代表输入的非恒定数据量,否则有时会遇到这种情况。您不能用实数表示实数的无限长度向量,但是可以以一定的固定数量有限地近似估计它们。但这仅仅是一个近似值。

尽管我还没有严格地证明这一点,但我的结论是,您想要的东西不可能高精度地完成,但是您可以使用log(n)空间(也可以是O( 1)对于许多实际应用)生成质量近似值。您也许可以使用更少的东西。

我尝试用Java编写几乎所有的代码。如前所述,您的目标是无法实现的。您只能从一定数量的上次记忆值中计算平均值。如果不需要精确,则可以近似使用较旧的值。我尝试通过准确地记住最后5个值,而仅记住较旧的值仅对5个值求和,并记住最后5个SUM来做到这一点。那么,用于记住最后的n + n * n个值的复杂度为O(2n)。这是一个非常粗略的近似值。

您可以根据需要修改“ lastValues”和“ lasAggregatedSums”数组大小。看到这张试图显示最后一个值的图形的ascii-art图片,它显示了第一列(较旧的数据)被记住为合计值(不是单独地),并且仅记住了最早的5个值。

values:
            #####
            #####       #####        #
      ##### #####       #####        #  #
      ##### ##### ##### #####       ## ##
      ##### ##### ##### ##### ##### #####
time: --->

挑战1

:我的示例不计算权重,但我认为适当地为“ lastAggregatedSums”添加权重应该不成问题-唯一的问题是,如果您希望降低权重值越大,则难度就越大,因为数组正在旋转,因此要知道哪个数组成员的权重并不是一件容易的事。也许您可以修改算法以始终在数组中“移动”值而不是旋转?然后增加权重应该不是问题。

挑战2:数组使用0值初始化,并且即使我们没有收到足够的值,这些值也从一开始就计入平均值。如果您长时间运行该算法,则可能不必担心它在一开始就学习了一段时间。如果这样做,您可以发布修改;-)

public class AverageCounter {
    private float[] lastValues = new float[5];
    private float[] lastAggregatedSums = new float[5];
    private int valIdx = 0;
    private int aggValIdx = 0;
    private float avg;

    public void add(float value) {
        lastValues[valIdx++] = value;
        if(valIdx == lastValues.length) {
            // count average of last values and save into the aggregated array.
            float sum = 0;
            for(float v: lastValues) {sum += v;}
            lastAggregatedSums[aggValIdx++] = sum;
            if(aggValIdx >= lastAggregatedSums.length) {
                // rotate aggregated values index
                aggValIdx = 0;
            }
            valIdx = 0;
        }
        float sum = 0;
        for(float v: lastValues) {sum += v;}
        for(float v: lastAggregatedSums) {sum += v;}
        avg = sum / (lastValues.length + lastAggregatedSums.length * lastValues.length);
    }

    public float getAvg() {
        return avg;
    }
}

您可以将(加权总和)指数均值与不同的有效窗口大小(N)结合起来,以获得所需的权重。使用更多的指数方法来更详细地定义您的体重曲线。(更多指数手段还意味着存储和计算更多值,因此这是一个折衷方案)


4
投票

> 但是我的任务是,每当新值到达时都重新计算平均值,并重新加权旧值。 –OP


2
投票

我认为您正在寻找这样的东西:


1
投票

在这里,我假设您希望权重之和为1。只要您可以在将来不改变权重的情况下生成相对权重,就可以得到一个模仿此行为的解决方案。


1
投票

此时间太长,无法在评论中发布,但可能会有用。


1
投票

我尝试用Java编写几乎所有的代码。如前所述,您的目标是无法实现的。您只能从一定数量的上次记忆值中计算平均值。如果不需要精确,则可以近似使用较旧的值。我尝试通过准确地记住最后5个值,而仅记住较旧的值仅对5个值求和,并记住最后5个SUM来做到这一点。那么,用于记住最后的n + n * n个值的复杂度为O(2n)。这是一个非常粗略的近似值。


0
投票

您可以将(加权总和)指数均值与不同的有效窗口大小(N)结合起来,以获得所需的权重。使用更多的指数方法来更详细地定义您的体重曲线。(更多指数手段还意味着存储和计算更多值,因此这是一个折衷方案)

© www.soinside.com 2019 - 2024. All rights reserved.