61

私は 1 日中アプリケーションのプロファイリングを行っており、いくつかのコードを最適化した結果、これが私の ToDo リストに残されています。これは、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())、および +/-1.5 の約 x までの小数点以下 2 桁まで正確であり、指定した範囲全体で正しい大きさのオーダー内にあります。(明らかに、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 を 2 倍または 256 倍 (および乗算を追加/削除) し続けます。n=4 の場合でも、x の値が -0.5 から 0.5 の間で、小数点以下約 1.5 桁の精度が得られます (Math.exp() よりも 15 倍高速に表示されます)。

PS 言い忘れましたが、実際には 256 で割るべきではありませ。定数 1/256 を掛けてください。Java の JIT コンパイラーはこの最適化を自動的に行います (少なくとも Hotspot は行います)。

于 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 倍以上高速です。この近似は、ニューラル ネットワークで使用するために正確に開発された論文「指数関数の高速でコンパクトな近似」に基づいています。これは基本的に、2048 エントリのルックアップ テーブルとエントリ間の線形近似と同じですが、これはすべて IEEE 浮動小数点のトリックを使用しています。

編集: Special Sauceによると、これは CLR 実装よりも ~3.25 倍高速です。ありがとう!

于 2009-01-05T12:38:13.280 に答える
14
  1. このアクティベーション関数を変更すると、異なる動作が発生することを忘れないでください。これには、float への切り替え (したがって精度の低下) やアクティベーション代替の使用も含まれます。ユースケースで実験するだけで、正しい方法が示されます。
  2. 単純なコードの最適化に加えて、計算の並列化(つまり、マシンの複数のコアまたは Windows Azure クラウドのマシンを活用すること) とトレーニング アルゴリズムの改善を検討することもお勧めします。

更新: ANN アクティベーション関数のルックアップ テーブルに投稿

UPDATE2: LUT を完全なハッシュと混同したため、LUT のポイントを削除しました。Henrik Gustafssonには、私を軌道に乗せてくれてありがとう。したがって、メモリは問題ではありませんが、探索空間はまだ局所的な極値で少し混乱しています。

于 2009-01-05T05:37:30.197 に答える
8

FWIW、これがすでに投稿された回答のC#ベンチマークです。(空は、関数呼び出しのオーバーヘッドを測定するために、0を返すだけの関数です)

空関数:79ms 0
オリジナル:1576ms 0.7202294
簡略化:(ソプラノ)681ms 0.7202294
おおよそ:(ニール)441ms 0.7198783
ビット操作:(マルティヌス)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 に答える
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 を使用すると、約 0.5 秒で 1 億要素すべての結果を計算できます。ただし、一度に多くのメモリを割り当てると、ギガバイトの 3 分の 2 近くのコストがかかるため、一度に処理する配列の数を減らして小さくすることをお勧めします。100K 以上の要素でダブル バッファリング アプローチを使用することを検討することもできます。

また、要素の数が大幅に増え始めた場合は、これらを GPU で処理することを選択することもできます (1D float4 テクスチャを作成し、非常に簡単なフラグメント シェーダーを実行するだけです)。

于 2009-01-05T11:15:17.690 に答える
8

1 億回の呼び出しで、プロファイラーのオーバーヘッドが結果をゆがめていないかどうか疑問に思うようになります。計算をノーオペレーションに置き換えて、実行時間の 60% を消費していると報告されているかどうかを確認します...

または、テスト データを作成し、ストップウォッチ タイマーを使用して 100 万件程度の通話をプロファイリングします。

于 2009-01-05T01:05:56.267 に答える
5

F# は、.NET 数学アルゴリズムで C# よりも優れたパフォーマンスを発揮します。したがって、F# でニューラル ネットワークを書き直すと、全体的なパフォーマンスが向上する可能性があります。

F# でLUT ベンチマーク スニペット(私は少し調整したバージョンを使用しています)を再実装すると、結果のコードは次のようになります。

  • sigmoid1 ベンチマークを3899.2ms ではなく 588.8ms で実行
  • sigmoid2 (LUT) ベンチマークを 411.4 ミリ秒ではなく 156.6 ミリ秒で実行

詳細については、ブログ投稿を参照してください。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 に答える
5

注:これはこの投稿のフォローアップです。

編集: thisおよびthisと同じことを計算するように更新し、thisからインスピレーションを得ています。

今、あなたが私に何をさせたか見てください!あなたは私に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 に答える
4

ソプラノはあなたの呼び出しにいくつかの素晴らしい最適化をしました:

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 の右上にあるリンクをクリックしてください)。ネット。

また、別のポイント: 大規模なネットワークをすばやくトレーニングするために、使用しているロジスティック シグモイドはかなりひどいものです。LeCun らによる Efficient Backpropのセクション 4.4 を参照して、ゼロ中心のものを使用してください (実際、その論文全体を読んでください。非常に便利です)。

于 2009-01-05T11:30:09.797 に答える
4

最初に考えたのは、values 変数の統計はどうですか?

  • 「値」の値は通常小さい -10 <= 値 <= 10 ですか?

そうでない場合は、範囲外の値をテストすることで、おそらくブーストを得ることができます

if(value < -10)  return 0;
if(value > 10)  return 1;
  • 値は頻繁に繰り返されますか?

もしそうなら、おそらくメモ化からいくらかの利益を得ることができますおそらくそうではありませんが、チェックするのに害はありません....)

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

これらのどちらも適用できない場合は、他の人が示唆しているように、シグモイドの精度を下げることで逃げることができるかもしれません...

于 2009-01-05T02:05:03.900 に答える
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 が 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());
    }
}
于 2009-01-06T17:42:48.313 に答える
2

アイデア: おそらく、事前に計算された値を使用して (大きな) ルックアップ テーブルを作成できますか?

于 2009-01-05T01:00:07.777 に答える
2

この質問が出てから 1 年が経ちましたが、C# と比較した F# と C のパフォーマンスについての議論が原因で、この質問に出くわしました。他のレスポンダーのサンプルをいくつか試してみたところ、デリゲートは通常のメソッド呼び出しよりも高速に実行されているように見えますが、F# には C# よりも明らかなパフォーマンス上の利点はありません

  • C: 166ms
  • C# (デリゲート): 275ms
  • C# (メソッド): 431ms
  • C# (メソッド、フロートカウンター): 2,656ms
  • F#: 404ms

float カウンターを使用する C# は、C コードをそのまま移植したものです。for ループで int を使用する方がはるかに高速です。

于 2010-12-31T00:15:51.833 に答える
2

非常に類似したことを行う、より高速な関数があります。

x / (1 + abs(x))– TAHN の迅速な交換

同様に:

x / (2 + 2 * abs(x)) + 0.5- SIGMOID の迅速な交換

プロットを実際のシグモイドと比較する

于 2016-10-29T17:32:05.563 に答える
1

また、評価が安価な代替の活性化関数を試すことも検討してください。例えば:

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

(これは次のように因数分解できます

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

1つ少ない乗算)。この関数は奇妙な対称性を持っており、その導関数は自明です。ニューラルネットワークに使用するには、入力の総数で割って入力の合計を正規化する必要があります(ドメインを[-1..1]に制限します。これも範囲です)。

于 2009-01-05T04:01:29.767 に答える
1

ここにはたくさんの良い答えがあります。念のため、この手法で実行することをお勧めします

  • 必要以上に呼び出すことはありません。
    (関数は簡単に呼び出せるという理由だけで、必要以上に呼び出されることがあります。)
  • 同じ引数で繰り返し呼び出していない
    (メモ化を使用できる場合)

ところで、あなたが持っている関数は逆ロジット関数、
または対数オッズ比関数の逆関数log(f/(1-f))です。

于 2009-11-29T01:51:02.253 に答える
1

ソプラノのテーマの軽度のバリエーション:

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

単精度の結果しか求めていないのに、Math.Exp 関数で double を計算するのはなぜですか? 反復合計を使用する指数計算機 ( e xの展開を参照) は、毎回、精度を上げるために時間がかかります。そしてダブルはシングルの2倍の働き!このように、最初に単一に変換してから、指数関数を実行します

しかし、expf 関数はさらに高速になるはずです。ただし、C# が暗黙的な float-double 変換を行わない限り、expf に渡す際にソプラノ (float) キャストの必要性はわかりません。

それ以外の場合は、FORTRAN などの実際の言語を使用してください...

于 2009-01-05T12:01:11.843 に答える
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

プロフィール


TODO:

改善すべき点と弱点を取り除く方法があります。どのように行うかは、読者への演習として残されています:)

  • 関数の範囲を調整して、テーブルの開始点と終了点でのジャンプを回避します。
  • わずかなノイズ機能を追加して、エイリアシング アーティファクトを非表示にします。
  • Rex が言ったように、補間により、パフォーマンスに関してはかなり安価でありながら、精度に関してはかなりの精度が得られる可能性があります。
于 2009-01-05T02:56:51.317 に答える
0

1)これを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

Google 検索を行ったところ、シグモイド関数の別の実装が見つかりました。

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 に答える