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 べき乗法 (最大固有値を求める)
# べき乗法により、絶対値最大の固有値とその固有ベクトルを求める関数
power_method <- function(A, num_simulations = 100, tol = 1e-10) {
# ランダムな初期ベクトルを生成
b_k <- rnorm(ncol(A))
for (i in 1:num_simulations) {
# 1. Ax を計算
b_k1 <- A %*% b_k
# 2. 正規化(長さを1にする)
b_k1_norm <- sqrt(sum(b_k1^2))
b_k <- b_k1 / b_k1_norm
}
# 収束した固有ベクトル b_k を使ってレイリー商から固有値を計算
# λ = (t(v)Av) / (t(v)v) (vが単位ベクトルなら t(v)Av)
lambda <- t(b_k) %*% A %*% b_k
return(list(eigenvalue = as.numeric(lambda), eigenvector = b_k))
}
# ---- べき乗法のテストと比較 ----
A_test <- matrix(c(2, 1, 1, 2), nrow = 2) # 固有値は3と1
cat("--- べき乗法のテスト (A_test) ---\n")
cat("元の行列 A_test:\n")
print(A_test)
# 実装
result_pm <- power_method(A_test)
cat("\n[実装 (べき乗法)]\n")
cat("絶対値最大の固有値:", result_pm$eigenvalue, "\n")
cat("対応する固有ベクトル:\n")
print(result_pm$eigenvector)
# Rの組み込み関数
eigen_builtin_test <- eigen(A_test)
cat("\n[Rの組み込み関数 eigen()]\n")
cat("全ての固有値:", eigen_builtin_test$values, "\n")
# 最大の固有値と対応する固有ベクトルを抽出
max_eigenvalue_builtin <- eigen_builtin_test$values[1] # eigen()は降順で返す
max_eigenvector_builtin <- eigen_builtin_test$vectors[, 1]
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法により、すべての固有値と固有ベクトルを求める関数
# 固有値がすべて実数である場合を想定しています。
eigen_qr_method <- function(A, num_simulations = 100) {
# 入力が正方行列である必要があります。
if (nrow(A) != ncol(A)) stop("入力は正方行列である必要があります。")
Ak <- A
n <- ncol(A)
# 全てのQ行列の積を保存するための行列Pを初期化
# Pは最終的に固有ベクトルを列として持つ行列になる
P <- diag(1, n)
for (i in 1:num_simulations) {
# 1. AkのQR分解
qr_res <- qr(Ak)
Q <- qr.Q(qr_res)
R <- qr.R(qr_res)
# 2. Ak+1 = R %*% Q で更新
Ak <- R %*% Q
# 3. Pに行列Qを右から掛けて更新
P <- P %*% Q
}
# 収束した行列の対角成分が固有値
eigenvalues <- diag(Ak)
# Pが固有ベクトル行列
eigenvectors <- P
return(list(values = eigenvalues, vectors = eigenvectors))
}
# ---- QR法のテストと比較 ----
# 対称行列を作成
A_sym <- matrix(c(
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")
result_qr <- eigen_qr_method(A_sym)
# QR法の結果はソートされていないので、比較のために降順にソートする
# まずソート順序を取得
order_qr <- order(result_qr$values, decreasing = TRUE)
# その順序に従って固有値と固有ベクトルを並べ替える
sorted_values_qr <- result_qr$values[order_qr]
sorted_vectors_qr <- result_qr$vectors[, order_qr]
cat("固有値 (降順ソート):\n")
print(sorted_values_qr)
cat("\n固有ベクトル (固有値の大きい順に並べ替え):\n")
print(sorted_vectors_qr)
# --- Rの組み込み関数による計算 ---
cat("\n\n--- Rの組み込み関数 eigen() による結果 ---\n")
eigen_builtin <- eigen(A_sym)
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番目の固有値・固有ベクトルで確認
lambda1 <- sorted_values_qr[1]
v1 <- sorted_vectors_qr[, 1]
Av1 <- A_sym %*% v1
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 以上です。

