我正在尝试计算信号的移动平均值。信号值(双精度)随机更新。 我正在寻找一种有效的方法来实时计算它在时间窗口内的时间加权平均值。我可以自己做,但它比我想象的更具挑战性。
我在互联网上找到的大部分资源都是计算周期性信号的移动平均值,但随机时间更新。
有没有人知道这方面的好资源?
由于
答案 0 :(得分:8)
诀窍如下:您通过void update(int time, float value)
随机获得更新。但是,您还需要跟踪更新何时脱落时间窗口,因此您设置了一个“警报”,该警报在time + N
处调用,从而删除之前的更新从计算中再次考虑过。
如果这是实时发生的,您可以请求操作系统调用void drop_off_oldest_update(int time)
time + N
如果这是模拟,则无法从操作系统获得帮助,您需要手动执行此操作。在模拟中,您将使用提供的时间作为参数调用方法(与实时不相关)。但是,一个合理的假设是保证调用时间参数增加。在这种情况下,您需要维护一个排序的警报时间值列表,并为每个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;
},
最后
moving_average = (sum + last_update * time_since_last_update) / window_length;
。
现在,如果只有一个更新从窗口中删除但没有新的更新到达,请将sum
调整为:
sum -= prior_update'.value * time_to_next_update + first_update_in_last_window.value * time_from_first_update_to_new_window_beginning;
(注意它是prior_update'
,其时间戳被修改为开始上一个窗口的开始)。如果只有一个更新进入窗口但没有新的更新消失,请将sum
调整为:
sum += previously_most_recent_update.value * corresponding_time_to_next_update.
显而易见,这是一个粗略的草图,但希望它能说明如何维持平均值,以便按摊销计算每次更新是O(1)操作。但请注意前一段中的进一步优化。还要注意旧答案中提到的稳定性问题,这意味着浮点错误可能会在大量此类增量操作中累积,从而与完全计算的结果存在差异,这对应用程序而言非常重要。
答案 1 :(得分:3)
如果近似值正常并且样本之间的最短时间,则可以尝试超级采样。有一个数组表示均匀间隔的时间间隔短于最小值,并在每个时间段存储收到的最新样本。间隔越短,平均值越接近真实值。期限不应超过最小值的一半,否则有可能遗漏样本。
答案 2 :(得分:3)
#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
}
答案 3 :(得分:0)
注意:显然这不是解决这个问题的方法。留在这里,以便参考这种方法的错误。检查评论。
更新 - 基于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评论。