【问题标题】:What's the “epsilon” of an Exponential Moving Average?指数移动平均线的“ε”是多少?
【发布时间】:2018-04-12 18:22:00
【问题描述】:

我正在应用 Exponential Moving Average 作为我的音频应用程序中平滑参数的过滤器:

a0 = 0.01
z += a0 * (input - z);

Here's代码和前50步:

#include <iostream>

int main ()
{
    double a0 = 0.1;
    double input = 0.8;
    double z = 0.0;

    std::cout << "init z: " << z << std::endl << std::endl;

    for(int i=0; i < 50; i++) {
        z += a0 * (input - z);
        std::cout << z << std::endl;
    }

    std::cout << std::endl << "final z: " << z << std::endl;
}

我需要检查前一个平滑值是否与当前值相同,这意味着过滤器已经“完成”了它的平滑过程,并且该值将始终相同。

但是z 总是不同于epsiloninput,所以我不能检查input == z 它总是错误的。 Here's 一个例子,无限循环。

zinput 之间的 epsilon 是多少?所以如果它在那个范围内,我可以检查并避免进一步的操作。

【问题讨论】:

  • zinput 之间的乘法 epsilon 可能更容易表征,并且计算更稳定。
  • 从哲学上讲:),打破循环的决定有两个代价:结果与“真实”值的距离迭代次数;直到您在脑海中弄清楚总成本是如何形成的(因此当达到最大成本并且循环中断时),问题是并且总是会是不正确的。
  • 拒绝将新的z 值与之前的z 值进行比较是错误的。通过使用浮点运算的属性,可以编写代码来精确测试添加a0 * (input - z) 是否会改变z 的值,但是该代码比简单地复制z 和比较新旧值。在没有直接比较或精确测试的情况下,替代方法是简单地测试与z 相比增量是否“小”。那么“小”是一个偏好问题或者是特定于应用程序的问题,并没有明确的答案。
  • @markzzz:我可以但不会。这是一个相当大的麻烦。您可以使用 frexp 和 ldexp 计算 z 的 ½ ULP,然后将 a0 * (input - z) 与它进行比较。然后,如果它恰好是 ½ ULP,则必须确定 z 的有效数字的低位奇偶校验。而且您必须处理各种特殊情况,例如次正规。这将是过多的代码。与z 之前的值相比,这是一个更好的解决方案。
  • @markzzz:首先你说你不想避免引入一个新变量,现在你说你想节省 CPU。执行建议给您的任何测试所需的操作都很快,肯定比通过检查所涉及的浮点特性而不是直接执行操作来计算在添加增量时z 是否会改变所需的操作要快。知道自己在说什么的人给了你很好的解决方案。使用它们。

标签: c++ floating-point precision moving-average


【解决方案1】:

对于音频应用程序,您需要考虑采样位数才能知道结果何时听不见。每个位代表 2 的幂。例如,16 位将是 216 或 65536,因此适当的 epsilon 将是您的样本比例除以 65536。对于 20 位,它是 220 或 1048576。

这些限制比大多数其他应用程序需要的要大得多。

【讨论】:

  • 你是说如果我的声卡是 24bit 并且刻度是 0.0 到 1.0,我需要一个 1.0/2^24 的 epsilon 吗? 0,000000059604644775390625? “增益”可能有意义,但一旦成为过滤器或其他不仅涉及音量的参数,这无关紧要。但我不知道...
  • @markzzz 如果我正确理解了您的问题,您正在尝试确定效果何时变得听不见。当效果的幅度低于输入信号的量化噪声时,它变得听不见。在此步骤之前或之后信号经过多少次操作并不重要。
  • 这个措辞不好。采样位数与人耳能感知到的音量或音量变化无关。特定采样值对应的声音大小可以有任意尺度,没有理由认为采样的低位就是人类感知的阈值。
  • @EricPostpischil 我没有说任何关于人类感知的内容,我说一旦错误低于输入的错误,你就已经走得够远了。也许你说得对,我说得不好。
  • @MarkRansom:您说“听不见”,即“听不见”,这取决于听众的感知。我们可以假设样本中有足够的位来提供有用的数据,但我们不知道是否勉强足够,以至于即使最低位的变化也可能被听众察觉到,或者有很多,所以大几个低位的变化不会有任何区别。将 epsilon 定位到 ULP 的幅度本质上是在说,“好吧,你已经有了这么多量化噪声,再加上更多不会有坏处。”但这并不总是正确的。
【解决方案2】:

考虑“新 z”与“旧 z”的比率,减去 1:

(z + a(i - z)) / z - 1

(这显然简化为ia / z - a)。如果这个量级小于,比如1e-6,则接受为已完成。如果z 为零,则始终继续。将此乘法容差调整为适合您要求的值。

(从科学上讲,容差与您的数据流的标准偏差有关 - 我什至敢于建议成比例,但如果不研究实际数据,我无法提供更多提示。)

【讨论】:

  • 那么您是在向我建议 1e-6 的 epsilon 吗?不知道你如何推断​​这个维度。例如,为什么不使用 1e-5 或 1e-7? :)
  • @markzzz:你需要从某个地方开始。构建算法。然后开始校准它。我的回答更集中于公差的应用,而不是如何找到最佳值。 (如果不研究我提到的实际数据,您就无法做到这一点。)
  • 我知道。但是,如果我真的放置 1e-91e-6,我就无法“听到”任何明显的人工制品。
  • 确切数据是:我正在平滑一个从 0.0 到 1.0 的参数(绑定,是一个规范化的参数)。
  • 至少您已将范围缩小到 3 个数量级。这是我书中的一项成就;-)
【解决方案3】:

您可以检查z 的新值和前一个值之间的ε,而不是检查zinput 之间的ε。

【讨论】:

  • 这将引入第三个变量 prevZ,我想避免使用它。
  • 可以计算和测试增量(a0 * (input - z)),如果它低于epsilon,则仅与z相加。
【解决方案4】:

在 C++ 中有

std::numeric_limits<double>::epsilon()

返回机器epsilon,即1.0与浮点类型T可表示的下一个值之间的差。

这里是修改后的代码:

#include <iostream>

int main()
{
    int counter = 0;
    double a0 = 0.1;
    double input = 0.8;
    double z = 0.0;

    std::cout << "init z: " << z << std::endl << std::endl;

    while (true) {
        z += a0 * (input - z);
        std::cout << counter++ << " | process: " << z << std::endl;

        double eps = std::numeric_limits<double>::epsilon();
        double diff = abs(z - input);
        if (diff <= 2 * eps) {
            break;
        }
    }

    std::cout << std::endl << "final z: " << z << std::endl;
}

【讨论】:

  • 这在实践中不太可能是一个非常有用的方法 - 这里重要的是 ULP,而不是 epsilon。
  • @OliverCharlesworth std::numeric_limits&lt;double&gt;::epsilon() 返回机器 epsilon,即 1.0 与浮点类型 T 可表示的下一个值之间的差异。在我看来,它表示 ULP。如果不是,我很想了解其中的区别。
  • 问题在于 ULP 是动态的 - 浮点数之间的间距取决于幅度。因此,您基本上需要将 epsilon 乘以当前值以获得相关的 ULP。
  • 每当我看到有人盲目地使用std::numeric_limits&lt;double&gt;::epsilon()作为某种宽容,我都会在里面死去。 没有理由这样做。
  • @OliverCharlesworth 没错!谢谢,我不认为这方面。
猜你喜欢
  • 2022-01-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-19
  • 1970-01-01
  • 2021-04-09
  • 2021-08-26
  • 1970-01-01
相关资源
最近更新 更多