我知道这可以通过提升实现:
Using boost::accumulators, how can I reset a rolling window size, does it keep extra history?
但我真的想避免使用提升。我用谷歌搜索,没有找到任何合适或可读的例子。
基本上我想使用最近的1000个数字作为数据样本来跟踪正在进行的浮点数流的移动平均值。
实现这一目标的最简单方法是什么?
我尝试使用圆形阵列,指数移动平均线和更简单的移动平均线,发现圆形阵列的结果最适合我的需要。
答案 0 :(得分:78)
如果您的需求很简单,您可以尝试使用指数移动平均线。
http://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average
简单地说,您创建一个累加器变量,并且当您的代码查看每个示例时,代码会使用新值更新累加器。你选择一个介于0和1之间的常量“alpha”,然后计算:
accumulator = (alpha * new_value) + (1.0 - alpha) * accumulator
您只需要找到“alpha”值,其中给定样本的效果仅持续约1000个样本。
嗯,我现在还不确定它是否适合你,现在我把它放在这里。问题是,对于指数移动平均线而言,1000是一个相当长的窗口;我不确定是否有一个alpha会在过去的1000个数字上传播平均值,而在浮点计算中没有下溢。但是如果你想要一个较小的平均值,比如30个数左右,这是一个非常简单快捷的方法。答案 1 :(得分:19)
你只需要一个包含1000个元素的圆形数组,在这里你可以将元素添加到前一个元素并存储它......它变成了一个增加的总和,你可以随时得到任意两对元素之间的和,并除以通过它们之间的元素数量,得出平均值。
答案 2 :(得分:14)
您可以通过在输入流上应用加权平均值来估算滚动平均值。
template <unsigned N>
double approxRollingAverage (double avg, double input) {
avg -= avg/N;
avg += input/N;
return avg;
}
这样,您不需要维护1000个桶。但是,这是一个近似值,因此它的值与真正的滚动平均值不完全匹配。
编辑:刚注意到@ steveha的帖子。这相当于指数移动平均线,alpha为1 / N(在这种情况下我将N设为1000来模拟1000个桶)。
答案 3 :(得分:13)
基本上我想使用最近的1000个数字作为数据样本来跟踪正在进行的浮点数流的移动平均值。
请注意,下面将total_
更新为添加/替换的元素,避免代价高昂的 O (N)遍历来计算平均值所需的总和 - 按需。
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
与T
的参数不同,以支持例如使用long long
时总计1000 long
,int
char
或double
总float
。
<强> 问题的强>
这有点有缺陷,因为num_samples_
可以在概念上回绕到0,但很难想象有人有2 ^ 64个样本:如果担心,请使用额外的bool数据成员来记录容器第一次在数组周围循环num_samples_
时填充(最好重命名为“pos
”无关紧要的东西。)
另一个问题是浮点精度所固有的,可用T = double,N = 2的简单场景来说明:我们从total_ = 0
开始,然后注入样本...
1E17,我们执行total_ += 1E17
,所以total_ == 1E17
,然后注入
1,我们执行total += 1
,但仍然total_ == 1E17
,因为“1”对于更改数字的64位double
表示来说太微不足道了1E17,然后我们注入
2,我们执行total += 2 - 1E17
,其中2 - 1E17
首先被评估并产生-1E17
,因为2失去了不精确/无意义,所以对于我们的总共1E17我们加上-1E17和total_
变为0,尽管我们希望total_
的当前样本为1和2,我们的移动平均值将计算为0而不是1.5。当我们添加另一个样本时,我们将从total_
中减去“最老的”1,尽管它从未被正确地合并到其中;我们的total_
和移动平均线可能仍然存在错误。
您可以添加存储最近最高total_
的代码,如果当前total_
太小(模板参数可以提供乘法阈值),则重新计算{{1}来自total_
数组中的所有样本(并设置samples_
到新highest_recent_total_
),但我会将其留给充分关心的读者。
答案 4 :(得分:3)
计算滚动平均值和滚动标准偏差的简单类:
#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);
}
};
答案 5 :(得分:1)
您可以实施ring buffer。创建一个包含1000个元素的数组,以及一些用于存储开始和结束索引以及总大小的字段。然后将最后1000个元素存储在环形缓冲区中,并根据需要重新计算平均值。
答案 6 :(得分:0)
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();
}
答案 7 :(得分:0)
一种方法是循环存储缓冲区数组中的值。并以这种方式计算平均值。
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
整个事情在一个循环中运行,其中最近的值是动态的。
答案 8 :(得分:0)
我经常在硬实时系统中使用它,这些系统具有相当疯狂的更新速率(50kilosamples / sec)因此我通常预先计算标量。
计算N个样本的移动平均值: 标量1 = 1 / N; scalar2 = 1 - 标量1; //或(1 - 1 / N) 然后:
Average = currentSample * scalar1 + Average * scalar2;
示例:平均10个元素的滑动
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);
}
}
注意:这只是上面steveha给出的答案的实际实现。 有时,更容易理解一个具体的例子。
答案 9 :(得分:0)
增加@Nilesh的答案(功劳归他所有),您可以:
这是未测试的示例代码,用于说明该想法,也可以将其包装到一个类中:
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;
}
应注意,如果将缓冲区重置为全零,则该方法在接收中的第一个值时效果很好,因为-buffer [oldPos]为零且计数器增加。第一个输出是收到的第一个数字。第二个输出仅是前两个的平均值,依此类推,直到它们到达size
项时,它们的值才逐渐消失。
还值得考虑的是,如果您在输入数组的结尾处停止,则与其他任何滚动平均值方法一样,该方法也是不对称的,因为在结尾处不会发生相同的衰减(它可能会在数据结束后通过正确的计算发生)。
是正确的。 100个元素的平均滚动和10个缓冲区的滚动会得到不同的结果:10个淡入,90个完美滚动10个元素,最后10个淡出,给出 100个数字总计110个结果!您可以决定显示哪些内容(如果最好的话,还是从旧到新,或者从新到旧,向后退)。
要在结束后正确淡出,可以继续逐个加零,直到达到size
元素为止,每次减少1项(仍然跟踪旧值的正确位置) )。
用法是这样的:
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();
答案 10 :(得分:0)
我使用了双端队列...似乎对我有用。此示例有一个向量,但您可以跳过该方面并将它们添加到双端队列中。
#include <deque>
template <typename T>
double mov_avg(vector<T> vec, int len){
deque<T> dq = {};
for(auto i = 0;i < vec.size();i++){
if(i < len){
dq.push_back(vec[i]);
}
else {
dq.pop_front();
dq.push_back(vec[i]);
}
}
double cs = 0;
for(auto i : dq){
cs += i;
}
return cs / len;
}
//Skip the vector portion, track the input number (or size of deque), and the value.
double len = 10;
double val; //Accept as input
double instance; //Increment each time input accepted.
deque<double> dq;
if(instance < len){
dq.push_back(val);
}
else {
dq.pop_front();
dq.push_back(val);
}
}
double cs = 0;
for(auto i : dq){
cs += i;
}
double rolling_avg = cs / len;
//为了进一步简化——向其添加值,然后简单地平均双端队列。
int MAX_DQ = 3;
void add_to_dq(deque<double> &dq, double value){
if(dq.size() < MAX_DQ){
dq.push_back(value);
}else {
dq.pop_front();
dq.push_back(value);
}
}
我偶尔使用的另一种技巧是使用 mod 覆盖向量中的值。
vector<int> test_mod = {0,0,0,0,0};
int write = 0;
int LEN = 5;
int instance = 0; //Filler for N -- of Nth Number added.
int value = 0; //Filler for new number.
write = instance % LEN;
test_mod[write] = value;
//Will write to 0, 1, 2, 3, 4, 0, 1, 2, 3, ...
//Then average it for MA.
//To test it...
int write_idx = 0;
int len = 5;
int new_value;
for(auto i=0;i<100;i++){
cin >> new_value;
write_idx = i % len;
test_mod[write_idx] = new_value;
最后一个(hack)没有桶、缓冲区、循环,什么都没有。只是一个被覆盖的向量。它是 100% 准确的(对于向量中的平均值/值)。很少维护正确的顺序,因为它开始向后重写(在 0 处),因此在示例 {5,1,2,3,4} 等中,第 5 个索引将位于 0。