シグモイド関数は次のように定義されます。
C 組み込み関数を使用してexp()
の値を計算するとf(x)
時間がかかることがわかりました。の値を計算するより高速なアルゴリズムはありますf(x)
か?
ニューラルネットワークアルゴリズムで実際の正確なシグモイド関数を使用する必要はありませんが、同様のプロパティを持つが計算が高速な近似バージョンに置き換えることができます。
たとえば、「高速シグモイド」関数を使用できます
f(x) = x / (1 + abs(x))
exp(x)の級数展開の最初の項を使用しても、f(x)の引数がゼロに近くない場合はあまり役に立ちません。また、引数が "の場合、シグモイド関数の級数展開でも同じ問題が発生します。大きい"。
別の方法は、テーブルルックアップを使用することです。つまり、指定された数のデータポイントのシグモイド関数の値を事前に計算し、必要に応じてそれらの間で高速(線形)補間を実行します。
最初にハードウェアで測定することをお勧めします。簡単なベンチマークスクリプトを見ると、私のマシン1/(1+|x|)
では最速でありtanh(x)
、2 番目に近いことがわかります。エラー関数erf
もかなり高速です。
% gcc -Wall -O2 -lm -o sigmoid-bench{,.c} -std=c99 && ./sigmoid-bench
atan(pi*x/2)*2/pi 24.1 ns
atan(x) 23.0 ns
1/(1+exp(-x)) 20.4 ns
1/sqrt(1+x^2) 13.4 ns
erf(sqrt(pi)*x/2) 6.7 ns
tanh(x) 5.5 ns
x/(1+|x|) 5.5 ns
使用するアーキテクチャーやコンパイラーによって結果が異なる可能性があると思いますがerf(x)
(C99 以降)、実行速度が速い可能性がありますtanh(x)
。x/(1.0+fabs(x))
x/sqrt(1+x^2)
この答えはおそらくほとんどの場合には関係ありませんが、CUDA コンピューティングについては、これまでのところ最速の関数であることがわかっていることを明らかにしたかっただけです。
たとえば、単精度 float 組み込み関数を使用すると、次のようになります。
__device__ void fooCudaKernel(/* some arguments */) {
float foo, sigmoid;
// some code defining foo
sigmoid = __fmul_rz(rsqrtf(__fmaf_rz(foo,foo,1)),foo);
}
また、シグモイドの大まかなバージョンを使用することもできます (オリジナルとの差は 0.2% 以下です):
inline float RoughSigmoid(float value)
{
float x = ::abs(value);
float x2 = x*x;
float e = 1.0f + x + x2*0.555f + x2*x2*0.143f;
return 1.0f / (1.0f + (value > 0 ? 1.0f / e : e));
}
void RoughSigmoid(const float * src, size_t size, const float * slope, float * dst)
{
float s = slope[0];
for (size_t i = 0; i < size; ++i)
dst[i] = RoughSigmoid(src[i] * s);
}
SSEを使用したRoughSigmoid関数の最適化:
#include <xmmintrin.h>
void RoughSigmoid(const float * src, size_t size, const float * slope, float * dst)
{
size_t alignedSize = size/4*4;
__m128 _slope = _mm_set1_ps(*slope);
__m128 _0 = _mm_set1_ps(-0.0f);
__m128 _1 = _mm_set1_ps(1.0f);
__m128 _0555 = _mm_set1_ps(0.555f);
__m128 _0143 = _mm_set1_ps(0.143f);
size_t i = 0;
for (; i < alignedSize; i += 4)
{
__m128 _src = _mm_loadu_ps(src + i);
__m128 x = _mm_andnot_ps(_0, _mm_mul_ps(_src, _slope));
__m128 x2 = _mm_mul_ps(x, x);
__m128 x4 = _mm_mul_ps(x2, x2);
__m128 series = _mm_add_ps(_mm_add_ps(_1, x), _mm_add_ps(_mm_mul_ps(x2, _0555), _mm_mul_ps(x4, _0143)));
__m128 mask = _mm_cmpgt_ps(_src, _0);
__m128 exp = _mm_or_ps(_mm_and_ps(_mm_rcp_ps(series), mask), _mm_andnot_ps(mask, series));
__m128 sigmoid = _mm_rcp_ps(_mm_add_ps(_1, exp));
_mm_storeu_ps(dst + i, sigmoid);
}
for (; i < size; ++i)
dst[i] = RoughSigmoid(src[i] * slope[0]);
}
AVXを使用したRoughSigmoid関数の最適化:
#include <immintrin.h>
void RoughSigmoid(const float * src, size_t size, const float * slope, float * dst)
{
size_t alignedSize = size/8*8;
__m256 _slope = _mm256_set1_ps(*slope);
__m256 _0 = _mm256_set1_ps(-0.0f);
__m256 _1 = _mm256_set1_ps(1.0f);
__m256 _0555 = _mm256_set1_ps(0.555f);
__m256 _0143 = _mm256_set1_ps(0.143f);
size_t i = 0;
for (; i < alignedSize; i += 8)
{
__m256 _src = _mm256_loadu_ps(src + i);
__m256 x = _mm256_andnot_ps(_0, _mm256_mul_ps(_src, _slope));
__m256 x2 = _mm256_mul_ps(x, x);
__m256 x4 = _mm256_mul_ps(x2, x2);
__m256 series = _mm256_add_ps(_mm256_add_ps(_1, x), _mm256_add_ps(_mm256_mul_ps(x2, _0555), _mm256_mul_ps(x4, _0143)));
__m256 mask = _mm256_cmp_ps(_src, _0, _CMP_GT_OS);
__m256 exp = _mm256_or_ps(_mm256_and_ps(_mm256_rcp_ps(series), mask), _mm256_andnot_ps(mask, series));
__m256 sigmoid = _mm256_rcp_ps(_mm256_add_ps(_1, exp));
_mm256_storeu_ps(dst + i, sigmoid);
}
for (; i < size; ++i)
dst[i] = RoughSigmoid(src[i] * slope[0]);
}
tanh 関数は一部の言語で最適化されている場合があり、カスタム定義の x/(1+abs(x)) よりも高速になります (Julia の場合など)。
Eureqa を使用してシグモイドの近似値を検索すると、近似値が見つかりまし1/(1 + 0.3678749025^x)
た。これはかなり近いです。x の否定で 1 つの操作を取り除くだけです。
ここに示されている他の機能のいくつかは興味深いものですが、電源操作は本当に遅いですか? 私はそれをテストし、実際には追加よりも高速でしたが、それはまぐれかもしれません. もしそうなら、それは他のすべてのものと同じかそれ以上に速いはずです。
編集:0.5 + 0.5*tanh(0.5*x)
精度が低くて0.5 + 0.5*tanh(n)
も機能します。また、シグモイドのように範囲 [0,1] の間で取得することを気にしない場合は、定数を取り除くことができます。ただし、tanh の方が高速であると想定しています。
組み込みの exp() よりも優れているとは思いませんが、別のアプローチが必要な場合は、級数展開を使用できます。WolframAlphaで計算できます。