수학적 최적화에서는 C#
-
03-07-2019 - |
문제
었어 프로파일링 응용 프로그램 모드에서 모든 데 최적화된 부부의 비트 코드,내가 왼쪽으로 이 내 일 목록.그것의 활성화 기능을 위해 신경망을 얻을 수있는,라는 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 배 빠릅니다. 감사!
- 기억 이 활성화 기능의 변경 사항은 다른 행동의 비용으로 발생합니다.. 여기에는 플로트로의 전환 (따라서 정밀도를 낮추거나 활성화 대체)을 사용하는 것이 포함됩니다. 유스 케이스를 실험하면 올바른 방법이 표시됩니다.
- 간단한 코드 최적화 외에도 고려하는 것이 좋습니다. 계산의 병렬화 (예 : 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를 사용하는 것은 드문 일이 아닙니다. 이러한 유형의 테이블을 기꺼이 포함하려는 경우 개념의 잘 입증 된 변형이 많이 있습니다. 그러나 이미 지적했듯이 별칭은 문제가 될 수 있지만 그 주위에도 방법이 있습니다. 몇 가지 추가 독서 :
- 신경 주사 ~에 의해 Giorgio Valentini (이것에 대한 논문도 있습니다)
- 디지털 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
할 것:
개선해야 할 것이 있으며 약점을 제거하는 방법이 있습니다. 방법은 독자에게 운동으로 남겨진다 :)
- 테이블이 시작되고 끝나는 점프를 피하기 위해 기능 범위를 조정하십시오.
- 별명 아티팩트를 숨기려면 약간의 노이즈 기능을 추가하십시오.
- 렉스가 말했듯이 보간은 성능이 저렴한 가격으로 상당히 더 정밀한 점이 될 수 있습니다.
매우 유사한 일을하는 훨씬 빠른 기능이 있습니다.
x / (1 + abs(x))
- Tahn의 빠른 교체
그리고 마찬가지로 :
x / (2 + 2 * abs(x)) + 0.5
- 시그 모이 드의 빠른 교체
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보다 빠릅니다.