Frage

Ich habe eine Anwendung wurde Profilierungs den ganzen Tag und ein paar Bits von Code optimiert zu haben, ich bin auf meiner To-do-Liste mit diesem links. Es ist die Aktivierungsfunktion für ein neuronales Netzwerk, das über 100 Millionen Mal aufgerufen wird. Nach DotTrace beträgt sie etwa 60% der Gesamtfunktion Zeit.

Wie würden Sie optimieren das?

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

Lösung

Versuchen:

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

EDIT: Ich habe eine schnelle Benchmark. Auf meinem Rechner ist der obige Code etwa 43% schneller als die Methode, und dieser mathematisch-Äquivalent-Code ist den teeniest etwas schneller (46% schneller als das Original):

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

EDIT 2: Ich bin mir nicht sicher, wie viel Overhead C # Funktionen haben, aber wenn Sie in Ihrem Quellcode #include <math.h>, sollten Sie in der Lage sein, dies zu verwenden, die einen Schwimmer-exp-Funktion verwendet. Es könnte ein wenig schneller sein.

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

Auch wenn Sie Millionen von Anrufen, der Funktion-Aufruf-Overhead könnte ein Problem tun sein. Versuchen Sie, eine Inline-Funktion und sehen, ob das ist keine Hilfe.

Andere Tipps

Wenn es für eine Aktivierungsfunktion ist, tut es Angelegenheit furchtbar viel, wenn die Berechnung von e ^ x ist völlig korrekt?

Wenn Sie beispielsweise die Annäherung verwenden (1 + x / 256) ^ 256, auf meinen Pentium-Tests in Java (ich gehe mal davon aus C # kompiliert im Wesentlichen der gleichen Prozessorbefehle) dies etwa 7-8 mal schneller ist als e ^ x (Math.exp ()), und ist genau auf 2 Dezimalstellen bis zu etwa x von +/- 1,5 und in der richtigen Größenordnung über den Bereich, den Sie angegeben. (Offensichtlich auf die 256 zu erhöhen, können Sie die Nummer 8 mal tatsächlich Platz - nicht verwenden Math.Pow für diese!) In Java:

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

Halten Sie verdoppeln oder 256 zu halbieren (und das Hinzufügen / Entfernen einer Multiplikation), je nachdem, wie genau Sie wollen, dass die Annäherung zu sein. Auch bei n = 4, es gibt immer noch etwa 1,5 Dezimalstellen Genauigkeit für Werte von x beween -0,5 und 0,5 (und scheint gut 15-mal schneller als Math.exp ()).

P. S. Ich vergaß zu erwähnen - Sie sollten natürlich nicht wirklich Division durch 256 multipliziert mit einer konstanten 1/256. Java JIT-Compiler diese Optimierung automatisch macht (zumindest Hotspot der Fall ist), und ich war der Annahme, dass C # tun müssen.

Hier finden Sie aktuelle diesen Beitrag . es hat eine Näherung für e ^ x in Java geschrieben, dies ist der C # -Code für sie sein sollte (nicht getestet):

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

In meiner Benchmarks ist dies mehr als 5-mal schneller als Math.exp () (in Java). Die Annäherung basiert auf dem Papier „ A Schnell, kompakt Approximation der Exponentialfunktion “ das war genau das entwickelt, um in neuronalen Netzen verwendet werden. Es ist im Grunde das gleiche wie eine Verweistabelle von 2048 Einträgen und lineare Annäherung zwischen den Einträgen, aber all dies mit IEEE Floating-Point-Tricks.

EDIT: Nach Special Sauce diese ~ 3.25x ist schneller als die CLR-Implementierung. Dank!

  1. Denken Sie daran, dass alle Änderungen in dieser Aktivierungsfunktion auf Kosten der verschiedenen Verhalten kommen . Dies schließt sogar Schalt zu schweben (und damit die Präzision Senken) oder Aktivierung Substitute verwenden. Nur mit Ihrem Anwendungsfall zu experimentieren wird den richtigen Weg zeigen.
  2. Zusätzlich zu den einfachen Code-Optimierungen, würde ich auch empfehlen Parallelisierung der Berechnungen zu prüfen (zB: mehrere Kerne Ihrer Maschine oder auch Maschinen an dem Windows Azure Clouds nutzen) und die Verbesserung der Trainingsalgorithmen.

UPDATE: Post auf Lookup-Tabellen für ANN Aktivierungsfunktionen

UPDATE2: Ich entfernte den Punkt auf LUTs, da ich diese mit dem kompletten Hashing verwechselt haben. Dank Henrik Gustafsson bringen mich auf die Strecke zurück. So dass der Speicher ist kein Problem, obwohl der Suchraum immer noch mit lokalen Extrema ein bisschen verwirrt wird.

Bei 100 Millionen Anrufe, würde ich beginnen sich zu fragen, wenn Profiler-Overhead ist nicht Ihre Ergebnisse verfälscht. Ersetzen Sie die Berechnung mit einer No-op und sehen, ob es noch berichtete 60% der Ausführungszeit verbrauchen ...

Oder noch besser, einige Testdaten erstellen und eine Stoppuhr verwendet eine Million oder so Anrufe zu profilieren.

Wenn Sie in der Lage sind, mit C ++ Interop sollte, können Sie alle Werte Speicherung in einem Array und Schleife über sie SSE wie folgt aus:

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));
    }
}

Beachten Sie jedoch, dass die Arrays sollten Sie _aligned_malloc (some_size * sizeof (float), 16) zugeordnet werden verwenden werden verwenden, da SSE-Speicher zu einer Grenze ausgerichtet erfordert.

Mit SSE, kann ich das Ergebnis für alle 100 Millionen Elemente berechnen in etwa einer halben Sekunde. Doch in einer Zeit, die viel Speicher Zuweisung werden Sie fast zwei Drittel von einem Gigabyte kostet so würde ich vorschlagen, zu einer Zeit mehr, aber kleinere Arrays zu verarbeiten. Man könnte sogar eine doppelte Pufferung Ansatz mit 100K Elemente berücksichtigen möchten, mit oder mehr.

Auch wenn die Anzahl der Elemente deutlich zu wachsen beginnt, möchten Sie vielleicht wählen, diese Dinge auf der GPU zu verarbeiten (erstellen nur ein 1D float4 einen sehr trivialen Fragment-Shader Textur und ausgeführt werden).

FWIW, hier ist mein C # Benchmarks für die Antworten bereits gebucht. (Leer ist eine Funktion, die nur 0 zurückgibt, den Funktionsaufruf Overhead zu messen)

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));
}

Aus der Spitze von meinem Kopf, diesem Beitrag erläutert eine Art und Weise der Annäherung die exponentiellen von Gleitkomma missbrauchen, (den Link oben rechts für PDF klicken), aber ich weiß nicht, ob es von großem nutzen für Sie in .NET sein wird.

Auch ein weiterer Punkt: zum Zweck schnell große Netzwerk für die Ausbildung, die logistischen sigmoid Sie verwenden ist ziemlich schrecklich. Abschnitt 4.4 von Efficient Backprop von LeCun et al und etwas verwenden Null-zentrierte (eigentlich, dass die ganze Zeitung lesen, es ist ungeheuer nützlich).

F # hat eine bessere Leistung als C # in .NET mathematischen Algorithmen. So neuronale Netzes in F # Umschreiben könnte die Gesamtleistung verbessern.

Wenn wir LUT Benchmarking Snippet neu implementieren (Ich habe mit leicht gezwickt Version wurde) in F #, dann dem resultierenden Code:

  • führt sigmoid1 Benchmark in 588.8ms statt 3899,2ms
  • führt sigmoid2 (LUT) Benchmark in 156.6ms statt 411,4 ms

Mehr Details in der Blog-Post . Hier ist die F # JIC-Snippet:

#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);

Und die Ausgabe (Release Kompilation gegen F # 1.9.6.2 CTP ohne Debugger):

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

UPDATE: aktualisiert Benchmarking 10 ^ 7 Iterationen zu verwenden, um vergleichbare Ergebnisse mit C zu machen

UPDATE2: hier sind die Leistungsergebnisse der C Implementierung von die gleiche Maschine zu vergleichen mit:

Max deviation is 0.001664
10^7 iterations using sigmoid1: 628 ms
10^7 iterations using sigmoid2: 157 ms

Hinweis: Dies ist ein Follow-up href="https://stackoverflow.com/questions/412019/code-optimizations#412176"> diese Post an

Edit: Aktualisieren, um die gleiche Sache wie diese und zu berechnen < a href = "https://stackoverflow.com/questions/412019/code-optimizations#416180"> diese , Inspiration von diese .

Nun sieh mal, was du mich tun! Du hast mich Mono installieren!

$ 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 ist kaum der Mühe mehr wert, die Welt bewegt sich nach vorn:)

Also, etwas mehr als ein Faktor 10 6 schneller. Jemand mit einer Windows-Box wird die Speichernutzung und Performance mit MS-Material zu untersuchen:)

LUTs für die Aktivierung mit Hilfe von Funktionen ist nicht so ungewöhnlich, especielly wenn in Hardware implementiert. Es gibt viele bewährten Varianten des Konzepts da draußen, wenn Sie bereit sind, diese Art von Tabellen aufzunehmen. wie sie bereits jedoch darauf hingewiesen, entpuppt Aliasing könnte ein Problem sein, aber es gibt Möglichkeiten, um auch. Einige weitere Lektüre:

Einige gotchas mit diesem:

  • Der Fehler geht auf, wenn Sie außerhalb der Tabelle erreichen (aber gegen 0 konvergiert an den Extremen); für x ca. + -7.0. Dies ist aufgrund des gewählten Skalierungsfaktors. Größere Werte von SCALE gibt höhere Fehler im mittleren Bereich, aber kleiner an den Rand.
  • Dies ist im Allgemeinen ein sehr dummer Test, und ich weiß nicht, C #, es ist nur eine einfache Umwandlung von meinem C-Code:)
  • Rinat Abdullin sehr richtig ist, dass Aliasing und Genauigkeitsverlust kann zu Problemen führen, aber da ich denn das ist nicht die Variablen gesehen, die ich Ihnen nur dies versuchen beraten können. In der Tat, ich stimme mit allem, was er sagt, mit Ausnahme der Ausgabe von Lookup-Tabellen.

Pardon die Copy-Paste-Codierung ...

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());
    }
}

Erster Gedanke: Wie wäre es mit Statistiken über die Werte Variable

  • Sind die Werte von "Wert" in der Regel kleiner -10 <= Wert <= 10?

Wenn nicht, können Sie wahrscheinlich einen Schub erhalten, indem die Prüfung für außerhalb der Grenzen Werte

if(value < -10)  return 0;
if(value > 10)  return 1;
  • sind oft die Werte wiederholt?

Wenn ja, können Sie wahrscheinlich einige Vorteile erhalten von memoization (wahrscheinlich nicht, aber es tut nicht weh zu überprüfen ....)

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

Wenn beides nicht angewendet werden kann, dann, wie einige andere vorgeschlagen haben, vielleicht können Sie mit Senken Sie die Genauigkeit Ihrer sigmoid weg ...

Sopran hatte ein paar schöne Optimierungen Ihren Anruf:

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

Wenn Sie eine Lookup-Tabelle versuchen, und finden es zu viel Speicher verwendet, die Sie immer auf dem Wert des Parameters für jeden aufeinanderfolgenden Aufrufen und Verwenden einige Caching-Technik suchen konnten.

Zum Beispiel versuchen, den letzten Wert und Ergebnis-Caching. Wenn der nächste Anruf den gleichen Wert wie die vorherige hat, brauchen Sie es nicht zu berechnen, wie Sie das letzte Ergebnis zwischengespeichert haben würde. Wenn der aktuelle Anruf gleich den vorherige Anruf sogar 1 aus einem 100-mal ist, könnten Sie möglicherweise selbst eine Million Berechnungen speichern.

Oder Sie können der Wert Parameter im Durchschnitt finden, dass innerhalb von 10 aufeinanderfolgenden Anrufen, sind die gleiche 2-mal, so konnte man dann versuchen, die letzten 10 Werte / Antworten-Caching.

Idee: Vielleicht können Sie eine (große) Lookup-Tabelle mit den Werten machen vorausberechneten

Das ist etwas off topic, aber nur aus Neugier, ich habe die gleiche Umsetzung wie die in C , C # und F # in Java. Ich werde dies gerade hier lassen, falls jemand anderes neugierig ist.

Ergebnis:

$ 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

Ich nehme an, die Verbesserung gegenüber C # in meinem Fall ist auf Java besser als Mono Auf einer ähnliche MS .NET-Implementierung für OS X optimiert (vs. Java 6, wenn jemand vergleichende Zahlen schreiben will) Ich nehme an, die Ergebnisse wäre anders.

Code:

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());
    }
}

Ich weiß, dass es ein Jahr her, seit dieser Frage auftauchte ist, aber ich lief wegen der Diskussion von F # und C Leistung im Vergleich zu C # über sie. Ich spielte mit einigen der Proben aus anderen Responder und entdecken, dass die Delegierten schneller auszuführen erscheinen als ein regulärer Methodenaufruf aber gibt es keinen offensichtlichen peformance Vorteil zu F # über C # .

  • C: 166ms
  • C # (Delegierter): 275ms
  • C # (Methode): 431ms
  • C # (Methode, Gleitzähler): 2,656ms
  • F #: 404ms

Die C # mit einem Schwimmer Zähler war ein gerader Anschluss des C-Codes. Es ist viel schneller ein int in dem for-Schleife zu verwenden.

Sie könnten auch erwägen alternative Aktivierungsfunktionen zu experimentieren, die billiger zu bewerten sind. Zum Beispiel:

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

(die als einkalkuliert werden könnte

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

für eine weniger Multiplikation). Diese Funktion hat ungerade Symmetrie und dessen Derivat ist trivial. es für ein neuronales Netz verwendet, erfordert durch die Gesamtzahl der Eingänge durch Dividieren (Begrenzung der Domäne [-1..1], die ebenfalls Bereich liegt), um die Summe-von-Eingänge zu normalisieren.

Eine milde Variante der Sopranos Thema:

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

Da Sie nur nach einem einzigen Präzisionsergebnis sind, warum die Math.exp Funktion machen eine doppelte berechnen? Jeder Exponent Rechner, der eine iterative Summe verwendet (siehe die Erweiterung des e x ) dauern wird, bis mehr Präzision mehr, jedes Mal. Und doppelt so hoch ist zweimal die Arbeit der einzelnen! Auf diese Weise, Sie konvertieren zuerst Single, und machen Sie Ihre exponentiell.

Aber die expf Funktion sollte noch schneller sein. Ich sehe nicht die Notwendigkeit für Sopran (float) gegossen beiläufig obwohl expf, es sei denn, C # nicht impliziten float-Doppelwandler macht.

Ansonsten benutzen Sie einfach eine real Sprache, wie Fortran ...

Es gibt hier viele gute Antworten. Ich würde vorschlagen, es durch diese Technik läuft , nur um sicherzustellen, dass

  • Du nennst es keine mehr Zeit als nötig.
    (Manchmal werden Funktionen mehr als notwendig bezeichnet, nur weil sie so einfach zu nennen.)
  • Sie nennen es nicht immer wieder mit den gleichen Argumenten
    (Wo man memoization verwenden könnte)

BTW die Funktion, die Sie haben, ist die inverse logit-Funktion,
oder das Inverse der Log-Odds-Ratio-Funktion log(f/(1-f)).

(mit Performance-Messungen aktualisiert) (Stand wieder mit realen Ergebnissen:)

Ich denke, eine Lookup-Tabelle Lösung würden Sie sehr weit kommen, wenn es um Leistung geht, bei vernachlässigbarem Speichern und Präzision Kosten.

Das folgende Snippet ist eine Beispiel-Implementierung in C (ich spreche nicht c # flüssig genug, um zu trocknen-Code it). Es läuft und funktioniert gut genug, aber ich bin sicher, dass es ein Fehler in ihm:)

#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;
}

Die bisherigen Ergebnisse wurden aufgrund der Optimierer seine Arbeit tun und die Berechnungen optimiert entfernt. So dass es ausführt tatsächlich die Code-Ausbeuten etwas anders und viel interessante Ergebnisse (auf dem Weg langsam 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

Profil


TODO:

Es gibt Dinge zu verbessern und Möglichkeiten, Schwächen zu beseitigen; wie zu tun ist, wird als Übung dem Leser überlassen:)

  • Stellen Sie den Bereich der Funktion der Sprung, wo die Tabelle beginnt und endet zu vermeiden.
  • Fügen Sie ein leises Geräusch Funktion, um die Aliasing-Artefakte zu verstecken.
  • Wie Rex sagte, Interpolation könnte erhalten Sie ein bisschen weiter Präzision weise, während sie eher billig Performance-weise.

Es gibt eine viel schnellere Funktionen, die sehr ähnliche Dinge tun:

x / (1 + abs(x)) - schnell Ersatz für Tahn

Und ähnlich:

x / (2 + 2 * abs(x)) + 0.5 - schnell Ersatz für SIGMOID

Vergleichen Plots mit tatsächlichen sigmoid

Doing eine Google-Suche, fand ich eine alternative Implementierung der Sigmoid-Funktion.

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

Ist das richtig für Ihre Bedürfnisse? Ist es schneller?

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

1) Rufen Sie dies nur von einem Ort? Wenn ja, können Sie eine kleine Menge an Leistung gewinnen, indem sie den Code aus dieser Funktion zu bewegen und setzt es genau richtig, wo Sie normalerweise die Sigmoid-Funktion aufgerufen haben. Ich mag es nicht, diese Idee in Bezug auf die Lesbarkeit des Codes und Organisation, aber wenn man jeden letzten Leistungsgewinn erhalten müssen, könnte dies helfen, weil ich denke, Funktionsaufrufe eine Push / Pop von Registern auf dem Stapel erfordern, die vermieden werden könnten, wenn Ihre Code war alles inline.

2) Ich habe keine Ahnung, ob das helfen könnte, aber versuchen Sie Ihre Funktionsparameter ein ref Parameter machen. Sehen Sie, wenn es schneller ist. Ich würde es konst vorgeschlagen haben, machen (was eine Optimierung gewesen wäre, wenn diese in c ++ waren), aber c # unterstützt keine const Parameter.

Wenn Sie einen Riesen-Geschwindigkeitsschub benötigen, könnten Sie wahrscheinlich Blick in die Funktion Parallelisierung der (ge) mit Gewalt. IOW, verwenden Sie DirectX die Grafikkarte in steuern, die für Sie tun. Ich habe keine Ahnung, wie dies zu tun, aber ich habe Leute für alle Arten von Berechnungen verwenden Grafikkarten gesehen.

Ich habe viele Leute gesehen, dass hier versucht Annäherung zu verwenden Sigmoid schneller zu machen. Allerdings ist es wichtig zu wissen, dass Sigmoid kann auch tanh ausgedrückt wird unter Verwendung nicht nur exp. Die Berechnung Sigmoid ist auf diese Weise rund 5-mal schneller als mit exponentiell, und mit dieser Methode nicht annähert nichts werden, so dass das ursprüngliche Verhalten von Sigmoid gehalten wird, wie sie ist.

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

Natürlich parellization der nächste Schritt zur Leistungsverbesserung sein würde, aber so weit wie die rohe Berechnung betrifft, Math.Tanh mit schneller ist als Math.exp.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top