Rで線形代数:固有値と固有ベクトル

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 

以上です。