Rで 線形代数:固有値と固有ベクトル を求めます。
1. 行列の固有値の説明
正方行列 A
、ゼロではないベクトル v
、スカラー λ
(ラムダ)の間に Av = λv
の関係式が成り立つとき、λ
を行列 A
の 固有値 (Eigenvalue)、v
をその固有値 λ
に対応する 固有ベクトル (Eigenvector) と呼びます。
幾何学的な意味
- 左辺
Av
は、ベクトルv
を行列A
で線形変換(回転、拡大・縮小、せん断など)した結果のベクトルです。 - 右辺
λv
は、元のベクトルv
を単純にλ
倍しただけのベクトルです。
つまり、Av = λv
という式は、 「行列 A
で変換しても、方向が変わらず、大きさだけが λ
倍になるような、ベクトル v
が存在する」 ということを意味しています。
- 固有ベクトル
v
: 行列A
の「変換の軸」となる方向を表します。 - 固有値
λ
: その軸方向への「伸び縮みの倍率」を表します。-
λ > 1
なら、その方向に伸びる。 -
0 < λ < 1
なら、その方向に縮む。 -
λ < 0
なら、方向が反転して伸び縮みする。
-
固有値の求め方
Av = λv
を変形すると、
Av - λv = 0
Av - λIv = 0
(Iは単位行列)
(A - λI)v = 0
この方程式で v
はゼロではないベクトルなので、v
がゼロ以外の解を持つためには、行列 (A - λI)
が正則であってはなりません(つまり、逆行列を持ってはならない)。逆行列が存在すると、両辺に左からそれを掛けて v=0
となり、前提に反してしまうからです。
行列が正則でない条件は、その行列式が0であることです。
det(A - λI) = 0
この方程式は λ
に関する多項式となり、固有多項式または特性方程式と呼ばれます。この方程式を解くことで、固有値 λ
を求めることができます。
2. R言語による実装
固有値を求めるためのアルゴリズムは様々ありますが、ここでは以下の2つの方法を実装します。
2.1 べき乗法 (最大固有値を求める)
# べき乗法により、絶対値最大の固有値とその固有ベクトルを求める関数
<- function(A, num_simulations = 100, tol = 1e-10) {
power_method # ランダムな初期ベクトルを生成
<- rnorm(ncol(A))
b_k
for (i in 1:num_simulations) {
# 1. Ax を計算
<- A %*% b_k
b_k1
# 2. 正規化(長さを1にする)
<- sqrt(sum(b_k1^2))
b_k1_norm <- b_k1 / b_k1_norm
b_k
}
# 収束した固有ベクトル b_k を使ってレイリー商から固有値を計算
# λ = (t(v)Av) / (t(v)v) (vが単位ベクトルなら t(v)Av)
<- t(b_k) %*% A %*% b_k
lambda
return(list(eigenvalue = as.numeric(lambda), eigenvector = b_k))
}
# ---- べき乗法のテストと比較 ----
<- matrix(c(2, 1, 1, 2), nrow = 2) # 固有値は3と1
A_test cat("--- べき乗法のテスト (A_test) ---\n")
cat("元の行列 A_test:\n")
print(A_test)
# 実装
<- power_method(A_test)
result_pm cat("\n[実装 (べき乗法)]\n")
cat("絶対値最大の固有値:", result_pm$eigenvalue, "\n")
cat("対応する固有ベクトル:\n")
print(result_pm$eigenvector)
# Rの組み込み関数
<- eigen(A_test)
eigen_builtin_test cat("\n[Rの組み込み関数 eigen()]\n")
cat("全ての固有値:", eigen_builtin_test$values, "\n")
# 最大の固有値と対応する固有ベクトルを抽出
<- eigen_builtin_test$values[1] # eigen()は降順で返す
max_eigenvalue_builtin <- eigen_builtin_test$vectors[, 1]
max_eigenvector_builtin cat("絶対値最大の固有値:", max_eigenvalue_builtin, "\n")
cat("対応する固有ベクトル:\n")
print(max_eigenvector_builtin)
# 比較
cat("\n[比較]\n")
cat("固有値は等しいか:", all.equal(result_pm$eigenvalue, max_eigenvalue_builtin), "\n")
cat("固有ベクトルは(符号の違いは別として)等しいか:", all.equal(abs(result_pm$eigenvector), abs(matrix(max_eigenvector_builtin))), "\n")
--- べき乗法のテスト (A_test) ---
元の行列 A_test:
[,1] [,2]
[1,] 2 1
[2,] 1 2
[実装 (べき乗法)]
絶対値最大の固有値: 3
対応する固有ベクトル:
[,1]
[1,] 0.7071068
[2,] 0.7071068
[Rの組み込み関数 eigen()]
全ての固有値: 3 1
絶対値最大の固有値: 3
対応する固有ベクトル:
[1] 0.7071068 0.7071068
[比較]
固有値は等しいか: TRUE
固有ベクトルは(符号の違いは別として)等しいか: TRUE
2.2 QR法 (すべての固有値を求める)
これは、行列を繰り返しQR分解していくことで、固有値を求めるアルゴリズムです。A_k = Q_k R_k
と分解し、次に A_{k+1} = R_k Q_k
を計算する、という操作を繰り返します。A_k
は最終的に、対角成分に固有値が並んだ上三角行列に収束します。
# QR法により、すべての固有値と固有ベクトルを求める関数
# 固有値がすべて実数である場合を想定しています。
<- function(A, num_simulations = 100) {
eigen_qr_method # 入力が正方行列である必要があります。
if (nrow(A) != ncol(A)) stop("入力は正方行列である必要があります。")
<- A
Ak <- ncol(A)
n
# 全てのQ行列の積を保存するための行列Pを初期化
# Pは最終的に固有ベクトルを列として持つ行列になる
<- diag(1, n)
P
for (i in 1:num_simulations) {
# 1. AkのQR分解
<- qr(Ak)
qr_res <- qr.Q(qr_res)
Q <- qr.R(qr_res)
R
# 2. Ak+1 = R %*% Q で更新
<- R %*% Q
Ak
# 3. Pに行列Qを右から掛けて更新
<- P %*% Q
P
}
# 収束した行列の対角成分が固有値
<- diag(Ak)
eigenvalues
# Pが固有ベクトル行列
<- P
eigenvectors
return(list(values = eigenvalues, vectors = eigenvectors))
}
# ---- QR法のテストと比較 ----
# 対称行列を作成
<- matrix(c(
A_sym 6, 2, -1,
2, 5, 1,
-1, 1, 4
nrow = 3, byrow = TRUE)
),
cat("--- QR法による全固有値・固有ベクトルの計算 ---\n")
cat("元の行列 A:\n")
print(A_sym)
# --- 実装による計算 ---
cat("\n\n--- 実装 (QR法) による結果 ---\n")
<- eigen_qr_method(A_sym)
result_qr
# QR法の結果はソートされていないので、比較のために降順にソートする
# まずソート順序を取得
<- order(result_qr$values, decreasing = TRUE)
order_qr # その順序に従って固有値と固有ベクトルを並べ替える
<- result_qr$values[order_qr]
sorted_values_qr <- result_qr$vectors[, order_qr]
sorted_vectors_qr
cat("固有値 (降順ソート):\n")
print(sorted_values_qr)
cat("\n固有ベクトル (固有値の大きい順に並べ替え):\n")
print(sorted_vectors_qr)
# --- Rの組み込み関数による計算 ---
cat("\n\n--- Rの組み込み関数 eigen() による結果 ---\n")
<- eigen(A_sym)
eigen_builtin cat("固有値:\n")
print(eigen_builtin$values)
cat("\n固有ベクトル:\n")
print(eigen_builtin$vectors)
# --- 最終的な比較 ---
cat("\n\n--- 最終比較 ---\n")
# 固有値の比較
cat(
"固有値の集合は等しいか:",
all.equal(sorted_values_qr, eigen_builtin$values), "\n"
)
# 固有ベクトルの比較
# 符号の違いを吸収するために絶対値で比較する
cat(
"固有ベクトルは(符号の違いは別として)等しいか:",
all.equal(abs(sorted_vectors_qr), abs(eigen_builtin$vectors)), "\n"
)
# --- 検算: Av = λv が成り立つか ---
cat("\n\n--- 検算 (実装の結果) ---\n")
# 1番目の固有値・固有ベクトルで確認
<- sorted_values_qr[1]
lambda1 <- sorted_vectors_qr[, 1]
v1 <- A_sym %*% v1
Av1 <- lambda1 * v1
lambda1_v1
cat("A %*% v1:\n")
print(Av1)
cat("λ1 * v1:\n")
print(lambda1_v1)
cat("Av = λv は成り立つか:", all.equal(Av1, matrix(lambda1_v1)), "\n")
--- QR法による全固有値・固有ベクトルの計算 ---
元の行列 A:
[,1] [,2] [,3]
[1,] 6 2 -1
[2,] 2 5 1
[3,] -1 1 4
--- 実装 (QR法) による結果 ---
固有値 (降順ソート):
[1] 7.571201 5.143277 2.285521
固有ベクトル (固有値の大きい順に並べ替え):
[,1] [,2] [,3]
[1,] 0.79848935 -0.3471550 0.4918314
[2,] 0.59942270 0.5341270 -0.5961550
[3,] -0.05574221 0.7708384 0.6345873
--- Rの組み込み関数 eigen() による結果 ---
固有値:
[1] 7.571201 5.143277 2.285521
固有ベクトル:
[,1] [,2] [,3]
[1,] 0.79848935 -0.3471550 0.4918314
[2,] 0.59942270 0.5341270 -0.5961550
[3,] -0.05574221 0.7708384 0.6345873
--- 最終比較 ---
固有値の集合は等しいか: TRUE
固有ベクトルは(符号の違いは別として)等しいか: TRUE
--- 検算 (実装の結果) ---
A %*% v1:
[,1]
[1,] 6.0455237
[2,] 4.5383500
[3,] -0.4220355
λ1 * v1:
[1] 6.0455237 4.5383500 -0.4220355
Av = λv は成り立つか: TRUE
以上です。