-
03-07-2019 - |
题
我一整天都在分析一个应用程序,并且优化了一些代码,我把它留在了我的待办事项列表中。它是神经网络的激活函数,被调用超过 1 亿次。根据 dotTrace 的数据,它约占整个函数时间的 60%。
你会如何优化这个?
public static float Sigmoid(double value) {
return (float) (1.0 / (1.0 + Math.Pow(Math.E, -value)));
}
解决方案
尝试:
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);
}
此外,如果您正在进行数百万次调用,则函数调用开销可能会出现问题。尝试制作内联函数,看看是否有任何帮助。
其他提示
如果是激活函数,如果e ^ x的计算完全准确,那么它是否非常重要?
例如,如果您使用近似值(1 + x / 256)^ 256,在我的Pentium Pentium测试中(我假设C#基本上编译为相同的处理器指令),这比使用快7-8倍e ^ x(Math.exp()),精确到2位小数,最大约为x +/- 1.5,并且在您所述范围内的正确数量级内。 (显然,要提高到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倍。)
P.S。我忘了提 - 你显然不应该真正除以256:乘以常数1/256。 Java的JIT编译器自动进行这种优化(至少Hotspot会这样做),我假设C#也必须这样做。
查看这篇文章一>。它有一个用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倍。近似值基于论文<!>; 指数函数的快速,紧凑近似< !/一> <> QUOT;它被开发用于神经网络。它基本上与2048条目的查找表和条目之间的线性近似相同,但所有这些都与IEEE浮点技巧有关。
编辑:根据特别酱,这比它快~3.25倍CLR实施。谢谢!
- 请记住,此激活功能的任何更改均以不同行为为代价。这甚至包括切换到浮动(从而降低精度)或使用激活替代品。只有尝试使用您的用例才会显示正确的方法。
- 除了简单的代码优化之外,我还建议考虑计算的并行化(即:利用机器的多个内核甚至Windows Azure云上的机器)并改进训练算法。 醇>
更新: 发布ANN激活函数的查找表
UPDATE2:我删除了LUT上的点,因为我把它们与完全散列混淆了。感谢 Henrik Gustafsson 让我回到赛道上。因此,内存不是问题,尽管搜索空间仍然与局部极值相混淆。
在1亿次通话中,我开始怀疑探查器开销是否会影响您的结果。用no-op替换计算,看看是否仍然报告占用了60%的执行时间......
或者更好的是,创建一些测试数据并使用秒表计时器来分析大约一百万个电话。
如果你能够与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上处理这些内容(只需创建一维float4纹理并运行一个非常简单的片段着色器)。
FWIW,这是我已经发布的答案的C#基准。 (Empty是一个只返回0的函数,用于测量函数调用开销)
Empty Function: 79ms 0 Original: 1576ms 0.7202294 Simplified: (soprano) 681ms 0.7202294 Approximate: (Neil) 441ms 0.7198783 Bit Manip: (martinus) 836ms 0.72318 Taylor: (Rex Logan) 261ms 0.7202305 Lookup: (Henrik) 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));
}
在我的脑海中,本文介绍了一种近似方法通过滥用浮点数指数,(单击右上角的PDF链接)但我不知道它在.NET中对你有多大用处。
另外,另一点:为了快速训练大型网络,你使用的后勤sigmoid非常可怕。请参阅 LeCun等人的高效Backprop 第4.4节并使用某些东西以零为中心(实际上,阅读整篇论文,它非常有用)。
在 .NET 数学算法中,F# 的性能优于 C#。 因此,用 F# 重写神经网络可能会提高整体性能。
如果我们重新实现 LUT 基准测试片段 (我一直在 F# 中使用稍微调整的版本),然后生成代码:
- 执行 sigmoid1 基准测试 588.8ms 而不是 3899,2ms
- 执行 sigmoid2 (LUT) 基准测试 156.6 毫秒而不是 411.4 毫秒
更多详细信息可以在 博客文章. 。这是 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 相当
更新2: 这是性能结果 C实现 来自同一台机器的比较:
Max deviation is 0.001664
10^7 iterations using sigmoid1: 628 ms
10^7 iterations using sigmoid2: 157 ms
笔记: 这是后续 这 邮政。
编辑: 更新以计算相同的东西 这 和 这, ,从中汲取一些灵感 这.
现在看看你让我做了什么!你让我安装 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 来实现激活功能并不罕见,特别是在硬件中实现时。如果您愿意包含这些类型的表格,那么这个概念有许多经过充分验证的变体。然而,正如已经指出的那样,混叠可能会成为一个问题,但也有一些方法可以解决这个问题。一些进一步阅读:
- NEUR对象 经过 乔治·瓦伦蒂尼 (还有一篇关于此的论文)
- 具有数字 LUT 激活功能的神经网络
- 通过降低精度的激活函数来促进神经网络特征提取
- 具有整数权重和量化非线性激活函数的神经网络新学习算法
- 量化对高阶函数神经网络的影响
一些问题:
- 当您到达表格之外时,误差会增大(但在极端情况下会收敛到 0);x 约为 +-7.0。这是由于选择的比例因子造成的。SCALE 值越大,中间范围的误差越大,但边缘的误差越小。
- 这通常是一个非常愚蠢的测试,而且我不懂 C#,这只是我的 C 代码的简单转换:)
- 里纳特·阿卜杜林 别名和精度损失可能会导致问题是非常正确的,但由于我还没有看到相应的变量,所以我只能建议您尝试一下。事实上,我同意他所说的一切,除了查找表的问题。
请原谅复制粘贴编码...
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());
}
}
第一个想法:关于值变量的一些统计数据怎么样?
- “value”的值通常很小 -10 <= value <= 10 吗?
如果没有,您可能可以通过测试越界值来获得提升
if(value < -10) return 0;
if(value > 10) return 1;
- 这些值是否经常重复?
如果是这样,您可能会从中受益 记忆化 (可能不是,但检查一下也没什么坏处......)
if(sigmoidCache.containsKey(value)) return sigmoidCache.get(value);
如果这些都不能应用,那么正如其他人所建议的那样,也许你可以通过降低 sigmoid 的准确性来逃脱......
Soprano对你的电话进行了一些很好的优化:
public static float Sigmoid(double value)
{
float k = Math.Exp(value);
return k / (1.0f + k);
}
如果您尝试查找表并发现它使用了太多内存,您可以随时查看每个连续调用的参数值并使用一些缓存技术。
例如,尝试缓存最后一个值和结果。如果下一个调用具有与前一个调用相同的值,则无需计算它,因为您已缓存最后一个结果。如果当前的呼叫与上一次呼叫相同,即使是100次中的1次,您也可以节省100万次计算。
或者,您可能会发现在10次连续调用中,value参数平均相同2次,因此您可以尝试缓存最后10个值/答案。
想法:也许您可以使用预先计算的值制作(大)查找表?
这有点偏离主题,但出于好奇,我做了与 C , C#和 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比OS X的Mono更优化。在类似的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());
}
}
我意识到这个问题已经一年了,但我遇到这个问题是因为讨论 F# 和 C 相对于 C# 的性能。我使用了其他响应者的一些示例,发现委托似乎比常规方法调用执行得更快,但是 F# 相对于 C# 没有明显的性能优势.
- C:166毫秒
- C#(委托):275毫秒
- C#(方法):431毫秒
- C#(方法,浮点计数器):2,656 毫秒
- F#:404毫秒
带有浮点计数器的 C# 是 C 代码的直接移植。在 for 循环中使用 int 会快得多。
您也可以考虑尝试更便宜的替代激活功能进行评估。例如:
f(x) = (3x - x**3)/2
(可以将其视为
f(x) = x*(3 - x*x)/2
少一次乘法)。该函数具有奇对称性,其导数是微不足道的。将它用于神经网络需要通过除以输入的总数来规范化输入和(将域限制为[-1..1],这也是范围)。
Soprano主题的温和变化:
public static float Sigmoid(double value) {
float v = value;
float k = Math.Exp(v);
return k / (1.0f + k);
}
由于你只是在一个精度结果之后,为什么让Math.Exp函数计算一个double?任何使用迭代求和的指数计算器(参见扩展e x )每次都需要更长时间才能获得更高的精确度。双倍是单身工作的两倍!这样,你首先转换为单个,然后做你的指数。
但是expf函数应该更快。虽然C#不进行隐式浮点双转换,但我没有看到传递给expf的女高音(浮动)强制转换的需要。
否则,只需使用真正的语言,例如FORTRAN ...
这里有很多好的答案。我建议运行它 这项技术, , 只想确认一下
- 您调用它的次数不会超过您需要的次数。
(有时,函数的调用次数超出了必要的范围,只是因为它们很容易调用。) - 您不会使用相同的参数重复调用它
(你可以使用记忆的地方)
顺便说一句,您拥有的函数是逆 logit 函数,
或对数优势比函数的反函数 log(f/(1-f))
.
(更新了性能测量)(再次更新了真实结果:)
我认为查找表解决方案可以让您在性能方面取得很大的进步,而内存和精度成本可以忽略不计。
以下代码片段是 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
去做:
有需要改进的地方和消除弱点的方法;如何做是留给读者的练习:)
- 调整函数的范围以避免表格开始和结束处的跳转。
- 添加轻微的噪声函数来隐藏锯齿伪影。
- 正如雷克斯所说,插值可以让你在精度方面更进一步,同时在性能方面相当便宜。
有一个更快的功能可以完成非常相似的事情:
x / (1 + abs(x))
<!>#8211;快速替代TAHN
同样地:
x / (2 + 2 * abs(x)) + 0.5
- 快速替换SIGMOID
进行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
1)你是否只在一个地方打电话?如果是这样,你可以通过将代码移出该函数并将其放在通常称为Sigmoid函数的位置来获得少量性能。我不喜欢这个想法在代码可读性和组织方面,但是当你需要获得每一个最后的性能增益时,这可能会有所帮助,因为我认为函数调用需要在堆栈上推送/弹出寄存器,这可以避免,如果你的代码全部是内联的。
2)我不知道这是否有帮助,但尝试将函数参数作为ref参数。看看它是否更快。我建议将它设为const(如果这是在c ++中,这将是一个优化)但c#不支持const参数。
如果你需要一个巨大的速度提升,你可能会考虑使用(ge)力并行化该功能。 IOW,使用DirectX控制显卡为你做这件事。我不知道怎么做,但我看到人们使用显卡进行各种计算。
我已经看到周围很多人都在尝试使用近似来使Sigmoid更快。然而,重要的是要知道Sigmoid也可以使用tanh表达,而不仅仅是exp。 以这种方式计算Sigmoid比使用指数快5倍左右,并且通过使用此方法,您不会逼近任何东西,因此Sigmoid的原始行为保持原样。
public static double Sigmoid(double value)
{
return 0.5d + 0.5d * Math.Tanh(value/2);
}
当然,parellization将是性能提升的下一步,但就原始计算而言,使用Math.Tanh比Math.Exp更快。