Numpy: начинающий nditer
Я пытаюсь узнать nditer для возможного использования в ускорении моего приложения. Здесь я пытаюсь создать программу для изменения формы, которая возьмет массив размером 20 и преобразует его в массив 5x4:
myArray = np.arange(20)
def fi_by_fo_100(array):
offset = np.array([0, 4, 8, 12, 16])
it = np.nditer([offset, None],
flags=['reduce_ok'],
op_flags=[['readonly'],
['readwrite','allocate']],
op_axes=[None, [0,1,-1]],
itershape=(-1, 4, offset.size))
while not it.finished:
indices = np.arange(it[0],(it[0]+4), dtype=int)
info = array.take(indices)
'''Just for fun, we'll perform an operation on data.\
Let's shift it to 100'''
info = info + 81
it.operands[1][...]=info
it.iternext()
return it.operands[1]
test = fi_by_fo_100(myArray)
>>> test
array([[ 97, 98, 99, 100]])
Очевидно, что программа перезаписывает каждый результат в одну строку. Поэтому я пытаюсь использовать функциональность индексирования nditer, но все еще не играю в кости.
flags=['reduce_ok','c_iter']
-> it.operands[1][it.index][...]=info
знак равноIndexError: index out of bounds
flags=['reduce_ok','c_iter']
-> it.operands[1][it.iterindex][...]=info
знак равноIndexError: index out of bounds
flags=['reduce_ok','multi_iter']
-> it.operands[1][it.multi_index][...]=info
знак равноIndexError: index out of bounds
it[0][it.multi_index[1]][...]=info
знак равно IndexError: 0-d arrays can't be indexed
...и так далее. Что мне не хватает? Заранее спасибо.
Бонусный вопрос
Я только что натолкнулся на эту прекрасную статью об nditer. Я могу быть новичком в Numpy, но я впервые вижу показатели скорости Numpy далеко позади. Насколько я понимаю, люди выбирают Numpy за его численную скорость и мастерство, но итерация является частью этого, не так ли? Какой смысл nditer, если он такой медленный?
1 ответ
Это действительно помогает сломать вещи, распечатывая то, что происходит на этом пути.
Во-первых, давайте заменим весь ваш цикл следующим образом:
i = 0
while not it.finished:
i += 1
print i
Он напечатает 20, а не 5. Это потому, что вы делаете итерацию 5x4, а не 5x1.
Итак, почему это даже близко к работе? Что ж, давайте посмотрим на цикл более внимательно:
while not it.finished:
print '>', it.operands[0], it[0]
indices = np.arange(it[0],(it[0]+4), dtype=int)
info = array.take(indices)
info = info + 81
it.operands[1][...]=info
print '<', it.operands[1], it[1]
Вы увидите, что первые пять циклов проходят [0 4 8 12 16]
пять раз, генерируя [[81 82 83 84]]
, затем [[85 86 87 88]]
и т. д. И затем следующие пять циклов делают то же самое, и снова и снова.
Это также, почему ваш c_index
решения не сработали - потому что it.index
будет колебаться от 0 до 19, и у вас нет 20 ничего в it.operands[1]
,
Если вы правильно сделали multi_index и проигнорировали столбцы, вы могли бы сделать эту работу... но, тем не менее, вы будете делать итерацию 5x4, просто повторяя каждый шаг 4 раза, вместо того, чтобы выполнять итерацию 5x1, которую вы хотите.
Ваш it.operands[1][...]=info
заменяет весь вывод строкой 5x1 каждый раз в цикле. Как правило, вам не нужно ничего делать it.operands[1]
- весь смысл nditer
это то, что вы просто заботитесь о каждом it[1]
и финал it.operands[1]
это результат.
Конечно, итерация 5x4 по строкам не имеет смысла. Либо выполните итерацию 5x4 для отдельных значений, либо итерацию 5x1 для строк.
Если вы хотите первое, самый простой способ сделать это изменить форму входного массива, а затем просто повторить это:
it = np.nditer([array.reshape(5, -1), None],
op_flags=[['readonly'],
['readwrite','allocate']])
for a, b in it:
b[...] = a + 81
return it.operands[1]
Но, конечно, это глупо - это просто более медленный и более сложный способ написания:
return array+81
И было бы немного глупо предполагать, что "способ написать свой собственный reshape
это первый звонок reshape
, а потом…"
Итак, вы хотите перебирать строки, верно?
Давайте немного упростим вещи, избавившись от allocate
и явно создать массив 5x4 для начала:
outarray = np.zeros((5,4), dtype=array.dtype)
offset = np.array([0, 4, 8, 12, 16])
it = np.nditer([offset, outarray],
flags=['reduce_ok'],
op_flags=[['readonly'],
['readwrite']],
op_axes=[None, [0]],
itershape=[5])
while not it.finished:
indices = np.arange(it[0],(it[0]+4), dtype=int)
info = array.take(indices)
'''Just for fun, we'll perform an operation on data.\
Let's shift it to 100'''
info = info + 81
it.operands[1][it.index][...]=info
it.iternext()
return it.operands[1]
Это немного злоупотребление nditer
, но, по крайней мере, это делает правильно.
Поскольку вы просто выполняете одномерную итерацию по источнику и в основном игнорируете вторую, на самом деле нет веской причины использовать nditer
Вот. Если вам нужно выполнить итерацию по нескольким массивам, for a, b in nditer([x, y], …)
чище, чем перебирать x
и используя индекс для доступа y
-как for a, b in zip(x, y)
вне numpy
, И если вам нужно перебирать многомерные массивы, nditer
обычно чище, чем альтернативы. Но здесь все, что вы действительно делаете, это перебирая [0, 4, 8, 16, 20]
делать что-то с результатом и копировать в другое array
,
Кроме того, как я уже упоминал в комментариях, если вы обнаружите, что используете итерацию в numpy
обычно ты делаешь что-то не так. Все преимущества скорости numpy
дать ему возможность выполнять узкие циклы в нативном C/Fortran или низкоуровневых векторных операциях. Как только вы перебираете array
s, вы фактически просто делаете медленные числа Python с немного более приятным синтаксисом:
import numpy as np
import timeit
def add10_numpy(array):
return array + 10
def add10_nditer(array):
it = np.nditer([array, None], [],
[['readonly'], ['writeonly', 'allocate']])
for a, b in it:
np.add(a, 10, b)
return it.operands[1]
def add10_py(array):
x, y = array.shape
outarray = array.copy()
for i in xrange(x):
for j in xrange(y):
outarray[i, j] = array[i, j] + 10
return out array
myArray = np.arange(100000).reshape(250,-1)
for f in add10_numpy, add10_nditer, add10_py:
print '%12s: %s' % (f.__name__, timeit.timeit(lambda: f(myArray), number=1))
В моей системе это печатает:
add10_numpy: 0.000458002090454
add10_nditer: 0.292730093002
add10_py: 0.127345085144
Это показывает вам стоимость использования nditer
без необходимости.