我正在尝试计算信号的移动平均值。该信号值(一个double类型)在随机时间更新。我正在寻找一种有效的方法,在实时情况下计算出其时间加权平均值,我可以自己完成,但这比我想象的更具挑战性。
我在互联网上找到的大多数资源都是针对周期性信号的移动平均值计算,而我的信号更新时间是随机的。
请问是否有好的资源可供参考?
谢谢
我正在尝试计算信号的移动平均值。该信号值(一个double类型)在随机时间更新。我正在寻找一种有效的方法,在实时情况下计算出其时间加权平均值,我可以自己完成,但这比我想象的更具挑战性。
我在互联网上找到的大多数资源都是针对周期性信号的移动平均值计算,而我的信号更新时间是随机的。
请问是否有好的资源可供参考?
谢谢
void update(int time, float value)
获得更新。但是,您还需要追踪何时更新在时间窗口之外,因此您设置了一个“警报”,该警报在 time + N
处被调用,从而从计算中删除了以前的更新。
如果这是实时发生的,您可以请求操作系统在 time + N
时调用一个方法 void drop_off_oldest_update(int time)
。update
和 read
调用检查时间参数是否大于警报列表的头部。只要它更大,您就进行与警报相关的处理(删除最旧的更新),然后删除头并再次检查,直到处理所有给定时间之前的所有警报。然后进行更新调用。float read (int time)
,您使用该方法读取值。目标是尽可能高效地进行此调用。因此,您不会在每次调用 read
方法时计算移动平均值。相反,您预先计算了最后一次更新或最后一个警报时的值,并通过一些浮点操作来“微调”该值,以考虑自上次更新以来的时间流逝。(即固定数量的操作,除非处理堆积的警报列表)。sum = 0;
updates_in_window = /* set of all updates within window */;
prior_update' = /* most recent update prior to window with timestamp tweaked to window beginning */;
relevant_updates = /* union of prior_update' and updates_in_window */,
然后按照时间顺序迭代 relevant_updates
:
for each update EXCEPT last {
sum += update.value * time_to_next_update;
},
最后,
移动平均值 = (总和 + 上次更新的数据 * 距离上次更新时间) / 窗口长度;
如果恰好有一个更新数据从窗口中被移除,但是没有新的更新数据到来,请调整总和
:
sum -= prior_update'.value * time_to_next_update + first_update_in_last_window.value * time_from_first_update_to_new_window_beginning;
sum += previously_most_recent_update.value * corresponding_time_to_next_update.
如您所见,这只是一个大致的草图,但希望它能展示出如何在摊销基础上仅需O(1)个操作来维护平均值。但请注意前一个段落中的进一步优化。还要注意旧回答中提到的稳定性问题,这意味着浮点误差可能会在大量此类增量操作中累积,从而导致与完整计算结果显著不同,这对应用程序非常重要。
#include <map>
#include <iostream>
// Sample - the type of a single sample
// Date - the type of a time notation
// DateDiff - the type of difference of two Dates
template <class Sample, class Date, class DateDiff = Date>
class TWMA {
private:
typedef std::map<Date, Sample> qType;
const DateDiff windowSize; // The time width of the sampling window
qType samples; // A set of sample/date pairs
Sample average; // The answer
public:
// windowSize - The time width of the sampling window
TWMA(const DateDiff& windowSize) : windowSize(windowSize), average(0) {}
// Call this each time you receive a sample
void
Update(const Sample& sample, const Date& now) {
// First throw away all old data
Date then(now - windowSize);
samples.erase(samples.begin(), samples.upper_bound(then));
// Next add new data
samples[now] = sample;
// Compute average: note: this could move to Average(), depending upon
// precise user requirements.
Sample sum = Sample();
for(typename qType::iterator it = samples.begin();
it != samples.end();
++it) {
DateDiff duration(it->first - then);
sum += duration * it->second;
then = it->first;
}
average = sum / windowSize;
}
// Call this when you need the answer.
const Sample& Average() { return average; }
};
int main () {
TWMA<double, int> samples(10);
samples.Update(1, 1);
std::cout << samples.Average() << "\n"; // 1
samples.Update(1, 2);
std::cout << samples.Average() << "\n"; // 1
samples.Update(1, 3);
std::cout << samples.Average() << "\n"; // 1
samples.Update(10, 20);
std::cout << samples.Average() << "\n"; // 10
samples.Update(0, 25);
std::cout << samples.Average() << "\n"; // 5
samples.Update(0, 30);
std::cout << samples.Average() << "\n"; // 0
}
deque<pair<Sample,Date>>
可能更有效率。我选择了map
以提高可读性,并且可以轻松调用map::upper_bound
。一如既往,先编写正确的代码,然后对其进行分析和测量增量更改。 - Robᵩ注意: 显然这不是正确的方法。在此留下参考,以了解这种方法的问题所在。请查看评论。
更新-基于Oli的评论...不过我不确定他所说的不稳定性是什么。
使用按“到达时间”排序的映射与值。在值到达时,将到达时间添加到已排序的映射中,以及它的值,并更新移动平均值。
警告:这是伪代码:
SortedMapType< int, double > timeValueMap;
void onArrival(double value)
{
timeValueMap.insert( (int)time(NULL), value);
}
//for example this runs every 10 seconds and the moving window is 120 seconds long
void recalcRunningAverage()
{
// you know that the oldest thing in the list is
// going to be 129.9999 seconds old
int expireTime = (int)time(NULL) - 120;
int removeFromTotal = 0;
MapIterType i;
for( i = timeValueMap.begin();
(i->first < expireTime || i != end) ; ++i )
{
}
// NOW REMOVE PAIRS TO LEFT OF i
// Below needs to apply your time-weighting to the remaining values
runningTotal = calculateRunningTotal(timeValueMap);
average = runningTotal/timeValueMap.size();
}
有些要注意的地方:
如我所说,上述为伪代码。您需要选择一个适当的映射。
在迭代过程中不要删除这些对,因为这会使迭代器失效并且必须重新开始。
另请参阅Oli下面的评论。
calculateRunningTotal
函数中应用的加权值能够解决这个问题。 - Mark Ransom