Медлительность numpy.ceil и numpy.clip в пороговой настройке ReLU: где узкое место? - PullRequest
1 голос
/ 06 марта 2019

Это вопрос, основанный на (или продолжении) другого вопроса: Ускоренная реализация производного ReLU .

В духе, чтобы придумать Самый быстрый способ вычисления производной, я написал несколько решений, из которых одно:

In [35]: np.random.seed(0)       
In [36]: X = np.random.randn(3072,10000) 

# computing ReLU derivative
In [42]: np.ceil(np.clip(X, 0, 1))

При сопоставлении этого с другими решениями Divakar , я обнаружил, что вышеупомянутый подход является мучительныммедленно (к северу от 30x ).Ниже приведены временные интервалы (от самых быстрых до самых медленных)

In [43]: %timeit -n100 ne.evaluate('X>=0').view('i1')  
10.6 ms ± 203 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [44]: %timeit -n100 (X>=0).view('i1')
13.6 ms ± 77.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [45]: %timeit -n100 ne.evaluate('(X>=0)+0') 
22.1 ms ± 16.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

# the super slowest one
In [46]: %timeit -n100 np.ceil(np.clip(X, 0, 1)) 
317 ms ± 2.14 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)

Что является / являются фактором (факторами), вызывающими эту медлительность?Где находится узкое место?

1 Ответ

2 голосов
/ 06 марта 2019

Во-первых, вы просто выполняете более сложную последовательность операций. Для каждого входа ваша функция ceil / clip делает следующее:

  • Входное значение меньше 0? Если это так, установите промежуточное значение равным 0.
  • В противном случае оно больше 1? Если это так, установите промежуточное значение равным 1.
  • В противном случае установите промежуточное значение для входного значения.
  • Рассчитать верхний предел промежуточного значения и установить выходное значение.

(Это происходит в два этапа, один, где все отсечение сделано, другой, когда все потолки сделаны.)

Вы синхронизируете это с параметрами, которые делают следующее для каждого ввода:

  • Выполните сравнение> = между входом и 0 и установите выход на него.

Не удивительно, что> = быстрее.


Во-вторых, ваша функция ceil / clip записывает в 16 раз больше байтов, чем> =. Символ> = производит один байт вывода для каждого элемента ввода (view - это представление, поэтому копирование данных там отсутствует), в то время как ваша функция ceil / clip создает промежуточный массив и выходной массив, оба типа dtype float64.


В-третьих, у предиктора ветки плохое время с этим clip в случайном массиве. Он не знает, какая ветвь будет использоваться каждый раз. Более предсказуемый массив проходит clip намного быстрее:

In [21]: %timeit X.clip(0, 1)
1 loop, best of 5: 211 ms per loop

In [22]: A = np.full_like(X, 0.5)

In [23]: %timeit A.clip(0, 1)
10 loops, best of 5: 86.6 ms per loop

Наконец, по крайней мере на машине и сборке NumPy, на которой я тестировал, numpy.ceil просто удивительно медленен:

In [24]: %timeit np.ceil(X)
10 loops, best of 5: 166 ms per loop

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

...