سؤال

لقد تم التنميط تطبيق طوال اليوم ، وبعد الأمثل بضعة أجزاء من التعليمات البرمجية, أنا مع ترك على قائمة ما يجب عمله.إنه تفعيل وظيفة الشبكة العصبية التي يحصل دعا أكثر من 100 مليون مرة.وفقا 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 ٪ أسرع من طريقة, و هذا رياضيا ما يعادل رمز هو teeniest أسرع قليلا (46 ٪ أسرع من الأصلي):

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

تحرير 2: أنا لست متأكدا كم علوية C# وظائف, ولكن إذا كنت #include <math.h> في التعليمات البرمجية المصدر الخاصة بك, يجب أن تكون قادرة على استخدام هذا الذي يستخدم تعويم-exp وظيفة.قد يكون أسرع قليلا.

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

أيضا إذا كنت تفعل الملايين من المكالمات وظيفة-الدعوة العامة قد يكون مشكلة.محاولة جعل مضمنة وظيفة ومعرفة ما إذا كان هذا أي مساعدة.

نصائح أخرى

إذا كان لوظيفة التنشيط، لا يهم كثيرا بشكل رهيب إذا كان حساب البريد ^ x غير دقيقة تماما؟

وعلى سبيل المثال، إذا كنت تستخدم التقريب (1 + س / 256) ^ 256، على بلدي اختبار بنتيوم في جافا (أفترض C # يجمع أساسا لتعليمات المعالج نفس) وهذا هو حوالي 7-8 مرات أسرع من ه ^ س (Math.exp ())، وغير دقيقة إلى 2 عشرية تصل إلى حوالي العاشر من +/- 1.5، وضمن الترتيب الصحيح من حيث الحجم عبر مجموعة ذكرتم. (من الواضح، أن يرفع إلى 256، كنت في الواقع تربيع عدد 8 مرات - لا تستخدم Math.Pow لهذا!) في جاوة:

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

وحافظ على مضاعفة أو خفض 256 (وإضافة / إزالة الضرب) اعتمادا على مدى دقة تريد تقريب أن يكون. حتى مع ن = 4، فإنه لا يزال يعطي حوالي 1.5 عشريين من الدقة لقيم س beween -0.5 و 0.5 (ويبدو جيدة 15 مرات أسرع من Math.exp ()).

وP.S. نسيت أن أذكر - يجب عليك الواضح أن لا <ط> حقا القسمة 256: ضرب من قبل ثابت ل1/256. مترجم JIT جافا يجعل هذا التحسين تلقائيا (على الأقل، نقطة ساخنة لا)، وكنت على افتراض أن C # يجب القيام به للغاية.

إلقاء نظرة على هذا المنصب.وقد تقريبي ل e^x مكتوب في جاوة ، يجب أن تكون هذه التعليمات البرمجية C# على ذلك (لم تختبر):

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

في المعايير وهذا هو أكثر من 5 مرات أسرع من الرياضيات.exp() (في جافا).تقريب يقوم على الورق "سريع وصغير تقريب الدالة الأسية"الذي تم تطويره بالضبط ليتم استخدامها في الشبكات العصبية.هو أساسا نفس جدول بحث 2048 إدخالات والتقريب الخطي بين الإدخالات ، ولكن كل هذا مع IEEE النقطة العائمة الحيل.

تحرير: وفقا صلصة خاصة هذا هو ~3.25 x أسرع من CLR التنفيذ.وذلك بفضل!

  1. تذكر أن أي تغييرات في هذا تفعيل وظيفة تأتي في تكلفة السلوك المختلفة.هذا يشمل حتى التحول إلى تعويم (وبالتالي خفض الدقة) أو باستخدام تفعيل بدائل.فقط تجريب الخاص بك استخدام الحالة سوف تظهر الطريق الصحيح.
  2. بالإضافة إلى رمز بسيط التحسينات ، وأود أن نوصي أيضا أن تنظر في الموازاة من الحسابات (أي:للاستفادة متعددة النوى من جهازك أو حتى الأجهزة في ويندوز أزور الغيوم) و تحسين الخوارزميات التدريب.

تحديث: وظيفة على جداول البحث عن آن تفعيل وظائف

UPDATE2: أزلت النقطة طرفيات المستعملين المحليين منذ الخلط هذه الكامل التجزئة.شكرا هنريك غوستافسون من أجل وضع لي مرة أخرى على المسار.وبالتالي فإن الذاكرة ليست قضية ، على الرغم من أن مساحة البحث لا تزال تحصل على افسدت مع القيم القصوى المحلية قليلا.

وفي 100 مليون المكالمات، فما استقاموا لكم فاستقيموا البدء في أتساءل عما إذا كان التعريف النفقات العامة لا انحراف النتائج. استبدال حساب مع أي المرجع ومعرفة ما إذا كان <م> لا تزال ذكرت أن تستهلك 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 (تعويم)، 16) لأن SSE يتطلب ذاكرة الانحياز إلى الحدود.

وعن طريق SSE، ويمكنني أن حساب النتيجة لجميع العناصر 100 مليون في جميع أنحاء نصف ثانية. ومع ذلك، وتخصيص ذلك الكثير من الذاكرة في وقت لن تكلفك ما يقرب من ثلثي غيغا بايت لذلك أود أن أقترح معالجة أكثر ولكن أصغر المصفوفات في وقت واحد. قد ترغب حتى في النظر في استخدام نهج التخزين المؤقت مزدوج مع 100K عناصر أو أكثر.

وأيضا، إذا كان عدد من العناصر يبدأ في النمو بشكل كبير قد ترغب في اختيار لمعالجة هذه الأمور على GPU (مجرد خلق 1D float4 الملمس وتشغيل تظليل جزء تافهة جدا).

وFWIW، وهنا قال لي C # معايير لإجابات السؤال بالفعل. (فارغ هو دالة تقوم بإرجاع فقط 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.

وأيضا، نقطة أخرى: لغرض تدريب الشبكات الكبيرة بسرعة، والسيني اللوجستي الذي تستخدمه هو رهيب جدا. انظر القسم 4.4 من كفاءة Backprop التي كتبها LeCun آخرون و استخدام شيء التي تركز على الصفر (في الواقع، وقراءة تلك الورقة كاملة، انها مفيدة للغاية).

F# لديها أداء أفضل من C# في .صافي الرياضيات والخوارزميات. حتى كتابة الشبكة العصبية في F# قد تحسين الأداء العام.

إذا أردنا إعادة تنفيذ طرفية القياس مقتطف (لقد تم استخدام أنب قليلا الإصدار) في F# ثم ينتج عن كود:

  • ينفذ sigmoid1 القياسي في 588.8 ms بدلا من 3899,2 ms
  • ينفذ sigmoid2 (LUT) معيارا في 156.6 ms بدلا من 411.4 ms

مزيد من التفاصيل يمكن العثور عليها في بلوق وظيفة.وهنا F# مقتطف كلية الجبيل الصناعية:

#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 التكرارات مقارنة النتائج مع ج

UPDATE2: وهنا نتائج الأداء من ج التنفيذ من نفس الجهاز مقارنة مع:

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

ملاحظة: هذا هو متابعة هذا ما بعد.

تحرير: تحديث لحساب نفس الشيء هذا و هذا, ، مع بعض الإلهام من هذا.

الآن انظر ما الذي جعلني أفعل!جعلتني تثبيت أحادية!

$ 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

ج يكاد يكون يستحق كل هذا الجهد أكثر, العالم يتحرك إلى الأمام :)

لذا فقط على عامل 10 6 أسرع.شخص ما مع ويندوز مربع يحصل للتحقيق في استخدام الذاكرة والأداء باستخدام MS-الاشياء :)

استخدام طرفيات المستعملين المحليين من أجل تفعيل مهام ليست من غير المألوف ، especielly عند تنفيذها في الأجهزة.هناك العديد من مجربة المتغيرات مفهوم من هناك إذا كنت على استعداد أن تشمل هذه الأنواع من الجداول.ومع ذلك ، كما سبق أن أشار إلى التعرج قد تتحول إلى مشكلة ، ولكن هناك طرق حول ذلك أيضا.بعض مزيد من القراءة:

بعض gotchas مع هذا:

  • الخطأ ترتفع عندما تصل إلى خارج الجدول (ولكن يتقاطع إلى 0 في النقيضين);س تقريبا +-7.0.ويرجع ذلك إلى اختيار عامل القياس.أكبر القيم الحجم تعطي أعلى الأخطاء في المدى المتوسط ، ولكن أصغر في الحواف.
  • عموما هذا هو غبي جدا الاختبار و أنا لا أعرف C#, انها مجرد عادي التحويل من C-كود :)
  • رينات 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());
    }
}

الفكر الأول:ماذا عن بعض الإحصائيات عن قيم متغير ؟

  • هي القيم "قيمة" عادة صغيرة -10 <= قيمة <= 10?

إن لم يكن, ربما يمكنك الحصول على دفعة من خلال اختبار خارج حدود القيم

if(value < -10)  return 0;
if(value > 10)  return 1;
  • هي القيم المتكررة في كثير من الأحيان ؟

إذا كان الأمر كذلك, ربما يمكنك الحصول على بعض الفوائد من التحفيظ (ربما لا, ولكن لا ضير في أن تحقق....)

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

إذا لم يكن أي من هذه يمكن تطبيقها ، ثم بعض واقترح آخرون ربما يمكنك الحصول على بعيدا مع خفض دقة الخاص بك السيني...

السوبرانو بعض التحسينات لطيفة المكالمات الخاص بك:

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

إذا كنت في محاولة جدول بحث وتجد أنه يستخدم الكثير من الذاكرة يمكنك أن تبحث دائما في قيمة المعلمة لكل المتعاقبة المكالمات وتوظيف بعض تقنية التخزين المؤقت.

على سبيل المثال محاولة التخزين المؤقت القيمة الأخيرة ونتيجة.إذا كانت المكالمة التالية له نفس القيمة كما في السابق, أنت لا تحتاج إلى حساب ذلك كما يجب مؤقتا نتيجة الماضية.إذا كانت المكالمة الحالية هي نفس الدعوة السابقة حتى 1 من 100 مرة ، يمكنك أن تنقذ نفسك من 1 مليون العمليات الحسابية.

أو قد تجد أنه في غضون 10 المتعاقبة المكالمات قيمة المعلمة في المتوسط نفس 2 مرات, لذلك يمكنك محاولة التخزين المؤقت الماضي القيم 10/أجوبة.

وفكرة: ربما يمكنك جعل (كبير) طاولة البحث مع القيم مسبقا محسوبة

هذا هو قليلا خارج الموضوع لكن من باب الفضول فقط, لقد فعلت نفس التنفيذ في ج, 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# في حالتي هو بسبب جافا كونها أفضل الأمثل من Mono for OS X.على غرار مرض التصلب العصبي المتعدد .صافي-تنفيذ (مقابلجافا 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.

  • ج:166ms
  • C# (مندوب):275ms
  • C# (الطريقة):431ms
  • C# (أسلوب تعويم العداد):2,656 ms
  • F#:404ms

C# مع تعويم مكافحة كان مستقيم المنفذ من التعليمات البرمجية C.وهو أسرع بكثير من استخدام الباحث في حلقة.

وكنت قد تنظر أيضا تجريب وظائف تفعيل البديلة التي هي أرخص لتقييم. على سبيل المثال:

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

(والتي يمكن أن يؤخذ على أنه

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

ولأحد أقل الضرب). هذه وظيفة لديها التماثل الغريب، ومشتقاته تافهة. استخدامه لشبكة العصبية يتطلب تطبيع المدخلات مبلغ من عن طريق قسمة إجمالي عدد المدخلات (الحد من نطاق إلى [-1..1]، التي تتراوح أيضا).

وهناك تباين معتدل على موضوع سوبرانو و:

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

ومنذ كنت فقط بعد نتيجة الدقة واحد، لماذا جعل وظيفة Math.Exp حساب مزدوج؟ أي الأس آلة حاسبة يستخدم الجمع تكرارية (انظر توسيع ه <سوب> س في ) سوف يستغرق وقتا أطول لمزيد من الدقة، في كل مرة. وضعف ضعف عمل واحد! بهذه الطريقة، يمكنك تحويل إفراد أولا، <ط> ثم تتبعونها الأسي.

ولكن يجب أن يكون وظيفة expf أسرع من ذلك. لا أرى ضرورة لالسوبرانو في (تعويم) يلقي في تمرير لexpf رغم ذلك، إلا إذا لم تفعل C # تحويل تعويم مزدوج الضمني.

وإلا، استخدم فقط <ط> حقيقية اللغة، مثل FORTRAN ...

هناك الكثير من إجابات جيدة هنا.أود أن أقترح على التوالي من خلال هذه التقنية, فقط للتأكد

  • أنت لا تدعو أي مرات أكثر مما كنت بحاجة إلى.
    (في بعض الأحيان الحصول على وظائف دعا أكثر من اللازم ، فقط لأنهم من السهل الاتصال.)
  • أنت لا يدعو مرارا وتكرارا مع نفس الحجج
    (حيث يمكن استخدام التحفيظ)

راجع للشغل وظيفة لديك هو معكوس الأرجحية اللوغاريتمية وظيفة ،
أو معكوس سجل الاحتمالات-نسبة وظيفة log(f/(1-f)).

(تحديث مع قياسات الأداء)(تحديث مرة أخرى مع نتائج حقيقية :)

أعتقد جدول بحث الحل لن تحصل بعيدا جدا عندما يتعلق الأمر بالأداء في تذكر الذاكرة ودقة التكلفة.

المقتطف التالي هو مثال على التنفيذ في ج (أنا لا أتكلم 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 الهواء):

$ 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

profile


ما يجب عمله:

هناك أشياء لتحسين طرق لإزالة مواطن الضعف ؛ كيفية القيام به هو ترك ممارسة للقارئ :)

  • لحن مجموعة من وظيفة إلى تجنب القفز حيث الجدول يبدأ وينتهي.
  • إضافة ضجيج طفيف وظيفة لإخفاء الآثار التعرج.
  • كما ريكس قال الاستيفاء يمكن أن تحصل قليلا جدا مزيد من الدقة والحكمة في حين يجري رخيصة بدلا من ذلك على أدائهم.

وهناك أسرع بكثير المهام التي تفعل أشياء مشابهة جدا:

وx / (1 + abs(x)) - استبدال سريع لTAHN

وبالمثل:

وx / (2 + 2 * abs(x)) + 0.5 - استبدال سريع لالسيني

قارن المؤامرات مع السيني الفعلي

والقيام جوجل للبحث، وجدت تنفيذ البديل وظيفة السيني.

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) هل نسمي هذا فقط من مكان واحد ؟ إذا كان الأمر كذلك, يمكنك الحصول على كمية صغيرة من الأداء عن طريق تحريك رمز من أن وظيفة فقط وضعه في المكان الذي عادة ما يسمى السيني وظيفة.أنا لا أحب هذه الفكرة من حيث مقروئية و المنظمة ولكن عندما كنت في حاجة إلى الحصول على كل آخر كسب الأداء ، وهذا قد يساعد لأنني أعتقد المكالمات وظيفة تتطلب دفع/البوب السجلات على المكدس ، والتي يمكن تجنبها إذا كان رمز كل مضمنة.

2) ليس لدي أي فكرة ما إذا كان هذا قد يساعد ولكن في محاولة لجعل الخاص بك وظيفة معلمة المرجع المعلمة.انظر إذا كان أسرع.كنت قد اقترحت مما يجعل const (التي كان من الأمثل إذا كان هذا في c++) ولكن c# لا يدعم const المعلمات.

إذا كنت في حاجة الى زيادة سرعة العملاقة، وربما يمكن أن ننظر إلى parallelizing وظيفة باستخدام (جنرال الكتريك) قوة. IOW، استخدم دايركت للسيطرة على بطاقة الرسومات في القيام بذلك نيابة عنك. ليس لدي أي فكرة عن كيفية القيام بذلك، ولكن رأيت الناس استخدام بطاقات الرسومات لجميع أنواع الحسابات.

ولقد رأيت أن الكثير من الناس في جميع أنحاء هنا تحاول استخدام تقريب لجعل السيني أسرع. ومع ذلك، فمن المهم أن نعرف أن السيني يمكن التعبير عنه أيضا باستخدام تان، وليس عملي فقط. حساب السيني بهذه الطريقة حوالي 5 مرات أسرع من مع الأسي، وباستخدام هذا الأسلوب الذي لا يقترب أي شيء، وبالتالي يتم الاحتفاظ السلوك الأصلي من السيني كما هو.

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

وبطبيعة الحال، parellization ستكون الخطوة التالية لتحسين الأداء، ولكن بقدر ما يتعلق الأمر حساب الخام، وذلك باستخدام Math.Tanh أسرع من Math.Exp.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top