Свертка в Numpy медленнее, чем в Matlab?

Свертка в Matlab оказывается в два раза быстрее, чем свертка в Numpy.

Код Python (на моей машине занимает 19 секунд):

import numpy as np
from scipy import ndimage
import time

img = np.ones((512,512,512))
kernel = np.ones((5,5,5))/125

start_time = time.time()
ndimage.convolve(img,kernel,mode='constant')
print "Numpy execution took ", (time.time() - start_time), "seconds"

Код Matlab (на моей машине занимает 8,7 секунды):

img = ones(512,512,512);
kernel = ones(5,5,5) / 125;
tic
convn(img, kernel, 'same');
toc

Оба дают идентичные результаты.

Есть ли способ улучшить Numpy, чтобы соответствовать или превзойти производительность Matlab здесь?

Интересно, что этот фактор или ~2 различия во времени выполнения постоянны при многих размерах входных данных.


person naroom    schedule 23.05.2013    source источник
comment
Здесь вы измеряете не производительность Python, а NumPy/SciPy. Можете ли вы улучшить производительность этих модулей? Конечно, но не путем написания кода на Python.   -  person kindall    schedule 23.05.2013
comment
Отредактировано (s/Python/Numpy/).   -  person naroom    schedule 23.05.2013
comment
Вы можете посмотреть, какие библиотеки numpy созданы для сравнения с matlab. Я знаю по личному опыту, что когда numpy построен на основе библиотеки Intel MKL, я получаю гораздо лучшую производительность для некоторых операций, чем с настройками по умолчанию.   -  person JoshAdel    schedule 23.05.2013
comment
Этот вопрос некорректен! Простой tic toc НЕ подходит для измерения производительности. Используйте класс timeit в numpy и, например, его аналог из fileexchange для MATLAB. В документации вы также найдете некоторые индикаторы того, что неправильно использовать просто tic toc.   -  person Jan    schedule 23.05.2013
comment
@Jan: я только что проверил это, и для этого конкретного случая альтернативные функции Matlab и Python для синхронизации дали идентичные результаты. Но я обязательно буду использовать их в будущем для более точного тайминга.   -  person naroom    schedule 23.05.2013
comment
@JoshAdel - В целом очень верно, но в данном конкретном случае это не должно (?) иметь большого значения. ndimage не делает кучу звонков BLAS, если я правильно помню.   -  person Joe Kington    schedule 23.05.2013
comment
@JoshAdel: я использую версию MKL, поставляемую с дистрибутивом WinPython: code.google. com/p/winpython/wiki/ChangeLog_27 Я только что перепроверил его на версии MKL NumPy Кристофа Гольке (lfd.uci.edu/~gohlke/pythonlibs/#numpy) и получил тот же результат времени.   -  person naroom    schedule 23.05.2013


Ответы (2)


Какую именно операцию вы делаете? Есть ряд оптимизаций, которые ndimage предоставляет, если вам не нужна общая N-d свертка.

Например, ваша текущая операция:

img = np.ones((512,512,512))
kernel = np.ones((5,5,5))/125
result = ndimage.convolve(img, kernel)

эквивалентно:

img = np.ones((512,512,512))
result = ndimage.uniform_filter(img, 5)

Однако есть большая разница в скорости выполнения:

In [22]: %timeit ndimage.convolve(img, kernel)
1 loops, best of 3: 25.9 s per loop

In [23]: %timeit ndimage.uniform_filter(img, 5)
1 loops, best of 3: 8.69 s per loop

Разница вызвана uniform_filter предварительным формированием одномерной свертки вдоль каждой оси вместо обычной трехмерной свертки.

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

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


Вы упомянули фильтр «лапласиан гаусса». Я всегда путаюсь с терминологией здесь.

Я думаю, что вы хотите с точки зрения ndimage функций либо scipy.ndimage.gaussian_laplace или scipy.ndimage.gaussian_filter с order=2 (что фильтрует по второй производной гауссова).

Во всяком случае, оба упрощают операцию вплоть до одномерной свертки по каждой оси, что должно дать значительное (в 2-3 раза) ускорение.

person Joe Kington    schedule 23.05.2013
comment
Хорошая мысль! Я работаю над проблемой 3D-сегментации, поэтому фильтр, который мне действительно нужно запустить, — это 3D-лапласиан гауссова (он же Мексиканская шляпа). Однако замена ядра Matlab на kernel = rand(5,5,5); не изменила время его выполнения, поэтому я не думаю, что Matlab здесь обманывает :) - person naroom; 23.05.2013
comment
Ах, да, тогда я думаю, что Matlab не жульничает! :) - person Joe Kington; 23.05.2013
comment
Я думаю, вы хотите scipy.ndimage.gaussian_laplace, но я не уверен на 100%... Меня всегда немного смущают различия между различными краевыми фильтрами. Надеюсь, что предложение немного поможет, во всяком случае! - person Joe Kington; 23.05.2013

Не ответ; просто комментарий:

Прежде чем сравнивать производительность, нужно убедиться, что две функции возвращают один и тот же результат:

Если convn Matlab возвращает тот же результат, что и convn Octave, то convn отличается от ndimage.convolve:

octave> convn(ones(3,3), ones(2,2))
ans =

   1   2   2   1
   2   4   4   2
   2   4   4   2
   1   2   2   1

In [99]: ndimage.convolve(np.ones((3,3)), np.ones((2,2)))
Out[99]: 
array([[ 4.,  4.,  4.],
       [ 4.,  4.,  4.],
       [ 4.,  4.,  4.]])

ndimage.convolve имеет другие режимы, 'reflect','constant','nearest','mirror', 'wrap', но ни один из них не соответствует поведению convn по умолчанию ("полному").


Для двумерных массивов scipy.signal.convolve2d быстрее, чем scipy.signal.convolve.

Для 3D-массивов scipy.signal.convolve ведет себя так же, как convn(A,B):

octave> x = convn(ones(3,3,3), ones(2,2,2))
x =

ans(:,:,1) =

   1   2   2   1
   2   4   4   2
   2   4   4   2
   1   2   2   1

ans(:,:,2) =

   2   4   4   2
   4   8   8   4
   4   8   8   4
   2   4   4   2

ans(:,:,3) =

   2   4   4   2
   4   8   8   4
   4   8   8   4
   2   4   4   2

ans(:,:,4) =

   1   2   2   1
   2   4   4   2
   2   4   4   2
   1   2   2   1

In [109]: signal.convolve(np.ones((3,3,3), dtype='uint8'), np.ones((2,2,2), dtype='uint8'))
Out[109]: 
array([[[1, 2, 2, 1],
        [2, 4, 4, 2],
        [2, 4, 4, 2],
        [1, 2, 2, 1]],

       [[2, 4, 4, 2],
        [4, 8, 8, 4],
        [4, 8, 8, 4],
        [2, 4, 4, 2]],

       [[2, 4, 4, 2],
        [4, 8, 8, 4],
        [4, 8, 8, 4],
        [2, 4, 4, 2]],

       [[1, 2, 2, 1],
        [2, 4, 4, 2],
        [2, 4, 4, 2],
        [1, 2, 2, 1]]], dtype=uint8)

Обратите внимание, что np.ones((n,m,p)) по умолчанию создает массив float. Matlab ones(n,m,p) создает массив целых чисел. Чтобы сделать хорошее сравнение, вы должны попытаться сопоставить dtype массивов numpy с типом матриц Matlab.

person unutbu    schedule 23.05.2013
comment
Кстати, scipy.signal.convolve2d по умолчанию ведет себя так же, как conv в matlab/octave. В scipy.signal kwarg mode управляет размером результата (аналогично conv в Matlab), а в ndimage kwarg mode (в основном) управляет граничными условиями. Это соглашение об именах, которое приводит к путанице при сравнении вещей... - person Joe Kington; 23.05.2013
comment
Матлабовские функции one() создают двойную матрицу, поэтому типы действительно совпадают. - person naroom; 23.05.2013
comment
@naroom - Тем не менее, если вам не нужны числа с плавающей запятой, использование целых чисел в этом случае даст примерно 20-кратное ускорение. - person Joe Kington; 23.05.2013
comment
@Джо: Хм. В идеале я бы предпочел числа с плавающей запятой, но подойдет и целочисленное приближение, если бы я мог получить такое ускорение. Но когда я изменил определения на img = np.ones((512,512,512),dtype=np.int) и kernel = np.ones((5,5,5),dtype=np.int), время выполнения осталось прежним. Есть ли другая функция, которую я должен запустить? - person naroom; 23.05.2013
comment
@naroom - Хм... У меня оно сократилось с 25 до ~1 с... Позвольте мне перепроверить это... - person Joe Kington; 23.05.2013
comment
@Joe: Если это сработает, отправьте код в качестве ответа, и вы станете нашим победителем! Matlab заставляет свою convn принимать входные данные с плавающей запятой, так что оптимизация там невозможна. - person naroom; 23.05.2013
comment
@naroom - Ой, я сделал что-то довольно глупое, когда проводил сравнение. Я просто бросил kernel = kernel.astype(int), что, конечно же, привело ко всем нулям. Умножение целых чисел на ноль — гораздо более быстрая операция, чем обычное умножение целых чисел! Во всяком случае, ускорение незначительно, как вы упомянули. - person Joe Kington; 23.05.2013
comment
@unutbu: у них действительно разное поведение на границах изображения. Итак, я исправил это и повторил тест (отредактированный в коде вопроса). Время Matlab увеличилось до 8,7 секунды; Python сократился до 19 секунд. Прогресс, но все еще разница в 2 раза. - person naroom; 23.05.2013