Подтвердить что ты не робот

Быстрый сигмоидный алгоритм

Сигмоидная функция определяется как

введите описание изображения здесь

Я обнаружил, что использование встроенной функции C exp() для вычисления значения f(x) является медленным. Есть ли более быстрый алгоритм для вычисления значения f(x)?

4b9b3361

Ответ 1

вам не нужно использовать фактическую точную сигмовидную функцию в алгоритме нейронной сети, но она может заменить ее аппроксимированной версией, которая имеет схожие свойства, но быстрее вычисляет.

Например, вы можете использовать функцию "быстрый сигмоид"

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

Использование первых членов разложения в ряд для exp (x) не поможет слишком много, если аргументы f (x) не близки к нулю, и у вас есть та же проблема с последовательным расширением сигмоидальной функции, если аргументы "большие".

Альтернативой является использование поиска в таблице. То есть вы предварительно вычисляете значения сигмоидной функции для заданного количества точек данных, а затем выполняете быструю (линейную) интерполяцию между ними, если хотите.

Ответ 2

Лучше всего измерить на вашем оборудовании. Просто быстрый тест script показывает, что на моей машине 1/(1+|x|) самый быстрый, а tanh(x) - второй. Функция ошибки 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)), скорее всего, будут быстрыми исполнителями.

Ответ 3

В основном люди обеспокоены тем, насколько быстро одна функция относится к другой, и создайте микро-тест, чтобы увидеть, работает ли f1(x) на 0,0001 мс быстрее, чем f2(x). Большая проблема заключается в том, что это в основном не имеет значения, потому что важно то, как быстро ваша сеть учится с вашей функцией активации, пытаясь минимизировать вашу функцию затрат.

В соответствии с текущей теорией выпрямительная функция и softplus введите описание изображения здесь

по сравнению с сигмовидной функцией или аналогичными функциями активации, разрешите для более быстрого и эффективного обучения глубоких нейронных архитектур на больших и сложных наборов данных.

Итак, я предлагаю отбросить микро-оптимизацию и взглянуть на то, какая функция позволяет быстрее учиться (а также смотреть на другую функцию стоимости).

Ответ 4

Чтобы сделать NN более гибким, обычно используется некоторая альфа-скорость, чтобы изменить угол графика вокруг 0.

Сигмоидная функция выглядит так:

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

Почти эквивалентная (но более быстрая функция):

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

Вы можете проверить графики здесь

Когда я использую функцию abs, сеть становится быстрее 100 раз.

Ответ 5

Этот ответ, вероятно, не имеет отношения к большинству случаев, но просто хотел выбросить туда, что для вычислений CUDA я нашел x/sqrt(1+x^2) как бы самую быструю функцию.

Например, выполняется с помощью встроенных операций с плавающей запятой:

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

Ответ 6

Также вы можете использовать грубую версию сигмоида (она отличается не более чем на 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);
    }

Оптимизация функции RoughSigmoid с использованием 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]);
    }

Оптимизация функции RoughSigmoid с использованием 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]);
    }

Ответ 7

Используя Eureqa для поиска приближений к сигмоиду, я нашел, что 1/(1 + 0.3678749025^x) приближает его. Это довольно близко, просто избавляется от одной операции с отрицанием x.

Некоторые из других функций, показанных здесь, интересны, но действительно ли операция питания медленна? Я тестировал его, и на самом деле это делалось быстрее, чем добавление, но это может быть просто случайностью. Если так, то это должно быть так же быстро или быстрее, как и все остальные.

EDIT: 0.5 + 0.5*tanh(0.5*x) и менее точный, 0.5 + 0.5*tanh(n) также работает. И вы можете просто избавиться от констант, если вам не нужно получать его между диапазоном [0,1], как сигмоид. Но он предполагает, что tanh быстрее.

Ответ 8

Функция tanh может быть оптимизирована на некоторых языках, что делает ее быстрее, чем пользовательский определенный x/(1 + abs (x)), как это имеет место в Julia.

Ответ 9

Я не думаю, что вы можете сделать лучше, чем встроенный exp(), но если вам нужен другой подход, вы можете использовать расширение серии. WolframAlpha может вычислить его для вас.