スペクトル分解

実数を成分とする対称行列(エルミート行列)はスペクトル分解という形で固有値と固有ベクトルにもとづいて分解することができる。

この記事ではスペクトル分解の概要とPythonにおける実装について説明する。

スペクトル分解に似た行列分解の手法に特異値分解がある。特異値分解は、推薦システムにおいてよく使用されるスペクトル分解の一形態である。

推薦システムで利用される特異値分解と低ランク近似については以下の記事に書いたので、時間があるときに参考にしていただきたい。

http://wayama.io/rec/linalg/base/

ソースコード

github

  • jupyter notebook形式のファイルはこちら

google colaboratory

  • google colaboratory で実行する場合はこちら

実行環境

OSはmacOSである。LinuxやUnixのコマンドとはオプションが異なりますので注意していただきたい。

!sw_vers
ProductName:		macOS
ProductVersion:		13.5.1
BuildVersion:		22G90
!python -V
Python 3.9.17

pandasのテーブルを見やすいようにHTMLのテーブルにCSSの設定を行う。

from IPython.core.display import HTML

style = """
<style>
    .dataframe thead tr:only-child th {
        text-align: right;
    }

    .dataframe thead th {
        text-align: left;
        padding: 5px;
    }

    .dataframe tbody tr th {
        vertical-align: top;
        padding: 5px;
    }

    .dataframe tbody tr:hover {
        background-color: #ffff99;
    }

    .dataframe {
        background-color: white;
        color: black;
        font-size: 16px;
    }

</style>
"""
HTML(style)

基本的なライブラリをインポートし watermark を利用してそのバージョンを確認する。 ついでにrandomとnumpyのseedの設定を行う。

%matplotlib inline
%config InlineBackend.figure_format = 'svg'
import random

import scipy
import numpy as np

import matplotlib
import matplotlib.pyplot as plt

import networkx as nx

import array_to_latex as a2l

seed = 123
random_state = 123

random.seed(seed)
np.random.seed(seed)


from watermark import watermark

print(watermark(python=True, watermark=True, iversions=True, globals_=globals()))
Python implementation: CPython
Python version       : 3.9.17
IPython version      : 8.17.2

numpy         : 1.25.2
array_to_latex: 0.91
matplotlib    : 3.8.1
networkx      : 3.1
scipy         : 1.11.2

Watermark: 2.4.3

スペクトル分解の概要

最初に簡単な対称行列の性質をおさらいする。

実数を成分とする対称行列を $\displaystyle \mathbf{A} = \left(a_{ij}\right) \in \mathbb{R}^{n\times n}$とする。対称行列なので、$\mathbf{A} = \mathbf{A^T}$である。この行列の固有値はすべて実数である。固有値を$\alpha_1, \alpha_2, \cdots, \alpha_n$とすると、小さい方から順に以下のように定義できる。

$$ \begin{equation} \alpha_1 \leqq \alpha_2 \leqq \ldots \leqq \alpha_n \end{equation} $$

各固有値に対応する固有ベクトルを$\mathbf{v_1} , \mathbf{v_2}, \cdots, \mathbf{v_n}$とすると、

$$ \begin{equation} \mathbf{A} \mathbf{x}_i=\alpha_i \mathbf{x}_i \quad(i=1,2, \ldots, n) \end{equation} $$

が成立する。

また、エルミート行列(実数の場合は対称行列)の固有ベクトルは、そのベクトル空間における正規直交基底を形成する。

$$ \begin{equation} \mathbf{x}_i^T \mathbf{x}_j=\delta _{i j} \end{equation} $$

よって、

$$ \begin{equation} \mathbf{A}\left(\begin{array}{llll} \mathbf{v_1} & \mathbf{v_2} & \cdots & \mathbf{v_n} \end{array}\right)=\left(\begin{array}{llll} \mathbf{v_1} & \mathbf{v_2} & \cdots & \mathbf{v_n} \end{array}\right)\left(\begin{array}{cccc} \alpha_1 & 0 & \cdots & 0 \\ 0 & \alpha_2 & \cdots & 0 \\ \vdots & \vdots & \ddots & \vdots \\ 0 & 0 & \cdots & \alpha_n \end{array}\right) \end{equation} $$

が成立する。ここで、$\mathbf{V}$を

$$ \mathbf{V}=\left(\begin{array}{llll} \mathbf{v_1} & \mathbf{v_2} & \cdots & \mathbf{v_n} \end{array}\right) $$

と定義すると、$\mathbf{V}^T\mathbf{V}=\mathbf{I}$で、$\mathbf{V}^T=\mathbf{V}^{-1}$となるので、$\mathbf{V}\mathbf{V}^{T}=\mathbf{I}$も成立する。

よって、

$$ \begin{equation} \mathbf{A}=\left(\begin{array}{llll} \mathbf{v}_1 & \mathbf{v}_2 & \cdots & \mathbf{v}_n \end{array}\right)\left(\begin{array}{cccc} \alpha_1 & 0 & \cdots & 0 \\ 0 & \alpha_2 & \cdots & 0 \\ \vdots & \vdots & \ddots & \vdots \\ 0 & 0 & \cdots & \alpha_n \end{array}\right)\left(\begin{array}{c} \mathbf{v}_1^T \\ \mathbf{v}_2^T \\ \vdots \\ \mathbf{v}_n^T \end{array}\right) \end{equation} $$

と書ける。これを各固有値に注目し計算すると以下のように和の形に分解できる。

$$ \begin{equation} \mathbf{A}=\alpha_1 \mathbf{v}_1 \mathbf{v}_1^T+\alpha_2 \mathbf{v}_2 \mathbf{v}_2^T+\cdots+\alpha_n \mathbf{v}_n \mathbf{v}_n^T \end{equation} $$

これがスペクトル分解と言われる行列の分解手法の一つである。

Pythonによるスペクトル分解の実装

例として以下の行列$\mathbf{A}$に対してスペクトル分解をPythonを利用して実装する。

$$ \mathbf{A}=\begin{pmatrix} 0 & 0 & 0 & 1 & 0 & 0 & 1 \\ 0 & 0 & 0 & 1 & 1 & 0 & 1 \\ 0 & 0 & 0 & 1 & 0 & 0 & 1 \\ 1 & 1 & 1 & 0 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 1 & 0 \\ 1 & 1 & 1 & 0 & 0 & 0 & 0 \end{pmatrix} $$

固有値の計算が必要だが、numpyを利用する。 $\mathbf{A}$は対称行列なので、numpy.linalg.eigh() が適用可能である。

# 行列Aの定義
A = np.array(
    [
        [0, 0, 0, 1, 0, 0, 1],
        [0, 0, 0, 1, 1, 0, 1],
        [0, 0, 0, 1, 0, 0, 1],
        [1, 1, 1, 0, 0, 0, 0],
        [0, 1, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 1, 0],
        [1, 1, 1, 0, 0, 0, 0],
    ]
)

# 固有値の計算
# aは固有値、vは固有ベクトルを示す
a, v = np.linalg.eigh(A)

固有ベクトルが正しいかを確認するために、以下の値を求める。

$$ \begin{equation} \left(\begin{array}{llll} \mathbf{v}_1 & \mathbf{v}_2 & \cdots & \mathbf{v}_n \end{array}\right)\left(\begin{array}{cccc} \alpha_1 & 0 & \cdots & 0 \\ 0 & \alpha_2 & \cdots & 0 \\ \vdots & \vdots & \ddots & \vdots \\ 0 & 0 & \cdots & \alpha_n \end{array}\right)\left(\begin{array}{c} \mathbf{v}_1^T \\ \mathbf{v}_2^T \\ \vdots \\ \mathbf{v}_n^T \end{array}\right) \end{equation} $$

# 再現確認
_A = v @ np.diagflat(a) @ v.T
a2l.to_ltx(np.abs(_A), frmt="{:.0f}", arraytype="pmatrix")

$$ \begin{pmatrix} 0 & 0 & 0 & 1 & 0 & 0 & 1 \\ 0 & 0 & 0 & 1 & 1 & 0 & 1 \\ 0 & 0 & 0 & 1 & 0 & 0 & 1 \\ 1 & 1 & 1 & 0 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 1 & 0 \\ 1 & 1 & 1 & 0 & 0 & 0 & 0 \end{pmatrix} $$

となり、正しく固有値と固有ベクトルが求められている。

最後にスペクトル分解の式(6)に従い、計算を行う。

# スペクトル分解の再現性確認
__A = 0
for i in range(len(v)):
    __A += a[i] * v[:, i : i + 1] @ v[:, i : i + 1].T

a2l.to_ltx(np.abs(__A), frmt="{:.0f}", arraytype="pmatrix")

$$ \begin{pmatrix} 0 & 0 & 0 & 1 & 0 & 0 & 1 \\ 0 & 0 & 0 & 1 & 1 & 0 & 1 \\ 0 & 0 & 0 & 1 & 0 & 0 & 1 \\ 1 & 1 & 1 & 0 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 1 & 0 \\ 1 & 1 & 1 & 0 & 0 & 0 & 0 \end{pmatrix} $$

スペクトル分解の式(6)を利用して、最初の行列$\mathbf{A}$を再現することが確認された。

結論

この記事ではスペクトル分解の概要を説明し、実際にPythonで実装を行った。