Rで線形代数:固有値分解

Rで 線形代数:固有値分解 を試みます。

1. 行列の固有値分解の説明

固有値分解とは、ある対角化可能な正方行列 A を、以下の3つの行列の積に分解することです。

A = P D P⁻¹

ここで、それぞれの行列には次のような意味があります。

行列 P (固有ベクトル行列)

  • 行列 A固有ベクトルを列として並べた行列です。
  • An x n 行列で、線形独立な固有ベクトルが n 個存在する場合(つまり、対角化可能な場合)、P は正則行列となり、逆行列 P⁻¹ が存在します。

行列 D (固有値行列)

  • 行列 A固有値を対角成分に持つ対角行列です。
  • Pk 番目の列にある固有ベクトルに対応する固有値が、Dk 番目の対角成分 D_kk に配置されます。
  • 非対角成分はすべて0です。
D = [λ₁,  0,  0]
    [ 0, λ₂,  0]
    [ 0,  0, λ₃]

行列 P⁻¹

  • 固有ベクトル行列 P逆行列です。

固有値分解の考え方

固有値分解は、固有値・固有ベクトルの定義式 Av = λv を、すべての固有ベクトルに対して一度に行列形式で表現したものであり、

AP = PD

という関係式が成り立ちます。

P が正則(逆行列を持つ)場合、この式の両辺に右から P⁻¹ を掛けることで、A = PDP⁻¹ が導かれます。

幾何学的な意味

固有値分解は、行列 A による線形変換を、以下の3つのステップに分解でき、ベクトル xA で変換する Ax は、PDP⁻¹x と考えることができます。

  1. P⁻¹x (基底変換): まず、ベクトル x を、固有ベクトルが作る座標系(基底)で表現し直します。
  2. D(P⁻¹x) (伸縮): 次に、新しい座標系の各軸(固有ベクトルの方向)に対して、対応する固有値 λ の倍率で単純な伸縮(引き伸ばしたり縮めたり)を行います。対角行列 D の役割はこれです。
  3. P(D(P⁻¹x)) (基底を戻す): 最後に、伸縮されたベクトルを、元の標準的な座標系に戻します。

つまり、どんな複雑に見える線形変換も、「座標系を変えて、それぞれの軸で単純に伸縮して、座標系を元に戻す」という操作に分解できることを示しています。

対角化可能性について

すべての正方行列が固有値分解できるわけではありません。線形独立な固有ベクトルが n 個(行列の次元の数だけ)存在しない場合があり、そのような行列は対角化可能ではない行列になります。

ただし、以下のケースでは、常に対角化が可能です。

  • すべての固有値が異なる値を持つ行列
  • 対称行列

対称行列の固有値分解

行列 A対称行列 (A = t(A)) の場合、固有値分解は以下の通りになります。

A = P D t(P)

このとき、

  • 固有値 λ はすべて実数になります。
  • 固有ベクトルは互いに直交します。
  • そのため、P正規直交行列となり、逆行列 P⁻¹ は単純に転置 t(P) で置き換えられます。

これは、SVDの A = UΣt(V) という形と似ています。実際、対称行列のSVDでは、UV は(符号の違いを除き)同じものになり、特異値 σ は固有値 λ の絶対値となります。


2. R言語による実装

# -------------------------------------------------------------------
# 1. 固有値・固有ベクトルを求めるQR法
# -------------------------------------------------------------------
eigen_qr_method <- function(A, num_simulations = 100) {
  if (nrow(A) != ncol(A)) stop("入力は正方行列である必要があります。")
  Ak <- A
  n <- ncol(A)
  P <- diag(1, n)
  for (i in 1:num_simulations) {
    qr_res <- qr(Ak)
    Q <- qr.Q(qr_res)
    R <- qr.R(qr_res)
    Ak <- R %*% Q
    P <- P %*% Q
  }
  return(list(values = diag(Ak), vectors = P))
}

# -------------------------------------------------------------------
# 2. 固有値分解を組み立てる関数
# -------------------------------------------------------------------
eigen_decomposition <- function(A) {
  # まず、固有値と固有ベクトルを求める
  eigen_res <- eigen_qr_method(A)

  # 固有値から対角行列Dを作成
  D <- diag(eigen_res$values)

  # 固有ベクトル行列P
  P <- eigen_res$vectors

  # Pの逆行列を計算 (組み込み関数solveを使用)
  # solve()が失敗する場合、行列は正則でない(対角化不可能)
  P_inv <- tryCatch(
    {
      solve(P)
    },
    error = function(e) {
      stop("固有ベクトル行列Pが正則でなく、逆行列を計算できませんでした。行列は対角化不可能です。")
    }
  )

  # 結果をリストで返す
  return(list(P = P, D = D, P_inv = P_inv))
}


# ===================================================================
# テストと比較
# ===================================================================
# 必ず対角化可能な対称行列をサンプルとします
A <- matrix(c(
  6, 2, -1,
  2, 5, 1,
  -1, 1, 4
), nrow = 3, byrow = TRUE)

cat("--- 固有値分解のテスト ---\n")
cat("元の行列 A:\n")
print(A)

# --- 実装による分解 ---
cat("\n\n--- 実装による結果 ---\n")
decomp_s <- eigen_decomposition(A)
cat("固有ベクトル行列 P:\n")
print(decomp_s$P)
cat("\n固有値行列 D:\n")
print(decomp_s$D)
cat("\nPの逆行列 P⁻¹:\n")
print(decomp_s$P_inv)

# 検算
A_reconstructed <- decomp_s$P %*% decomp_s$D %*% decomp_s$P_inv
cat("\n検算: P %*% D %*% P⁻¹ は A と等しいか:", all.equal(A, A_reconstructed), "\n")


# --- Rの組み込み関数による結果との比較 ---
cat("\n\n--- Rの組み込み関数 eigen() を使った分解 ---\n")
eigen_b <- eigen(A)
P_b <- eigen_b$vectors
D_b <- diag(eigen_b$values)
P_inv_b <- solve(P_b)
A_reconstructed_b <- P_b %*% D_b %*% P_inv_b
cat("検算: P %*% D %*% P⁻¹ は A と等しいか:", all.equal(A, A_reconstructed_b), "\n")

# 注意:
# 固有値・固有ベクトルの順序や、固有ベクトルの符号はアルゴリズムによって異なる場合があります。
# 対称行列ですので P⁻¹ は t(P) と等しくなることも確認できます。
cat("\n対称行列の特性: P⁻¹ は t(P) と等しいか:", all.equal(decomp_s$P_inv, t(decomp_s$P)), "\n")
--- 固有値分解のテスト ---
元の行列 A:
     [,1] [,2] [,3]
[1,]    6    2   -1
[2,]    2    5    1
[3,]   -1    1    4


--- 実装による結果 ---
固有ベクトル行列 P:
            [,1]       [,2]       [,3]
[1,]  0.79848935 -0.3471550  0.4918314
[2,]  0.59942270  0.5341270 -0.5961550
[3,] -0.05574221  0.7708384  0.6345873

固有値行列 D:
         [,1]     [,2]     [,3]
[1,] 7.571201 0.000000 0.000000
[2,] 0.000000 5.143277 0.000000
[3,] 0.000000 0.000000 2.285521

Pの逆行列 P⁻¹:
           [,1]       [,2]        [,3]
[1,]  0.7984893  0.5994227 -0.05574221
[2,] -0.3471550  0.5341270  0.77083835
[3,]  0.4918314 -0.5961550  0.63458730

検算: P %*% D %*% P⁻¹ は A と等しいか: TRUE 


--- Rの組み込み関数 eigen() を使った分解 ---
検算: P %*% D %*% P⁻¹ は A と等しいか: TRUE 

対称行列の特性: P⁻¹ は t(P) と等しいか: TRUE 

以上です。