web-dev-qa-db-ja.com

素人の派手なストライドをどのように理解するのですか?

私は現在numpyを行っており、numpyには「ストライド」と呼ばれるトピックがあります。私はそれが何であるかを理解しています。しかし、それはどのように機能しますか?オンラインで役立つ情報は見つかりませんでした。誰もが私に普通の言葉で理解させてくれますか?

25
Atif

Numpy配列の実際のデータは、データバッファーと呼ばれる同種の連続したメモリブロックに格納されます。詳細は NumPy internals を参照してください。 (デフォルト) row-major 順序を使用すると、2D配列は次のようになります。

enter image description here

多次元配列のインデックスi、j、k、...をデータバッファー内の位置(オフセット、バイト単位)にマッピングするために、NumPyはstrides。ストライドは、配列の各方向/次元に沿って1つの項目からnext項目に移動するためにメモリ内でジャンプするバイト数です。言い換えると、それは各次元の連続したアイテム間のバイト区切りです。

例えば:

>>> a = np.arange(1,10).reshape(3,3)
>>> a
array([[1, 2, 3],
       [4, 5, 6],
       [7, 8, 9]])

この2D配列には2つの方向があり、axes 行を横切って垂直方向に下向きに実行)とaxis-1(列を横切って水平方向に実行)があり、各アイテムのサイズは次のとおりです。

>>> a.itemsize  # in bytes
4  

したがって、a[0, 0] -> a[0, 1]から移動する(0番目の行に沿って、0番目の列から1番目の列に水平に移動する)データバッファーのバイトステップは4です。a[0, 1] -> a[0, 2]a[1, 0] -> a[1, 1]などでも同じです。これは、水平方向のストライド数が方向(axis-1)は4バイトです。

ただし、a[0, 0] -> a[1, 0]から移動する(0番目の列に沿って、0番目の行から1番目の行に垂直に移動する)には、最初に0番目の行の残りのすべての項目をトラバースして1番目の行に移動し、次に1番目の行に移動する必要があります。 a[1, 0]、つまりa[0, 0] -> a[0, 1] -> a[0, 2] -> a[1, 0]の項目に移動する行。したがって、垂直方向(axis-0)のストライド数は3 * 4 = 12バイトです。配列aが行優先順で格納されているため、a[0, 2] -> a[1, 0]から、一般的にi番目の行の最後の項目から(i + 1)番目の行の最初の項目までは、4バイトになることに注意してください。 。

それが理由です

>>> a.strides  # (strides[0], strides[1])
(12, 4)  

2D配列の水平方向(軸1)のストライドstrides[1]が、項目サイズ(列優先順の配列など)に必ずしも等しい必要がないことを示す別の例を次に示します。

>>> b = np.array([[1, 4, 7],
                  [2, 5, 8],
                  [3, 6, 9]]).T
>>> b
array([[1, 2, 3],
       [4, 5, 6],
       [7, 8, 9]])

>>> b.strides
(4, 12)

ここでstrides[1]はitem-sizeの倍数です。配列bは配列aと同一に見えますが、配列は異なります。内部的にb|1|4|7|2|5|8|3|6|9|として格納されます(転置はデータバッファーに影響を与えず、ストライドと形状のみを交換するため)、a|1|2|3|4|5|6|7|8|9|として。それらを同じように見せるのは、異なるストライドです。つまり、b[0, 0] -> b[0, 1]のバイトステップは3 * 4 = 12バイトで、b[0, 0] -> b[1, 0]は4バイトですが、a[0, 0] -> a[0, 1]は4バイトで、a[0, 0] -> a[1, 0]は12バイトです。

最後に重要なことですが、NumPyは、ストライドと形状を変更するオプションを使用して、既存の配列のビューを作成することを許可します。---(stride tricks を参照してください。例えば:

>>> np.lib.stride_tricks.as_strided(a, shape=a.shape[::-1], strides=a.strides[::-1])
array([[1, 4, 7],
       [2, 5, 8],
       [3, 6, 9]])

これは、配列aを転置することと同じです。

追加しますが、詳細には触れませんが、アイテムサイズの倍数ではないストライドを定義することもできます。次に例を示します。

>>> a = np.lib.stride_tricks.as_strided(np.array([1, 512, 0, 3], dtype=np.int16), 
                                        shape=(3,), strides=(3,))
>>> a
array([1, 2, 3], dtype=int16)

>>> a.strides[0]
3

>>> a.itemsize
2
48
Andreas K.

@AndyKによるすばらしい回答に加えて、私は Numpy MedKit からnumpyストライドについて学びました。そこで彼らは次のように問題のある使用を示します:

与えられた入力

x = np.arange(20).reshape([4, 5])
>>> x
array([[ 0,  1,  2,  3,  4],
       [ 5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14],
       [15, 16, 17, 18, 19]])

予想される出力

array([[[  0,  1,  2,  3,  4],
        [  5,  6,  7,  8,  9]],

       [[  5,  6,  7,  8,  9],
        [ 10, 11, 12, 13, 14]],

       [[ 10, 11, 12, 13, 14],
        [ 15, 16, 17, 18, 19]]])

これを行うには、次の用語を知っておく必要があります。

shape-各軸に沿った配列の次元。

strides-特定の次元に沿って次の項目に進むためにスキップする必要があるメモリのバイト数。

>>> x.strides
(20, 4)

>>> np.int32().itemsize
4

期待される出力を見ると:

array([[[  0,  1,  2,  3,  4],
        [  5,  6,  7,  8,  9]],

       [[  5,  6,  7,  8,  9],
        [ 10, 11, 12, 13, 14]],

       [[ 10, 11, 12, 13, 14],
        [ 15, 16, 17, 18, 19]]])

配列の形状とストライドを操作する必要があります。出力形状は(3、2、5)、つまり3つの項目である必要があり、各項目には2つの行(m == 2)が含まれ、各行には5つの要素があります。

ストライドを(20、4)から(20、20、4)に変更する必要があります。新しい出力配列の各項目は新しい行から始まり、各行は20バイト(各4バイトの5つの要素)で構成され、各要素は4バイト(int32)を占めます。

そう:

>>> from numpy.lib import stride_tricks
>>> stride_tricks.as_strided(x, shape=(3, 2, 5),
                                strides=(20, 20, 4))
...
array([[[  0,  1,  2,  3,  4],
        [  5,  6,  7,  8,  9]],

       [[  5,  6,  7,  8,  9],
        [ 10, 11, 12, 13, 14]],

       [[ 10, 11, 12, 13, 14],
        [ 15, 16, 17, 18, 19]]])

別の方法は次のとおりです。

>>> d = dict(x.__array_interface__)
>>> d['shape'] = (3, 2, 5)
>>> s['strides'] = (20, 20, 4)

>>> class Arr:
...     __array_interface__ = d
...     base = x

>>> np.array(Arr())
array([[[  0,  1,  2,  3,  4],
        [  5,  6,  7,  8,  9]],

       [[  5,  6,  7,  8,  9],
        [ 10, 11, 12, 13, 14]],

       [[ 10, 11, 12, 13, 14],
        [ 15, 16, 17, 18, 19]]])

私は numpy.hstack または numpy.vstack の代わりにこの方法を頻繁に使用しており、計算上ははるかに高速です。

注意:

このトリックで非常に大きな配列を使用する場合、正確なstridesを計算することはそれほど簡単ではありません。私は通常、目的の形状のnumpy.zeroes配列を作成し、array.stridesを使用してストライドを取得し、これを関数stride_tricks.as_stridedで使用します。

それが役に立てば幸い!

13
Rick M.