Je sais que cela est réalisable avec boost selon:
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.
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.
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).
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 long
s, un int
pour char
s ou un double
pour totaliser float
s.
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.
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);
}
};
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.
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.
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();
}
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.