9

アプリケーションの高速化に使用できるように、 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]])

明らかに、プログラムは各結果を 1 つの行に上書きしています。だから私は 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のポイントは何ですか?

4

1 に答える 1

13

途中で何が起こっているのかを印刷することで、物事を分解するのに本当に役立ちます.

まず、ループ全体をこれに置き換えましょう。

i = 0
while not it.finished:
    i += 1
print i

5 ではなく 20 を出力します。これは、5x1 ではなく 5x4 の繰り返しを行っているためです。

では、なぜこれがほぼ機能しているのでしょうか。さて、ループをもっと注意深く見てみましょう。

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]最初の 5 つのループが5 回実行され、 が生成さ[[81 82 83 84]]れ、次にが生成されることがわかります[[85 86 87 88]]。そして、次の 5 つのループは同じことを何度も繰り返します。

これは、c_indexソリューションが機能しなかった理由でもあります。it.indexは 0 から 19 の範囲になり、 には 20 が含まれていないためですit.operands[1]

multi_index を正しく実行し、列を無視した場合、これを機能させることができますが、それでも、必要な 5x1 反復を行う代わりに、各ステップを 4 回繰り返すだけで、5x4 反復を行うことになります。

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が、少なくとも正しいことを行います。

ソースに対して 1D 反復を実行しているだけで、基本的に 2 番目を無視しているため、ここで使用する正当な理由はありませんnditer。複数の配列に対してロックステップ反復を実行する必要がある場合は、の外側と同様に、反復してインデックスを使用してアクセスするfor a, b in nditer([x, y], …)よりもクリーンです。また、多次元配列を反復処理する必要がある場合は、通常、代替方法よりもクリーンです。しかし、ここで実際に行っていることは、 を反復処理し、結果を使って何かを行い、それを別の にコピーすることだけです。xyfor a, b in zip(x, y)numpynditer[0, 4, 8, 16, 20]array

また、コメントで述べたように、 で反復を使用していることに気付いたnumpy場合は、通常、何か間違ったことをしています。速度の利点はすべて、numpyネイティブの C/Fortran または低レベルのベクトル操作でタイトなループを実行できることから得られます。s をループしてarrayいると、事実上、わずかに優れた構文で遅い 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

于 2013-01-07T03:22:59.143 に答える