web-dev-qa-db-ja.com

Numpy / Scipy疎行列と密行列の効率的な乗算

私は次の方程式を実装するために取り組んでいます:

X =(Y.T * Y + Y.T * C * Y) ^ -1

Yは(n x f)行列で、Cは(n x n)対角行列です。 nは約300kで、fは100から200の間で変化します。最適化プロセスの一部として、この方程式はほぼ1億回使用されるため、非常に高速に処理する必要があります。

Yはランダムに初期化され、Cは非常にスパースな行列であり、対角線上の300kのうち数個だけが0とは異なります。Numpyの対角関数は密な行列を作成するため、Cをスパースなcsr行列として作成しました。しかし、方程式の最初の部分を解こうとすると、次のようになります。

r = dot(C, Y)

メモリ制限が原因でコンピュータがクラッシュします。次に、Yをcsr_matrixに変換して、同じ操作を行うことにしました。

r = dot(C, Ysparse)

このアプローチには1.38 msかかりました。しかし、疎行列を使用して密行列を格納しているため、このソリューションはやや「トリッキー」です。これが実際にどれほど効率的か疑問に思います。

だから私の質問は、Yをスパースに変えてパフォーマンスを向上させることなく、スパースCとデンスYを乗算する方法があるかどうかです。どういうわけか、大量のメモリを消費せずにCを対角線密度で表すことができれば、非常に効率的なパフォーマンスにつながる可能性がありますが、これが可能かどうかはわかりません。

私はあなたの助けに感謝します!

16
Willian Fuks

R = dot(C、Y)を計算するときにドット積がメモリの問題に遭遇する理由は、numpyのドット関数がスパース行列の処理をネイティブでサポートしていないためです。何が起こっているのかというと、numpyはスパース行列Cをpythonオブジェクトであり、numpy配列ではないと考えています。小規模で調べると、問題を直接確認できます。

>>> from numpy import dot, array
>>> from scipy import sparse
>>> Y = array([[1,2],[3,4]])
>>> C = sparse.csr_matrix(array([[1,0], [0,2]]))
>>> dot(C,Y)
array([[  (0, 0)    1
  (1, 1)    2,   (0, 0) 2
  (1, 1)    4],
  [  (0, 0) 3
  (1, 1)    6,   (0, 0) 4
  (1, 1)    8]], dtype=object)

明らかに、上記はあなたが興味を持っている結果ではありません。代わりにあなたがしたいのは、scipyのsparse.csr_matrix.dot関数を使用して計算することです。

r = sparse.csr_matrix.dot(C, Y)

以上コンパクトに

r = C.dot(Y)
24
M.H.

試してください:

import numpy as np
from scipy import sparse

f = 100
n = 300000

Y = np.random.Rand(n, f)
Cdiag = np.random.Rand(n) # diagonal of C
Cdiag[np.random.Rand(n) < 0.99] = 0

# Compute Y.T * C * Y, skipping zero elements
mask = np.flatnonzero(Cdiag)
Cskip = Cdiag[mask]

def ytcy_fast(Y):
    Yskip = Y[mask,:]
    CY = Cskip[:,None] * Yskip  # broadcasting
    return Yskip.T.dot(CY)

%timeit ytcy_fast(Y)

# For comparison: all-sparse matrices
C_sparse = sparse.spdiags([Cdiag], [0], n, n)
Y_sparse = sparse.csr_matrix(Y)
%timeit Y_sparse.T.dot(C_sparse * Y_sparse)

私のタイミング:

In [59]: %timeit ytcy_fast(Y)
100 loops, best of 3: 16.1 ms per loop

In [18]: %timeit Y_sparse.T.dot(C_sparse * Y_sparse)
1 loops, best of 3: 282 ms per loop
8
pv.

最初に、問題で完全な行列反転を実行する必要があると本当に確信していますか?ほとんどの場合、本当に必要なのはx = A ^ -1 yだけです。これは、はるかに簡単に解決できる問題です。

これが本当にそうである場合は、完全な行列の反転ではなく、逆行列の近似を計算することを検討します。行列の反転は本当にコストがかかるためです。逆行列の効率的な近似については、たとえば ランチョスアルゴリズム を参照してください。おまけとして、近似値をまばらに格納できます。さらに、行列とベクトルの演算のみが必要なため、逆行列に完全な行列を格納する必要もありません。

別の方法として、pyoperatorを使用して、.todenseメソッドで効率的な行列ベクトル演算を使用して逆行列を計算することもできます。対角行列用の特別なスパースコンテナーがあります。

Lanczosアルゴリズムの実装については、 pyoperators をご覧ください(免責事項:私はこのソフトウェアの共著者の1人です)。

2
Nicolas Barbey