Участник:Dlitvinov/Алгоритм Ланцоша для арифметики с плавающей точкой с выборочной ортогонализацией
Авторы статьи: Литвинов Д.А., 611, Шер А.В., 628. Авторы внесли равнозначный вклад в написание статьи.
| Алгоритм Ланцоша для арифметики с плавающей точкой с выборочной ортогонализацией | |
| Последовательный алгоритм | |
| Последовательная сложность | [math]\displaystyle{ O(kn^2 + k^2n) }[/math] |
| Объём входных данных | [math]\displaystyle{ n^2 + n + 1 }[/math] |
| Объём выходных данных | [math]\displaystyle{ nk }[/math] |
| Параллельный алгоритм | |
| Высота ярусно-параллельной формы | [math]\displaystyle{ O(kn + k^2) }[/math] |
| Ширина ярусно-параллельной формы | [math]\displaystyle{ O(n) }[/math] |
Свойства и структура алгоритма
Общее описание алгоритма
Алгоритм Ланцоша применяется для поиска небольшого числа собственных значений большой разреженной симметричной матрицы и объединяет метод Ланцоша для построения крыловского подпространства с процедурой Рэлея-Ритца интерпретации собственных значений некоторой вычисляемой матрицы как приближений к собственным значениям заданной матрицы.
Пусть [math]\displaystyle{ Q = [Q_k,Q_u] }[/math] - ортогональная матрица порядка [math]\displaystyle{ n }[/math], причем [math]\displaystyle{ Q_k }[/math] и [math]\displaystyle{ Q_u }[/math] имеют соответственно размеры [math]\displaystyle{ n \times k }[/math] и [math]\displaystyle{ n \times (n-k) }[/math]. Столбцы матрицы [math]\displaystyle{ Q_k }[/math] вычисляются методом Ланцоша. Запишем следующие соотношения:
[math]\displaystyle{ T = Q^T A Q = [Q_k, Q_u]^T A [Q_k, Q_u] = \left[ \begin{array}{cc} Q_k^T A Q_k & Q_k^T A Q_u\\ Q_u^T A Q_k & Q_u^T A Q_u \end{array} \right] = \left[ \begin{array}{cc} T_{k} & T_{ku}^T\\ T_{ku} & T_{u} \end{array} \right] }[/math]
Метод Рэлея-Ритца заключается в интерпретации собственных значений матрицы [math]\displaystyle{ T_k = Q_k^T A Q_k }[/math] как приближенных к собственным значениям матрицы [math]\displaystyle{ A }[/math]. Эти приближения называются числами Ритца. Если [math]\displaystyle{ A = V \Lambda V^T }[/math] есть спектральное разложение матрицы [math]\displaystyle{ T_k }[/math], то столбцы матрицы [math]\displaystyle{ Q_k V }[/math] являются приближениями соответствующих собственных векторов и называются векторами Ритца.
Числа и векторы Ритца являются оптимальными приближениями к собственным значениям и собственным векторам матрицы [math]\displaystyle{ A }[/math]: минимум величины [math]\displaystyle{ \Vert A P_k - P_k D \Vert_2 }[/math] достигается при [math]\displaystyle{ P_k = Q_k V }[/math] и [math]\displaystyle{ D = \Lambda }[/math] и равен [math]\displaystyle{ \Vert T_ku \Vert_2 }[/math].
При применении метода Ланцоша в арифметике с плавающей точкой ортогональность в вычисленных векторах Ланцоша [math]\displaystyle{ q_k }[/math] пропадает из-за ошибок округления. Для того чтобы поддерживать ортогональность, проводится повторный процесс ортогонализации Грама-Шмидта. Доказано, что векторы [math]\displaystyle{ q_k }[/math] теряют ортогональность весьма систематическим образом, приобретая большие компоненты в отношении уже сошедшихся векторов Ритца. Поэтому можно выборочно ортогонализировать векторы [math]\displaystyle{ q_k }[/math] вместо того, чтобы на каждом шаге ортогонализировать вектор ко всем более ранним векторам, как это делается при полной ортогонализации. Таким образом достигается очень высокая степень ортогонализации векторов Ланцоша, и затрачивается очень небольшая дополнительная работа.
Математическое описание алгоритма
Пусть [math]\displaystyle{ A }[/math] - заданная симметричная матрица, [math]\displaystyle{ b }[/math] - вектор начального приближения метода Ланцоша. Тогда алгоритм Ланцоша вычисления собственных векторов и собственных значений матрицы [math]\displaystyle{ A }[/math] с выборочной ортогонализацией записывается следующим образом:
[math]\displaystyle{ \begin{align} q_1 = & b / \Vert b \Vert_2, \; \beta_0 = 0,\; q_0 = 0\\ for \; & j = 1 \; to \; k \\ & z = A\,q_j\\ & \alpha_j = q_j^T z\\ & z = z - \alpha_j q_j - \beta_{j-1}q_{j-1}\\ & for \; i \le k \\ & \; \; \; if \; \beta_k \|v_i (k)\| \le \sqrt{\epsilon} \Vert T_k \Vert_2\\ & \; \; \; \; \; \; z = z - (y_{i,k} ^ T z) y_{i,k}\\ & end \; for\\ & \beta_j = \Vert z \Vert_2\\ & if \; \beta_j = 0 \; then\\ & \; \; \; Quit\; the\; algorithm\\ & q_{j+1} = z / \beta_j\\ end \; & for \end{align} }[/math]
Где [math]\displaystyle{ v_i }[/math] - это столбцы ортогональной матрицы [math]\displaystyle{ V }[/math] из спектрального разложения [math]\displaystyle{ T_k = V \Lambda V^T }[/math], а [math]\displaystyle{ y_{k,i} = Q_k v_i }[/math] - столбцы матрицы [math]\displaystyle{ Q_k V }[/math] - векторы Ритца.
Согласно теореме Пейджа, [math]\displaystyle{ y_{k,i}^T q_{k+1} = O(\epsilon \Vert A \Vert_2) / \beta_k \Vert v_i(k) \Vert }[/math], то есть компонента [math]\displaystyle{ y_{k,i}^T q_{k+1} }[/math] вычисленного вектора Ланцоша [math]\displaystyle{ q_{k+1} }[/math] в направлении вектора Ритца [math]\displaystyle{ y_{k,i} = Q_k v_i }[/math] обратно пропорциональна величине [math]\displaystyle{ \beta_k \Vert v_i(k) \Vert }[/math], являющейся оценкой погрешности для соответствующего числа Ритца. Поэтому, когда число Ритца сходится, а его оценка погрешности приближается к нулю, вектор Ланцоша приобретает большую компоненту в направлении вектора Ритца [math]\displaystyle{ q_{k,i} }[/math], и следует произвести процедуру переортогонализации.
Величина погрешности [math]\displaystyle{ \beta_k \Vert v_i(k) \Vert }[/math] считается малой, если она меньше, чем [math]\displaystyle{ \sqrt{\epsilon} \Vert A \Vert_2 }[/math], так как в этом случае, согласно теореме Пейджа, компонента [math]\displaystyle{ \Vert y_{i,k}^T q_{k+1} \Vert = \Vert y_{i,k}^T z \Vert / \Vert z \Vert_2 }[/math] скорее всего превосходит уровень [math]\displaystyle{ \sqrt{\epsilon} }[/math] (на практике используется [math]\displaystyle{ \Vert T_k \Vert_2 }[/math] вместо [math]\displaystyle{ \Vert A \Vert_2 }[/math], так как первое известно, а второе не всегда).
Вычислительное ядро алгоритма
У описанного алгоритма два ядра, на которые приходится основное время работы алгоритма:
- Вычисление вектора [math]\displaystyle{ z = A q_j : O(n^2) }[/math] операций умножения
- Выборочная переортогонализация [math]\displaystyle{ z = z - (y_{i,k} ^ T z) y_{i,k} : O(n k^2) }[/math] операций умножения (на практике коэффициент маленький, что обеспечивает бОльшую скорость выполнения алгоритма, чем в случае с полной переортогонализацией)
Макроструктура алгоритма
Макроструктура алгоритма Ланцоша с выборочной переортогонализацией представляется в виде совокупности метода Ланцоша с выборочной переортогонализацией построения крыловского подпространства и процедуры Рэлея-Ритца.
Каждая итерация первого этапа может быть разложена на следующие макроединицы:
- Умножение матрицы на вектор
- Вычисление скалярного произведения векторов
- Умножение векторов на числа и вычитание векторов
- При выполнении условия необходимости переортогонализации – перемножение и вычитание векторов
- Вычисление нормы вектора
- Умножение вектора на число
Второй этап, процедура Рэлея-Ритца, заключается в нахождении собственных значений и собственных векторов матрицы и интерпретации их как приближенных значений собственных чисел и собственных векторов исходной матрицы, он может быть выполнен любым способом, например, QR-итерацией.
Схема реализации последовательного алгоритма
На вход алгоритму подаются исходная матрица [math]\displaystyle{ A }[/math], вектор начального приближения [math]\displaystyle{ b }[/math] и число [math]\displaystyle{ k }[/math]. Далее последовательно выполняются итерационно для всех [math]\displaystyle{ j: 1 \le j \le k }[/math] следующие шаги:
- Умножается матрица на вектор для нахождения вектора [math]\displaystyle{ z }[/math]
- Вычисляется коэффициент [math]\displaystyle{ \Alpha_j }[/math] посредством скалярного перемножения векторов
- Вычисляется новое значение вектора [math]\displaystyle{ z }[/math]
- Производятся оценка погрешностей [math]\displaystyle{ \beta_k \Vert v_i(k) \Vert }[/math] и выборочная переортогонализация
- Вычисление очередного значения [math]\displaystyle{ \beta_j }[/math] - норма вектора [math]\displaystyle{ z }[/math]
- Вычисление и добавление к матрице [math]\displaystyle{ Q }[/math] нового столбца
- Вычисление собственных значений и собственных векторов матрицы [math]\displaystyle{ T_j }[/math]
Последовательная сложность алгоритма
- Вычисление вектора [math]\displaystyle{ z }[/math] - [math]\displaystyle{ n^2 }[/math] операций умножения и [math]\displaystyle{ n(n-1) }[/math] операций сложения
- Вычисление скалярного произведения векторов - [math]\displaystyle{ n }[/math] операций умножения и [math]\displaystyle{ (n-1) }[/math] сложений
- Вычисление вектора [math]\displaystyle{ z }[/math] - [math]\displaystyle{ 2n }[/math] умножений и [math]\displaystyle{ 2n }[/math] сложений
- Оценка погрешностей - [math]\displaystyle{ kn }[/math] операций умножения
- Выборочная переортогонализация - [math]\displaystyle{ O(2nj) }[/math] умножений и [math]\displaystyle{ O(2j(n-1) + n) }[/math] сложений
- Вычисление нормы вектора - [math]\displaystyle{ n }[/math] операций умножения и [math]\displaystyle{ (n-1) }[/math] сложений
- Вычисление нового столбца матрицы [math]\displaystyle{ Q }[/math] - [math]\displaystyle{ n }[/math] операций умножения
Итого на каждой итерации:
- Умножений: [math]\displaystyle{ n^2 + n + 2n + kn + O(2nj) + n + n = n^2 + 5n + kn + O(2nj) }[/math]
- Сложений: [math]\displaystyle{ n(n-1) + n-1 + 2n + O(2j(n-1) + n) + n-1 = n^2 + 3n + O(2j(n-1) + n) -2 }[/math]
Итого за все время выполнения алгоритма:
- Умножений: [math]\displaystyle{ kn^2 + 5kn + k^2n + O(k^2n + kn) }[/math]
- Сложений: [math]\displaystyle{ kn^2 + 3kn + O((k^2+k)(n-1)+kn) = kn^2 + 3kn + O(k^2(n-1)+2kn-k) }[/math]
Суммарная последовательная сложность алгоритма - [math]\displaystyle{ O(kn^2 + k^2n) }[/math].
Информационный граф
Ниже представлен граф одной итерации алгоритма с разделением на [math]\displaystyle{ n }[/math] потоков.

Oper - простая операция сложения/вычитания/умножения/деления с числами с плавающей точкой.
'?' - проверка условия на необходимость переортогонализации.
На вход итерации подается матрица [math]\displaystyle{ A }[/math] и вектор [math]\displaystyle{ q_j }[/math], на выходе получаем новый вектор [math]\displaystyle{ q_{j+1} }[/math].
Ресурс параллелизма алгоритма
На каждой из итераций алгоритма можно получить выигрыш за счет распараллеливания следующих шагов:
- Вычисление вектора [math]\displaystyle{ z }[/math] - умножение матрицы размера [math]\displaystyle{ n \times n }[/math] на вектор длины [math]\displaystyle{ n }[/math] - [math]\displaystyle{ n }[/math] ярусов сложений, [math]\displaystyle{ n }[/math] операций умножений в каждом
- Вычисление коэффициента [math]\displaystyle{ \alpha_j }[/math] - скалярное произведение векторов - [math]\displaystyle{ n }[/math] ярусов сложений, 1 операция умножения в каждом
- Вычисление нового значения вектора [math]\displaystyle{ z }[/math]: 2 яруса умножений длины [math]\displaystyle{ n }[/math] (умножение вектора на число), 2 яруса вычитаний длины [math]\displaystyle{ n }[/math]
- Выборочная переортогонализация: последовательно для всех [math]\displaystyle{ i \le k }[/math] выполняется [math]\displaystyle{ j }[/math] ярусов сложений, [math]\displaystyle{ n }[/math] операций умножений в каждом, [math]\displaystyle{ n }[/math] ярусов сложений, [math]\displaystyle{ j }[/math] операций умножений в каждом, один ярус вычитаний размера [math]\displaystyle{ n }[/math]
- Вычисление [math]\displaystyle{ \beta_j }[/math] - скалярное произведение векторов - [math]\displaystyle{ n }[/math] ярусов сложений, 1 операция умножения в каждом
- Вычисление [math]\displaystyle{ q_j }[/math] - деление вектора на число - один ярус делений размера [math]\displaystyle{ n }[/math]
Сложность алгоритма по ширине ЯПФ - [math]\displaystyle{ O(n) }[/math].
Входные и выходные данные алгоритма
Входные данные: вещественная матрица [math]\displaystyle{ A }[/math] размера [math]\displaystyle{ n \times n }[/math], вектор [math]\displaystyle{ b }[/math] длины [math]\displaystyle{ n }[/math], число [math]\displaystyle{ k }[/math]
Объем входных данных: [math]\displaystyle{ n^2 + n + 1 }[/math]
Выходные данные: матрица [math]\displaystyle{ Q }[/math] размера [math]\displaystyle{ n \times k }[/math]
Объем выходных данных: [math]\displaystyle{ nk }[/math]
Свойства алгоритма
- Соотношение последовательной и параллельной сложности алгоритма равно [math]\displaystyle{ n }[/math], что говорит о том, что параллельная реализация теоретически должна давать ощутимый выигрыш
- Вычислительная мощность алгоритма - отношение числа операций к суммарному объему входных и выходных данных - примерно равно [math]\displaystyle{ 2k }[/math]
- Алгоритм устойчив, так как операция переортогонализации как раз направлена на исключение проблемы округления вычисляемых значений
- Алгоритм не является детерминированным, так как, во-первых, используется для разряженных матриц, и, во-вторых, является итерационным с выходом по точности - число операций заранее не определено
- Алгоритм содержит длинные дуги, так как на протяжении всего процесса его выполнения нужно хранить исходную матрицу [math]\displaystyle{ A }[/math] и матрицу [math]\displaystyle{ Q }[/math] - они нужны на каждой итерации
Программная реализация алгоритма
Особенности реализации последовательного алгоритма
Локальность данных и вычислений
Возможные способы и особенности параллельной реализации алгоритма
Масштабируемость алгоритма и его реализации
Исследование масштабируемости алгоритма проводилось на суперкомпьютере IBM Blue Gene/P ВМК МГУ.
Для тестирования была выбрана реализация BLZPACK. BLZPACK написан на FORTRAN 77 в 2001 году и использует MPI для параллелизма.
Для запуска BLZPACK необходима программа-драйвер. Использовался один из встроенных драйверов DRVMPI (файл drv/double/drvmpi.f) c измененными параметрами -- они меняются в зависимости от размера матрицы и количества требуемых пар собственных значений/векторов. Параметры устанавливаются в начале файла drvmpi.f.
Поскольку в мануале (doc/manual.ps) нету полного описания этих параметров, прокомментируем некоторые из них. Например, для матрицы размера 7500х7500 использовались следующие значения:
INTEGER LEIG
PARAMETER (LEIG = 30)
INTEGER LISTOR
PARAMETER (LISTOR = 15000)
INTEGER LN
PARAMETER (LN = 7500)
INTEGER LRSTOR
PARAMETER (LRSTOR = 10000000)
INTEGER NCUV
PARAMETER (NCUV = 1)
INTEGER NNZMAX
PARAMETER (NNZMAX = 28128750)
DOUBLE PRECISION ONE
PARAMETER (ONE = 1.0D0)
DOUBLE PRECISION ZERO
PARAMETER (ZERO = 0.0D0)
- LEIG -- размер массива для хранения полученных собственных
векторов/значений.
- LISTOR -- размер массива ISTOR. Формула определения его минимального размера приведена в мануале.
- LN -- порядок матрицы A, т.е. n.
- LRSTOR -- размер массива RSTOR. Формула определения его минимального размера приведена в мануале.
- NNZMAX -- количество строк во входном файле с матрицей А. Драйвер DRVMPI прочитывает симметричную верхнетреугольную матрицу из файла в координатном формате (строка, столбец, значение); таким образом, для плотной матрицы NNZMAX будет равен [math]\displaystyle{ (n^2 + n)/2 }[/math]
Сам запуск DRVMPI конфигурируется в файле drvsp1.dat -- там задаётся количество требуемых собственных значений/векторов, имя файла со входной матрицей и некоторые другие параметры.
Входные матрицы генерировались следующим простым скриптом на Python 2.7:
import random
if __name__ == "__main__":
n = 7500
num_rows = (n*n + n) / 2
with open('matr7500.data', 'w') as f:
for i in xrange(1, n + 1):
j = i # column number
while j <= n:
value = random.uniform(-1, 1)
f.write(" " + str(i) + " " + str(j) + " " + '{:.14e}'.format(value) + "\n")
j += 1
Был использован компилятор GNU Fortran 77 (g77), точнее, его обертка mpif77 с поддержкой MPI. На Blue Gene/P используется собственная реализация MPI от IBM, основанная на MPICH. Для сборки BLZPACK на Blue Gene/P нужно заменить имя компилятора (ключ FC) в файле sys/MACROS/Linux на mpif77 и запустить ./creator -f -mpi в корне проекта. После этого можно собрать драйвер, зайдя в директорию drv/data и выполнив make drvmpi.x.
Использовался SMP-режим работы Blue Gene/P, когда на каждом вычислительном узле выполняется всего один MPI-процесс.
Пример запуска программы:
mpisubmit.bg -n 64 -w 00:15:00 -m smp --stdout eigen.out --stderr eigen.err drvmpi.x
Тестировались следующие конфигурации:
- Порядок матриц 1000, 2500, 5000, 7500
- Количество процессоров 4, 8, 16, 32, 64, 128
У матрицы размера 1000 производился поиск 100 пар собственных векторов/значений, у остальных -- 30 пар. Результаты приведены в таблице 1:
| Число процессоров | |||||||
|---|---|---|---|---|---|---|---|
| 4 | 8 | 16 | 32 | 64 | 128 | ||
| Размер матрицы | 1000 | 24 | 17 | 14 | 15 | 16 | 21 |
| 2500 | 48 | 27 | 18 | 13 | 12 | 13 | |
| 5000 | 207 | 92 | 61 | 37 | 24 | 23 | |
| 7500 | 612 | 298 | 153 | 90 | 50 | 42 | |
Заметно, при всех размерах матриц алгоритм перестает масштабироваться начиная с некоторого числа процессоров: для матрицы размера 1000 это 32 узла, для матрицы размера 2500 64 узла и т.д. Это связано с тем, что затраты на коммуникацию между узлами становится больше, чем выгода от распараллеливания вычислений. При меньшем же количестве процессоров алгоритм хорошо масштабируется, соответствуя теоретически вычисленной высоте ярусно-параллельной формы [math]\displaystyle{ O(kn + k^2) }[/math]
Динамические характеристики и эффективность реализации алгоритма
Выводы для классов архитектур
Существующие реализации алгоритма
Неплохой обзор существующих реализаций находится здесь: A Survey of Software for Sparse Eigenvalue Problems
Ниже будут перечислены только те реализации, которые поддерживают именно выборочную ортогонализацию.
- BLZPACK FORTRAN 77, MPI. 2000 год.
- BLKLAN C, MATLAB. Нет поддержки параллелизма. 2003 год.
- PROPACK FORTRAN 77, MATLAB. OpenMP. Предназначен для нахождения SVD разложения, но алгоритм Ланцоша может быть вызван и напрямую. 2005 год.
- LANZ FORTRAN 77. Нет поддержки параллелизма. 1991 год.
- SVDPACK C, FORTRAN 77. Нет поддержки параллелизма. Предназначен для нахождения SVD разложения. 1992 год.
Литература
- Дж. Деммель, Вычислительная линейная алгебра. Теория и приложения. Пер. с англ. - М.: Мир, 2001.