【问题标题】:Replace floating point math with integer in sigmoid transfer function用 sigmoid 传递函数中的整数替换浮点数学
【发布时间】:2014-04-06 12:02:42
【问题描述】:

我想在不损失太多精度的情况下替换这些函数中的浮点数学,因为我没有 FPU。这可能吗?我认为逗号后面的 3 个数字就足够了。

inline float smaller_f(float value, float bias) {
  return value < bias ? value : bias;
}

inline float pow2_f(float fVal) {
  return fVal * fVal;
}

float sigm_f(float fX, float fSlope) {
  float fVal = (180.f - smaller_f(fabs(fSlope * fX), 179.9f) ) / 180.f;
  return fVal / sqrt(1.f + pow2_f(fVal) );
}

【问题讨论】:

  • 也许,取决于 huw 多少精度就足够了。您唯一需要自己编写的是 sqrt 函数。然后放大所有内容,例如使用整数 10 亿代替浮点数。你有 64 位整数吗?如果没有,也许你需要模仿它们,另一个棘手的部分......

标签: c math floating-point embedded


【解决方案1】:

您需要的是定点数学库。我的首选解决方案是Anthony Williams' fixed-Point math C++ library。因为它是在 C++ 中并定义了一个具有广泛函数和运算符重载的fixed 类,所以它可以在很大程度上通过将现有代码中的floatdouble 替换为fixed 来使用。它使用int64_t 作为底层整数数据类型,具有34 个整数位和28 个小数位(34Q28),因此适用于大约8 个小数位,并且比int32_t 范围更广。

如果您的编译器支持 C++,您仍然可以使用本质上的 C 子集来编写代码,如果您愿意,只使用 C++ 来支持这个库。

在 32 位 ARM 上,该库的执行速度比软件浮点快大约 5 倍,并且在性能上与 ARM 的 C 代码 VFP 单元相当。

请注意,此库中的 sqrt() 函数对于非常小的值的精度性能很差,因为它在可以保留的中间计算中丢失了低位。可以通过用我在this question 中提供的代码替换它来改进它。

毫无疑问,C 库用于定点数学,但它们缺乏该库提供的“真正的”定点数据类型的简单性和便利性,并且该库具有一套完整的标准库数学函数等价物,而许多定点解决方案只提供基本的算术运算符。

【讨论】:

  • 我使用 Arduino,所以使用 ATMega。我以前从未想过,FPU 可能不存在
  • @dgrat:我想不出一个有 FPU 的 8 位微控制器,即使是 32 位,它们也很不寻常。瑞萨 SuperH 和一些 ARM 部件一样支持 FPU。 ARM Cortex-F4 上的 FPU 只有单精度。 64 位整数运算在 8 位目标上仍然会相当慢,32 位定点可能更可取。
  • @dgrat 你在那个 Arduino 中有什么 CPU?反正我从来没用过,维基百科说有 8 位版本和 32 位版本。这在这里有很大的不同。
  • ATMega2560,应该是8位。
  • @BuellaGábor:32 位 Arduino 是基于 ARM Cortex-M3 的; Atmel AVR ATMega(dgrat 已经提到)是 8 位的。
【解决方案2】:

看看最慢/最难的部分:

fVal/sqrt(1 + fVal**2)

这就是你需要考虑的全部。

http://www.wolframalpha.com/input/?i=x%2Fsqrt%281+%2B+x%5E2%29

很明显你的 fVal 小于等于 1。

您在 x = 0 到 x = 1 的范围内进行近似,因此如下所示: http://www.wolframalpha.com/input/?i=expand+x%2Fsqrt%281+%2B+x%5E2%29+around+x+%3D+0.5

这可能足以满足您的需求。按一下“更多条款”按钮以获得更高的准确性。

要使整数表现得像浮点数,您可以使用简单的乘法器方案,例如 int = float*10000,但是当您需要五次方时,这会产生问题 - 您会溢出。最好对所有内容进行缩放,以使所有数字都小于 1,然后使用小数整数数学库来乘以您的数字。

我构建的一个简单的分数库使用 LONG_MAX 来表示 1.0(大约 9 个小数位精度),然后将其中两个相乘(这样 LONG_MAX*LONG_MAX = LONG_MAX)我使用了两行汇编程序。您可能有权访问系统中的分数数学库。

所以基本上,缩放所有内容,以使您在途中获得的最大值为 1.0。

完成后,通过检查大约一百万个值并将它们与浮点版本进行比较来测试此函数相当容易。

请参阅http://gameprogrammer.com/4-fixed.html 和类似页面了解如何使用定点。

【讨论】:

  • 你基本上搞定了。但我建议使用切比雪夫近似而不是泰勒近似。
【解决方案3】:

你可以尝试一件简单的事情,这对你来说可能还不够好,但相当简单:

 unsigned int scale = 1000; /* three number after the comma */

 inline int smaller_i(int value, int bias) {
       return value < bias ? value : bias;
 }

 inline int pow2_i(int iVal) {
     return (iVal * iVal) / scale;
 }

 int sigm_i(int iX, int Slope) {
     int iVal = (180*scale - smaller_i(abs(iX) * slope, (179*scale + 9*(scale/10))) / (180*scale);
     return iVal / sqrt_i(1*scale + pow2_i(iVal));
 }

如果您有 64 位整数,这对您来说就足够了。如果你只有 32 位,我不确定。如果只有 16 位,这些计算可能会溢出,所以你需要一些更复杂的东西。

另请注意,您需要为自己写sqrt_i

【讨论】:

  • 使用二进制不动点可以获得更好的性能;即比例因子是 2 的幂。
【解决方案4】:

瓶颈大概是fVal / sqrt(1.f + pow2_f(fVal) )

尝试使用Fast Inverse Square Root 过程,该过程使用整数运算得出1.0 / sqrt(x) 的非常准确的近似值。

【讨论】:

    【解决方案5】:

    我想在 Raspberry Pi 3 上实现一个神经网络(权重在 -127 和 127 之间),我遇到了这个问题,我发现最快的方法是实现为嵌套 if 语句的二进制搜索;显然,if 语句需要自动生成,Python 来救援。

    代码

    给定一个 C 函数:

    static
    uint16_t sigmoid_lookup(int32_t i) {
    #include "autogen_sigmoid_index.i"
    }
    

    (sigmoid_value, at_argument) 的排序 Python 列表,此函数创建 if-else 树:

    def produce_c_code(numbers_list, idxs, idxe, level):
        if idxs >= idxe:
            raise RuntimeError("idxs=%d idxe=%d")
    
        indent= " "*level
    
        if idxs + 1 == idxe: # end of recursion
            yield indent + "return %d;" % numbers_list[idxs][0]
        else:
            idxm= (idxe+idxs)//2
            yield indent + "if(i>=%d)" % numbers_list[idxm][1]
            yield from produce_c_code(numbers_list, idxm, idxe, level+1)
            yield indent + "else"
            yield from produce_c_code(numbers_list, idxs, idxm, level+1)
    

    示例

    对于这个号码列表:[(0, 0), (1, 9), (2, 25), (3, 41), (4, 57), (5, 73), (6, 89)],产生的代码是:

     if(i>=41)
      if(i>=73)
       if(i>=89)
        return 6;
       else
        return 5;
      else
       if(i>=57)
        return 4;
       else
        return 3;
     else
      if(i>=9)
       if(i>=25)
        return 2;
       else
        return 1;
      else
       return 0;
    

    基准

    基准测试基于我的案例的127 * n / sqrt(n*n + 4194304) sigmoid 函数,它们超出了输入范围 [-8000000, 8000000]。

    奔腾 M 1.2 GHz

    测试查找 CPU时间:300000 测试数学 CPU时间:1460000

    树莓派 2 800 MHz

    测试查找 CPU时间:474094 测试数学 CPU时间:2897385

    树莓派 3 1.2GHz

    测试查找 CPU时间:369665 测试数学 CPU时间:1570066

    英特尔酷睿™2 Q6600 2.4 GHz

    测试查找 CPU时间:73623 测试数学 CPU时间:797847

    【讨论】:

      猜你喜欢
      • 2020-06-18
      • 2011-11-19
      • 1970-01-01
      • 2017-08-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-12-31
      • 2019-05-10
      相关资源
      最近更新 更多