2012-05-24 13 views
19

La función sigmoide se define comoalgoritmo sigmoide Fast

enter image description here

he encontrado que el uso de la C función incorporada de exp() para calcular el valor de f(x) es lento. ¿Hay algún algoritmo más rápido para calcular el valor de f(x)?

Respuesta

15

no tiene que utilizar la función real, exacta sigmoide en un algoritmo de red neuronal, pero puede reemplazarlo con una versión aproximada que tiene propiedades similares pero es más rápido el cálculo.

Por ejemplo, puede utilizar la función "sigmoide rápida"

f(x) = x/(1 + abs(x)) 

Usando primeros términos de la expansión de la serie de exp (x) no ayudará demasiado si los argumentos de f (x) son no cerca de cero, y usted tiene el mismo problema con una expansión en serie de la función sigmoide si los argumentos son "grandes".

Una alternativa es usar la búsqueda de tablas. Es decir, calcula previamente los valores de la función sigmoidea para un número determinado de puntos de datos y luego realiza una interpolación rápida (lineal) entre ellos si lo desea.

-1

No creo que se puede hacer mejor que el exp integrado(), pero si quieres otro enfoque, puede utilizar la expansión de serie. WolframAlpha puede calcularlo por usted.

13

Lo mejor es medir primero en su hardware. Solo un breve punto de referencia script muestra que en mi máquina 1/(1+|x|) es la más rápida, y tanh(x) es la segunda más cercana. La función de error erf es bastante rápida también.

% 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 

espero que los resultados pueden variar dependiendo de la arquitectura y el compilador utilizado, pero erf(x) (desde C99), tanh(x) y x/(1.0+fabs(x)) es probable que sean los artistas rápidas.

+1

También cree que quería decir 'x/sqrt (1 + x^2)' en lugar de '1/sqrt (1 + x^2)'. – pqn

6

Para hacer el NN más flexible por lo general utilizado algún tipo alfa para cambiar el ángulo de la gráfica alrededor de 0.

La función sigmoide se parece a:

f(x) = 1/(1+exp(-x*alpha)) 

La función casi equivalente, (pero más rápido) es:

f(x) = 0.5 * (x * alpha/(1 + abs(x*alpha))) + 0.5 

puede comprobar los gráficos here

Cuando uso la función abs la red se vuelve más rápida más de 100 veces.

+0

¿Dónde se supone que se cierra la primera ronda en la segunda ecuación? –

+0

Reparado, vea en línea. – Nosyara

4

Esta respuesta probablemente no es relevante para la mayoría de los casos, pero solo quería decir que para la computación CUDA he encontrado que x/sqrt(1+x^2) es la función más rápida con diferencia.

Por ejemplo, hecho con las características intrínsecas de flotador de precisión simple:

__device__ void fooCudaKernel(/* some arguments */) { 
    float foo, sigmoid; 
    // some code defining foo 
    sigmoid = __fmul_rz(rsqrtf(__fmaf_rz(foo,foo,1)),foo); 
} 
+0

Bueno. Aunque solo sería si calcularas las neuronas como una Matriz Completamente conectada, no un Vector para una sola fila/Matriz dispersa. – user1496062

1

Usando Eureqa para buscar aproximaciones a sigmoide encontré 1/(1 + 0.3678749025^x) se aproxima a ella.Está bastante cerca, simplemente se deshace de una operación con la negación de x.

Algunas de las otras funciones que se muestran aquí son interesantes, pero ¿la operación de potencia realmente es tan lenta? Lo probé y de hecho lo hizo más rápido que la suma, pero eso podría ser un golpe de suerte. Si es así, debería ser tan rápido o más rápido que todos los demás.

EDITAR: 0.5 + 0.5*tanh(0.5*x) y menos preciso, 0.5 + 0.5*tanh(n) también funciona. Y podrías deshacerte de las constantes si no te importa ponerlas entre el rango [0,1] como sigmoide. Pero asume que tanh es más rápido.

+0

El término de potencia generalmente es lento para ejecutar sí, de ahí que esta aproximación no evite ese aspecto de la pregunta original, ya que pow() se implementará a menudo en circuitos de CPU como un ajuste a una ejecución/evaluación exp(). – redcalx

8

La mayoría de las personas aquí están preocupadas acerca de qué tan rápido una función es relativa a otra y crean una micro-referencia para ver si f1(x) corre 0.0001 ms más rápido que f2(x). El gran problema es que esto es casi irrelevante, porque lo que importa es qué tan rápido aprenda su red con su función de activación tratando de minimizar su función de costos.

A partir de la teoría actual, rectifier function and softplusenter image description here

comparación con sigmoide función o funciones de activación similares, permitirá más rápida y eficaz la formación de arquitecturas neuronales profundas en conjuntos de datos grandes y complejos.

Así que sugiero que se descarte la micro-optimización, y observe qué función permite un aprendizaje más rápido (también teniendo en cuenta otras funciones de costos).

3

También es posible utilizar la versión aproximada del sigmoide (IT diferencias no mayor de 0,2% a partir de originales):

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); 
    } 

optimización de la función RoughSigmoid con el uso de SSE:

#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]); 
    } 

optimización de la función RoughSigmoid con el uso de AVX:

#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]); 
    } 
1

La función tanh se puede optimizar i En algunos idiomas, por lo que es más rápido que una x/(1 + abs (x)) definida a medida, tal es el caso de Julia.