Как найти собственные числа и собственные значения матрицы? Методы, излагаемые в курсе линейной алгебры, основанные на определении — применимы ли они к реальным данным? Существует ли простой алгоритм поиска этих величин, который можно понять, а не просто поверить?

Теория


Кратко обрисуем предмет нашего интереса: пусть A — квадратная матрица размера n на n, тогда ненулевой вектор x, удовлетворяющий равенству $Ax=\lambda x$, называется собственным вектором, а число $\lambda$ — собственным значением. Интуиция, стоящая за определением, говорит нам следующее: линейный оператор, выраженный матрицей A, действует в направлении вектора X как сжатие/растяжение.

И ещё немного теоретических отступлений
Несложно заметить, что я действительно обрисовываю теорию очень краткими мазками: более того, здесь и всюду дальше я буду считать, что элементы матрицы — вещественные числа из $\mathbb{R}$, и только при редких оказиях я буду упоминать комплексные числа $\mathbb{C}$. Тот факт, что линейное пространство и соответствующие линейные операторы и матрицы могут быть построены над произвольным полем — здесь нам не нужен, бесконечномерные линейные пространства — тем более. Если у вас есть какие-то вопросы к этому параграфу, но не к предыдущему — просто продолжайте читать дальше.


Непосредственно из матричных операций и определения детерминанта получается характеристическое — оно же вековое — уравнение: $\det(A - \lambda I)$, раскрываемое в монический многочлен степени n относительно лямбда. Согласно основной теореме алгебры и теореме Безу, такое уравнение всегда имеет n возможно комплексных корней (вопрос кратности корней и связанных с этим неприятностей — рассмотрим позднее).

Наивный метод поиска предлагает нам решить характеристическое уравнение, подставить полученные корни в систему $(A - \lambda I) x = 0$, решить полученную однородную систему линейных уравнений, получив таким образом собственные вектора x.

Казалось бы — а что тут плохого? Тут всё отлично, если у вас n=2, потому что все мы со школы умеем решать уравнения второй степени, таким образом мы легко найдём собственные числа, и, наверное, систему из двух линейных уравнений — тоже решим. Более того, уже за нас всё давно решили, даже в аналитическом (общем) виде, смотреть здесь. В случае n=3 — если это пример с «подогнанными» числами, скорее всего, получится угадать собственные значения и решить системы. Если числа вполне произвольные, то, в принципе, есть формула корней кубического уравнения, в ещё более ужасном виде она существует для уравнений 4-й степени, для степеней 5 и выше — её нет, иначе говоря, единственный рабочий вариант — применение численных методов. Вот к этому мы и перейдём.

Почему «прямой» метод не прямой
Может возникнуть идея прямой работы через определение, с уже численными методами решения характеристического уравнения. На самом деле при попытке такого подхода упускается ещё один шаг: получение характеристического уравнения. Действительно, чтобы получить его, нужно раскрыть детерминант, причём в символьном виде, если опять же следовать по его определению, то сложность его вычисления будет n!.. Таким образом, сначала нужно применить менее наивный метод нахождения характеристического полинома. И только после этого приступать к его численным решениям.

Степенной метод



▍ Идея метода


Предположим, для матрицы A существует собственный базис $\{e_i\}_i, i=1..n$ — базис, состоящий из собственных векторов, имеющий размерность n. Возьмём произвольный (случайный) вектор x, рассмотрим $A^kx$, казалось бы, какое-то странное выражение, и непонятно, что дальше с ним делать. Но не зря был упомянут собственный базис: с одной стороны, x раскладывается в нём в следующую сумму $\sum_{n=1}^{n} x_i e_i$, а с другой — собственные вектора $e_i$, «проходя» через умножение на A, дают выражения $\{\lambda_i e_i\}_i, i=1..n$. Из вышесказанного получаем $A^k x = \sum_{i=1}^{i=n} \lambda_i^k x_i e_i$. Теперь сделаем ещё одно предположение: одно из собственных чисел строго больше всех остальных (по модулю). Тогда становится достаточно ясно, что одно из слагаемых будет превосходить все другие, при росте k в выражении $A^kx$. Сначала покажем, как «вытащить» собственный вектор, позже вернёмся к собственному числу.

На данный момент в наших рассуждениях мы всё ещё оперируем значениями, которых мы не знаем, всё, что нам по-настоящему известно — это матрица A и вектор $A^kx$, но на самом деле этого достаточно. Последнее недостающее звено — деление $A^kx$ на его норму $\lVert \vec{A^kx} \rVert$, где под последней мы будем понимать квадратный корень из суммы квадратов компонентов, иначе говоря — Евклидову норму. Действительно, пусть $\lambda_1$ — наибольшее собственное число, вытащив $ x_1 \lambda_1^k$ одновременно из числителя и знаменателя дроби $\frac{A^kx}{\lVert A^kx \rVert}$ $= \frac{x_1 \lambda_1^k (e_1 + \frac{\sum_{i=2}^{i=n} \lambda_i^k x_i}{x_1 \lambda_1^k})}{x_1 \lambda_1^k \sqrt{1 + \frac{\sum_{i=2}^{i=n} (\lambda_i^k x_i)^2}{(x_1 \lambda_1^k)^2})}}$, мы получим числитель, стремящийся к $e_1$, и знаменатель, стремящийся к 1.

Имея собственный вектор, можно получить соответствующее собственное число, используя отношение Рэлея:

$\frac{((Ax),x)}{(x,x)} = \lambda \frac{(x,x)}{(x,x)} = \lambda$


▍ Об условиях и ограничениях


В предыдущем пункте я затребовал существование собственного базиса и наличие собственного числа, которое по модулю больше всех остальных. От первого из этих условий мы сейчас избавимся, второе условие — перефразируем и добавим маленькую ремарку.

Итак, я почти уверен, что в любом курсе линейной алгебры затрагивается тема собственного базиса, но вот то, что он не обязан существовать — вполне возможно, часто пропускается или «зажёвывается». Не погружаясь глубоко в детали: для кратных корней характеристического уравнения может не существовать достаточно (то есть столько, сколько есть кратность корня) линейно-независимых собственных векторов, в связи с чем вводится понятие алгебраических и геометрических кратностей. Пожалуй, я ограничусь примером, рассмотрев матрицу: $\begin{pmatrix} 1 & 1\\ 0 & 1 \end{pmatrix}$, чьё собственное значение, имеющее кратность два, равно одному и собственный вектор $\begin{pmatrix} 1\\ 0 \end{pmatrix}$ — предлагаю проверить читателям самим, для развития некоторой интуиции. Но на самом деле, это не просто пример «плохой» матрицы, без собственного базиса, это пример Жордановой клетки, где на диагонали стоит собственное число (единица), и единицы (для любого значения собственного числа) расположены в верхнем треугольнике над ней.

На Хабре есть очень обстоятельная статья на эту тему, здесь же я замечу только, что существует некий аналог собственного базиса (причём часть там будет собственными векторами, а часть так называемыми присоединёнными) в котором любая матрица представляется в виде блоков — клеток Жордана, причём наше обычное разложение по собственному базису можно рассматривать как частный случай, с очень простой клеткой Жордана. И самое главное: Жорданова матрица нильпотентна, то есть при умножении саму на себя, единицы будут сдвигаться вправо, а значит не позже, чем через n итераций, для нас не будет никакой разницы между собственным базисом и этим базисом, а значит, метод может работать вне зависимости от диагнолизируемости матрицы A.

Пункт насчёт существования максимального по модулю собственного значения, на самом деле означает, что максимальное значение не может быть кратным корнем, и оно же не может парой комплексно-сопряжённых комплексных корней, потому что их модуль одинаков, в обеих случаях последовательность $\frac{A^kx}{\lVert A^kx \rVert}$ не сойдётся.
Парочка примеров на эту тему, единичная матрица $\begin{pmatrix} 1 & 0\\ 0 & 1 \end{pmatrix}$, которая уже представлена в диагональном виде, в тоже время очевидно, что $\frac{A^kx}{\lVert A^kx \rVert}$ для любого сколько угодно большого k мы опять получим вектор x, пусть и нормированный.

Пример похитрее, оператор вращения вектора на плоскости — тоже линейный, матрицу его приводить не нужно — мы и так знаем, что делает умножение вектора на него — вращает вектор, соответственно, наша последовательность никогда не сойдётся к собственному (и любому другому) вектору. Более того, это в принципе невозможно: из этого же определения ясно, что собственные вектора такого оператора — комплексные, кроме случая углов поворота, кратным 180-ти градусам — наша последовательность не может выдать комплекснозначный результат.

Обещанная ремарка: есть исчезающе малый шанс выбрать начальный вектор x не удачно, а именно так, что в его разложении в собственном/жордановом базисе его проекция на искомый $x_i$ равняется нулю, иначе говоря, x ортогонален $x_i$.

▍ Имплементация


Посмотрим результаты работы метода, вооружившись для начала матрицей размера n=2 — в этом случае легко наглядно показать результаты работы метода и его сходимость к собственному вектору. Будем использовать язык python с библиотеками numpy и matplotlib, как удобные средства оперирования матрицами и создания визуализаций.

import numpy as np
from matplotlib.animation import FuncAnimation
import matplotlib.pyplot as plt
from numpy import linalg as LA

# given matrix
A = np.array([[4, 2], [3, 4]])

eigenvalues, eigenvectors = LA.eig(A)
# an eigenvector corresponding to the max eigenvalue
eigenvector = -eigenvectors[:,0]

print("eigenvalues", eigenvalues)
print("eigenvector",eigenvector)

k = 10
X_origin = [0, 0]
Y_origin = [0, 0]

# Starting vector
#X = np.random.rand(A.shape[0])
# initial guess vector
X = np.array([-0.7, 0.7])

fig, ax = plt.subplots()
plt.title("Convergence of the power iteration method")
plt.grid()
plt.xlim(-1, 1)
plt.ylim(-1, 1)
U = [eigenvector[0], X[0]]
V = [eigenvector[1], X[1]]

q = ax.quiver(X_origin, Y_origin, U, V, color=['b', 'r'], angles='xy', scale_units='xy', scale=1)

def animate(i):
    # Power it
    global X
    if i: 
        X = np.matmul(A, X)
        # Normalize it
        X = X / np.linalg.norm(X, ord=2)

    U = [eigenvector[0], X[0]]
    V = [eigenvector[1], X[1]]
    q.set_UVC(U, V)

ani = FuncAnimation(fig, animate, frames=k, repeat=False, interval=1000)

plt.show()

l = np.dot(np.matmul(A, X), X) / np.dot(X, X)

print("lamda = ", l)
print("X = ", X)


Синим цветом обозначен искомый собственный вектор, красным — начальное приближение и результаты итераций. Видно, что, несмотря на начальный угол, близкий к 90 градусам, вектор приближений очень быстро поворачивается до искомого вектора, начиная визуально совпадать с ним с 5-й итерации.

Посмотрим вывод:

eigenvalues [6.44948974 1.55051026]
eigenvector [-0.63245553 -0.77459667]
lamda = 6.449515757348442
X = [-0.63247569 -0.77458021]

Точность в собственном векторе — на уровне 5-го знака после запятой, в собственном значении — на уровне 4-го знака.

Возьмём теперь матрицу размера 50 и посмотрим, как быстро, по норме, метод сойдётся к искомому вектору. Чтобы избежать проблем с неуникальными максимальными значениями, можно сделать матрицу симметричной — такая матрица гарантированно имеет n вещественных собственных значений и n ортогональных собственных векторов.

import numpy as np
import matplotlib.pyplot as plt
from numpy import linalg as LA

np.random.seed(42)
n = 50
A = np.random.randint(-100, 100, size=(n, n))
# make it symmetric 
A = (A + A.T) / 2
eigenvalues, eigenvectors = LA.eig(A)
eigenvalues_abs = np.abs(eigenvalues)
index = np.argmax(eigenvalues_abs) 
lamda1 = eigenvalues[index]

eigenvector = eigenvectors[:,index]
X = np.random.rand(A.shape[0])
k = 40 

diff = np.zeros(k)
diff_x = np.linspace(1, k, num=k)

for i in range(k):
    X = np.matmul(A, X)
    # Normalize it
    X = X / np.linalg.norm(X, ord=2)
    
    diff[i] = np.linalg.norm(X - eigenvector)

eigenvalues_abs[index] = 0
index = np.argmax(eigenvalues_abs)

lamda2 = eigenvalues[index]

print("rate = ", lamda2 / lamda1)
print("diff", diff)

fig, ax = plt.subplots()
ax.plot(diff_x, diff)
plt.title("Convergence of the power iteration method")
plt.grid()
plt.show()

В результате чего мы получим следующий график:


Тут можно понять, что норма разницы уменьшается, но как именно? Лучше перерисовать ошибку в логарифмическом масштабе:


Тут можно заметить, что после определённого шага ошибка становится «линейна», то есть ведёт себя как геометрическая прогрессия со знаменателем, равным отношению второго по модулю собственного числа к максимальному собственному числу (rate в скрипте).

Вывод работы скрипта, на котором можно проверить, что это действительно так:

rate = 0.9139630521574826
diff [1.33026844 1.28732128 1.24311358 1.19893046 1.15496257 1.1105661
1.06502103 1.01786565 0.96895095 0.91839837 0.86653833 0.81384951
0.7609016 0.70830146 0.65664456 0.60647469 0.55825471 0.51234941
0.46902015 0.42842897 0.39064931 0.3556805 0.32346342 0.29389557
0.26684447 0.24215888 0.21967773 0.19923697 0.18067448 0.16383357
0.14856524 0.13472953 0.12219622 0.110845 0.10056539 0.09125633
0.08282571 0.07518982 0.0682727 0.06200557]

Что могло бы быть дальше


Если рассматривать метод формально, то у него существует очевидное ограничение: нахождение только одной пары вектор/число за раз. Это легко преодолимо: если вместо исходной матрицы A, рассмотреть матрицу $A-\lambda E$, где лямбда — найденное собственное значение, то лямбда «зануляется», позволяя искать следующее по величине значение.

Давайте вернёмся к матрице n=2 (для простоты) и проверим это:

A = np.array([[4, 2], [3, 4]])

eigenvalues, eigenvectors = LA.eig(A)

print("eigenvectors", eigenvectors)
print("eigenvalues", eigenvalues)

k = 10

A = A - np.identity(2) * eigenvalues[0]
X = np.random.rand(A.shape[0])

for i in range(k):
    X = np.matmul(A, X)
    # Normalize it
    X = X / np.linalg.norm(X, ord=2)
    
print("calculated eigenvector = ", X)
print("calculated eigenvalue=", np.dot(np.matmul(A, X), X) / np.dot(X, X) + eigenvalues[0])

Получаемый вывод:

eigenvectors [[ 0.63245553 -0.63245553]
[ 0.77459667 0.77459667]]
eigenvalues [6.44948974 1.55051026]
calculated eigenvector = [ 0.63245553 -0.77459667]
calculated eigenvalue= 1.5505102572168221

Всё работает, очевидно, что можно обобщить метод на случай n > 2, но… Это предполагает довольно много повторяющихся действий. В примере с n = 50 я уже употреблял приём «симметризации» матрицы, можно пойти дальше и сказать, что на самом деле, часто нас интересуют собственные значения и вектора именно симметричных матриц. Что, если нам изначально вооружиться n случайными, причём ортогональными (потому что искомые вектора — ортогональны) векторами, и попробовать реализовать тот же самый алгоритм? — На самом деле это не сработает, но это послужит своеобразным мостиком к QR алгоритму поиска собственных значений.

Также похожие идеи используются в методах, основанных на подпространстве Крылова.

Выводы


Степенной метод обладает рядом несомненных достоинств:

  • Простота теории, лежащей в его основе.
  • Простота реализации.

К недостаткам можно отнести:

  • Находит одну пару вектор/значение за раз.
  • Скорость сходимости может быть плохой при близких величинах собственных чисел.
  • Для произвольной матрицы без дополнительных проверок может не сойтись.

Источники


  1. https://en.wikipedia.org/wiki/Fundamental_theorem_of_algebra
  2. https://ru.wikipedia.org/wiki/%D0%A2%D0%B5%D0%BE%D1%80%D0%B5%D0%BC%D0%B0_%D0%91%D0%B5%D0%B7%D1%83
  3. https://ru.wikipedia.org/wiki/%D0%A4%D0%BE%D1%80%D0%BC%D1%83%D0%BB%D0%B0_%D0%9A%D0%B0%D1%80%D0%B4%D0%B0%D0%BD%D0%BE
  4. https://habr.com/ru/articles/755950
  5. https://en.wikipedia.org/wiki/Nilpotent_matrix
  6. https://en.wikipedia.org/wiki/Power_iteration
  7. https://ru.wikipedia.org/wiki/%D0%9F%D0%BE%D0%B4%D0%BF%D1%80%D0%BE%D1%81%D1%82%D1%80%D0%B0%D0%BD%D1%81%D1%82%D0%B2%D0%BE_%D0%9A%D1%80%D1%8B%D0%BB%D0%BE%D0%B2%D0%B0

© 2024 ООО «МТ ФИНАНС»

Telegram-канал со скидками, розыгрышами призов и новостями IT ?

Комментарии (20)


  1. JakErdy
    09.10.2024 12:09

    Было бы здорово в начале статьи привести пару примеров, где собственно эти собственные числа и векторы применяются. В чем их практический смысл. Чтобы у человека в первый раз их встречающего сформировалась более плотная ассоциация, и ему было проще вспомнить про вашу статью. В остальном, спасибо за материал.


    1. StarPilgrim Автор
      09.10.2024 12:09

      Хороший совет - возможно, добавлю такой раздел, если буду писать продолжение


  1. haqreu
    09.10.2024 12:09

    Чтобы избежать проблем с неуникальными максимальными значениями, можно сделать матрицу симметричной

    Например, единичную?


    1. StarPilgrim Автор
      09.10.2024 12:09

      Шансов, что в матрице со случайными элементами получается одинаковые вещественные собственные числа - очень мало


      1. haqreu
        09.10.2024 12:09

        Ну вопрос не в этом. Я просто отметил, что симметричная матрица не обязательно имеет разные собственные числа.

        А теперь серьёзный вопрос (я ищу ответ, и я его не знаю): если у меня есть положительноопределённая симметричная разреженная (большая, из разряда 100k x 100k) матрица, как мне найти её собственный вектор, соответствующий наименьшему ненулевому собственному числу, не прибегая к обратному степенному методу? Очень не хочется мне разложение Холецкого считать...


        1. Imaginarium
          09.10.2024 12:09

          как мне найти её собственный вектор, соответствующий наименьшему ненулевому собственному числу, не прибегая к обратному степенному методу?

          У Вас уже были какие-то подозрения на возможные решения этой задачи? Вопрос важный, присоединяюсь к нему, спасибо.


          1. haqreu
            09.10.2024 12:09

            Кое-что слышал, но краем уха. Попытаюсь побольше узнать, будет что интересное - сообщу.


            1. Imaginarium
              09.10.2024 12:09

              Спасибо!


        1. StarPilgrim Автор
          09.10.2024 12:09

          Могу пока что посоветовать посмотреть вот это https://en.wikipedia.org/wiki/LOBPCG


          1. haqreu
            09.10.2024 12:09

            Cпасибо, посмотрю!


            1. haqreu
              09.10.2024 12:09

              Можно я буду использовать комментарии для собственных заметок по теме? :)

              Копи-паста просто для того, чтобы я потом искал по этим ключевым словам:

              Since the Fiedler vector is associated with the smallest non-zero eigenvalue of a sparse matrix, calculating it with most common eigensolvers will be both inaccurate and inefficient. Using the default solver in R or Matlab will not yield usable results as these solvers focus on providing the eigenvectors associated with the largest eigenvalues. For best results you must use a sparse solver that can be specifically targeted to find a few eigenvectors at the low end of the spectrum. We precompute the Fiedler vectors using a smoothed aggregation preconditioner from pyAMG with scipy.sparse.linalg.lobpcg as in this example. This computation takes seconds on graphs with thousands of nodes.


              1. StarPilgrim Автор
                09.10.2024 12:09

                ладно))


        1. lgorSL
          09.10.2024 12:09

          Наверно вы уже видели, наткнулся на Википедии на статью со списком разложений: https://ru.m.wikipedia.org/wiki/Разложение_матрицы


  1. qss53770
    09.10.2024 12:09

    Опечатка ''оно же вековое'


    1. StarPilgrim Автор
      09.10.2024 12:09

      в чём опечатка? вековое уравнение - от слова "век"


  1. Pavel1117
    09.10.2024 12:09

    Не сочтите меня занудой, но скопировав ваш код, я получил совершенно другой вывод ответа: lamda = 1.5000000000000002
    X = [-0.7 0.7] , т.е. ответ не верный!

    В третьем блоке кода если убрать A = A - np.identity(2) * eigenvalues[0] , чтобы получить не второе, а первое собственное значение, получим:calculated eigenvalue = 12.898979579001677, что ровно в два раза больше искомого, следовательно и здесь алгоритм не работает.

    В общем и целом, я и сам не так давно интересовался данным алгоритмом, но столкнулся с проблемами при n>2, вроде бы и цифры схожи, и закономерности какие то прослеживаются, но не имея профильного математического образования, я так и не смог разобраться, как использовать этот итерационный метод, для больших матриц. Очень обрадовался, увидев вашу статью, но оказалось, что только код чуть другой и ни чего нового. А очень бы хотелось находить все собственные значения.


    1. StarPilgrim Автор
      09.10.2024 12:09

      1) Не знаю, что и куда вы скопировали: на всякий случай я перепроверил и опять получил верный результат

      2)Нужно из матрицы A "вычитать" все предшествующие собственные значения, то есть для 3-го надо отнять и первое и второе

      3)Не нужно быть "профессиональным математиком", что б разобраться с этим методом, нужно только найти в себе концентрацию и усидчивость

      4) Если вы внимательно всё прочли - там написано, что для нахождения всех собственных значений - это не лучший метод

      Кстати, я же не изобретатель этого алгоритма - действительно думаете, что вот в стольких местах он упомянут - и не работает?


      1. Pavel1117
        09.10.2024 12:09

        1) Может быть нам нужно мнение третьего человека? Скриншот №1.

        2) Да согласен, у нас одна и та же матрица А с двумя собственными значениями, в первом блоке кода мы находим первое значение, в третьем блоке кода мы вычитаем из А её первое собственное значение, прогоняем через алгоритм, то находим второе. Но если мы не вычтем (как я сделал на скриншоте №2) мы что получим? Тот же ответ, что и в первом блоке. Верно? Первое собственное значение eigenvalue = 6.44948 и там и там. Вы видите в скриншотах вашего кода этот ответ и там и там. Я нет.

        Я прогнал эту матрицу через свой код и всё находит без ошибок.

        3) Чтобы юзать чужой код - да, чтобы самостоятельно улучшить математический метод - нет.

        4) Я думаю у вас ошибка в коде, а не в методе. А ещё я думаю ваше " очевидно, что можно обобщить метод на случай n > 2 " не далеко от истины, но пока, этот много где упомянутый алгоритм работает "через пень колоду" для n > 2.

        Первый блок кода
        Первый блок кода
        Третий блок кода
        Третий блок кода


        1. haqreu
          09.10.2024 12:09

          Ну давайте я буду третьим человеком!

          А вы как быстро окошко матплотлиба закрыли? :)


  1. rcl
    09.10.2024 12:09

    Дайте знать, когда вернемся к вопросу кратности корней.