web-dev-qa-db-ja.com

複素数のnumpyndarrayのabs()** 2を計算するための最もメモリ効率の高い方法

複雑なnumpyndarrayの絶対二乗値を計算するための最もメモリ効率の良い方法を探しています

_arr = np.empty((250000, 150), dtype='complex128')  # common size
_

正確にnp.abs()**2を実行するufuncは見つかりませんでした。

そのサイズとタイプの配列は約0.5GBを占めるので、私は主にメモリ効率の良い方法を探しています。

また、ポータブルにしたいので、理想的にはufuncのいくつかの組み合わせです。

これまでのところ、これは最高のはずだというのが私の理解です

_result = np.abs(arr)
result **= 2
_

不必要に_(**0.5)**2_を計算しますが、_**2_をインプレースで計算する必要があります。全体として、ピークメモリ要件は元の配列サイズ+結果の配列サイズのみです。結果は実際のものであるため、1.5 *元の配列サイズである必要があります。

役に立たない_**2_呼び出しを取り除きたい場合は、次のようなことをする必要があります

_result = arr.real**2
result += arr.imag**2
_

しかし、私が間違っていない場合、これは、両方実数部と虚数部の計算にメモリを割り当てる必要があることを意味します。したがって、ピークメモリ使用量は2.0 *元の配列サイズになります。 _arr.real_プロパティも、連続していない配列を返します(ただし、それほど問題にはなりません)。

足りないものはありますか?これを行うためのより良い方法はありますか?

編集1:明確にしないで申し訳ありません、arrを上書きしたくないので、outとして使用できません。

21
Ondřej Grover

おかげで numba.vectorize numbaの最近のバージョンでは、タスク用のnumpyユニバーサル関数の作成は非常に簡単です。

@numba.vectorize([numba.float64(numba.complex128),numba.float32(numba.complex64)])
def abs2(x):
    return x.real**2 + x.imag**2

私のマシンでは、中間配列を作成する純粋なnumpyバージョンと比較して3倍のスピードアップが見られます。

>>> x = np.random.randn(10000).view('c16')
>>> y = abs2(x)
>>> np.all(y == x.real**2 + x.imag**2)   # exactly equal, being the same operation
True
>>> %timeit np.abs(x)**2
10000 loops, best of 3: 81.4 µs per loop
>>> %timeit x.real**2 + x.imag**2
100000 loops, best of 3: 12.7 µs per loop
>>> %timeit abs2(x)
100000 loops, best of 3: 4.6 µs per loop
9
burnpanck

編集:このソリューションの最小メモリ要件は2倍で、わずかに高速です。ただし、コメントでの議論は参考になります。

これがより高速なソリューションで、結果はresに保存されます。

_import numpy as np
res = arr.conjugate()
np.multiply(arr,res,out=res)
_

ここで、複素数のabsのプロパティ、つまりabs(z) = sqrt(z*z.conjugate)を利用して、abs(z)**2 = z*z.conjugate

4
gg349

arr.realおよびarr.imagは、複雑な配列へのビューのみです。したがって、追加のメモリは割り当てられません。

1
Daniel

主な目標がメモリの節約である場合、NumPyのufuncsはオプションのoutパラメーターを取り、選択した配列に出力を送ることができます。所定の位置で操作を実行する場合に役立ちます。

最初のメソッドにこの小さな変更を加えると、arrで操作を完全に実行できます。

np.abs(arr, out=arr)
arr **= 2

littleの追加メモリのみを使用する複雑な方法の1つは、arrをその場で変更し、実数値の新しい配列を計算してから、arrを復元することです。

これは、符号に関する情報を格納することを意味します(複素数がすべて正の実数部と虚数部を持っていることがわかっている場合を除く)。各実数値または虚数値の符号に必要なビットは1つだけなので、これは1/16 + 1/16 == 1/8arrのメモリを使用します(作成したfloatの新しい配列に加えて)。

>>> signs_real = np.signbit(arr.real) # store information about the signs
>>> signs_imag = np.signbit(arr.imag)
>>> arr.real **= 2 # square the real and imaginary values
>>> arr.imag **= 2
>>> result = arr.real + arr.imag
>>> arr.real **= 0.5 # positive square roots of real and imaginary values
>>> arr.imag **= 0.5
>>> arr.real[signs_real] *= -1 # restore the signs of the real and imagary values
>>> arr.imag[signs_imag] *= -1

サインビットを格納することを犠牲にして、arrは変更されず、resultは必要な値を保持します。

1
Alex Riley