web-dev-qa-db-fra.com

Calculer la moyenne roulante / mobile en C ++

Je sais que cela est réalisable avec boost selon:

En utilisant boost :: accumulators, comment puis-je réinitialiser une taille de fenêtre roulante, conserve-t-il un historique supplémentaire?

Mais je voudrais vraiment éviter d'utiliser boost. J'ai googlé et je n'ai trouvé aucun exemple approprié ou lisible.

Fondamentalement, je veux suivre la moyenne mobile d'un flux continu d'un flux de nombres en virgule flottante en utilisant les 1 000 nombres les plus récents comme échantillon de données.

Quel est le moyen le plus simple d'y parvenir?


J'ai expérimenté l'utilisation d'un tableau circulaire, d'une moyenne mobile exponentielle et d'une moyenne mobile plus simple et j'ai constaté que les résultats du réseau circulaire répondaient le mieux à mes besoins.

38
goji

Vous avez simplement besoin d'un tableau circulaire de 1000 éléments, où vous ajoutez l'élément à l'élément précédent et que vous le stockez ... Il devient une somme croissante, où vous pouvez toujours obtenir la somme entre deux paires d'éléments, et diviser par le nombre d'éléments entre eux, pour obtenir la moyenne.

Si vos besoins sont simples, essayez d’utiliser une moyenne mobile exponentielle.

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

En termes simples, vous créez une variable d'accumulateur et, lorsque votre code examine chaque exemple, il met à jour l'accumulateur avec la nouvelle valeur. Vous choisissez une constante "alpha" comprise entre 0 et 1 et calculez ceci:

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

Vous devez juste trouver une valeur "alpha" où l'effet d'un échantillon donné ne dure que pour environ 1000 échantillons.

Hmm, je ne suis pas vraiment sûr que cela vous convienne, maintenant que je l'ai mis ici. Le problème est que 1000 est une fenêtre assez longue pour une moyenne mobile exponentielle; Je ne suis pas sûr qu'il existe un alpha qui répartirait la moyenne sur les 1 000 derniers chiffres sans déborder dans le calcul en virgule flottante. Mais si vous vouliez une moyenne plus petite, comme 30 chiffres ou plus, c'est un moyen très facile et rapide de le faire.

77
steveha

Vous pouvez approximer une moyenne glissante en appliquant une moyenne pondérée sur votre flux d’entrée.

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

De cette façon, vous n'avez pas besoin de maintenir 1000 seaux. Cependant, il s'agit d'une approximation et sa valeur ne correspond donc pas exactement à une moyenne mobile réelle.

Edit: vient de remarquer le post de @ steveha. Ceci est équivalent à la moyenne mobile exponentielle, avec l'alpha étant de 1/N (je prenais N comme étant 1000 dans ce cas pour simuler 1000 compartiments).

14
jxh

Fondamentalement, je veux suivre la moyenne mobile d'un flux continu d'un flux de nombres en virgule flottante en utilisant les 1 000 nombres les plus récents comme échantillon de données.

Notez que ce qui suit met à jour le total_ En tant qu'éléments ajoutés/remplacés, en évitant des coûts élevés [~ # ~] o [~ # ~] (N) parcours pour calculer la somme - nécessaire pour la moyenne - à la demande.

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

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

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

Total devient un paramètre différent de T pour prendre en charge, par exemple. utiliser un long long pour un total de 1000 longs, un int pour chars ou un double pour totaliser floats.

Problèmes

Ceci est un peu imparfait en ce que num_samples_ Pourrait conceptuellement revenir à 0, mais il est difficile d’imaginer une personne ayant 2 ^ 64 échantillons: le cas échéant, utilisez un membre de données bool supplémentaire pour enregistrer le moment où le conteneur est rempli pour la première fois. cycler num_samples_ autour du tableau (il est préférable alors de renommer quelque chose d'inoffensif tel que "pos").

Un autre problème est inhérent à la précision en virgule flottante et peut être illustré avec un scénario simple pour T = double, N = 2: nous commençons avec total_ = 0, Puis nous injectons des échantillons ...

  • 1E17, nous exécutons total_ += 1E17, Donc total_ == 1E17, Puis injectons

  • 1, nous exécutons total += 1, Mais total_ == 1E17 Reste, car le "1" est trop insignifiant pour modifier la représentation double à 64 bits d'un nombre aussi grand que 1E17, puis nous injectons

  • 2, nous exécutons total += 2 - 1E17, Dans lequel 2 - 1E17 Est évalué en premier et donne -1E17, Le 2 étant perdu en imprécision/insignifiance, nous ajoutons -1E17 à notre total de 1E17 et total_ devient 0, malgré les échantillons actuels de 1 et 2 pour lesquels nous voudrions que total_ soit 3. Notre moyenne mobile calculera 0 au lieu de 1,5. Au fur et à mesure que nous ajoutons un autre échantillon, nous soustrayons le "plus ancien" 1 de total_ Même s'il n'y a jamais été incorporé correctement; nos total_ et nos moyennes mobiles risquent de rester fausses.

Vous pouvez ajouter du code contenant le plus récent total_ Récent et si le total_ Actuel est trop petit (un paramètre de modèle pourrait fournir un seuil multiplicatif), vous recalculez le total_ de tous les échantillons du tableau samples_ (et définissez highest_recent_total_ sur le nouveau total_), mais je laisserai cela au lecteur qui s'en soucie suffisamment.

13
Tony Delroy

Classe simple pour calculer la moyenne glissante et l'écart type glissant:

#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);
    }

};
4
Erik Aronesty

Vous pouvez implémenter un tampon en annea . Créez un tableau de 1000 éléments et de certains champs pour stocker les index de début et de fin et la taille totale. Ensuite, stockez simplement les 1000 derniers éléments dans la mémoire tampon circulaire et recalculez la moyenne si nécessaire.

1
Tim

Une solution consiste à stocker de manière circulaire les valeurs dans le tableau de mémoire tampon. et calculer la moyenne de cette façon.

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

Le tout tourne dans une boucle où la valeur la plus récente est dynamique.

0
Nilesh Kumar Jha

une moyenne mobile simple pour 10 éléments, en utilisant une liste:

#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();
}
0
Pedro Soares

J'utilise cela assez souvent dans des systèmes en temps réel durs qui ont des taux de mises à jour assez insensés (50 kilosamples/s). En conséquence, je calcule généralement les scalaires.

Pour calculer une moyenne mobile de N échantillons: scalar1 = 1/N; scalaire2 = 1 - scalaire1; // ou (1 - 1/N) alors:

Moyenne = currentSample * scalar1 + Average * scalar2;

Exemple: moyenne glissante de 10 éléments

double scalar1 = 1.0/10.0;  // 0.1
double scalar2 = 1.0 - scalar1; // 0.9
bool first_sample = true;
double average=0.0;
while(someCondition)
{
   double newSample = getSample();
   if(first_sample)
   {
    // everybody forgets the initial condition *sigh*
      average = newSample;
      first_sample = false;
   }
   else
   {
      average = (sample*scalar1) + (average*scalar2);
   }
 }

Remarque: il ne s'agit que d'une mise en œuvre pratique de la réponse donnée par steveha ci-dessus. Parfois, il est plus facile de comprendre un exemple concret.

0
baumann