查找表快速Sigmoidal函数

时间:2010-08-20 23:43:47

标签: java

private static double [] sigtab = new double[1001];  // values of f(x) for x values 

static {
  for(int i=0; i<1001; i++) {
      double ifloat = i;
      ifloat /= 100;
      sigtab[i] = 1.0/(1.0 + Math.exp(-ifloat));
  }
}

public static double fast_sigmoid (double x) {
    if (x <= -10)
        return 0.0;
    else if (x >= 10)
        return 1.0;
    else {
        double normx = Math.abs(x*100);
        int i = (int)normx;
        double lookup = sigtab[i] + (sigtab[i+1] - sigtab[i])*(normx - Math.floor(normx));
        if (x > 0)
            return lookup;
        else // (x < 0)
            return (1 - lookup);
    }
}

任何人都知道为什么这个“快速sigmoid”实际上比使用Math.exp的确切版本运行得慢?

2 个答案:

答案 0 :(得分:1)

你的意思是查找一个双元素数组并执行一些微积分应该比当场计算更快吗?

尽管CPU只有基本操作,但它可以很容易地处理取幂。我会在不到5个基本操作中说。

你在这里做的事情有点复杂,需要实际上不得不去取内存中的一些元素。 64位* 1001肯定适合您的缓存,但缓存访问时间肯定与注册表访问时间不匹配。

这种情况至少不会让我感到惊讶。

答案 1 :(得分:1)

你应该对你的代码进行概要分析,但我敢打赌,Math.floor调用大约一半的CPU周期(这很慢,因为它调用本机方法StrictMath.floor(double),导致JNI开销。 )

可能比(精确)硬件实现更快地计算(不太准确)sigmoid函数版本。以下是tanh的示例,它应该很容易转换为您的函数(是expit(-x)吗?)

此处使用的两个技巧通常在基于LUT的近似中很有用:

  • 通过添加一个大常量来模拟舍入(强制FPU将截断它,具有太少的位来表示总和)
  • 使您的桌面大小为2的幂(意味着每次调用少一次)

public static float fastTanH(float x) {
    if (x<0) return -fastTanH(-x);
    if (x>8) return 1f;
    float xp = TANH_FRAC_BIAS + x;
    short ind = (short) Float.floatToRawIntBits(xp);
    float tanha = TANH_TAB[ind];
    float b = xp - TANH_FRAC_BIAS;
    x -= b;
    return tanha + x * (1f - tanha*tanha);
}

private static final int TANH_FRAC_EXP = 6; // LUT precision == 2 ** -6 == 1/64
private static final int TANH_LUT_SIZE = (1 << TANH_FRAC_EXP) * 8 + 1;
private static final float TANH_FRAC_BIAS =
    Float.intBitsToFloat((0x96 - TANH_FRAC_EXP) << 23);
private static float[] TANH_TAB = new float[TANH_LUT_SIZE];
static {
    for (int i = 0; i < TANH_LUT_SIZE; ++ i) {
        TANH_TAB[i] = (float) Math.tanh(i / 64.0); 
    }
}