Individuazione degli autovettori più piccoli con matrice sparsa di grandi dimensioni, oltre 100 volte più lenta in SciPy che in Octave


12

Sto cercando di calcolare pochi (5-500) autovettori corrispondenti ai più piccoli autovalori di grandi matrici quadrate simmetriche (fino a 30000x30000) con meno dello 0,1% dei valori diversi da zero.

Attualmente sto usando scipy.sparse.linalg.eigsh in modalità shift-invert (sigma = 0.0), che ho capito attraverso vari post sull'argomento è la soluzione preferita. Tuttavia, nella maggior parte dei casi sono necessari fino a 1 ora per risolvere il problema. D'altra parte la funzione è molto veloce, se chiedo gli autovalori più grandi (sotto secondi sul mio sistema), come previsto dalla documentazione.

Dato che ho più familiarità con Matlab dal lavoro, ho provato a risolvere il problema in Octave, il che mi ha dato lo stesso risultato usando gli eigs (sigma = 0) in pochi secondi (sotto 10s). Dal momento che voglio fare una scansione dei parametri dell'algoritmo incluso il calcolo degli autovettori, quel tipo di guadagno di tempo sarebbe fantastico avere anche in Python.

Per prima cosa ho modificato i parametri (in particolare la tolleranza), ma ciò non è cambiato molto nei tempi.

Sto usando Anaconda su Windows, ma ho provato a cambiare il LAPACK / BLAS usato da Scipy (che era un grande dolore) da Mkl (predefinito Anaconda) a OpenBlas (usato da Octave secondo la documentazione), ma non sono riuscito a vedere un cambiamento in prestazione.

Non sono riuscito a capire se c'era qualcosa da cambiare sull'ARPACK usato (e come)?

Ho caricato una testcase per il codice seguente nella seguente cartella dropbox: https://www.dropbox.com/sh/l6aa6izufzyzqr3/AABqij95hZOvRpnnjRaETQmka?dl=0

In 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)

In Octave:

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

Qualsiasi aiuto è valutato!

Alcune opzioni aggiuntive che ho provato in base ai commenti e ai suggerimenti:

Octave: eigs(M,6,0)e eigs(M,6,'sm')dammi lo stesso risultato:

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

mentre eigs(M,6,'sa',struct('tol',2))converge a

[1.0423 2.7604 6.1548 11.1310 18.0207 25.3933] 

molto più veloce, ma solo se i valori di tolleranza sono superiori a 2, altrimenti non converge affatto e i valori sono fortemente diversi.

Python: eigsh(M,k=6,which='SA')ed eigsh(M,k=6,which='SM')entrambi non convergono (errore ARPACK su nessuna convergenza raggiunta). eigsh(M,k=6,sigma=0.0)Fornisce solo alcuni autovalori (dopo quasi un'ora), che sono diversi dall'ottava per i più piccoli (viene trovato anche 1 piccolo valore aggiuntivo):

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

Se la tolleranza è abbastanza alta ottengo anche risultati da eigsh(M,k=6,which='SA',tol='1'), che si avvicinano agli altri valori ottenuti

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

di nuovo con un diverso numero di piccoli autovalori. Il tempo di calcolo è ancora di quasi 30 minuti. Mentre i diversi valori molto piccoli potrebbero essere comprensibili, poiché potrebbero rappresentare multipli di 0, la diversa molteplicità mi confonde.

Inoltre, sembrano esserci alcune differenze fondamentali in SciPy e Octave, che non riesco ancora a capire.


2
1 - Suppongo che intendevi mettere parentesi attorno a [eval, evecs] nel codice di ottava? 2 - puoi includere un piccolo esempio per M? o forse uno script del generatore per uno, se possibile?
Nick J,

1 - Sì, esattamente, ho modificato il mio post. 2 - Ho provato le prestazioni di alcune sotto-matrici dei miei dati e sembra che Octave sia sempre più veloce, ma per le matrici più piccole inferiori a 5000x5000 è solo un fattore di 2-5 volte, al di sopra che diventa davvero brutto. E poiché i suoi "dati reali" non posso dare uno script del generatore. Esiste un modo standard per caricare un esempio in qualche modo? A causa della scarsità, un file npz è ragionevolmente piccolo.
Spacekiller23

Suppongo che tu possa condividere un link a qualsiasi struttura di archiviazione cloud.
Patol75,

Grazie. Ho incluso un link dropbox nel post originale e ho aggiornato il codice con un esempio funzionante.
Spacekiller23

1
Juts per rafforzare il tuo punto, ho testato su Matlab R2019b e ho ottenuto 84 secondi contro 36,5 minuti in Python 3.7, Scipy 1.2.1 (26 volte più veloce).
Bill

Risposte:


1

Una congettura e alcuni commenti, dal momento che non ho Matlab / Octave:

Per trovare piccoli autovalori di matrici simmetriche con autovalori> = 0, come il tuo, quanto segue è più veloce di maiusc / inversione:

# 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 )per grandi autovetture è più veloce di shift-invert per piccoli, perché A * xè più veloce di quello solve()che deve fare shift-invert. Matlab / Octave potrebbe concepibilmente farlo Aflipautomaticamente, dopo un rapido test per il positivo-definitivo con Cholesky.
Riesci a correre eigsh( Aflip )in Matlab / Octave?

Altri fattori che possono influire sulla precisione / velocità:

L'impostazione predefinita di Arpack per il vettore iniziale v0è un vettore casuale. Uso v0 = np.ones(n), il che può essere terribile per alcuni Ama è riproducibile :)

Questa Amatrice è quasi esattamente sigolare, A * ones~ 0.

Multicore: scipy-arpack con openblas / Lapack utilizza ~ 3,9 dei 4 core del mio iMac; Matlab / Octave usano tutti i core?


Ecco gli autovalori di scipy-Arpack per diversi ke tol, catturati da file di registro sotto gist.github :

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 sono quasi uguali? In caso contrario, tutte le scommesse sono disattivate: prima controlla la correttezza, quindi la velocità.

Perché gli autovalori oscillano così tanto? Minuscolo <0 per una matrice presumibilmente non negativa definita sono un segno di errore di arrotondamento , ma il solito trucco di un minuscolo spostamento, A += n * eps * sparse.eye(n)non aiuta.


Da dove Aviene, quale area problematica? Puoi generare simili A, più piccoli o più sparsi?

Spero che sia di aiuto.


Grazie per il tuo contributo e scusa per la (molto) risposta tardiva. Il progetto per cui l'ho usato è già completo, ma sono ancora curioso, quindi ho controllato. Purtroppo, gli autovalori in Ocatve risultano diversi, per k = 10 trovo [-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] che è anche indipendente dal valore di tolleranza nell'intervallo da 1e-5 a 1e-7. Quindi c'è un'altra differenza qui. Non pensi che sia strano che scipy (incluso il tuo suggerimento) produca diversi piccoli valori dipendenti dal numero di valori interrogati?
Spacekiller23

@ Spacekiller23, questo era un bug, ora corretto in scipy 1.4.1 (vedi scipy / issues / 11198 ); potresti controllare la tua versione? Inoltre tolè disordinato per piccoli autovalori: fai una nuova domanda su questo, se vuoi, fammi sapere.
denis

1

So che ora è vecchio, ma ho avuto lo stesso problema. Hai recensito qui ( https://docs.scipy.org/doc/scipy/reference/tutorial/arpack.html )?

Sembra che quando si imposta sigma su un numero basso (0) è necessario impostare quale = 'LM', anche se si desidera valori bassi. Questo perché l'impostazione sigma trasforma i valori desiderati (basso in questo caso) in modo che appaiano alti e quindi si è ancora in grado di sfruttare i metodi "LM", che sono molto più veloci per ottenere ciò che si desidera (gli autovalori bassi ).


Questo ha davvero cambiato le prestazioni per te? Sarebbe una sorpresa per me. Conoscevo il link che postate e ho anche implicitamente specificato quale = 'LM' nel mio esempio. Perché il valore predefinito per un unset che è 'LM'. Ho comunque controllato, e le prestazioni sono invariate per il mio esempio.
Spacekiller23

In effetti, sembra che tu abbia una differenza simile a quella di Python in ottava. Avevo anche una grande matrice che stavo cercando di decomporre e ho finito per usare eigsh (matrice, k = 7, che = 'LM', sigma = 1e-10). Inizialmente stavo specificando erroneamente quale = "SM" pensavo di doverlo fare per ottenere gli autovalori più piccoli e ci volevo un'eternità per darmi una risposta. Poi, ho trovato quell'articolo e ho capito che dovevi solo specificarlo con la 'LM' più veloce, e ho impostato k come desiderato e ciò avrebbe accelerato le cose. La tua matrice è davvero eremita?
Anthony Gatti

0

Voglio dire innanzitutto che non ho idea del perché i risultati che tu e @Bill avete riportato siano così. Mi chiedo semplicemente se eigs(M,6,0)in Octave corrisponde k=6 & sigma=0, o forse è qualcos'altro?

Con scipy, se non fornisco sigma, posso ottenere un risultato in un tempo decente in questo modo.

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)

Non sono del tutto sicuro se questo abbia senso però.

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]

L'unico modo in cui ho scoperto di usare sigma e di ottenere un risultato in un tempo decente è fornire M come LinearOperator. Non ho molta familiarità con questa cosa, ma da quello che ho capito la mia implementazione rappresenta una matrice di identità, che è ciò che M dovrebbe essere se non specificato nella chiamata. La ragione di ciò è che invece di eseguire una risoluzione diretta (decomposizione LU), scipy utilizzerà un solutore iterativo, che è potenzialmente più adatto. A titolo di confronto, se si fornisce M = np.identity(a.shape[0]), che dovrebbe essere esattamente lo stesso, quindi eigsh impiega un'eternità per ottenere un risultato. Si noti che questo approccio non funziona se sigma=0fornito. Ma non sono sicuro se sigma=0sia davvero così utile?

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)))

Ancora una volta, non ho idea se sia corretto ma sicuramente diverso da prima. Sarebbe bello avere il contributo di qualcuno di 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]

Grazie per il tuo contributo e feedback. Ho provato alcune cose per dare una risposta decente ai tuoi punti. 1. Il mio compito a portata di mano richiede la ricerca dei k autovalori / vettori più piccoli. Pertanto l'approccio che utilizza sigma = 0 è persino indicato nei documenti SciPy: docs.scipy.org/doc/scipy/reference/tutorial/arpack.html 2. Ho provato alcune altre opzioni, che ho modificato nella domanda originale. 3. A quanto ho capito i documentari di Octave e SciPy, eigs (M, 6,0) e k = 6, simga = 0 dovrebbe essere lo stesso.
Spacekiller23

4. Dato che la mia matrice è reale e quadrata, ho pensato che non ci dovrebbe essere differenza tra SA e SM come opzione, ma ovviamente c'è, almeno nel calcolo. Sono su una strada sbagliata qui? Complessivamente ciò significa più domande e ma nessuna vera risposta o soluzione da parte mia.
Spacekiller23
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.