随着时间的推移平滑值:移动平均线还是更好的东西?
Posted
技术标签:
【中文标题】随着时间的推移平滑值:移动平均线还是更好的东西?【英文标题】:Smoothing values over time: moving average or something better? 【发布时间】:2011-04-15 04:49:48 【问题描述】:我正在编写一些东西,我正在从硬件罗盘中获取大量值。这个指南针非常准确并且经常更新,因此如果它轻微晃动,我最终会得到与其邻居大相径庭的奇数值。我想平滑这些值。
在阅读了一些资料后,看来我想要的是高通滤波器、低通滤波器或移动平均线。我可以使用移动平均线,只需保留最后 5 个值或其他值的历史记录,然后在我的代码下游使用这些值的平均值,而我曾经只是使用最近的值。
我认为,这应该可以很好地消除这些抖动,但令我震惊的是,它可能效率很低,这可能是适合程序员的已知问题之一,有一个非常简洁的 Clever Math 解决方案。
然而,我是那些糟糕的自学成才的程序员之一,在任何与 CompSci 或数学模糊相关的方面都没有接受过任何正规教育。仔细阅读表明这可能是一个高通或低通滤波器,但我找不到任何可以用像我这样的黑客可以理解的术语来解释这些算法对一组值的影响,更不用说如何数学有效。例如,here 给出的答案在技术上确实回答了我的问题,但只是用那些可能已经知道如何解决问题的人可以理解的术语。
这将是一个非常可爱和聪明的人,他可以用文科毕业生可以理解的方式来解释这种问题,以及解决方案的工作原理。
【问题讨论】:
【参考方案1】:如果您尝试去除偶尔出现的奇数值,则低通滤波器是您确定的三个选项中的最佳选择。低通滤波器允许低速变化,例如用手旋转指南针引起的变化,同时拒绝高速变化,例如道路颠簸引起的变化。
移动平均线可能还不够,因为数据中的单个“光点”会影响多个后续值,具体取决于移动平均线窗口的大小。
如果奇数值很容易被检测到,那么使用完全忽略它们的故障消除算法可能会更好:
if (abs(thisValue - averageOfLast10Values) > someThreshold)
thisValue = averageOfLast10Values;
这里有一个快速图表来说明:
第一张图是输入信号,有一个令人不快的故障。第二张图显示了 10 个样本移动平均线的影响。最终图是 10 个样本的平均值和上面所示的简单故障检测算法的组合。当检测到毛刺时,使用 10 个样本的平均值而不是实际值。
【讨论】:
哇.. 很少看到这么好的答案! 移动平均线是低通滤波器。 尝试使用运行/流媒体中位数。【参考方案2】:如果您的移动平均线必须很长才能实现所需的平滑,并且您实际上并不需要任何特定形状的内核,那么最好使用指数衰减移动平均线:
a(i+1) = tiny*data(i+1) + (1.0-tiny)*a(i)
您选择tiny
作为适当的常数(例如,如果您选择tiny = 1- 1/N,它将具有与大小为N 的窗口相同的平均量,但在较旧的点上分布不同)。
无论如何,由于移动平均线的下一个值仅取决于前一个值和您的数据,因此您不必保留队列或其他任何东西。你可以认为这是在做类似的事情,“嗯,我有一个新观点,但我并不真正相信它,所以我将保留我对测量结果的 80% 的旧估计,并且只有相信这个新数据点 20%”。这与说“嗯,我只信任这个新点 20%,我将使用我信任相同数量的其他 4 个点”几乎是一样的,除了你没有明确地接受其他 4 个点,而是假设你上次做的平均是合理的,所以你可以使用你以前的工作。
【讨论】:
很好的解释,谢谢雷克斯。我是否认为另一种表达相同事物的方式是:workingAverage = (newValue*smoothingFactor) + (workingAverage * (1.0 - smoothingFactor))? @Henry - 没错,这就是不使用任何额外存储空间的方法。 嘿,我知道这已经晚了 5 年,但感谢您的精彩回答。我正在开发一个游戏,声音会根据你的速度而变化,但由于在慢速计算机上运行游戏,速度会剧烈波动,这对于转向来说很好,但在声音方面却非常烦人。对于我认为非常复杂的问题,这是一个非常简单且廉价的解决方案。 我想可能有错字?我相信你会选择 tiny = 1/N 而不是 tiny = (1 - 1/N) ?否则引号中的示例解释与描述不匹配。【参考方案3】:移动平均线我可以接受... 但令我震惊的是,这可能是 效率很低。
移动平均线确实没有理由效率低下。您将所需的数据点数量保留在某个缓冲区(如循环队列)中。在每个新数据点上,弹出最旧的值并将其从总和中减去,然后推送最新的值并将其添加到总和中。所以每个新数据点实际上只需要一个弹出/推送、一个加法和一个减法。您的移动平均值始终是这个移动总和除以缓冲区中值的数量。
如果您同时从多个线程接收数据,它会变得有点棘手,但因为您的数据来自我认为非常可疑的硬件设备。
哦,还有:可怕的自学成才的程序员团结起来! ;)
【讨论】:
移动平均线对我来说似乎效率低下,因为您必须存储值的缓冲区 - 更好地使用您的输入值和当前工作值做一些聪明的数学?我认为这就是指数移动平均线的工作原理。我看到的这种移动平均线的优化涉及使用固定长度的队列和指向您在该队列中的位置的指针,并且只是将指针包裹起来(使用 % 或 if)。瞧!没有昂贵的推送/弹出。给业余爱好者的力量,兄弟! @Henry:对于直线移动平均线,您确实需要缓冲区,这样您就知道在下一个值被推送时会弹出什么值。也就是说,您所描述的“固定长度队列和指针”正是我所说的“循环队列”。这就是为什么我说它不是低效的。你认为我的意思是什么?如果你的回答是“一个在每次索引删除时将其值移回的数组”(如 C++ 中的std::vector
)......那么,我很伤心,我什至不想再和你说话了; )
不,我以为您的意思是实际的高级 array.push() / array.unshift() 或其他东西,就像 AS3 或 Java 程序员会做的那样。原谅我。文科毕业生,记得吗?
@Henry:我不了解 AS3,但是 Java 程序员可以使用 CircularQueue
之类的集合(我不是 Java 开发人员,所以我确信有更好的那里的例子;这正是我从快速谷歌搜索中找到的),它精确地实现了我们正在谈论的功能。我相当有信心大多数具有标准库的中低级语言都有类似的东西(例如,在 .NET 中有Queue<T>
)。无论如何,我自己就是哲学,所以......一切都被原谅了。【参考方案4】:
如果您使用正确的值,则可以“手动”仅根据趋势计算指数衰减的移动平均线。如果您正在寻找“10% 平滑的指数平滑移动平均线”,请参阅 http://www.fourmilab.ch/hackdiet/e4/ 了解如何用笔和纸快速完成此操作。但是由于您有一台计算机,您可能希望进行二进制移位而不是十进制移位;)
这样,您只需要一个用于当前值的变量和一个用于平均值的变量。然后可以从中计算下一个平均值。
【讨论】:
【参考方案5】:有一种称为距离门的技术可以很好地处理低出现率的虚假样本。假设使用上述过滤器技术之一(移动平均,指数),一旦你有“足够的”历史(一个时间常数),你可以测试新的,传入的数据样本的合理性,之前它被添加到计算中。
需要了解信号的最大合理变化率。将原始样本与最近的平滑值进行比较,如果该差异的绝对值大于允许的范围,则该样本被丢弃(或用一些启发式方法代替,例如基于斜率的预测;微分或双指数平滑的“趋势”预测值)
【讨论】:
以上是关于随着时间的推移平滑值:移动平均线还是更好的东西?的主要内容,如果未能解决你的问题,请参考以下文章