[英]Math optimization in C#
我整天都在分析一個應用程序,優化了一些代碼后,我把這個留在了我的待辦事項列表中。 它是神經網絡的激活函數,被調用超過 1 億次。 據 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>
,你應該能夠使用它,它使用一個 float-exp 函數。 可能會快一點。
public static float Sigmoid(double value) {
float k = expf((float) value);
return k / (1.0f + k);
}
此外,如果您要進行數百萬次調用,則函數調用開銷可能是一個問題。 嘗試創建一個內聯函數,看看是否有幫助。
如果是用於激活函數,那么如果 e^x 的計算完全准確,那么重要嗎?
例如,如果您使用近似值 (1+x/256)^256,在我用 Java 進行的 Pentium 測試中(我假設 C# 基本上編譯為相同的處理器指令),這大約比 e^x 快 7-8 倍(Math.exp()),精確到小數點后 2 位,最多約為 +/-1.5 的 x,並且在您所述范圍內的正確數量級內。 (顯然,要提高到 256,您實際上要對數字進行 8 次平方——不要為此使用 Math.Pow!)在 Java 中:
double eapprox = (1d + x / 256d);
eapprox *= eapprox;
eapprox *= eapprox;
eapprox *= eapprox;
eapprox *= eapprox;
eapprox *= eapprox;
eapprox *= eapprox;
eapprox *= eapprox;
eapprox *= eapprox;
根據您希望近似值的准確程度,將 256 加倍或減半(並添加/刪除乘法)。 即使 n=4,它仍然為 -0.5 和 0.5 之間的 x 值提供大約 1.5 個小數位的精度(並且看起來比 Math.exp() 快 15 倍)。
divide by 256: multiply by a constant 1/256. PS 我忘了提到——你顯然不應該除以 256:乘以常數 1/256。 Java 的 JIT 編譯器自動進行這種優化(至少 Hotspot 是這樣),我假設 C# 也必須這樣做。
看看這個帖子。 它有一個用 Java 編寫的 e^x 的近似值,這應該是它的 C# 代碼(未經測試):
public static double Exp(double val) {
long tmp = (long) (1512775 * val + 1072632447);
return BitConverter.Int64BitsToDouble(tmp << 32);
}
在我的基准測試中,這比 Math.exp() (在 Java 中)快 5 倍以上。 該近似基於論文“指數函數的快速、緊湊的近似”,該論文正是為在神經網絡中使用而開發的。 它與 2048 個條目的查找表和條目之間的線性近似基本相同,但這一切都帶有 IEEE 浮點技巧。
編輯:根據Special Sauce,這比 CLR 實現快約 3.25 倍。 謝謝!
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));
}
}
但是,請記住,您將使用的數組應該使用 _aligned_malloc(some_size * sizeof(float), 16) 進行分配,因為 SSE 需要與邊界對齊的內存。
使用 SSE,我可以在大約半秒內計算出所有 1 億個元素的結果。 但是,一次分配這么多內存將花費您將近三分之二的 GB,因此我建議一次處理更多但更小的數組。 您甚至可能要考慮使用具有 100K 或更多元素的雙緩沖方法。
此外,如果元素數量開始大幅增長,您可能希望選擇在 GPU 上處理這些事情(只需創建一個 1D float4 紋理並運行一個非常簡單的片段着色器)。
FWIW,這是我已經發布的答案的 C# 基准測試。 (Empty是一個只返回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));
}
注意:這是這篇文章的后續。
編輯:更新以計算與this和this相同的東西,從this 中獲取一些靈感。
現在看看你讓我做什么! 你讓我安裝 Mono!
$ gmcs -optimize test.cs && mono test.exe
Max deviation is 0.001663983
10^7 iterations using Sigmoid1() took 1646.613 ms
10^7 iterations using Sigmoid2() took 237.352 ms
C 已經不值得付出努力了,世界正在向前發展 :)
因此,速度快了
10
6 倍。 有 Windows 盒子的人可以使用 MS-stuff 調查內存使用情況和性能:)
將 LUT 用於激活函數並不少見,尤其是在硬件中實現時。 如果您願意包含這些類型的表格,則有許多經過充分驗證的概念變體。 然而,正如已經指出的那樣,混疊可能會成為一個問題,但也有辦法解決這個問題。 一些進一步的閱讀:
一些問題:
請原諒復制粘貼編碼...
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());
}
}
在 .NET 數學算法中,F# 比 C# 具有更好的性能。 因此,在 F# 中重寫神經網絡可能會提高整體性能。
如果我們在 F# 中重新實現LUT 基准測試片段(我一直在使用稍微調整過的版本),那么生成的代碼:
可以在博客文章中找到更多詳細信息。 這是 F# 代碼段 JIC:
#light
let Scale = 320.0f;
let Resolution = 2047;
let Min = -single(Resolution)/Scale;
let Max = single(Resolution)/Scale;
let range step a b =
let count = int((b-a)/step);
seq { for i in 0 .. count -> single(i)*step + a };
let lut = [|
for x in 0 .. Resolution ->
single(1.0/(1.0 + exp(-double(x)/double(Scale))))
|]
let sigmoid1 value = 1.0f/(1.0f + exp(-value));
let sigmoid2 v =
if (v <= Min) then 0.0f;
elif (v>= Max) then 1.0f;
else
let f = v * Scale;
if (v>0.0f) then lut.[int (f + 0.5f)]
else 1.0f - lut.[int(0.5f - f)];
let getError f =
let test = range 0.00001f -10.0f 10.0f;
let errors = seq {
for v in test ->
abs(sigmoid1(single(v)) - f(single(v)))
}
Seq.max errors;
open System.Diagnostics;
let test f =
let sw = Stopwatch.StartNew();
let mutable m = 0.0f;
let result =
for t in 1 .. 10 do
for x in 1 .. 1000000 do
m <- f(single(x)/100000.0f-5.0f);
sw.Elapsed.TotalMilliseconds;
printf "Max deviation is %f\n" (getError sigmoid2)
printf "10^7 iterations using sigmoid1: %f ms\n" (test sigmoid1)
printf "10^7 iterations using sigmoid2: %f ms\n" (test sigmoid2)
let c = System.Console.ReadKey(true);
和輸出(針對 F# 1.9.6.2 CTP 發布編譯,沒有調試器):
Max deviation is 0.001664
10^7 iterations using sigmoid1: 588.843700 ms
10^7 iterations using sigmoid2: 156.626700 ms
更新:更新基准測試以使用 10^7 次迭代使結果與 C 相當
UPDATE2:這里是來自同一台機器的C 實現的性能結果進行比較:
Max deviation is 0.001664
10^7 iterations using sigmoid1: 628 ms
10^7 iterations using sigmoid2: 157 ms
第一個想法:values 變量的一些統計數據怎么樣?
如果沒有,您可能可以通過測試越界值來獲得提升
if(value < -10) return 0;
if(value > 10) return 1;
如果是這樣,您可能會從Memoization 中獲得一些好處(可能不會,但檢查一下也無妨....)
if(sigmoidCache.containsKey(value)) return sigmoidCache.get(value);
如果這些都不能應用,那么正如其他人所建議的那樣,也許您可以通過降低 sigmoid 的准確性來逃脫...
Soprano 有一些不錯的優化你的電話:
public static float Sigmoid(double value)
{
float k = Math.Exp(value);
return k / (1.0f + k);
}
如果您嘗試查找表並發現它使用了太多內存,您可以隨時查看每個連續調用的參數值並使用一些緩存技術。
例如嘗試緩存最后一個值和結果。 如果下一個調用的值與前一個調用的值相同,則不需要像緩存最后一個結果那樣計算它。 如果當前調用與前一個調用相同,即使是 100 次中的 1 次,您也有可能節省 100 萬次計算。
或者,您可能會發現在 10 次連續調用中,value 參數平均有 2 次相同,因此您可以嘗試緩存最后 10 個值/答案。
在我的腦海里, 這篇論文解釋了一種通過濫用浮點來逼近指數的方法,(單擊右上角的 PDF 鏈接)但我不知道它是否對你在 .網。
另外,還有一點:為了快速訓練大型網絡,您使用的邏輯 sigmoid 非常糟糕。 請參閱LeCun 等人的Efficient Backprop 的第 4.4 節並使用以零為中心的內容(實際上,閱讀整篇論文,它非常有用)。
有一個更快的函數可以做非常相似的事情:
x / (1 + abs(x))
– 快速替代 TAHN
同樣:
x / (2 + 2 * abs(x)) + 0.5
- SIGMOID 的快速替代
想法:也許您可以使用預先計算的值制作(大)查找表?
這有點偏離主題,但出於好奇,我做了與 Java 中的C 、 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# 的改進是由於 Java 比 OS X 的 Mono 優化得更好。在類似的 MS .NET 實現上(如果有人想發布比較數字,則與 Java 6 相比)我想結果會有所不同.
代碼:
public class LUTTest {
private static final float SCALE = 320.0f;
private static final int RESOLUTION = 2047;
private static final float MIN = -RESOLUTION / SCALE;
private static final float MAX = RESOLUTION / SCALE;
private static final float[] lut = initLUT();
private static float[] initLUT() {
float[] lut = new float[RESOLUTION + 1];
for (int i = 0; i < RESOLUTION + 1; i++) {
lut[i] = (float)(1.0 / (1.0 + Math.exp(-i / SCALE)));
}
return lut;
}
public static float sigmoid1(double value) {
return (float) (1.0 / (1.0 + Math.exp(-value)));
}
public static float sigmoid2(float value) {
if (value <= MIN) return 0.0f;
if (value >= MAX) return 1.0f;
if (value >= 0) return lut[(int)(value * SCALE + 0.5f)];
return 1.0f - lut[(int)(-value * SCALE + 0.5f)];
}
public static float error(float v0, float v1) {
return Math.abs(v1 - v0);
}
public static float testError() {
float emax = 0.0f;
for (float x = -10.0f; x < 10.0f; x+= 0.00001f) {
float v0 = sigmoid1(x);
float v1 = sigmoid2(x);
float e = error(v0, v1);
if (e > emax) emax = e;
}
return emax;
}
public static long sigmoid1Perf() {
float y = 0.0f;
long t0 = System.currentTimeMillis();
for (int i = 0; i < 10; i++) {
for (float x = -5.0f; x < 5.0f; x+= 0.00001f) {
y = sigmoid1(x);
}
}
long t1 = System.currentTimeMillis();
System.out.printf("",y);
return t1 - t0;
}
public static long sigmoid2Perf() {
float y = 0.0f;
long t0 = System.currentTimeMillis();
for (int i = 0; i < 10; i++) {
for (float x = -5.0f; x < 5.0f; x+= 0.00001f) {
y = sigmoid2(x);
}
}
long t1 = System.currentTimeMillis();
System.out.printf("",y);
return t1 - t0;
}
public static void main(String[] args) {
System.out.printf("Max deviation is %f\n", testError());
System.out.printf("10^7 iterations using sigmoid1() took %d ms\n", sigmoid1Perf());
System.out.printf("10^7 iterations using sigmoid2() took %d ms\n", sigmoid2Perf());
}
}
我意識到這個問題出現已經一年了,但是因為 F# 和 C 性能相對於 C# 的討論,我遇到了它。 我使用了其他響應者的一些示例,發現委托似乎比常規方法調用執行得更快,但F# 與 C# 相比沒有明顯的性能優勢。
帶有浮點計數器的 C# 是 C 代碼的直接移植。 在 for 循環中使用 int 會快得多。
這里有很多很好的答案。 我建議通過這種技術運行它,只是為了確保
順便說一句,您擁有的函數是逆 logit 函數,
或對數優勢比函數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;
}
以前的結果是由於優化器完成了它的工作並優化了計算。 讓它實際執行代碼會產生稍微不同但更有趣的結果(在我的路上慢 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
去做:
有改進的地方和消除弱點的方法; 如何做是留給讀者的練習:)
您還可以考慮嘗試使用評估成本更低的替代激活函數。 例如:
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 函數計算雙精度呢? 任何使用迭代求和(參見e x的擴展)的指數計算器每次都需要更長的時間才能獲得更高的精度。 雙是單工作的兩倍! do your exponential.這樣,您首先轉換為單身,進行指數計算。
但是 expf 函數應該更快。 不過,除非 C# 不進行隱式 float-double 轉換,否則我認為不需要將女高音的 (float) 轉換為傳遞給 expf。
language, like FORTRAN...否則,只需使用語言,例如 FORTRAN ......
通過 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)force 來並行化該功能。 IOW,使用DirectX控制顯卡為您做。 我不知道如何做到這一點,但我見過人們使用顯卡進行各種計算。
我已經看到這里的很多人都在嘗試使用近似來使 Sigmoid 更快。 但是,重要的是要知道 Sigmoid 也可以使用 tanh 表示,而不僅僅是 exp。 以這種方式計算 Sigmoid 比使用指數計算快 5 倍左右,並且通過使用這種方法,您不會逼近任何東西,因此 Sigmoid 的原始行為保持原樣。
public static double Sigmoid(double value)
{
return 0.5d + 0.5d * Math.Tanh(value/2);
}
當然,parellization 將是性能改進的下一步,但就原始計算而言,使用 Math.Tanh 比 Math.Exp 更快。
請記住, Sigmoid約束的結果范圍在 0 和 1 之間。小於約 -10 的值返回非常非常接近 0.0 的值。 大於 10 的值會返回非常非常接近 1 的值。
回到過去,當計算機無法很好地處理算術上溢/下溢時,通常會使用 if 條件來限制計算。 如果我真的關心它的性能(或基本上是 Math 的性能),我會將您的代碼更改為老式方式(並注意限制),以便它不會不必要地調用 Math:
public double Sigmoid(double value)
{
if (value < -45.0) return 0.0;
if (value > 45.0) return 1.0;
return 1.0 / (1.0 + Math.Exp(-value));
}
我意識到任何閱讀此答案的人都可能參與了某種神經網絡開發。 請注意上述情況如何影響您的訓練分數的其他方面。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.