Нахождение наименьших собственных векторов большой разреженной матрицы, в SciPy более чем в 100 раз медленнее, чем в Octave

12

Я пытаюсь вычислить несколько (5-500) собственных векторов, соответствующих наименьшим собственным значениям больших симметричных квадратных разреженных матриц (до 30000x30000) с ненулевыми значениями менее 0,1%.

В настоящее время я использую scipy.sparse.linalg.eigsh в режиме shift-invert (sigma = 0.0), который, как я выяснил, в различных сообщениях на эту тему является предпочтительным решением. Однако для решения проблемы в большинстве случаев требуется до 1 часа. С другой стороны, функция очень быстрая, если я запрашиваю самые большие собственные значения (в секундах в моей системе), что ожидалось из документации.

Поскольку я больше знаком с Matlab с работы, я попытался решить проблему в Octave, которая дала мне тот же результат, используя eigs (sigma = 0) всего за несколько секунд (sub 10s). Поскольку я хочу выполнить поиск параметров алгоритма, включая вычисление собственных векторов, такой выигрыш во времени был бы также полезен в python.

Сначала я изменил параметры (особенно допуск), но это не сильно изменилось в сроки.

Я использую Anaconda в Windows, но попытался переключить LAPACK / BLAS, используемый scipy (что было очень трудно), с mkl (по умолчанию Anaconda) на OpenBlas (используемый Octave в соответствии с документацией), но не увидел изменений в представление.

Я не смог выяснить, было ли что-то изменить в используемом ARPACK (и как)?

Я загрузил тестовый пример для кода ниже в следующую папку dropbox: https://www.dropbox.com/sh/l6aa6izufzyzqr3/AABqij95hZOvRpnnjRaETQmka?dl=0

В питоне

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

Любая помощь ценится!

Некоторые дополнительные опции, которые я опробовал на основе комментариев и предложений:

Октава: 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 дополнительное небольшое значение):

[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, похоже, есть некоторые принципиальные различия, которые я пока не могу понять.

Spacekiller23
источник
2
1 - Я полагаю, вы хотели заключить скобки [evals, evecs] в октавный код? 2 - можете ли вы привести небольшой пример для М? или, может быть, сценарий генератора для одного, если это возможно?
Ник Дж
1 - Да, именно я редактировал свой пост. 2 - я проверил производительность для некоторых подматриц моих данных, и кажется, что Octave всегда быстрее, но для меньших матриц ниже 5000x5000 это всего лишь в 2-5 раз выше, чем выше, это становится действительно уродливым. А поскольку его «реальные данные» я не могу дать сценарию генератора. Есть ли стандартный способ загрузить пример как-нибудь? Из-за разреженности npz-файл достаточно мал.
Spacekiller23
Я думаю, вы можете поделиться ссылкой на любое облачное хранилище.
Patol75
Спасибо. Я включил ссылку на Dropbox в исходное сообщение и обновил код до рабочего примера.
Spacekiller23
1
Чтобы подкрепить вашу точку зрения, я протестировал на Matlab R2019b и получил 84 секунды против 36,5 минут в Python 3.7, Scipy 1.2.1 (в 26 раз быстрее).
Билл

Ответы:

1

Гипотеза и некоторые комментарии, так как у меня нет Matlab / Octave:

Чтобы найти малые собственные значения симметричных матриц с собственными значениями> = 0, как и у вас, следующее быстрее, чем инверсия сдвига:

# 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автоматически, после быстрой проверки на положительную определенность с помощью Cholesky.
Можете ли вы бегать eigsh( Aflip )в Matlab / Octave?

Другие факторы, которые могут повлиять на точность / скорость:

Arpack по умолчанию для начального вектора v0является случайным вектором. Я использую v0 = np.ones(n), что может быть ужасно для некоторых, Aно воспроизводимо :)

Эта Aматрица почти точно сигулярная, A * ones~ 0.

Multicore: scipy-arpack с openblas / Lapack использует ~ 3,9 из 4 ядер на моем iMac; Matlab / Octave используют все ядра?


Вот собственные значения scipy-Arpack для нескольких kи tolизвлеченные из лог- файлов в 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 примерно одинаковы? Если нет, все ставки выключены - сначала проверьте правильность, затем скорость.

Почему собственные значения так сильно колеблются? Крошечные <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это грязно для небольших собственных значений - задайте новый вопрос, если хотите, дайте мне знать.
Денис
1

Я знаю, что сейчас это старо, но у меня была та же проблема. Вы просматривали здесь ( https://docs.scipy.org/doc/scipy/reference/tutorial/arpack.html )?

Кажется, что когда вы устанавливаете сигма на низкое число (0), вы должны установить, который = 'LM', даже если вы хотите низкие значения. Это связано с тем, что настройка sigma преобразует значения, которые вы хотите (в данном случае, низкими), в высокие, и поэтому вы все еще можете использовать методы LM, которые намного быстрее получают то, что вы хотите (низкие собственные значения) ).

Энтони Гатти
источник
Это на самом деле изменило производительность для вас? Это было бы сюрпризом для меня. Я знал ссылку, которую вы постете, и я также неявно указал, какой = 'LM' в моем примере. Потому что значением по умолчанию для unset является «LM». Я все еще проверял, и производительность для моего примера не изменилась.
Spacekiller23
Действительно, похоже, что для вас есть разница, аналогичная Python от октавной. У меня также была большая матрица, которую я пытался разложить, и в итоге я использовал eigsh (matrix, k = 7, которая = 'LM', sigma = 1e-10). Первоначально я неправильно указывал, какой = «СМ», думая, что мне нужно это сделать, чтобы получить наименьшие собственные значения, и мне требовался вечный ответ. Затем я нашел эту статью и понял, что вам просто нужно указать ее для более быстрого «LM», и установить k так, как вам нужно, и это ускорит процесс. Ваша матрица на самом деле эрмитова?
Энтони Гатти
0

Сначала я хочу сказать, что понятия не имею, почему результаты, о которых вы и @Bill сообщили, такие, какие они есть. Мне просто интересно, соответствует ли eigs(M,6,0)Октава 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]

Единственный способ найти сигма и получить результат за приемлемое время - это предоставить M в качестве линейного оператора. Я не слишком знаком с этой вещью, но из того, что я понял, моя реализация представляет собой матрицу идентичности, которая должна быть 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)))

Опять же, не знаю, если это правильно, но определенно отличается от ранее. Было бы замечательно, если бы кто-то из Сципи сделал свой вклад.

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
источник
Спасибо за ваш вклад и отзывы. Я попробовал некоторые вещи, чтобы дать достойный ответ на ваши вопросы. 1. Моя задача - найти k наименьших собственных значений / векторов. Поэтому подход с использованием sigma = 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