Отображение массива NumPy на месте
Возможно ли сопоставить массив NumPy? Если да, то как?
Учитывая a_values
- 2D-массив - это бит кода, который делает трюк для меня в данный момент:
for row in range(len(a_values)):
for col in range(len(a_values[0])):
a_values[row][col] = dim(a_values[row][col])
Но это так уродливо, что я подозреваю, что где-то внутри NumPy должна существовать функция, которая делает то же самое с чем-то вроде:
a_values.map_in_place(dim)
но если что-то вроде выше, существует, я не смог его найти.
Ответы
Ответ 1
Стоит только попытаться сделать это на месте, если вы находитесь под значительными космическими ограничениями. В этом случае можно немного ускорить ваш код, итерации по сплющенному виду массива. Поскольку reshape
возвращает новый вид когда это возможно, сами данные не копируются (если оригинал не имеет необычной структуры).
Я не знаю лучшего способа добиться добросовестного применения произвольной функции Python на месте.
>>> def flat_for(a, f):
... a = a.reshape(-1)
... for i, v in enumerate(a):
... a[i] = f(v)
...
>>> a = numpy.arange(25).reshape(5, 5)
>>> flat_for(a, lambda x: x + 5)
>>> a
array([[ 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14],
[15, 16, 17, 18, 19],
[20, 21, 22, 23, 24],
[25, 26, 27, 28, 29]])
Некоторые тайминги:
>>> a = numpy.arange(2500).reshape(50, 50)
>>> f = lambda x: x + 5
>>> %timeit flat_for(a, f)
1000 loops, best of 3: 1.86 ms per loop
Это примерно в два раза быстрее, чем версия вложенного цикла:
>>> a = numpy.arange(2500).reshape(50, 50)
>>> def nested_for(a, f):
... for i in range(len(a)):
... for j in range(len(a[0])):
... a[i][j] = f(a[i][j])
...
>>> %timeit nested_for(a, f)
100 loops, best of 3: 3.79 ms per loop
Конечно, vectorize еще быстрее, поэтому, если вы можете сделать копию, используйте это:
>>> a = numpy.arange(2500).reshape(50, 50)
>>> g = numpy.vectorize(lambda x: x + 5)
>>> %timeit g(a)
1000 loops, best of 3: 584 us per loop
И если вы можете переписать dim
с помощью встроенных ufuncs, пожалуйста, пожалуйста, не vectorize
:
>>> a = numpy.arange(2500).reshape(50, 50)
>>> %timeit a + 5
100000 loops, best of 3: 4.66 us per loop
numpy
работают такие операции, как +=
, как и следовало ожидать, поэтому вы можете бесплатно получить скорость ufunc с помощью приложения на месте. Иногда это даже быстрее! См. здесь для примера.
Кстати, мой оригинальный ответ на этот вопрос, который можно просмотреть в его истории изменений, смехотворен и связан с векторизации по индексам на a
. Мало того, что он должен был делать некоторые напуганные вещи, чтобы обойти vectorize
механизм обнаружения типов, он оказался таким же медленным, как и вложенный. Так много для умения!
Ответ 2
Это резюме вкладов, разбросанных в ответах и комментарии, которые я написал после принятия ответа на вопрос. Прибыль всегда приветствуется, но если вы подтвердите этот ответ, пожалуйста, не пропустите, чтобы перенести также те из senderle и (если (а) он пишет один) eryksun, который предложил методы ниже.
Q: Можно ли отображать массив numpy на месте?
A: Да, но не с помощью метода одиночного массива. Вы должны написать свой собственный код.
Ниже script, который сравнивает различные реализации, обсуждаемые в потоке:
import timeit
from numpy import array, arange, vectorize, rint
# SETUP
get_array = lambda side : arange(side**2).reshape(side, side) * 30
dim = lambda x : int(round(x * 0.67328))
# TIMER
def best(fname, reps, side):
global a
a = get_array(side)
t = timeit.Timer('%s(a)' % fname,
setup='from __main__ import %s, a' % fname)
return min(t.repeat(reps, 3)) #low num as in place --> converge to 1
# FUNCTIONS
def mac(array_):
for row in range(len(array_)):
for col in range(len(array_[0])):
array_[row][col] = dim(array_[row][col])
def mac_two(array_):
li = range(len(array_[0]))
for row in range(len(array_)):
for col in li:
array_[row][col] = int(round(array_[row][col] * 0.67328))
def mac_three(array_):
for i, row in enumerate(array_):
array_[i][:] = [int(round(v * 0.67328)) for v in row]
def senderle(array_):
array_ = array_.reshape(-1)
for i, v in enumerate(array_):
array_[i] = dim(v)
def eryksun(array_):
array_[:] = vectorize(dim)(array_)
def ufunc_ed(array_):
multiplied = array_ * 0.67328
array_[:] = rint(multiplied)
# MAIN
r = []
for fname in ('mac', 'mac_two', 'mac_three', 'senderle', 'eryksun', 'ufunc_ed'):
print('\nTesting `%s`...' % fname)
r.append(best(fname, reps=50, side=50))
# The following is for visually checking the functions returns same results
tmp = get_array(3)
eval('%s(tmp)' % fname)
print tmp
tmp = min(r)/100
print('\n===== ...AND THE WINNER IS... =========================')
print(' mac (as in question) : %.4fms [%.0f%%]') % (r[0]*1000,r[0]/tmp)
print(' mac (optimised) : %.4fms [%.0f%%]') % (r[1]*1000,r[1]/tmp)
print(' mac (slice-assignment) : %.4fms [%.0f%%]') % (r[2]*1000,r[2]/tmp)
print(' senderle : %.4fms [%.0f%%]') % (r[3]*1000,r[3]/tmp)
print(' eryksun : %.4fms [%.0f%%]') % (r[4]*1000,r[4]/tmp)
print(' slice-assignment w/ ufunc : %.4fms [%.0f%%]') % (r[5]*1000,r[5]/tmp)
print('=======================================================\n')
Вывод выше script - по крайней мере в моей системе - это:
mac (as in question) : 88.7411ms [74591%]
mac (optimised) : 86.4639ms [72677%]
mac (slice-assignment) : 79.8671ms [67132%]
senderle : 85.4590ms [71832%]
eryksun : 13.8662ms [11655%]
slice-assignment w/ ufunc : 0.1190ms [100%]
Как вы можете заметить, с использованием numpy ufunc
увеличивает скорость более 2 и почти на 3 порядка по сравнению со вторыми лучшими и худшими альтернативами соответственно.
Если использование ufunc
не является опцией, сравните только другие альтернативы:
mac (as in question) : 91.5761ms [672%]
mac (optimised) : 88.9449ms [653%]
mac (slice-assignment) : 80.1032ms [588%]
senderle : 86.3919ms [634%]
eryksun : 13.6259ms [100%]
НТН!
Ответ 3
Почему бы не использовать реализацию numpy и out_ трюк?
from numpy import array, arange, vectorize, rint, multiply, round as np_round
def fmilo(array_):
np_round(multiply(array_ ,0.67328, array_), out=array_)
получил:
===== ...AND THE WINNER IS... =========================
mac (as in question) : 80.8470ms [130422%]
mac (optimised) : 80.2400ms [129443%]
mac (slice-assignment) : 75.5181ms [121825%]
senderle : 78.9380ms [127342%]
eryksun : 11.0800ms [17874%]
slice-assignment w/ ufunc : 0.0899ms [145%]
fmilo : 0.0620ms [100%]
=======================================================
Ответ 4
если ufuncs невозможно, вам, возможно, стоит рассмотреть использование cython.
он легко интегрируется и дает большие ускорения при конкретном использовании массивов numpy.
Ответ 5
Это всего лишь обновленная версия макроса, актуализированная для Python 3.x и numba и numpy.frompyfunc.
numpy.frompyfunc принимает произвольную функцию python и возвращает функцию, которая при нажатии на numpy.array применяет функцию elementwise.
Однако он изменяет тип данных массива на объект, поэтому его нет на месте, и будущие вычисления в этом массиве будут медленнее.
Чтобы избежать этого недостатка, в тесте numpy.ndarray.astype будет вызываться, возвращая тип данных в int.
Как примечание стороны:
Numba не входит в базовые библиотеки Python и должен быть загружен извне, если вы хотите его протестировать. В этом тесте он фактически ничего не делает, и если бы он был вызван с помощью @jit (nopython = True), он дал бы сообщение об ошибке, говорящее, что он ничего не может оптимизировать. Поскольку, однако, numba часто может ускорить код, написанный в функциональном стиле, он включен для целостности.
import timeit
from numpy import array, arange, vectorize, rint, frompyfunc
from numba import autojit
# SETUP
get_array = lambda side : arange(side**2).reshape(side, side) * 30
dim = lambda x : int(round(x * 0.67328))
# TIMER
def best(fname, reps, side):
global a
a = get_array(side)
t = timeit.Timer('%s(a)' % fname,
setup='from __main__ import %s, a' % fname)
return min(t.repeat(reps, 3)) #low num as in place --> converge to 1
# FUNCTIONS
def mac(array_):
for row in range(len(array_)):
for col in range(len(array_[0])):
array_[row][col] = dim(array_[row][col])
def mac_two(array_):
li = range(len(array_[0]))
for row in range(len(array_)):
for col in li:
array_[row][col] = int(round(array_[row][col] * 0.67328))
def mac_three(array_):
for i, row in enumerate(array_):
array_[i][:] = [int(round(v * 0.67328)) for v in row]
def senderle(array_):
array_ = array_.reshape(-1)
for i, v in enumerate(array_):
array_[i] = dim(v)
def eryksun(array_):
array_[:] = vectorize(dim)(array_)
@autojit
def numba(array_):
for row in range(len(array_)):
for col in range(len(array_[0])):
array_[row][col] = dim(array_[row][col])
def ufunc_ed(array_):
multiplied = array_ * 0.67328
array_[:] = rint(multiplied)
def ufunc_frompyfunc(array_):
udim = frompyfunc(dim,1,1)
array_ = udim(array_)
array_.astype("int")
# MAIN
r = []
totest = ('mac', 'mac_two', 'mac_three', 'senderle', 'eryksun', 'numba','ufunc_ed','ufunc_frompyfunc')
for fname in totest:
print('\nTesting `%s`...' % fname)
r.append(best(fname, reps=50, side=50))
# The following is for visually checking the functions returns same results
tmp = get_array(3)
eval('%s(tmp)' % fname)
print (tmp)
tmp = min(r)/100
results = list(zip(totest,r))
results.sort(key=lambda x: x[1])
print('\n===== ...AND THE WINNER IS... =========================')
for name,time in results:
Out = '{:<34}: {:8.4f}ms [{:5.0f}%]'.format(name,time*1000,time/tmp)
print(Out)
print('=======================================================\n')
И, наконец, результаты:
===== ...AND THE WINNER IS... =========================
ufunc_ed : 0.3205ms [ 100%]
ufunc_frompyfunc : 3.8280ms [ 1194%]
eryksun : 3.8989ms [ 1217%]
mac_three : 21.4538ms [ 6694%]
senderle : 22.6421ms [ 7065%]
mac_two : 24.6230ms [ 7683%]
mac : 26.1463ms [ 8158%]
numba : 27.5041ms [ 8582%]
=======================================================