QR алгоритм#
QR алгоритм был предложен в 1961 г. независимо В. Н. Кублановской и Ј. Francis’ом. Статью про историю этого алгоритма и его авторов можно прочитать тут. https://www.atm.org.uk/write/MediaUploads/Resources/Mid_Plenary_FrancisGolub.pdf
Не путайте QR алгоритм и QR разложение!
QR разложение - это представление матрицы в виде произведения двух матриц, а QR алгоритм использует QR разложение для вычисления разложения Шypa.
Является «золотым стандартом» для вычисления собственных значений
Теория QR алгоритма#
Рассмотрим выражение
и перепишем его в виде
Слева замечаем QR разложение матрицы \(A Q\).
Запишем следующий итерационный процесс
Введём новую матрицу
тогда аппроксимация для \(A_{k+1}\) имеет вид
Итак, мы получили стандартную форму записи QR алгоритма. Финальные формулы обычно записывают в QRRQ-форме:
Инициализируем \(A_0=A\).
Вычислим QR разложение матрицы \(A_k: A_k=Q_k R_k\).
Обновим аппроксимацию \(A_{k+1}=R_k Q_k\).
Продолжаем итерации пока \(A_k\) не станет достаточно треугольной (например, норма подматрицы под главной диагональю не станет достаточно мала).
Утверждение Матрицы \(A_k\) унитарно подобны матрице \(A\)
а произведение унитарных матриц - унитарная матрица. Сложность одной итерации \(\mathcal{O}\left(n^3\right)\), если используется \(\mathrm{QR}\) разложение для общего случая. Мы ожидаем, что \(A_k\) будет очень близка к треугольной матрице для достаточно большого \(k\).
import numpy as np
n = 4
a = [[1.0/(i + j + 0.5) for i in range(n)] for j in range(n)]
niters = 200
for k in range(niters):
q, rmat = np.linalg.qr(a)
a = rmat.dot(q)
print('Leading 3x3 block of a:')
display(a[:3, :3])
Leading 3x3 block of a:
array([[ 2.41052440e+000, 2.69817894e-018, -4.95361754e-017],
[ 2.42500623e-168, 3.49984625e-001, 1.75163388e-017],
[ 0.00000000e+000, 6.56745067e-273, 1.53236733e-002]])
Реализация QR-алгоритма#
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
def qr_algorithm(A, num_iter, eps):
T = A.copy()
U = np.eye(A.shape[0])
conv = [(T, U)]
for i in range(num_iter):
Q, R = np.linalg.qr(T)
T = R @ Q
U = U @ Q
conv.append((T, U))
if np.sum(np.abs(np.tril(T, k=-1))) < eps:
break
return T, U, conv[1:]
n = 7
A = np.random.randn(n, n)
# A = A.T @ A
A = A + A.T
true_eigvals, true_eigvec = np.linalg.eig(A)
print(true_eigvals)
[-4.90241018 6.15117923 -3.18614889 -2.09635507 -0.34667853 3.92398239
2.59969224]
T, U, conv = qr_algorithm(A, 2000, 1e-6)
print(np.linalg.norm(A - U @ T @ U.T))
7.410718418419184e-14
plt.spy(T, markersize=10, precision=1e-6)
<matplotlib.lines.Line2D at 0x7f925d409760>
plt.figure(figsize=(10, 7))
conv_qr = np.array([np.sum(np.abs(np.tril(T, k=-1))) for T, U in conv])
plt.plot(conv_qr)
plt.yscale("log")
plt.xticks(fontsize=20)
plt.yticks(fontsize=20)
plt.xlabel("\# iterations", fontsize=20)
plt.ylabel("Sum of lower triangular part in modulos", fontsize=20)
plt.grid(True)
U.round(4), true_eigvec.round(4)
(array([[-0.4441, -0.3348, -0.1244, 0.4476, -0.0476, -0.6016, -0.3326],
[ 0.6661, 0.1351, 0.3278, -0.1021, -0.2134, -0.5688, -0.2261],
[-0.3834, 0.0422, 0.6656, -0.2172, -0.1887, 0.2568, -0.5093],
[ 0.3495, -0.2065, 0.0116, 0.6566, -0.4027, 0.4643, -0.1616],
[ 0.282 , -0.4769, -0.281 , -0.2959, 0.424 , 0.1729, -0.5629],
[-0.0987, 0.3416, -0.5925, -0.2812, -0.5804, 0.0219, -0.3257],
[-0.0258, -0.6935, 0.0624, -0.3798, -0.4876, -0.0511, 0.3603]]),
array([[-0.3348, -0.4441, 0.4476, 0.6016, -0.3326, 0.1244, 0.0476],
[ 0.1351, 0.6661, -0.1021, 0.5688, -0.2261, -0.3278, 0.2134],
[ 0.0422, -0.3834, -0.2172, -0.2568, -0.5093, -0.6656, 0.1887],
[-0.2065, 0.3495, 0.6566, -0.4643, -0.1616, -0.0116, 0.4027],
[-0.4769, 0.282 , -0.2959, -0.1729, -0.5629, 0.281 , -0.424 ],
[ 0.3416, -0.0987, -0.2812, -0.0219, -0.3257, 0.5925, 0.5804],
[-0.6935, -0.0258, -0.3798, 0.0511, 0.3603, -0.0624, 0.4876]]))
Ускорение QR алгоритма#
Матрица \(A\) имеет верхне-гессенбергову форму, если \(a_{i j}=0\), при \(i \geq j+2\).
С помощью отражений Хаусхолдера можно привести любую матрицу к верхне-гессенберговой форме:
Сложность такого приведения \(\mathcal{O}\left(n^3\right)\) операций
Если матрица приведена к верхне-гессенберговой форме, то одна итерация QR алгоритма имеет сложность \(\mathcal{O}\left(n^2\right)\) операций (например, используя вращения Гивенса) (почему?)
Также верхне-гессенбергова форма матрицы сохраняется после выполнения одной итерации QR алгоритма (проверим ниже).
Матрица Хаусхолдера - это матрица вида
где \(v-n \times 1\) вектор и \(v^* v=1\). Покажите, что \(H\) унитарна и Эрмитова. Это также матрица отражения относительно плоскости с нормалью \(v\):
Свойства матрицы Хаусхолдера#
Преобразование Хаусхолдера может занулить все элементы в столбце матрицы, кроме первого:
Доказательство
Пусть \(e_1=(1,0, \ldots, 0)^T\), тогда нам надо найти такой вектор \(v\) что
где \(\alpha\) неизвестная константа. В силу унитарной инвариантности \(\|\cdot\|_2\) мы получим
и
Также, можем выращить \(v\) из равенства \(x-2\left(v^* x\right) v=\alpha e_1\):
Умножив последнее выражение на \(x^*\) получим
Итак, \(v\) существует и равна
Случай симметричной (эрмитовой) матрицы
Если матрица \(A\) симметричная (эрмитова), то \(A=A^*\), тогда \(H=H^*\) и верхне-гессенбергова форма оказывается трёхдиагональной матрицей
Далее мы будем говорить только о симметричном трёхдиагональном виде верхне-гессенберговой формы
Любая эрмитова матрица может быть приведена к трёхдиагональной форме с помощью отражений Хаусхолдера
Основная идея: трёхдиагональная форма сохраняется при выполнении QR алгоритма, и сложность одной итерации может быть сокращена до \(\mathcal{O}(n)\) !
Одна итерация QR алгоритма сохраняет верхне-гессенбергову форму матрицы#
Одна итерация QR алгоритма имеет следующий вид:
Если \(A_0 = A\) симметричная трёхдиагональная матрица , то эта форма сохраняется.
Давайте это проверим!
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_style('white')
#Generate a random tridiagonal matrix
n = 20
d = np.random.randn(n)
sub_diag = np.random.randn(n-1)
mat = np.diag(d) + np.diag(sub_diag, -1) + np.diag(sub_diag, 1)
plt.spy(mat)
plt.title("Original matrix", fontsize=16)
q, r = np.linalg.qr(mat)
plt.figure()
b = r.dot(q)
b[abs(b) <= 1e-12] = 0
plt.spy(b)
plt.title("After one iteration of QR algorithm", fontsize=16)
#plt.figure()
#plt.imshow(np.abs(r.dot(q)))
b[0, :]
array([-0.96810585, 1.51121923, 0. , 0. , 0. ,
0. , 0. , 0. , 0. , 0. ,
0. , 0. , 0. , 0. , 0. ,
0. , 0. , 0. , 0. , 0. ])
Трёхдиагональная форма
Работая с трёхдиагональной формой, вам не нужно вычислять матрицу \(Q\) : нужно лишь вычислить трёхдиагональную часть, которая получается после итерации
в случае \(A_k=A_k^*\)
Такая матрица определяется \(\mathcal{O}(n)\) параметрами
Вычисление QR разложения более сложное, но возможно вычислить \(A_{k+1}\) напрямую без вычисления \(Q_k\).
Это называется неявный QR-шаr.
Теорема о неявном QR алгоритме
Bce реализации неявного QR алгоритма основаны на следующей теореме
Теорема.
Пусть
верхне-гессенбергова форма матрицы. Тогда первый столбец матрицы \(Q\) определяет все остальные её столбцы. Он может быть найден из следующего уравнения
Сходимость QR алгоритма#
Сходимость QR алгоритма - непростой вопрос (см. Е.Е. Тыртышников «Краткий курс численного анализа»)! Итог. Если у нас есть разложение вида
и
а также есть зазор между собственными значениями в матрице \(\Lambda_1\) и \(\Lambda_2\left(\left|\lambda_1\right| \geq \cdots \geq\left|\lambda_m\right|>\left|\lambda_{m+1}\right| \geq \cdots \geq\left|\lambda_r\right|>0\right)\), тогда блок \(A_{21}^{(k)}\) матрицы \(A_k\) сходится к нулевому в процессе работы QR алгоритма со скоростью
где \(m\) размер матрицы \(\Lambda_1\)
Таким образом, нам нужно увеличить зазор между \(\Lambda_1\) и \(\Lambda_2\). Это можно сделать с помощью QR алгоритма со сдвигами.
Модификации QR алгоритма#
QR алгоритм со сдвигами#
Сходимость такого алгоритма линейная с фактором
где \(\lambda_m-m\)-ое большее по модулю собственное значение. Если сдвиг близок к собственному вектору, сходимость более быстрая.
Существуют различные стратегии выбора сдвигов.
Использование сдвигов - это общий подход к ускорению сходимости итерационных методов вычисления собственных значений. Далее покажем, как выбирать сдвиги для более простых алгоритмов