52

これは、次のようにブーストで達成できることを知っています。

boost::accumulators を使用して、ローリング ウィンドウのサイズをリセットするにはどうすればよいですか?

しかし、ブーストの使用は本当に避けたいです。私はグーグルで検索しましたが、適切な、または読みやすい例は見つかりませんでした。

基本的に、最新の 1000 個の数値をデータ サンプルとして使用して、浮動小数点数のストリームの進行中のストリームの移動平均を追跡したいと考えています。

これを達成する最も簡単な方法は何ですか?


循環配列、指数移動平均、およびより単純な移動平均を使用して実験したところ、循環配列の結果が私のニーズに最も適していることがわかりました。

4

11 に答える 11

91

ニーズが単純な場合は、指数移動平均を使用してみてください。

http://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average

簡単に言うと、アキュムレータ変数を作成し、コードが各サンプルを調べると、コードはアキュムレータを新しい値で更新します。0 から 1 の間の定数「アルファ」を選択し、次のように計算します。

accumulator = (alpha * new_value) + (1.0 - alpha) * accumulator

特定のサンプルの効果が約 1000 サンプルの間しか持続しない「アルファ」の値を見つけるだけです。

うーん、ここに置いたので、これがあなたに適しているかどうかはわかりません. 問題は、1000 が指数移動平均のかなり長いウィンドウであることです。浮動小数点計算でアンダーフローすることなく、最後の 1000 個の数値に平均を分散させるアルファがあるかどうかはわかりません。しかし、30 個程度の数値など、より小さな平均値が必要な場合は、これが非常に簡単かつ迅速な方法です。

于 2012-06-12T04:44:29.080 に答える
20

要素を前の要素に追加して格納する、1000 要素の循環配列 (循環バッファー) が必要なだけです。

これは増加する合計になり、要素の任意の 2 つのペア間の合計を常に取得し、それらの間の要素の数で割って平均を求めることができます。

于 2012-06-12T04:50:22.003 に答える
19

基本的に、データサンプルとして最新の1000の数値を使用して、浮動小数点数のストリームの進行中のストリームの移動平均を追跡したいと思います。

total_以下は、追加/置換されたas要素を更新し、オンデマンドで合計(平均に必要)を計算するためのコストのかかるO (N)トラバーサルを回避することに注意してください。

template <typename T, typename Total, size_t N>
class Moving_Average
{
  public:
    Moving_Average& operator()(T sample)
    {
        total_ += sample;
        if (num_samples_ < N)
            samples_[num_samples_++] = sample;
        else
        {
            T& oldest = samples_[num_samples_++ % N];
            total_ -= oldest;
            oldest = sample;
        }
        return *this;
    }

    operator double() const { return total_ / std::min(num_samples_, N); }

  private:
    T samples_[N];
    size_t num_samples_{0};
    Total total_{0};
};

例:

// average of last 3 (from 4) samples...
std::cout << Moving_Average<double, double, 3>{}(4)(7)(2)(6) << '\n';
    // "5\n"

// average of last 3 squares...
Moving_Average<double, double, 3> ma;
for (int i = 0; i < 10; ++i)
    std::cout << (i * i) << ':' << ma(i * i) << ' ';
std::cout << '\n';
    // 0:0 1:0.5 4:1.66667 9:4.66667 16:9.66667 25:16.6667 36:25.6667 49:36.6667 64:49.6667 81:64.6667

TotalTは、をサポートするためとは異なるパラメータになります。たとえば、合計long longが1000long秒の場合、intfor chars、またはa tototalsを使用します。doublefloat

問題

num_samples_これは、概念的に0に戻る可能性があるという点で少し欠陥がありますが、 2 ^ 64のサンプルを持っている人を想像するのは難しいです。懸念がある場合は、追加のデータメンバーを使用して、配列boolを循環しているときにコンテナが最初に満たされたときを記録します( num_samples_「」のように無害な名前に変更しましposた。

T=doubleもう1つの問題は浮動小数点の精度に固有のものであり、の簡単なシナリオで説明できますN=2。:から始めてtotal_ = 0、サンプルを注入します{1E17, 1, 2}...

  • 1E17、実行するtotal_ += 1E17のでtotal_ == 1E17、注入します

  • 1、実行total += 1しますが、total_ == 1E17それでも、「1」は重要ではないため、1E17までの数値の64ビットdouble表現を変更できないため、注入します。

  • 2を実行total += 2 - 1E17します。これ2 - 1E17は最初に評価され、2が不正確/無意味に失われると生成されます。したがって、現在の1と2のサンプルにもかかわらず、-1E171E17の合計に-1E17を追加して0になります。移動平均は1.5ではなく0を計算します。別のサンプルを追加するときに、適切に組み込まれていなかったにもかかわらず、「最も古い」1を減算します。私たちと移動平均は間違ったままになる可能性があります。total_total_total_total_

最新のものを格納するコードを追加できますtotal_。現在のtotal_値がその一部である場合(テンプレートパラメーターが乗法のしきい値を提供する可能性があります)、配列total_内のすべてのサンプルからを再計算します(そしてnewに設定します)。十分気になる読者にお任せします。samples_highest_recent_total_total_

于 2012-06-12T05:19:52.887 に答える
15

入力ストリームに加重平均を適用することで、ローリング平均を概算できます。

template <unsigned N>
double approxRollingAverage (double avg, double input) {
    avg -= avg/N;
    avg += input/N;
    return avg;
}

この方法では、1000 個のバケットを維持する必要はありません。ただし、これは概算であるため、値が実際のローリング平均と正確に一致するわけではありません。

編集: @steveha の投稿に気付きました。これは指数移動平均に相当し、アルファは 1/N です (この場合、1000 バケットをシミュレートするために N を 1000 としました)。

于 2012-06-12T05:29:49.903 に答える
4

ローリング平均とローリング標準偏差を計算する単純なクラス:

#define _stdev(cnt, sum, ssq) sqrt((((double)(cnt))*ssq-pow((double)(sum),2)) / ((double)(cnt)*((double)(cnt)-1)))

class moving_average {
private:
    boost::circular_buffer<int> *q;
    double sum;
    double ssq;
public:
    moving_average(int n)  {
        sum=0;
        ssq=0;
        q = new boost::circular_buffer<int>(n);
    }
    ~moving_average() {
        delete q;
    }
    void push(double v) {
        if (q->size() == q->capacity()) {
            double t=q->front();
            sum-=t;
            ssq-=t*t;
            q->pop_front();
        }
        q->push_back(v);
        sum+=v;
        ssq+=v*v;
    }
    double size() {
        return q->size();
    }
    double mean() {
        return sum/size();
    }
    double stdev() {
        return _stdev(size(), sum, ssq);
    }

};
于 2013-12-09T20:58:30.787 に答える
1

1 つの方法は、バッファ配列に値を循環的に格納することです。この方法で平均を計算します。

int j = (int) (counter % size);
buffer[j] = mostrecentvalue;
avg = (avg * size - buffer[j - 1 == -1 ? size - 1 : j - 1] + buffer[j]) / size;

counter++;

// buffer[j - 1 == -1 ? size - 1 : j - 1] is the oldest value stored

すべてがループで実行され、最新の値は動的です。

于 2015-12-01T17:36:55.007 に答える
0

リング バッファを実装できます。1000 要素の配列と、開始インデックスと終了インデックス、および合計サイズを格納するいくつかのフィールドを作成します。次に、最後の 1000 個の要素をリング バッファーに格納し、必要に応じて平均を再計算します。

于 2012-06-12T04:50:58.307 に答える
0

@Nileshの回答を増やすと(クレジットは彼に送られます)、次のことができます。

  • 合計を追跡します。毎回除算してから乗算する必要がなく、エラーが発生します
  • % 演算子を使用した if 条件を避ける

これはアイデアを示す未テストのサンプル コードです。クラスにラップすることもできます。

const unsigned int size=10; // ten elements buffer

unsigned int counterPosition=0;
unsigned int counterNum=0;

int buffer[size];
long sum=0;

void reset() {
    for(int i=0;i<size;i++) {
        buffer[i]=0;
    }
}

float addValue(int value) {
    unsigned  int oldPos = ((counterPosition + 1) % size);

    buffer[counterPosition] = value;
    sum = (sum - buffer[oldPos] + value); 

    counterPosition=(counterPosition+1) % size;
    if(counterNum<size) counterNum++;

    return ((float)sum)/(float)counterNum;
}

float removeValue() {
    unsigned  int oldPos =((counterPosition + 1) % size);

    buffer[counterPosition] = 0;
    sum = (sum - buffer[oldPos]); 

    if(counterNum>1) { // leave one last item at the end, forever
        counterPosition=(counterPosition+1) % size;
        counterNum--; // here the two counters are different
    }
    return ((float)sum)/(float)counterNum;
}

バッファがすべてゼロにリセットされた場合、バッファ[oldPos] がゼロでカウンタが増加するため、最初の値を受信して​​いる間、このメソッドは正常に機能することに注意してください。最初の出力は、最初に受け取った数値です。size2 番目の出力は、最初の 2 つのみの平均であり、以下同様に、値が到着する間、項目に到達するまで値がフェードインします。

入力配列の最後で停止する場合、この方法は他のローリング平均と同様に非対称であることも考慮する価値があります。正しい計算で)。

それは正しいです。バッファ 10 で 100 要素のローリング平均を行うと、異なる結果が得られます。10 要素がフェード インし、90 要素が完全にローリングし、最後に 10 要素がフェード アウトします。100 の数値が入力された場合、合計 110 の結果が得られます。どちらを表示するかはあなた次第です (そして、古いものから最近のものへ、または最近のものから古いものへとまっすぐ進む方がよい場合)。

終了後に正しくフェードアウトするには、ゼロを 1 つずつ追加し、要素に到達するまでアイテムの数を 1 つずつ減らしますsize(古い値の正しい位置を追跡し続けます)。

使い方はこんな感じです。

int avg=0;
reset();

avg=addValue(2); // Rpeat for 100 times
avg=addValue(3); // Use avg value

...

avg=addValue(-4);
avg=addValue(12); // last numer, 100th input 

// If you want to fade out repeat 10 times after the end of data:

avg=removeValue(); // Rpeat for last 10 times after data has finished
avg=removeValue(); // Use avg value
...
avg=removeValue();
avg=removeValue();
于 2020-11-03T08:48:50.473 に答える
-1

リストを使用した 10 項目の単純移動平均:

#include <list>

std::list<float> listDeltaMA;

float getDeltaMovingAverage(float delta)
{
    listDeltaMA.push_back(delta);
    if (listDeltaMA.size() > 10) listDeltaMA.pop_front();
    float sum = 0;
    for (std::list<float>::iterator p = listDeltaMA.begin(); p != listDeltaMA.end(); ++p)
        sum += (float)*p;
    return sum / listDeltaMA.size();
}
于 2014-01-27T00:46:21.230 に答える