web-dev-qa-db-ja.com

大きなNumpyアレイを操作するためのテクニック?

1つまたは複数の大きなNumpyアレイで多くの中間操作を実行する必要がある場合があります。これにより、すぐにMemoryErrorsになる可能性があります。これまでの私の調査では、UはPickling(Pickle、CPickle、Pytablesなど)とgc.collect()がこれを軽減する方法であることを発見しました。経験豊富なプログラマーが大量のデータを処理するときに使用する他の手法があるかどうか疑問に思いました(もちろん、戦略/コードの冗長性を削除する以外に)。

また、私が確信していることが1つあるとすれば、無料のものは何もないということです。これらの手法のいくつかでは、トレードオフ(つまり、速度、堅牢性など)は何ですか?

20
Noob Saibot

私はあなたの痛みを感じます...あなたは時々あなたが後で捨てる値にあなたの配列の数倍のサイズを保存することになるでしょう。配列内の1つのアイテムを一度に処理する場合、これは関係ありませんが、ベクトル化するときにあなたを殺す可能性があります。

説明のために、仕事の例を使用します。私は最近、説明されているアルゴリズムをコーディングしました ここ numpyを使用しています。これは、RGB画像を取得してCMYK画像に変換するカラーマップアルゴリズムです。ピクセルごとに繰り返されるプロセスは次のとおりです。

  1. 3次元ルックアップテーブルへのインデックスとして、すべてのRGB値の最上位4ビットを使用します。これにより、LUT内の立方体の8つの頂点のCMYK値が決定されます。
  2. 前の手順の頂点値に基づいて、すべてのRGB値の最下位4ビットを使用して、そのキューブ内を補間します。これを行う最も効率的な方法は、処理される画像のサイズのuint8の16配列を計算する必要があります。画像を処理するために画像の6倍のストレージが必要になるのと同等の24ビットRGB画像の場合。

これを処理するためにできることがいくつかあります。

1.分割統治

たぶん、1回のパスで1,000x1,000の配列を処理することはできません。しかし、100x1,000の10個の配列を反復するforループでpython forループを使用してそれを実行できる場合でも、a python = 1,000,000アイテムを超えるイテレータ!遅くなりますが、それほどではありません。

2.高価な計算をキャッシュする

これは上記の補間の例に直接関係しており、目を離さないでおく価値はありますが、見つけるのは困難です。各次元に4ビットの3次元キューブを補間しているため、16x16x16バイトの16配列に格納できる、16x16x16の可能な結果のみがあります。そのため、膨大なメモリコストですべてのピクセルに対して同じ操作をやり直すのではなく、事前に計算して64KBのメモリを使用して保存し、画像全体の値を1つずつ検索できます。これは、64x64ピクセルの小さな画像に対してすでに効果があり、基本的に、配列を細分化することなく、ピクセル数のx6倍の画像を処理できます。

3. dtypesを賢く使用します

中間値が単一のuint8に収まる場合は、int32sの配列を使用しないでください。これは、サイレントオーバーフローによる不思議なエラーの悪夢に変わる可能性がありますが、注意すれば、リソースを大幅に節約できます。

22
Jaime

最初の最も重要なトリック:いくつかの大きな配列を割り当て、それらの一部を使用してリサイクルするのではなく、一時的なを大量に収集して廃棄/ガベージコレクションします配列。少し古風に聞こえますが、注意深くプログラミングすると、スピードアップが印象的です。 (配置とデータの局所性をより適切に制御できるため、数値コードをより効率的にすることができます。)

2番目:使用 numpy.memmap そしてディスクへのアクセスのOSキャッシングが十分に効率的であることを願っています。

3番目:@Jaimeが指摘しているように、マトリックス全体が大きすぎる場合は、サブマトリックスのブロックを解除します。

編集:

SEのこの answer で指摘されているように、不必要なリスト内包表記は避けてください。

9
Stefano M

dask.array ライブラリは、ブロックされたアルゴリズムを使用して、複数のコアを持つメモリより大きい配列を処理するnumpyインターフェイスを提供します。

SpartanDistarray 、および Biggus を調べることもできます。

5
MRocklin

可能であれば、 numexpr を使用してください。 a**2 + b**2 + 2*a*bのような数値計算の場合(aおよびbが配列の場合)

  1. 同じ配列が式で複数回発生した場合に、メモリの局所性を処理して(したがって、キャッシュの最適化を)、高速で最小限のメモリオーバーヘッドで実行されるマシンコードをコンパイルします。

  2. デュアルコアまたはクアッドコアCPUのすべてのコアを使用します。

  3. numpyの拡張であり、代替ではありません。

中規模および大規模のアレイの場合、numpyだけの場合よりも高速です。

上記のWebページを見てください。numexprがあなたに適しているかどうかを理解するのに役立つ例があります。

3
Hansemann

計算のすべての中間結果を保存したい場合(常に中間結果をメモリに保持する必要がないため)、他の回答で述べられているすべてに加えて、さまざまなタイプの集計の後にaccumulateからnumpyを使用することもできます。

集計

バイナリufuncの場合、オブジェクトから直接計算できる興味深い集計がいくつかあります。たとえば、特定の操作で配列を縮小したい場合は、任意のufuncのreduceメソッドを使用できます。 reduceは、結果が1つだけになるまで、配列の要素に特定の操作を繰り返し適用します。

たとえば、add ufuncでreduceを呼び出すと、配列内のすべての要素の合計が返されます。

x = np.arange(1, 6)
np.add.reduce(x) # Outputs 15

同様に、multiply ufuncでreduceを呼び出すと、すべての配列要素の積が得られます。

np.multiply.reduce(x) # Outputs 120

累積

計算のすべての中間結果を保存したい場合は、代わりにaccumulateを使用できます。

np.add.accumulate(x) # Outputs array([ 1,  3,  6, 10, 15], dtype=int32)
np.multiply.accumulate(x) # Outputs array([  1,   2,   6,  24, 120], dtype=int32)

1つ以上の大きなNumpy配列で多くの中間操作を実行しながら、これらのnumpy操作を賢く使用すると、追加のライブラリを使用せずに優れた結果を得ることができます。

0
Harvey