문제

었어 프로파일링 응용 프로그램 모드에서 모든 데 최적화된 부부의 비트 코드,내가 왼쪽으로 이 내 일 목록.그것의 활성화 기능을 위해 신경망을 얻을 수있는,라는 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% 빠르며,이 수학적으로 동등한 코드는 가장 약간 빠릅니다 (원본보다 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);
}

또한 수백만 건의 호출을하는 경우 기능을 부르는 오버 헤드가 문제가 될 수 있습니다. 인라인 함수를 만들고 도움이되는지 확인하십시오.

다른 팁

활성화 기능이라면 e^x의 계산이 완전히 정확하다면 매우 중요합니까?

예를 들어, Java의 펜티엄 테스트에서 근사치 (1+x/256)^256을 사용하는 경우 (C#이 본질적으로 동일한 프로세서 지침으로 컴파일한다고 가정합니다) E^x보다 약 7-8 배 빠릅니다. (math.exp ()), 약 x의 x +/- 1.5까지, 그리고 당신이 언급 한 범위에 걸쳐 올바른 크기의 순서 내에서 정확합니다. (분명히, 256으로 올리려면 실제로 8 번 숫자를 제곱합니다.

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

근사치가 얼마나 정확한 지에 따라 256을 두 배로 늘리거나 절반으로 유지하고 곱셈을 추가/제거하십시오. n = 4 인 경우에도 x Be Be Be -0.5와 0.5 사이의 값에 대해 약 1.5 십수 자리의 정확도를 제공합니다 (Math.exp ()보다 15 배 더 빨리 나타납니다).

추신 나는 언급하는 것을 잊었다 - 당신은 분명히 진짜 256으로 나누기 : 일정한 1/256을 곱하십시오. Java의 JIT 컴파일러는이 최적화를 자동으로 (적어도 핫스팟이 함)로 만들었습니다. C#도해야한다고 가정했습니다.

살펴보십시오 이 게시물. Java로 작성된 e^x에 대한 근사치가 있습니다. 이것은 C# 코드가되어야합니다 (테스트되지 않은).

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

내 벤치 마크에서 이것은 그 이상입니다 math.exp ()보다 5 배 빠릅니다. (자바에서). 근사치는 종이를 기반으로합니다. "지수 함수의 빠르고 소형 근사치"신경망에서 정확히 사용되도록 개발되었습니다. 기본적으로 2048 항목의 조회 테이블 및 항목 사이의 선형 근사치와 동일하지만 IEEE 플로팅 포인트 트릭 으로이 모든 것이 있습니다.

편집하다: 에 따르면 특별한 소스 이것은 CLR 구현보다 ~ 3.25 배 빠릅니다. 감사!

  1. 기억 이 활성화 기능의 변경 사항은 다른 행동의 비용으로 발생합니다.. 여기에는 플로트로의 전환 (따라서 정밀도를 낮추거나 활성화 대체)을 사용하는 것이 포함됩니다. 유스 케이스를 실험하면 올바른 방법이 표시됩니다.
  2. 간단한 코드 최적화 외에도 고려하는 것이 좋습니다. 계산의 병렬화 (예 : Windows Azure Clouds의 기계 또는 기계의 여러 코어를 활용) 및 교육 알고리즘을 향상시킵니다.

업데이트: 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));
    }
}

그러나 사용할 배열은 SSE가 경계에 정렬 된 메모리가 필요하기 때문에 _aligned_malloc (some_size * sizeof (float), 16)를 사용하여 할당해야합니다.

SSE를 사용하여 약 0.5 초 안에 1 억 요소의 결과를 계산할 수 있습니다. 그러나 한 번에 많은 기억을 할당하면 기가 바이트의 거의 3 분의 1이 비용이 들기 때문에 한 번에 더 작은 배열을 처리하는 것이 좋습니다. 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의 오른쪽 상단 링크를 클릭하십시오).

또한 또 다른 요점 : 대형 네트워크를 빠르게 훈련시키기 위해 사용하는 물류 시그 모이 드는 꽤 끔찍합니다. 4.4 절을 참조하십시오 Lecun et al 그리고 중심이없는 것을 사용하십시오 (실제로 종이 전체를 읽으십시오. 엄청나게 유용합니다).

F#은 .NET 수학 알고리즘에서 C#보다 성능이 향상됩니다. 따라서 F#에서 신경망을 다시 작성하면 전반적인 성능이 향상 될 수 있습니다.

우리가 재 구현한다면 LUT 벤치마킹 스 니펫 (약간 조정 된 버전을 사용했습니다) F#에서 결과 코드를 사용했습니다.

  • Sigmoid1 벤치 마크를 실행합니다 3899,2ms 대신 588.8ms
  • Sigmoid2 (LUT) 벤치 마크를 실행합니다 411.4ms 대신 156.6ms

자세한 내용은 다음에 있습니다 블로그 게시물. 다음은 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 반복을 사용하여 업데이트 된 벤치마킹을 위해 결과를 비교할 수 있습니다.

Update2 : 다음은 다음과 같습니다 C 구현 동일한 기계에서 다음과 비교할 수 있습니다.

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

C는 더 이상 노력의 가치가 거의 없으며, 세상은 앞으로 나아가고 있습니다 :)

그래서, 한 가지가 넘습니다 10 6 더 빨리. Windows 상자가있는 사람은 MS-Stuff를 사용하여 메모리 사용 및 성능을 조사하게됩니다 :)

활성화 기능에 LUT를 사용하는 것은 드문 일이 아닙니다. 이러한 유형의 테이블을 기꺼이 포함하려는 경우 개념의 잘 입증 된 변형이 많이 있습니다. 그러나 이미 지적했듯이 별칭은 문제가 될 수 있지만 그 주위에도 방법이 있습니다. 몇 가지 추가 독서 :

일부는 이것으로 얻을 수 있습니다.

  • 테이블 외부에 도달하면 오류가 올라갑니다 (그러나 극단에서 0으로 수렴). x 약 +-7.0의 경우. 이것은 선택된 스케일링 계수 때문입니다. 스케일 값이 클수록 중간 범위에서는 더 높은 오류가 있지만 가장자리에서는 더 작습니다.
  • 이것은 일반적으로 매우 어리석은 테스트이며, 나는 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());
    }
}

첫 번째 생각 : 값 변수에 대한 일부 통계는 어떻습니까?

  • "값"의 값은 일반적으로 작은 -10 <= value <= 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);
}

조회 테이블을 시도하고 너무 많은 메모리를 사용하는 경우 각 연속 호출에 대한 매개 변수의 값을 항상보고 일부 캐싱 기술을 사용할 수 있습니다.

예를 들어 마지막 값과 결과를 캐싱하십시오. 다음 호출이 이전 호출과 동일한 값을 갖는 경우 마지막 호출을 캐시했을 때 계산할 필요가 없습니다. 현재 통화가 이전 통화와 동일 한 경우 100 번 중 1 개 중 1 개도 1 백만 건의 계산을 저장할 수 있습니다.

또는 10 번의 연속 호출 내에서 값 매개 변수가 평균 2 배나 동일하므로 마지막 10 값/답변을 캐싱 할 수 있습니다.

아이디어 : 아마도 당신은 사전 계산 된 값으로 (큰) 조회 테이블을 만들 수 있습니까?

이것은 약간의 주제가 아니지만 호기심으로 인해 , 씨# 그리고 에프# 자바에서. 다른 사람이 궁금한 점이 있으면 여기에 남겨 둘 것입니다.

결과:

$ 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#에 대한 개선은 OS X의 Java보다 Mono보다 더 잘 최적화 되었기 때문이라고 생각합니다. 비슷한 MS .NET-Implementation (누군가 비교 번호를 게시하려면 Java 6 vs. 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());
    }
}

나는이 질문이 튀어 나온 지 1 년이 지났다는 것을 알고 있지만, C#에 대한 F# 및 C 성능에 대한 논의로 인해 그것을 가로 질러 달렸다. 나는 다른 응답자들의 일부 샘플과 함께 연주했는데 대표가 일반 메소드 호출보다 더 빨리 실행되는 것처럼 보이지만 C#을 통해 F#에 대한 명백한 Peformance 이점이 없습니다..

  • C : 166ms
  • C# (Delegate) : 275ms
  • C# (메소드) : 431ms
  • C# (메소드, 플로트 카운터) : 2,656ms
  • F#: 404ms

플로트 카운터가있는 C#은 C 코드의 직선 포트였습니다. for 루프에서 int를 사용하는 것이 훨씬 빠릅니다.

평가하기가 더 저렴한 대체 활성화 기능 실험을 고려할 수도 있습니다. 예를 들어:

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

(고려 될 수 있습니다

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

덜 곱셈). 이 함수는 홀수 대칭을 가지고 있으며 그 미분은 사소합니다. 신경망에 IT를 사용하려면 총 입력 수로 나누어 입력 합계를 정규화해야합니다 (도메인을 [-1..1]로 제한하는 것도 범위입니다).

온화한 변화에 소프라노의 주제:

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

때문에 당신은 후에 하나의 정밀한 결과,왜 만들 수학이다.특급 기능을 계산하니까?어떤 지수 계산기를 사용하는 반복 합계(참조하십시오 의 확장자x 는)오래 걸릴 위해 더 많은 정밀도,주시기 바랍니다.더블은 두 번의 작업 단일!이 방법은,당신을 변환하는 단일 먼저, 다음 당신의 기하 급수적으로하고 있습니다.

하지만 expf 함수해야 합리고 여전히 더욱 빨라졌습니다.나는 볼 수 없는 필요한 소프라노의(float)캐스트에 통과하 expf 지 않는 한,C#하지 않는 암시적 float-두 배 변환입니다.

그렇지 않으면,사용 실시 언어 같은 프로그램...

여기에는 좋은 답변이 많이 있습니다. 나는 그것을 실행하는 것이 좋습니다 이 기술, 단지 확인하기 위해

  • 당신은 당신이 필요로하는 것보다 더 이상 그것을 부르지 않습니다.
    (때로는 기능이 필요 이상으로 호출되는 경우가 많습니다.
  • 당신은 같은 주장으로 그것을 반복적으로 부르지 않습니다
    (회고록을 사용할 수있는 곳)

BTW 당신이 가진 함수는 역수 기능입니다.
또는 로그 오드-비율 함수의 역수 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;
}

이전 결과는 Optimizer가 작업을 수행하고 계산을 최적화했기 때문입니다. 실제로 코드를 실행하게 만드는 코드는 약간 다르고 훨씬 더 흥미로운 결과를 얻습니다 (저의 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 - 시그 모이 드의 빠른 교체

플롯을 실제 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는 Exp뿐만 아니라 TANH를 사용하여 표현 될 수 있음을 아는 것이 중요합니다. 이 방식으로 Sigmoid를 계산하는 것은 지수보다 약 5 배 빠르며,이 방법을 사용하면 아무것도 근사하지 않으므로 Sigmoid의 원래 동작은 그대로 유지됩니다.

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

물론, 성능 개선의 다음 단계가 될 것이지만, 원시 계산에 관한 한 Math.tanh를 사용하는 것은 Math.exp보다 빠릅니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top