61

我整天都在分析一个应用程序,并且优化了一些代码,我的待办事项列表中只剩下了这个。它是神经网络的激活函数,被调用超过 1 亿次。根据 dotTrace,它约占整个函数时间的 60%。

你会如何优化这个?

public static float Sigmoid(double value) {
    return (float) (1.0 / (1.0 + Math.Pow(Math.E, -value)));
}
4

25 回答 25

61

尝试:

public static float Sigmoid(double value) {
    return 1.0f / (1.0f + (float) Math.Exp(-value));
}

编辑:我做了一个快速的基准测试。在我的机器上,上面的代码比你的方法快大约 43%,这个数学等效的代码是最快的(比原始代码快 46%):

public static float Sigmoid(double value) {
    float k = Math.Exp(value);
    return k / (1.0f + k);
}

编辑 2:我不确定 C# 函数有多少开销,但如果你#include <math.h>在源代码中,你应该能够使用它,它使用 float-exp 函数。可能会快一点。

public static float Sigmoid(double value) {
    float k = expf((float) value);
    return k / (1.0f + k);
}

此外,如果您正在进行数百万次调用,函数调用开销可能是个问题。尝试制作一个内联函数,看看是否有帮助。

于 2009-01-05T01:02:24.120 回答
30

如果是用于激活函数,那么如果 e^x 的计算完全准确,那么重要吗?

例如,如果您使用近似值 (1+x/256)^256,在我用 Java 进行的 Pentium 测试中(我假设 C# 基本上编译为相同的处理器指令),这大约比 e^x 快 7-8 倍(Math.exp()),并且精确到小数点后 2 位,直到大约 +/-1.5 的 x,并且在您声明的范围内的正确数量级内。(显然,要提高到 256,实际上是对数字进行 8 次平方——不要为此使用 Math.Pow!)在 Java 中:

double eapprox = (1d + x / 256d);
eapprox *= eapprox;
eapprox *= eapprox;
eapprox *= eapprox;
eapprox *= eapprox;
eapprox *= eapprox;
eapprox *= eapprox;
eapprox *= eapprox;
eapprox *= eapprox;

根据您希望近似值的准确度,将 256 加倍或减半(并添加/删除乘法)。即使 n=4,它仍然为 -0.5 和 0.5 之间的 x 值提供大约 1.5 个小数位的精度(并且看起来比 Math.exp() 快 15 倍)。

PS 我忘了提——你显然不应该除以256:乘以常数 1/256。Java 的 JIT 编译器会自动进行这种优化(至少 Hotspot 会),我假设 C# 也必须这样做。

于 2009-01-05T01:59:38.017 回答
24

看看这个帖子。它有一个用 Java 编写的 e^x 的近似值,这应该是它的 C# 代码(未经测试):

public static double Exp(double val) {  
    long tmp = (long) (1512775 * val + 1072632447);  
    return BitConverter.Int64BitsToDouble(tmp << 32);  
}

在我的基准测试中,这比 Math.exp() (在 Java 中)快 5 倍以上。该近似值基于论文“ A Fast, Compact Approximation of the Exponential Function ”,该论文被开发用于神经网络。它基本上与 2048 个条目的查找表和条目之间的线性近似相同,但所有这些都使用 IEEE 浮点技巧。

编辑:根据Special Sauce,这比 CLR 实现快约 3.25 倍。谢谢!

于 2009-01-05T12:38:13.280 回答
14
  1. 请记住,此激活函数的任何更改都会以不同的行为为代价。这甚至包括切换到浮点数(从而降低精度)或使用激活替代品。只有尝试您的用例才能显示正确的方法。
  2. 除了简单的代码优化之外,我还建议考虑计算的并行化(即:利用机器的多个内核,甚至是 Windows Azure 云上的机器)并改进训练算法。

更新: 发布关于 ANN 激活函数的查找表

UPDATE2:我删除了 LUT 上的点,因为我将这些与完整的散列混淆了。感谢Henrik Gustafsson让我重回正轨。所以内存不是问题,尽管搜索空间仍然会被局部极值弄乱。

于 2009-01-05T05:37:30.197 回答
8

在 1 亿次调用时,我开始怀疑分析器开销是否不会影响您的结果。将计算替换为no-op,看看是否报消耗60%的执行时间...

或者更好的是,创建一些测试数据并使用秒表计时器来分析一百万左右的电话。

于 2009-01-05T01:05:56.267 回答
8

如果您能够与 C++ 互操作,则可以考虑将所有值存储在一个数组中,并使用 SSE 循环它们,如下所示:

void sigmoid_sse(float *a_Values, float *a_Output, size_t a_Size){
    __m128* l_Output = (__m128*)a_Output;
    __m128* l_Start  = (__m128*)a_Values;
    __m128* l_End    = (__m128*)(a_Values + a_Size);

    const __m128 l_One        = _mm_set_ps1(1.f);
    const __m128 l_Half       = _mm_set_ps1(1.f / 2.f);
    const __m128 l_OneOver6   = _mm_set_ps1(1.f / 6.f);
    const __m128 l_OneOver24  = _mm_set_ps1(1.f / 24.f);
    const __m128 l_OneOver120 = _mm_set_ps1(1.f / 120.f);
    const __m128 l_OneOver720 = _mm_set_ps1(1.f / 720.f);
    const __m128 l_MinOne     = _mm_set_ps1(-1.f);

    for(__m128 *i = l_Start; i < l_End; i++){
        // 1.0 / (1.0 + Math.Pow(Math.E, -value))
        // 1.0 / (1.0 + Math.Exp(-value))

        // value = *i so we need -value
        __m128 value = _mm_mul_ps(l_MinOne, *i);

        // exp expressed as inifite series 1 + x + (x ^ 2 / 2!) + (x ^ 3 / 3!) ...
        __m128 x = value;

        // result in l_Exp
        __m128 l_Exp = l_One; // = 1

        l_Exp = _mm_add_ps(l_Exp, x); // += x

        x = _mm_mul_ps(x, x); // = x ^ 2
        l_Exp = _mm_add_ps(l_Exp, _mm_mul_ps(l_Half, x)); // += (x ^ 2 * (1 / 2))

        x = _mm_mul_ps(value, x); // = x ^ 3
        l_Exp = _mm_add_ps(l_Exp, _mm_mul_ps(l_OneOver6, x)); // += (x ^ 3 * (1 / 6))

        x = _mm_mul_ps(value, x); // = x ^ 4
        l_Exp = _mm_add_ps(l_Exp, _mm_mul_ps(l_OneOver24, x)); // += (x ^ 4 * (1 / 24))

#ifdef MORE_ACCURATE

        x = _mm_mul_ps(value, x); // = x ^ 5
        l_Exp = _mm_add_ps(l_Exp, _mm_mul_ps(l_OneOver120, x)); // += (x ^ 5 * (1 / 120))

        x = _mm_mul_ps(value, x); // = x ^ 6
        l_Exp = _mm_add_ps(l_Exp, _mm_mul_ps(l_OneOver720, x)); // += (x ^ 6 * (1 / 720))

#endif

        // we've calculated exp of -i
        // now we only need to do the '1.0 / (1.0 + ...' part
        *l_Output++ = _mm_rcp_ps(_mm_add_ps(l_One,  l_Exp));
    }
}

但是,请记住,您将使用的数组应使用 _aligned_malloc(some_size * sizeof(float), 16) 分配,因为 SSE 需要与边界对齐的内存。

使用 SSE,我可以在大约半秒内计算出所有 1 亿个元素的结果。但是,一次分配这么多内存将花费您近三分之二的千兆字节,因此我建议一次处理更多但更小的数组。您甚至可能要考虑使用具有 100K 或更多元素的双缓冲方法。

此外,如果元素的数量开始显着增加,您可能希望选择在 GPU 上处理这些东西(只需创建一个 1D float4 纹理并运行一个非常简单的片段着色器)。

于 2009-01-05T11:15:17.690 回答
8

FWIW,这是我已经发布的答案的 C# 基准。(empty是一个只返回0的函数,用来衡量函数调用开销)

空函数:79ms 0
原文:1576ms 0.7202294
简化:(女高音)681ms 0.7202294
近似值:(尼尔)441ms 0.7198783
位操作:(martinus)836ms 0.72318
泰勒:(雷克斯洛根)261ms 0.7202305
查找:(亨里克)182ms 0.7204863
public static object[] Time(Func<double, float> f) {
    var testvalue = 0.9456;
    var sw = new Stopwatch();
    sw.Start();
    for (int i = 0; i < 1e7; i++)
        f(testvalue);
    return new object[] { sw.ElapsedMilliseconds, f(testvalue) };
}
public static void Main(string[] args) {
    Console.WriteLine("Empty:       {0,10}ms {1}", Time(Empty));
    Console.WriteLine("Original:    {0,10}ms {1}", Time(Original));
    Console.WriteLine("Simplified:  {0,10}ms {1}", Time(Simplified));
    Console.WriteLine("Approximate: {0,10}ms {1}", Time(ExpApproximation));
    Console.WriteLine("Bit Manip:   {0,10}ms {1}", Time(BitBashing));
    Console.WriteLine("Taylor:      {0,10}ms {1}", Time(TaylorExpansion));
    Console.WriteLine("Lookup:      {0,10}ms {1}", Time(LUT));
}
于 2009-01-06T19:49:26.827 回答
5

注意:这是这篇文章的后续。

编辑:更新以计算与thisthis相同的东西,从中获取一些灵感。

现在看看你让我做什么!你让我安装 Mono!

$ gmcs -optimize test.cs && mono test.exe
Max deviation is 0.001663983
10^7 iterations using Sigmoid1() took 1646.613 ms
10^7 iterations using Sigmoid2() took 237.352 ms

C 几乎不值得再努力了,世界正在向前发展 :)

因此,速度快了10 6 倍。有 Windows 盒子的人可以使用 MS-stuff 调查内存使用情况和性能 :)

将 LUT 用于激活功能并不少见,尤其是在硬件中实现时。如果您愿意包含这些类型的表格,那么该概念有许多经过充分验证的变体。然而,正如已经指出的那样,混叠可能会成为一个问题,但也有一些方法可以解决这个问题。一些进一步的阅读:

一些问题:

  • 当您到达桌子外时,误差会上升(但在极端情况下会收敛到 0);对于 x 大约 +-7.0。这是由于选择的比例因子。较大的 SCALE 值在中间范围内产生较高的误差,但在边缘处较小。
  • 这通常是一个非常愚蠢的测试,我不懂 C#,这只是我的 C 代码的简单转换:)
  • Rinat Abdullin非常正确,混叠和精度损失可能会导致问题,但由于我没有看到变量,我只能建议你尝试一下。事实上,我同意他所说的一切,除了查找表的问题。

请原谅复制粘贴编码...

using System;
using System.Diagnostics;

class LUTTest {
    private const float SCALE = 320.0f;
    private const int RESOLUTION = 2047;
    private const float MIN = -RESOLUTION / SCALE;
    private const float MAX = RESOLUTION / SCALE;

    private static readonly float[] lut = InitLUT();

    private static float[] InitLUT() {
      var lut = new float[RESOLUTION + 1];

      for (int i = 0; i < RESOLUTION + 1; i++) {
        lut[i] = (float)(1.0 / (1.0 + Math.Exp(-i / SCALE)));
      }
      return lut;
    }

    public static float Sigmoid1(double value) {
        return (float) (1.0 / (1.0 + Math.Exp(-value)));
    }

    public static float Sigmoid2(float value) {
      if (value <= MIN) return 0.0f;
      if (value >= MAX) return 1.0f;
      if (value >= 0) return lut[(int)(value * SCALE + 0.5f)];
      return 1.0f - lut[(int)(-value * SCALE + 0.5f)];
    }

    public static float error(float v0, float v1) {
      return Math.Abs(v1 - v0);
    }

    public static float TestError() {
        float emax = 0.0f;
        for (float x = -10.0f; x < 10.0f; x+= 0.00001f) {
          float v0 = Sigmoid1(x);
          float v1 = Sigmoid2(x);
          float e = error(v0, v1);
          if (e > emax) emax = e;
        }
        return emax;
    }

    public static double TestPerformancePlain() {
        Stopwatch sw = new Stopwatch();
        sw.Start();
        for (int i = 0; i < 10; i++) {
            for (float x = -5.0f; x < 5.0f; x+= 0.00001f) {
                Sigmoid1(x);
            }
        }
        sw.Stop();
        return sw.Elapsed.TotalMilliseconds;
    }    

    public static double TestPerformanceLUT() {
        Stopwatch sw = new Stopwatch();
        sw.Start();
        for (int i = 0; i < 10; i++) {
            for (float x = -5.0f; x < 5.0f; x+= 0.00001f) {
                Sigmoid2(x);
            }
        }
        sw.Stop();
        return sw.Elapsed.TotalMilliseconds;
    }    

    static void Main() {
        Console.WriteLine("Max deviation is {0}", TestError());
        Console.WriteLine("10^7 iterations using Sigmoid1() took {0} ms", TestPerformancePlain());
        Console.WriteLine("10^7 iterations using Sigmoid2() took {0} ms", TestPerformanceLUT());
    }
}
于 2009-01-05T19:10:05.703 回答
5

F# 在 .NET 数学算法中的性能优于 C#。因此在 F# 中重写神经网络可能会提高整体性能。

如果我们在 F# 中重新实现LUT 基准测试片段(我一直在使用稍微调整过的版本),那么生成的代码:

  • 在588.8 毫秒而不是 3899.2 毫秒内执行 sigmoid1 基准测试
  • 在156.6 毫秒而不是 411.4 毫秒内执行 sigmoid2 (LUT) 基准测试

更多细节可以在文中找到。这是 F# 片段 JIC:

#light

let Scale = 320.0f;
let Resolution = 2047;

let Min = -single(Resolution)/Scale;
let Max = single(Resolution)/Scale;

let range step a b =
  let count = int((b-a)/step);
  seq { for i in 0 .. count -> single(i)*step + a };

let lut = [| 
  for x in 0 .. Resolution ->
    single(1.0/(1.0 +  exp(-double(x)/double(Scale))))
  |]

let sigmoid1 value = 1.0f/(1.0f + exp(-value));

let sigmoid2 v = 
  if (v <= Min) then 0.0f;
  elif (v>= Max) then 1.0f;
  else
    let f = v * Scale;
    if (v>0.0f) then lut.[int (f + 0.5f)]
    else 1.0f - lut.[int(0.5f - f)];

let getError f = 
  let test = range 0.00001f -10.0f 10.0f;
  let errors = seq { 
    for v in test -> 
      abs(sigmoid1(single(v)) - f(single(v)))
  }
  Seq.max errors;

open System.Diagnostics;

let test f = 
  let sw = Stopwatch.StartNew(); 
  let mutable m = 0.0f;
  let result = 
    for t in 1 .. 10 do
      for x in 1 .. 1000000 do
        m <- f(single(x)/100000.0f-5.0f);
  sw.Elapsed.TotalMilliseconds;

printf "Max deviation is %f\n" (getError sigmoid2)
printf "10^7 iterations using sigmoid1: %f ms\n" (test sigmoid1)
printf "10^7 iterations using sigmoid2: %f ms\n" (test sigmoid2)

let c = System.Console.ReadKey(true);

以及输出(针对 F# 1.9.6.2 CTP 发布编译,没有调试器):

Max deviation is 0.001664
10^7 iterations using sigmoid1: 588.843700 ms
10^7 iterations using sigmoid2: 156.626700 ms

更新:更新了基准测试以使用 10^7 次迭代以使结果与 C 相当

UPDATE2:以下是来自同一台机器的C 实现的性能结果,用于比较:

Max deviation is 0.001664
10^7 iterations using sigmoid1: 628 ms
10^7 iterations using sigmoid2: 157 ms
于 2009-01-06T11:30:18.533 回答
4

第一个想法:关于 values 变量的一些统计信息怎么样?

  • “值”的值通常很小 -10 <= value <= 10?

如果没有,您可能可以通过测试超出范围的值来获得提升

if(value < -10)  return 0;
if(value > 10)  return 1;
  • 这些值是否经常重复?

如果是这样,您可能会从记忆中获得一些好处可能不会,但检查一下也无妨....)

if(sigmoidCache.containsKey(value)) return sigmoidCache.get(value);

如果这些都不能应用,那么正如其他一些人所建议的那样,也许你可以降低 sigmoid 的准确性......

于 2009-01-05T02:05:03.900 回答
4

Soprano 对您的通话进行了一些不错的优化:

public static float Sigmoid(double value) 
{
    float k = Math.Exp(value);
    return k / (1.0f + k);
}

如果您尝试查找表并发现它使用了太多内存,您可以始终查看每次连续调用的参数值并采用一些缓存技术。

例如尝试缓存最后一个值和结果。如果下一个调用与前一个调用具有相同的值,则不需要计算它,因为您已经缓存了最后一个结果。如果当前调用与前一次调用相同,即使 100 次中有 1 次,您可能会为自己节省 100 万次计算。

或者,您可能会发现在 10 次连续调用中,value 参数平均有 2 次相同,因此您可以尝试缓存最后 10 个值/答案。

于 2009-01-05T03:35:54.967 回答
4

在我的脑海中,这篇论文解释了一种通过滥用浮点来逼近指数的方法,(单击 PDF 右上角的链接),但我不知道它是否对你在 .网。

另外,还有一点:为了快速训练大型网络,您使用的逻辑 sigmoid 非常糟糕。请参阅LeCun 等人的 Efficient Backprop 的第 4.4 节,并使用以零为中心的东西(实际上,阅读整篇论文,它非常有用)。

于 2009-01-05T11:30:09.797 回答
2

想法:也许您可以使用预先计算的值制作一个(大)查找表?

于 2009-01-05T01:00:07.777 回答
2

这有点离题,但出于好奇,我做了与Java中的CC#F#中的实现相同的实现。我会把这个留在这里,以防其他人好奇。

结果:

$ javac LUTTest.java && java LUTTest
Max deviation is 0.001664
10^7 iterations using sigmoid1() took 1398 ms
10^7 iterations using sigmoid2() took 177 ms

我想在我的情况下,对 C# 的改进是由于 Java 比 Mono 对 OS X 进行了更好的优化。在类似的 MS .NET 实现上(如果有人想发布比较数字,则与 Java 6 相比)我想结果会有所不同.

代码:

public class LUTTest {
    private static final float SCALE = 320.0f;
    private static final  int RESOLUTION = 2047;
    private static final  float MIN = -RESOLUTION / SCALE;
    private static final  float MAX = RESOLUTION / SCALE;

    private static final float[] lut = initLUT();

    private static float[] initLUT() {
        float[] lut = new float[RESOLUTION + 1];

        for (int i = 0; i < RESOLUTION + 1; i++) {
            lut[i] = (float)(1.0 / (1.0 + Math.exp(-i / SCALE)));
        }
        return lut;
    }

    public static float sigmoid1(double value) {
        return (float) (1.0 / (1.0 + Math.exp(-value)));
    }

    public static float sigmoid2(float value) {
        if (value <= MIN) return 0.0f;
        if (value >= MAX) return 1.0f;
        if (value >= 0) return lut[(int)(value * SCALE + 0.5f)];
        return 1.0f - lut[(int)(-value * SCALE + 0.5f)];
    }

    public static float error(float v0, float v1) {
        return Math.abs(v1 - v0);
    }

    public static float testError() {
        float emax = 0.0f;
        for (float x = -10.0f; x < 10.0f; x+= 0.00001f) {
            float v0 = sigmoid1(x);
            float v1 = sigmoid2(x);
            float e = error(v0, v1);
            if (e > emax) emax = e;
        }
        return emax;
    }

    public static long sigmoid1Perf() {
        float y = 0.0f;
        long t0 = System.currentTimeMillis();
        for (int i = 0; i < 10; i++) {
            for (float x = -5.0f; x < 5.0f; x+= 0.00001f) {
                y = sigmoid1(x);
            }
        }
        long t1 = System.currentTimeMillis();
        System.out.printf("",y);
        return t1 - t0;
    }    

    public static long sigmoid2Perf() {
        float y = 0.0f;
        long t0 = System.currentTimeMillis();
        for (int i = 0; i < 10; i++) {
            for (float x = -5.0f; x < 5.0f; x+= 0.00001f) {
                y = sigmoid2(x);
            }
        }
        long t1 = System.currentTimeMillis();
        System.out.printf("",y);
        return t1 - t0;
    }    

    public static void main(String[] args) {

        System.out.printf("Max deviation is %f\n", testError());
        System.out.printf("10^7 iterations using sigmoid1() took %d ms\n", sigmoid1Perf());
        System.out.printf("10^7 iterations using sigmoid2() took %d ms\n", sigmoid2Perf());
    }
}
于 2009-01-06T17:42:48.313 回答
2

我意识到这个问题出现已经一年了,但由于讨论了 F# 和 C 相对于 C# 的性能,我遇到了它。我使用了来自其他响应者的一些示例,发现委托似乎比常规方法调用执行得更快,但F# 与 C# 相比没有明显的性能优势

  • C:166毫秒
  • C#(委托):275ms
  • C#(方法):431ms
  • C#(方法,浮点计数器):2,656ms
  • F#:404 毫秒

带有浮点计数器的 C# 是 C 代码的直接端口。在 for 循环中使用 int 会快得多。

于 2010-12-31T00:15:51.833 回答
2

有一个更快的函数可以做非常相似的事情:

x / (1 + abs(x))– 快速替换 TAHN

同样:

x / (2 + 2 * abs(x)) + 0.5- 快速替换 SIGMOID

将绘图与实际 sigmoid 进行比较

于 2016-10-29T17:32:05.563 回答
1

(更新了性能测量)(再次更新了真实结果:)

我认为查找表解决方案在性能方面会让您走得更远,而内存和精度成本可以忽略不计。

下面的代码片段是 C 中的一个示例实现(我的 c# 说得不够流利,无法对其进行干式编码)。它运行和性能足够好,但我确信它有一个错误:)

#include <math.h>
#include <stdio.h>
#include <time.h>

#define SCALE 320.0f
#define RESOLUTION 2047
#define MIN -RESOLUTION / SCALE
#define MAX RESOLUTION / SCALE

static float sigmoid_lut[RESOLUTION + 1];

void init_sigmoid_lut(void) {
    int i;    
    for (i = 0; i < RESOLUTION + 1; i++) {
        sigmoid_lut[i] =  (1.0 / (1.0 + exp(-i / SCALE)));
    }
}

static float sigmoid1(const float value) {
    return (1.0f / (1.0f + expf(-value)));
}

static float sigmoid2(const float value) {
    if (value <= MIN) return 0.0f;
    if (value >= MAX) return 1.0f;
    if (value >= 0) return sigmoid_lut[(int)(value * SCALE + 0.5f)];
    return 1.0f-sigmoid_lut[(int)(-value * SCALE + 0.5f)];
}

float test_error() {
    float x;
    float emax = 0.0;

    for (x = -10.0f; x < 10.0f; x+=0.00001f) {
        float v0 = sigmoid1(x);
        float v1 = sigmoid2(x);
        float error = fabsf(v1 - v0);
        if (error > emax) { emax = error; }
    } 
    return emax;
}

int sigmoid1_perf() {
    clock_t t0, t1;
    int i;
    float x, y = 0.0f;

    t0 = clock();
    for (i = 0; i < 10; i++) {
        for (x = -5.0f; x <= 5.0f; x+=0.00001f) {
            y = sigmoid1(x);
        }
    }
    t1 = clock();
    printf("", y); /* To avoid sigmoidX() calls being optimized away */
    return (t1 - t0) / (CLOCKS_PER_SEC / 1000);
}

int sigmoid2_perf() {
    clock_t t0, t1;
    int i;
    float x, y = 0.0f;
    t0 = clock();
    for (i = 0; i < 10; i++) {
        for (x = -5.0f; x <= 5.0f; x+=0.00001f) {
            y = sigmoid2(x);
        }
    }
    t1 = clock();
    printf("", y); /* To avoid sigmoidX() calls being optimized away */
    return (t1 - t0) / (CLOCKS_PER_SEC / 1000);
}

int main(void) {
    init_sigmoid_lut();
    printf("Max deviation is %0.6f\n", test_error());
    printf("10^7 iterations using sigmoid1: %d ms\n", sigmoid1_perf());
    printf("10^7 iterations using sigmoid2: %d ms\n", sigmoid2_perf());

    return 0;
}

以前的结果是由于优化器完成了它的工作并优化了计算。让它实际执行代码会产生稍微不同且更有趣的结果(在我的路上慢 MB Air):

$ gcc -O2 test.c -o test && ./test
Max deviation is 0.001664
10^7 iterations using sigmoid1: 571 ms
10^7 iterations using sigmoid2: 113 ms

轮廓


去做:

有需要改进的地方和消除弱点的方法;怎么做留给读者作为练习:)

  • 调整函数的范围以避免表格开始和结束的跳转。
  • 添加轻微噪声功能以隐藏混叠伪影。
  • 正如 Rex 所说,插值可以让你在精度方面更进一步,同时在性能方面相当便宜。
于 2009-01-05T02:56:51.317 回答
1

您也可以考虑尝试评估成本更低的替代激活函数。例如:

f(x) = (3x - x**3)/2

(可以考虑为

f(x) = x*(3 - x*x)/2

减一乘法)。该函数具有奇对称性,其导数微不足道。将其用于神经网络需要通过除以输入总数来归一化输入总和(将域限制为 [-1..1],这也是范围)。

于 2009-01-05T04:01:29.767 回答
1

女高音主题的轻微变化:

public static float Sigmoid(double value) {
    float v = value;
    float k = Math.Exp(v);
    return k / (1.0f + k);
}

既然你只追求单精度结果,为什么要让 Math.Exp 函数计算双精度?任何使用迭代求和的指数计算器(请参阅e x的扩展)每次都需要更长的时间才能获得更高的精度。双倍是单倍的工作量!这样,您首先转换为单一,然后执行指数。

但是 expf 函数应该更快。不过,除非 C# 不进行隐式浮点-双精度转换,否则我认为不需要将女高音的 (float) 强制转换为 expf。

否则,只需使用真正的语言,例如 FORTRAN ...

于 2009-01-05T12:01:11.843 回答
1

这里有很多很好的答案。我建议通过这种技术运行它,只是为了确保

  • 你不会比你需要的更多次调用它。
    (有时函数会被调用超过必要,只是因为它们很容易调用。)
  • 您不会使用相同的参数重复调用它
    (您可以使用记忆化)

顺便说一句,您拥有的函数是逆 logit 函数,
或 log-odds-ratio 函数的倒数log(f/(1-f))

于 2009-11-29T01:51:02.253 回答
0

通过 Google 搜索,我找到了 Sigmoid 函数的替代实现。

public double Sigmoid(double x)
{
   return 2 / (1 + Math.Exp(-2 * x)) - 1;
}

这对您的需求是否正确?它更快吗?

http://dynamicnotions.blogspot.com/2008/09/sigmoid-function-in-c.html

于 2009-01-05T01:18:10.953 回答
0

1)你只从一个地方打电话吗?如果是这样,您可以通过将代码移出该函数并将其放在您通常调用 Sigmoid 函数的正确位置来获得少量性能。在代码可读性和组织方面我不喜欢这个想法,但是当你需要获得最后的性能提升时,这可能会有所帮助,因为我认为函数调用需要在堆栈上推送/弹出寄存器,如果你的代码都是内联的。

2)我不知道这是否会有所帮助,但请尝试将您的函数参数设置为 ref 参数。看看是不是更快。我会建议将其设为 const (如果这是在 c++ 中,这将是一种优化)但 c# 不支持 const 参数。

于 2009-01-05T03:56:10.603 回答
0

如果你需要一个巨大的速度提升,你可能会考虑使用 (ge)force 并行化函数。IOW,使用 DirectX 来控制显卡为你做这件事。我不知道该怎么做,但我见过人们使用显卡进行各种计算。

于 2009-01-05T17:06:52.257 回答
0

我看到这里很多人都在尝试使用近似来使 Sigmoid 更快。但是,重要的是要知道 Sigmoid 也可以使用 tanh 来表示,而不仅仅是 exp。以这种方式计算 Sigmoid 比使用指数方式快 5 倍左右,并且通过使用这种方法,您不会逼近任何东西,因此 Sigmoid 的原始行为保持原样。

    public static double Sigmoid(double value)
    {
        return 0.5d + 0.5d * Math.Tanh(value/2);
    }

当然,并行化将是性能改进的下一步,但就原始计算而言,使用 Math.Tanh 比 Math.Exp 更快。

于 2017-07-09T16:26:28.303 回答
0

请记住,Sigmoid约束的结果范围在 0 和 1 之间。小于约 -10 的值返回一个非常非常接近 0.0 的值。大于 10 的值会返回一个非常非常接近 1 的值。

回到过去,当计算机无法很好地处理算术上溢/下溢时,通常使用 if 条件来限制计算。如果我真的关心它的性能(或者基本上是 Math 的性能),我会将您的代码更改为老式的方式(并注意限制),这样它就不会不必要地调用 Math:

public double Sigmoid(double value)
{
    if (value < -45.0) return 0.0;
    if (value > 45.0) return 1.0;
    return 1.0 / (1.0 + Math.Exp(-value));
}

我意识到任何阅读此答案的人都可能参与某种 NN 开发。请注意上述内容如何影响训练分数的其他方面。

于 2020-05-11T09:57:48.960 回答