Encontrar los vectores propios más pequeños de matriz dispersa grande, más de 100 veces más lento en SciPy que en Octave

12

Estoy tratando de calcular pocos (5-500) vectores propios correspondientes a los valores propios más pequeños de matrices dispersas cuadradas simétricas grandes (hasta 30000x30000) con menos del 0.1% de los valores que no son cero.

Actualmente estoy usando scipy.sparse.linalg.eigsh en modo shift-invert (sigma = 0.0), que descubrí a través de varias publicaciones sobre el tema es la solución preferida. Sin embargo, lleva hasta 1 hora resolver el problema en la mayoría de los casos. Por otro lado, la función es muy rápida, si solicito los valores propios más grandes (segundos secundarios en mi sistema), que se esperaba de la documentación.

Como estoy más familiarizado con Matlab desde el trabajo, intenté resolver el problema en Octave, que me dio el mismo resultado usando eigs (sigma = 0) en solo unos segundos (sub 10s). Dado que quiero hacer un barrido de parámetros del algoritmo, incluido el cálculo del vector propio, ese tipo de ganancia de tiempo también sería genial tener en Python.

Primero cambié los parámetros (especialmente la tolerancia), pero eso no cambió mucho en las escalas de tiempo.

Estoy usando Anaconda en Windows, pero intenté cambiar el LAPACK / BLAS utilizado por scipy (que fue un gran dolor) de mkl (Anaconda predeterminado) a OpenBlas (utilizado por Octave según la documentación), pero no pude ver un cambio en actuación.

No pude averiguar si había algo que cambiar sobre el ARPACK usado (y cómo).

Subí un caso de prueba para el siguiente código a la siguiente carpeta de dropbox: https://www.dropbox.com/sh/l6aa6izufzyzqr3/AABqij95hZOvRpnnjRaETQmka?dl=0

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

En octava:

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

Cualquier ayuda es solicitada!

Algunas opciones adicionales que probé en función de los comentarios y sugerencias:

Octave: eigs(M,6,0)y eigs(M,6,'sm')dame el mismo resultado:

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

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

[1.0423 2.7604 6.1548 11.1310 18.0207 25.3933] 

mucho más rápido, pero solo si los valores de tolerancia son superiores a 2, de lo contrario no converge en absoluto y los valores son muy diferentes.

Python: eigsh(M,k=6,which='SA')y eigsh(M,k=6,which='SM')ambos no convergen (error ARPACK al no alcanzarse la convergencia). Solo eigsh(M,k=6,sigma=0.0)da algunos valores propios (después de casi una hora), que son diferentes de octava para los más pequeños (incluso se encuentra 1 valor pequeño adicional):

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

Si la tolerancia es lo suficientemente alta, también obtengo resultados eigsh(M,k=6,which='SA',tol='1'), que se acercan a los otros valores obtenidos

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

de nuevo con un número diferente de pequeños valores propios. El tiempo de cálculo aún es de casi 30 minutos. Si bien los diferentes valores muy pequeños pueden ser comprensibles, ya que pueden representar múltiplos de 0, la multiplicidad diferente me desconcierta.

Además, parece haber algunas diferencias fundamentales en SciPy y Octave, que todavía no puedo entender.

Spacekiller23
fuente
2
1 - ¿Asumo que querías poner corchetes [evals, evecs] en el código de octava? 2 - ¿puedes incluir un pequeño ejemplo para M? o tal vez un script generador para uno si eso es posible?
Nick J
1 - Sí exactamente, edité mi publicación. 2 - Probé el rendimiento para algunas submatrices de mis datos y parece que Octave siempre es más rápido, pero para matrices más pequeñas por debajo de 5000x5000 es solo un factor de 2-5 veces, por encima de eso se pone realmente feo. Y dado que sus "datos reales" no puedo dar un script generador. ¿Hay alguna forma estándar de cargar un ejemplo de alguna manera? Debido a la escasez, un archivo npz es razonablemente pequeño.
Spacekiller23
Supongo que puedes compartir un enlace a cualquier instalación de almacenamiento en la nube.
Patol75
Gracias. Incluí un enlace de Dropbox en la publicación original y actualicé el código a un ejemplo de trabajo.
Spacekiller23
1
Juts para reforzar su punto, probé en Matlab R2019b y obtuve 84 segundos frente a 36.5 minutos en Python 3.7, Scipy 1.2.1 (26 veces más rápido).
Bill

Respuestas:

1

Una conjetura y algunos comentarios, ya que no tengo Matlab / Octave:

Para encontrar pequeños valores propios de matrices simétricas con valores propios> = 0, como el suyo, lo siguiente es mucho más rápido que 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 )para grandes pares propios es más rápido que shift-invert para pequeños, porque A * xes más rápido que lo solve()que debe hacer shift-invert. Matlab / Octave podría hacer esto Aflipautomáticamente, después de una prueba rápida para positivo definitivo con Cholesky.
¿Puedes correr eigsh( Aflip )en Matlab / Octave?

Otros factores que pueden afectar la precisión / velocidad:

El valor predeterminado de Arpack para el vector de inicio v0es un vector aleatorio. Yo uso v0 = np.ones(n), lo que puede ser terrible para algunos Apero es reproducible :)

Esta Amatriz es casi exactamente sigular, A * ones~ 0.

Multinúcleo: scipy-arpack con openblas / Lapack utiliza ~ 3.9 de los 4 núcleos en mi iMac; Cómo utiliza Matlab / Octave todos los núcleos?


Aquí están los valores propios de scipy-Arpack para varios ky tol, extraídos de archivos de registro bajo 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 

¿Son Matlab / Octave más o menos iguales? Si no, todas las apuestas están apagadas: primero verifique la corrección, luego la velocidad.

¿Por qué los valores propios se tambalean tanto? Diminuto <0 para una matriz supuestamente no negativa definida es un signo de error de redondeo , pero el truco habitual de un pequeño cambio, A += n * eps * sparse.eye(n)no ayuda.


¿De dónde Aviene esto , qué área problemática? ¿Se puede generar similar A, más pequeño o más escaso?

Espero que esto ayude.

denis
fuente
Gracias por tu aporte y perdón por la (muy) tardía respuesta El proyecto para el que usé esto ya está completo, pero todavía tengo curiosidad, así que lo revisé. Lamentablemente, los valores propios en Ocatve resultan diferentes, para k = 10 encuentro [-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] que también es independiente del valor de tolerancia en el rango de 1e-5 a 1e-7. Entonces hay otra diferencia aquí. ¿No le parece extraño que scipy (incluida su sugerencia) produzca valores pequeños diferentes dependiendo del número de valores consultados?
Spacekiller23
@ Spacekiller23, esto era un error, ahora corregido en scipy 1.4.1 (ver scipy / issues / 11198 ); ¿podrías revisar tu versión? También toles complicado para pequeños valores propios: haga una nueva pregunta sobre eso, si lo desea, hágamelo saber.
Denis
1

Sé que esto es viejo ahora, pero tuve el mismo problema. ¿Revisaste aquí ( https://docs.scipy.org/doc/scipy/reference/tutorial/arpack.html )?

Parece que cuando establece sigma en un número bajo (0), debe establecer which = 'LM', aunque desee valores bajos. Esto se debe a que la configuración de sigma transforma los valores que desea (bajo en este caso) para que parezcan altos, por lo que aún puede aprovechar los métodos 'LM', que son mucho más rápidos para obtener lo que desea (los valores propios bajos )

Anthony Gatti
fuente
¿Esto realmente cambió el rendimiento para ti? Eso sería una sorpresa para mí. Conocía el enlace que publicaste y también especifiqué implícitamente cuál = 'LM' en mi ejemplo. Porque el valor predeterminado para un desarmado que es 'LM'. Sin embargo, aún lo verifiqué, y el rendimiento no ha cambiado para mi ejemplo.
Spacekiller23
De hecho, parece tener una diferencia similar de Python a octava para usted. También tenía una matriz grande que estaba tratando de descomponer y terminé usando eigsh (matriz, k = 7, que = 'LM', sigma = 1e-10). Originalmente, estaba especificando incorrectamente cuál = 'SM' pensaba que necesitaba hacer eso para obtener los valores propios más pequeños y me tomó una eternidad darme una respuesta. Luego, encontré ese artículo y me di cuenta de que solo tenía que especificarlo al 'LM' más rápido, y configurar k para que fuera lo que quisiera y aceleraría las cosas. ¿Es tu matriz realmente ermitaña?
Anthony Gatti hace
0

Quiero decir primero que no tengo idea de por qué los resultados que usted y @Bill informaron son como son. Simplemente me pregunto si eigs(M,6,0)en Octave corresponde k=6 & sigma=0, o tal vez es algo más?

Con scipy, si no proporciono sigma, puedo obtener un resultado en un tiempo decente de esta manera.

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)

Sin embargo, no estoy completamente seguro de si esto tiene sentido.

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]

La única forma en que encontré usar sigma y obtener un resultado en un tiempo decente es proporcionar M como LinearOperator. No estoy muy familiarizado con esto, pero por lo que entendí, mi implementación representa una matriz de identidad, que es lo que M debería ser si no se especifica en la llamada. La razón de esto es que, en lugar de realizar una resolución directa (descomposición LU), scipy utilizará un solucionador iterativo, que es potencialmente más adecuado. En comparación, si proporciona M = np.identity(a.shape[0]), que debería ser exactamente el mismo, entonces eigsh tarda una eternidad en producir un resultado. Tenga en cuenta que este enfoque no funciona si sigma=0se proporciona. Pero no estoy seguro de si sigma=0es realmente tan útil.

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

De nuevo, no tengo idea si es correcto pero definitivamente diferente de antes. Sería genial tener el aporte de alguien desde 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]
Patol75
fuente
Gracias por su aporte y comentarios. Probé algunas cosas para dar una respuesta decente a tus puntos. 1. Mi tarea en cuestión requiere encontrar los k valores propios / vectores más pequeños. Por lo tanto, el enfoque que usa sigma = 0 se da incluso en los documentos de SciPy: docs.scipy.org/doc/scipy/reference/tutorial/arpack.html 2. Probé algunas opciones más, que edité en la pregunta original. 3. Según entiendo los documentales de Octave y SciPy, eigs (M, 6,0) y k = 6, simga = 0 debería ser lo mismo.
Spacekiller23
4. Como mi matriz es real y cuadrada, pensé que no debería haber una diferencia entre SA y SM como opción, pero obviamente lo hay, al menos en el cálculo. ¿Estoy en un camino equivocado aquí? En general, eso significa más preguntas y no respuestas reales o soluciones de mi parte
Spacekiller23