連続配列と非連続配列の違いは何ですか?


99

reshape()関数についての派手なマニュアルでは、

>>> a = np.zeros((10, 2))
# A transpose make the array non-contiguous
>>> b = a.T
# Taking a view makes it possible to modify the shape without modifying the
# initial object.
>>> c = b.view()
>>> c.shape = (20)
AttributeError: incompatible shape for a non-contiguous array

私の質問は:

  1. 連続および非連続配列とは何ですか?それは連続メモリブロックとは何かのようなCの連続メモリブロックに似ていますか?
  2. これら2つの間にパフォーマンスの違いはありますか?どちらを使用すればよいですか?
  3. 転置により配列が不連続になるのはなぜですか?
  4. なぜc.shape = (20)エラーをスローするのincompatible shape for a non-contiguous arrayですか?

ご回答有難うございます!

回答:


219

連続した配列は、メモリの途切れのないブロックに格納された配列です。配列の次の値にアクセスするには、次のメモリアドレスに移動します。

2D配列を考えarr = np.arange(12).reshape(3,4)ます。次のようになります。

ここに画像の説明を入力してください

コンピュータのメモリに、の値はarr次のように保存されます。

ここに画像の説明を入力してください

この手段ではarrあるC連続のでアレイの行がメモリの連続ブロックとして格納されます。次のメモリアドレスは、その行の次の行の値を保持します。列を下に移動する場合は、3つのブロックをジャンプするだけです(たとえば、0から4にジャンプするには、1、2、3をスキップすることを意味します)。

で配列を転置するとarr.T、隣接する行エントリが隣接するメモリアドレスに存在しないため、Cの隣接性が失われます。しかし、arr.TあるFortranの連続したので、列がメモリの連続ブロックにあります。

ここに画像の説明を入力してください


パフォーマンスの点で、互いに隣接しているメモリアドレスへのアクセスは、より「分散」しているアドレスへのアクセスよりも非常に高速です(RAMから値をフェッチすると、CPUにフェッチされてキャッシュされる多くの隣接アドレスが必要になる場合があります)。多くの場合、隣接する配列に対する操作はより高速になります。

Cの連続したメモリレイアウトの結果として、通常、行単位の演算は列単位の演算よりも高速です。たとえば、通常、

np.sum(arr, axis=1) # sum the rows

よりも少し速いです:

np.sum(arr, axis=0) # sum the columns

同様に、Fortran連続配列の場合、列の操作はわずかに速くなります。


最後に、新しい形状を割り当ててFortranの連続配列をフラット化できないのはなぜですか?

>>> arr2 = arr.T
>>> arr2.shape = 12
AttributeError: incompatible shape for a non-contiguous array

これを可能にするために、NumPyは次のように行をarr.Tまとめる必要があります。

ここに画像の説明を入力してください

shape属性の設定はCの順序を直接前提としています-つまり、NumPyは行単位で操作を実行しようとします。)

これは不可能です。いずれの軸でも、NumPy は、配列の次の要素に到達するために一定のストライド長(移動するバイト数)を持つ必要があります。arr.Tこの方法でフラット化すると、配列の連続した値を取得するために、メモリ内を前後にスキップする必要があります。

arr2.reshape(12)代わりに書き込んだ場合、NumPyはarr2の値を新しいメモリブロックにコピーします(この図形の元のデータのビューを返すことができないため)。


これが理解できません。少し詳しく説明してください。私の意見では、メモリ内の不可能な順序付けの最新のグラフィック表現では、実際にはストライドは一定です。たとえば、0から1に移動するには、ストライドは1バイト(各要素が1バイトであるとしましょう)で、各列で同じです。同様に、ストライドは、行の1つの要素から次の要素に移動するための4バイトであり、これも一定です。
Vesnog

2
@Vesnog失敗した2D arr2から1Dへの形状変更(12,)はCオーダーを使用します。つまり、軸1は軸0の前に巻き戻されます(つまり、4つの行のそれぞれを隣り合わせに配置して、目的の1D配列を作成する必要があります)。この整数のシーケンス(0、4、8、1、5、9、2、6、10、3、7、11)を一定のストライド長(ジャンプしてアクセスするバイト)を使用してバッファーから読み取ることはできません。これらの要素の順番は、4、4、-7、4、4、4、-7、4、4、7、4、4)です。NumPyでは、軸ごとに一定のストライド長が必要です。
アレックスライリー

最初はありがとうと思いましたが、新しい配列を作成すると思いましたが、古い配列のメモリを使用しています。
Vesnog

@AlexRiley配列がCまたはFの最も近い順序でマークされている場合、舞台裏で何が起こりますか?たとえば、すべてのNxD配列arrを取得し、print(arr [:、::-1] .flags)を実行します。この状況ではどうなりますか?配列は確かにCまたはFの順序になっていると思いますが、どちらが正しいのでしょうか。そして、両方のフラグがFalseの場合、numpyのどの最適化が失われますか?
チャン

@Jjang:NumPyが配列をCと見なすかFと見なすかは、形状とストライドに完全に依存します(基準はここにあります)。したがって、はとarr[:, ::-1]同じメモリバッファのビューですがarr、NumPyはバッファ内の値を「非標準」の順序でトラバースしているため、CまたはFの順序とは見なしません...
Alex Riley

12

たぶん、12の異なる配列値を持つこの例は役立つでしょう:

In [207]: x=np.arange(12).reshape(3,4).copy()

In [208]: x.flags
Out[208]: 
  C_CONTIGUOUS : True
  F_CONTIGUOUS : False
  OWNDATA : True
  ...
In [209]: x.T.flags
Out[209]: 
  C_CONTIGUOUS : False
  F_CONTIGUOUS : True
  OWNDATA : False
  ...

C order値は、それらが生成された順序である。転置ものではありません

In [212]: x.reshape(12,)   # same as x.ravel()
Out[212]: array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11])

In [213]: x.T.reshape(12,)
Out[213]: array([ 0,  4,  8,  1,  5,  9,  2,  6, 10,  3,  7, 11])

両方の1Dビューを取得できます

In [214]: x1=x.T

In [217]: x.shape=(12,)

形状xも変更可能です。

In [220]: x1.shape=(12,)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-220-cf2b1a308253> in <module>()
----> 1 x1.shape=(12,)

AttributeError: incompatible shape for a non-contiguous array

ただし、トランスポーズの形状は変更できません。data以下のままである0,1,2,3,4...としてアクセスアクセスすることができないため、0,4,8...1Dアレイです。

ただし、のコピーはx1変更できます。

In [227]: x2=x1.copy()

In [228]: x2.flags
Out[228]: 
  C_CONTIGUOUS : True
  F_CONTIGUOUS : False
  OWNDATA : True
  ...
In [229]: x2.shape=(12,)

見てstridesも役立つかもしれません。ストライドは、次の値に到達するためにどれだけ(バイト単位で)ステップする必要があるかを示します。2D配列の場合、2つのストライド値があります。

In [233]: x=np.arange(12).reshape(3,4).copy()

In [234]: x.strides
Out[234]: (16, 4)

次の行に移動するには、16バイトずつステップし、次の列のみを4にします。

In [235]: x1.strides
Out[235]: (4, 16)

トランスポーズは、ストライドの順序を切り替えるだけです。次の行はわずか4バイト、つまり次の数値です。

In [236]: x.shape=(12,)

In [237]: x.strides
Out[237]: (4,)

形状を変更すると、ストライドも変更されます。一度に4バイトずつバッファをステップスルーするだけです。

In [238]: x2=x1.copy()

In [239]: x2.strides
Out[239]: (12, 4)

x2ようx1に見えますが、独自のデータバッファがあり、値の順序が異なります。次の列は4バイトオーバーになり、次の行は12(3 * 4)になります。

In [240]: x2.shape=(12,)

In [241]: x2.strides
Out[241]: (4,)

と同様にx、形状を1dに変更すると、ストライドがに減少し(4,)ます。

の場合x1、データが0,1,2,...順序どおりであるため、1dストライドはありません0,4,8...

__array_interface__ 配列情報を表示するもう1つの便利な方法です。

In [242]: x1.__array_interface__
Out[242]: 
{'strides': (4, 16),
 'typestr': '<i4',
 'shape': (4, 3),
 'version': 3,
 'data': (163336056, False),
 'descr': [('', '<i4')]}

x1データ・バッファ・アドレスは、の場合と同じになりx、それがデータを共有します、。 x2別のバッファアドレスがあります。

およびコマンドにorder='F'パラメータを追加して実験することもできます。copyreshape

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.