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 или низкоуровневых векторных операциях. Как только вы перебираете arrays, вы фактически просто делаете медленные числа 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 без необходимости.

Другие вопросы по тегам