OctaveよりもSciPyの方が100倍以上遅い、大きなスパース行列の最小の固有ベクトルを見つける


12

大きな対称正方スパース行列(最大30000x30000)の最小固有値に対応する少数(5-500)の固有ベクトルを計算しようとしています。値の0.1%未満が非ゼロです。

私は現在scipy.sparse.linalg.eigshをシフトインバートモード(sigma = 0.0)で使用しています。これは、トピックに関するさまざまな投稿でわかった解決策です。ただし、ほとんどの場合、問題を解決するのに最大1時間かかります。一方、ドキュメントから予想された最大固有値(私のシステムではサブ秒)を要求すると、関数は非常に高速になります。

私は仕事でMatlabに慣れているので、Octaveで問題を解決しようとしました。これにより、eigs(sigma = 0)を使用して数秒(サブ10秒)で同じ結果が得られました。固有ベクトルの計算を含むアルゴリズムのパラメータースイープを実行したいので、Pythonでもそのような時間の増加が得られるでしょう。

最初にパラメーター(特に許容誤差)を変更しましたが、タイムスケールではあまり変わりませんでした。

私はWindowsでAnacondaを使用していますが、scipyが使用するLAPACK / BLAS(これは非常に苦痛でした)をmkl(デフォルトのAnaconda)からOpenBlas(ドキュメントに従ってOctaveが使用)に切り替えようとしましたが、変更を確認できませんでしたパフォーマンス。

使用したARPACKに変更点があるかどうか(およびその方法)はわかりませんでした。

以下のコードのテストケースを次のドロップボックスフォルダーにアップロードしました:https ://www.dropbox.com/sh/l6aa6izufzyzqr3/AABqij95hZOvRpnnjRaETQmka?dl=0

Pythonで

import numpy as np
from scipy.sparse import csr_matrix, csc_matrix, linalg, load_npz   
M = load_npz('M.npz')
evals, evecs = linalg.eigsh(M,k=6,sigma=0.0)

オクターブで:

M=dlmread('M.txt');
M=spconvert(M);
[evecs,evals] = eigs(M,6,0);

どんな助けもappriciated!

コメントと提案に基づいて試したいくつかの追加オプション:

オクターブ: eigs(M,6,0)eigs(M,6,'sm')私は同じ結果を与えます:

[1.8725e-05 1.0189e-05 7.5622e-06 7.5420e-07 -1.2239e-18 -2.5674e-16]

eigs(M,6,'sa',struct('tol',2))収束しながら

[1.0423 2.7604 6.1548 11.1310 18.0207 25.3933] 

はるかに高速ですが、許容値が2を超える場合のみ、それ以外の場合はまったく収束せず、値は大きく異なります。

Pythonの: eigsh(M,k=6,which='SA')eigsh(M,k=6,which='SM')の両方が(無収束のARPACKエラーに達して)収束しません。eigsh(M,k=6,sigma=0.0)(ほぼ1時間後)いくつかの固有値のみを提供します。これは、最小値のオクターブとは異なります(1つの追加の小さな値でも見つかります)。

[3.82923317e-17 3.32269886e-16 2.78039665e-10 7.54202273e-07 7.56251500e-06 1.01893934e-05]

許容誤差が十分に高い場合、からも結果がeigsh(M,k=6,which='SA',tol='1')得られます。これは、他の取得された値に近づきます

[4.28732218e-14 7.54194948e-07 7.56220703e-06 1.01889544e-05, 1.87247350e-05 2.02652719e-05]

ここでも、小さな固有値の数が異なります。計算時間はまだほぼ30分です。非常に小さなさまざまな値は理解できるかもしれませんが、それらは0の倍数を表す可能性があるため、異なる多重度は私を困惑させます。

さらに、SciPyとOctaveにはいくつかの根本的な違いがあるようですが、まだわかりません。


2
1-オクターブコードの[evals、evecs]を角かっこで囲むつもりだったと思いますか?2-Mの小さな例を含めることができますか?それが可能であれば、おそらく1つのジェネレータスクリプトですか?
Nick J

1-はい、正確に、投稿を編集しました。2-データのいくつかのサブマトリックスのパフォーマンスを試しましたが、Octaveは常に高速であるように見えますが、5000x5000未満の小さいマトリックスの場合、2〜5倍の係数であり、それを超えると本当に醜くなります。そして、その「実際のデータ」なので、ジェネレータースクリプトを指定できません。どういうわけかサンプルをアップロードする標準的な方法はありますか?スパース性のため、npzファイルはかなり小さいです。
Spacekiller23

クラウドストレージ施設へのリンクを共有できると思います。
Patol75

THX。元の投稿にDropboxリンクを含め、コードを実際の例に更新しました。
Spacekiller23

1
要点を強調するために、私はMatlab R2019bでテストしましたが、Python 3.7、Scipy 1.2.1(26倍高速)では84秒対36.5分でした。
ビル

回答:


1

Matlab / Octaveを持っていないので、推測といくつかのコメント:

あなたのように固有値> = 0の対称行列の小さな固有値を見つけるには、shift-invertよりも次の方法が高速です。

# flip eigenvalues e.g.
# A:     0 0 0 ... 200 463
# Aflip: 0 163 ... 463 463 463
maxeval = eigsh( A, k=1 )[0]  # biggest, fast
Aflip = maxeval * sparse.eye(n) - A
bigevals, evecs = eigsh( Aflip, which="LM", sigma=None ... )  # biggest, near 463
evals = maxeval - bigevals  # flip back, near 463 -> near 0
# evecs are the same

eigsh( Aflip )大きい固有ペアの場合は、小さい場合のシフト反転A * xよりも高速です。これはsolve()、シフト反転が実行する必要がある速度よりも速いためです。Matlab / OctaveはAflip、コレスキーで正定性の簡単なテストを行った後、おそらくこれを自動的に行うことができます。Matlab / Octaveで
実行できますeigsh( Aflip )か?

精度/速度に影響を与える可能性があるその他の要因:

Arpackの開始ベクトルのデフォルトv0はランダムベクトルです。私はを使用していますがv0 = np.ones(n)、これはひどいかもしれませんAが、再現可能です:)

このA行列はほぼ正確にA * ones0です。

マルチコア:openblas / Lapackを備えたscipy-arpackは、私のiMacの4つのコアのうち3.9を使用します。Matlab / Octaveはすべてのコアを使用しますか?


いくつかのkおよびのscipy-Arpack固有値を次に示します。gist.githubのtol下のログファイルからgrepさ​​れています

k 10  tol 1e-05:    8 sec  eigvals [0 8.5e-05 0.00043 0.0014 0.0026 0.0047 0.0071 0.0097 0.013 0.018] 
k 10  tol 1e-06:   44 sec  eigvals [0 3.4e-06 2.8e-05 8.1e-05 0.00015 0.00025 0.00044 0.00058 0.00079 0.0011] 
k 10  tol 1e-07:  348 sec  eigvals [0 3e-10 7.5e-07 7.6e-06 1.2e-05 1.9e-05 2.1e-05 4.2e-05 5.7e-05 6.4e-05] 

k 20  tol 1e-05:   18 sec  eigvals [0 5.1e-06 4.5e-05 0.00014 0.00023 0.00042 0.00056 0.00079 0.0011 0.0015 0.0017 0.0021 0.0026 0.003 0.0037 0.0042 0.0047 0.0054 0.006
k 20  tol 1e-06:   73 sec  eigvals [0 5.5e-07 7.4e-06 2e-05 3.5e-05 5.1e-05 6.8e-05 0.00011 0.00014 0.00016 0.0002 0.00025 0.00027 0.0004 0.00045 0.00051 0.00057 0.00066
k 20  tol 1e-07:  267 sec  eigvals [-4.8e-11 0 7.5e-07 7.6e-06 1e-05 1.9e-05 2e-05 2.2e-05 4.2e-05 5.1e-05 5.8e-05 6.4e-05 6.9e-05 8.3e-05 0.00011 0.00012 0.00013 0.00015

k 50  tol 1e-05:   82 sec  eigvals [-4e-13 9.7e-07 1e-05 2.8e-05 5.9e-05 0.00011 0.00015 0.00019 0.00026 0.00039 ... 0.0079 0.0083 0.0087 0.0092 0.0096 0.01 0.011 0.011 0.012
k 50  tol 1e-06:  432 sec  eigvals [-1.4e-11 -4e-13 7.5e-07 7.6e-06 1e-05 1.9e-05 2e-05 2.2e-05 4.2e-05 5.1e-05 ... 0.00081 0.00087 0.00089 0.00096 0.001 0.001 0.0011 0.0011
k 50  tol 1e-07: 3711 sec  eigvals [-5.2e-10 -4e-13 7.5e-07 7.6e-06 1e-05 1.9e-05 2e-05 2.2e-05 4.2e-05 5.1e-05 ... 0.00058 0.0006 0.00063 0.00066 0.00069 0.00071 0.00075

versions: numpy 1.18.1  scipy 1.4.1  umfpack 0.3.2  python 3.7.6  mac 10.10.5 

Matlab / Octaveはほぼ同じですか?そうでない場合は、すべての賭けがオフになっています。最初に正しさを確認してから、速度を確認します。

なぜ固有値がそれほど揺れているのですか?負ではないと考えられる行列のTiny <0は、丸め誤差の兆候です が、小さなシフトの通常のトリックはA += n * eps * sparse.eye(n)役に立ちません。


これはAどこから来たのですか?同様のA、より小さな、またはスパースなものを生成できますか?

お役に立てれば。


ご入力いただきありがとうございます。(非常に)返信が遅くなりました。私がこれを使用したプロジェクトはすでに完了していますが、私はまだ興味があるのでチェックしました。悲しいことに、Ocatveの固有値は異なります。k= 10の場合[-2.5673e-16 -1.2239e-18 7.5420e-07 7.5622e-06 1.0189e-05 1.8725e-05 2.0265e-05 2.1568e- 05 4.2458e-05 5.1030e-05]これも1e-5から1e-7の範囲の許容値とは無関係です。したがって、ここには別の違いがあります。scipy(提案を含む)が、照会された値の数に応じて異なる小さな値を生成するのは奇妙だと思いませんか?
Spacekiller23

@ Spacekiller23、これはバグでしたが、scipy 1.4.1で修正されました(scipy / issues / 11198を参照)。バージョンを確認できますか?またtol、小さな固有値の場合は面倒です。必要に応じて、新しい質問をしてください。
denis

1

これは古いことはわかっていますが、同じ問題がありました。ここで確認しましたか(https://docs.scipy.org/doc/scipy/reference/tutorial/arpack.html)?

sigmaを低い数値(0)に設定する場合、低い値にしたい場合でも、which = 'LM'を設定する必要があるようです。これは、シグマを設定すると、必要な値(この場合は低い値)が高く見えるように変換されるため、必要な値(低い固有値)を取得するのに非常に高速な「LM」メソッドを利用できるためです。 )。


これは実際にパフォーマンスを変えましたか?それは私にとって驚きです。私はあなたが投稿したリンクを知っていて、私の例ではwhich = 'LM'を暗黙的に指定しました。未設定のデフォルト値は「LM」だからです。私はまだチェックしましたが、私の例ではパフォーマンスは変わりません。
Spacekiller23

確かに、Pythonとオクターブの違いは似ています。また、分解しようとしている大きな行列があり、eigsh(matrix、k = 7、which = 'LM'、sigma = 1e-10)を使用してしまいました。もともと、私はwhich = 'SM'を誤って指定していましたが、最小の固有値を取得するためにそれを行う必要があると考えていました。次に、その記事を見つけて、より高速な「LM」に指定し、kを任意の値に設定するだけで高速化できることに気づきました。あなたのマトリックスは実際に仙人ですか?
アンソニー・ガッティ

0

私が最初に言いたいのは、なぜあなたと@Billが報告した結果がそうであるかがわからないということです。eigs(M,6,0)Octaveがに対応するのk=6 & sigma=0か、それとも他の何かなのかと単に疑問に思います。

scipyでは、シグマを指定しなければ、この方法で適切な時間で結果を得ることができます。

import numpy as np
from scipy.sparse import csr_matrix
from scipy.sparse.linalg import eigsh
from time import perf_counter
M = np.load('M.npz')
a = csr_matrix((M['data'], M['indices'], M['indptr']), shape=M['shape'])
t = perf_counter()
b, c = eigsh(a, k=50, which='SA', tol=1e1)
print(perf_counter() - t)
print(b)

これが理にかなっているのかどうかは、私にはまったくわかりません。

0.4332823531003669
[4.99011753e-03 3.32467891e-02 8.81752215e-02 1.70463893e-01
 2.80811313e-01 4.14752072e-01 5.71103821e-01 7.53593653e-01
 9.79938915e-01 1.14003837e+00 1.40442848e+00 1.66899183e+00
 1.96461415e+00 2.29252666e+00 2.63050114e+00 2.98443218e+00
 3.38439528e+00 3.81181747e+00 4.26309942e+00 4.69832271e+00
 5.22864462e+00 5.74498014e+00 6.22743988e+00 6.83904055e+00
 7.42379697e+00 7.97206446e+00 8.62281827e+00 9.26615266e+00
 9.85483434e+00 1.05915030e+01 1.11986296e+01 1.18934953e+01
 1.26811461e+01 1.33727614e+01 1.41794599e+01 1.47585155e+01
 1.55702295e+01 1.63066947e+01 1.71564622e+01 1.78260727e+01
 1.85693454e+01 1.95125277e+01 2.01847294e+01 2.09302671e+01
 2.18860389e+01 2.25424795e+01 2.32907153e+01 2.37425085e+01
 2.50784800e+01 2.55119112e+01]

私がsigmaを使用して適切な時間で結果を得る唯一の方法は、MをLinearOperatorとして提供することです。私はこのことにあまり慣れていませんが、私の理解では、実装が単位行列を表していることがわかります。これは、呼び出しで指定されていない場合にMになるべきものです。これは、直接解決(LU分解)を実行する代わりに、scipyが反復ソルバーを使用するためです。比較として、M = np.identity(a.shape[0])完全に同じである必要があるを指定した場合、eigshは結果を生成するまでに時間がかかります。sigma=0が提供されている場合、このアプローチは機能しないことに注意してください。しかし、sigma=0本当にそれが便利かどうかはわかりません。

import numpy as np
from scipy.sparse import csr_matrix
from scipy.sparse.linalg import eigs, eigsh, LinearOperator
from time import perf_counter


def mv(v):
    return v


M = np.load('M.npz')
a = csr_matrix((M['data'], M['indices'], M['indptr']), shape=M['shape'])
t = perf_counter()
b, c = eigsh(a, M=LinearOperator(shape=a.shape, matvec=mv, dtype=np.float64),
             sigma=5, k=50, which='SA', tol=1e1, mode='cayley')
print(perf_counter() - t)
print(np.sort(-5 * (1 + b) / (1 - b)))

繰り返しますが、それが正しいかどうかはわかりませんが、以前とは明らかに異なります。それはscipyからの誰かの入力があると素晴らしいでしょう。

1.4079377939924598
[3.34420263 3.47938816 3.53019328 3.57981026 3.60457277 3.63996294
 3.66791416 3.68391585 3.69223712 3.7082205  3.7496456  3.76170023
 3.76923989 3.80811939 3.81337342 3.82848729 3.84137264 3.85648208
 3.88110869 3.91286153 3.9271108  3.94444577 3.97580798 3.98868207
 4.01677424 4.04341426 4.05915855 4.08910692 4.12238969 4.15283192
 4.16871081 4.1990492  4.21792125 4.24509036 4.26892806 4.29603036
 4.32282475 4.35839271 4.37934257 4.40343219 4.42782208 4.4477206
 4.47635849 4.51594603 4.54294049 4.56689989 4.58804775 4.59919363
 4.63700551 4.66638214]

ご意見とフィードバックをありがとうございます。私はあなたのポイントに適切な答えを与えるためにいくつかのことを試みました。1.私の手元のタスクでは、k個の最小固有値/ベクトルを見つける必要があります。シグマ= 0を使用してのアプローチそのためにもscipyのダウンロードのドキュメントに記載されている:docs.scipy.org/doc/scipy/reference/tutorial/arpack.html 2.私は、元の質問に編集したいくつかのより多くのオプションを、試してみました。3. OctaveとSciPy、eigs(M、6,0)とk = 6のドキュメンタリーを理解しているので、simga = 0は同じはずです。
Spacekiller23

4.私の行列は実数で正方であるため、オプションとしてSAとSMの間に違いはないはずだと思いましたが、少なくとも計算には明らかです。私はここで間違ったトラックにいますか?全体としては、より多くの質問を意味しますが、私からの実際の回答や解決策はありません。
Spacekiller23
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.