Rで線形代数:シュミットの直交化法

Rで 線形代数:シュミットの直交化法 を確認します。

1. シュミットの直交化法(Gram-Schmidt Orthonormalization)とは

目的

シュミットの直交化法は、「互いに斜めに交わっているベクトルの組」から、「互いに直交し、かつ長さが1のベクトルの組(正規直交基底)」を作り出すための手順(アルゴリズム)です。

直交行列は、その列ベクトルが正規直交基底をなす行列ですが、シュミットの直交化法は、その正規直交基底を生成するための手法です。

中心的なアイデア:「射影」して「引き算」する

この手法は、「射影 (Projection)」の考え方に基づいています。

あるベクトルから、別のベクトル方向の成分を「引き算」することで、残った成分が元のベクトルと直交する、という性質を利用します。

アルゴリズムのステップ

n個の線形独立なベクトル v₁, v₂, ..., vₙ が与えられたとします。

ここから正規直交基底 e₁, e₂, ..., eₙ を作る手順は以下の通りです。

Step 1: 最初のベクトルを決める

  • 最初の基底ベクトル u₁ は、元のベクトル v₁ をそのまま使います。

    u₁ = v₁

Step 2: 2番目のベクトルを直交化する

  • v₂ から、u₁ 方向の成分を引き算します。u₁方向の成分とは、v₂u₁射影したベクトル proj_u₁(v₂) のことです。
  • u₂ = v₂ - proj_u₁(v₂)
  • こうしてできた u₂ は、 u₁ と直交します。

Step 3: 3番目のベクトルを直交化する

  • v₃ から、すでに確定した u₁ 方向の成分と u₂ 方向の成分を両方とも引き算します。
  • u₃ = v₃ - proj_u₁(v₃) - proj_u₂(v₃)
  • こうしてできた u₃ は、u₁u₂ の両方に直交します。

Step 4以降: 同様に繰り返す

  • k番目のベクトル uₖ は、vₖ から、それ以前に確定したすべての基底ベクトル u₁, u₂, ..., uₖ₋₁ の方向成分をすべて引き算して作ります。

    uₖ = vₖ - Σᵢ₌₁ᵏ⁻¹ proj_uᵢ(vₖ)

最終ステップ: 正規化 (Normalization)

  • 上記の手順で、互いに直交するベクトルの組 u₁, u₂, ..., uₙ が得られました。
  • 最後に、これらの各ベクトル uᵢ を、それぞれの長さ(ノルム)||uᵢ|| で割って、長さを1にします。
  • eᵢ = uᵢ / ||uᵢ||
  • この e₁, e₂, ..., eₙ が、最終的に求めたい正規直交基底です。

応用

  • QR分解: 行列を直交行列Qと上三角行列Rに分解するQR分解は、シュミットの直交化法をアルゴリズムとして実行しています。

2. Rによるシミュレーション

Rコード

# --------------------------------------------------
# シュミットの直交化法のシミュレーション
# --------------------------------------------------

# --- 準備 ---

# 1. 元となる線形独立なベクトルの組を3つ定義
v1 <- c(1, 1, 0)
v2 <- c(1, 0, 1)
v3 <- c(0, 1, 1)

# 元のベクトルをまとめた行列 V
V <- cbind(v1, v2, v3)
cat("元のベクトル (行列Vの列):\n")
print(V)

# ベクトルの内積を計算するヘルパー関数
dot_product <- function(a, b) {
  sum(a * b)
}

# 射影ベクトルを計算するヘルパー関数
# v の u 上への射影を計算
projection <- function(v, u) {
  scalar_projection <- dot_product(v, u) / dot_product(u, u)
  return(scalar_projection * u)
}


# --- Step by Step で直交化を実行 ---

cat("\n--- 直交化のプロセス ---\n")

# u1 の計算
u1 <- v1
cat("u1 (v1をそのまま使用):\n")
print(u1)

# u2 の計算
proj_u1_v2 <- projection(v2, u1)
u2 <- v2 - proj_u1_v2
cat("\nu2 (v2 - proj_u1(v2)):\n")
print(u2)

# u3 の計算
proj_u1_v3 <- projection(v3, u1)
proj_u2_v3 <- projection(v3, u2)
u3 <- v3 - proj_u1_v3 - proj_u2_v3
cat("\nu3 (v3 - proj_u1(v3) - proj_u2(v3)):\n")
print(u3)

# --- 確認1: u1, u2, u3 が互いに直交しているか? ---
cat("\n--- 確認1: u1, u2, u3の直交性の確認 (内積が0か?) ---\n")
cat("u1・u2 =", round(dot_product(u1, u2), 10), "\n")
cat("u1・u3 =", round(dot_product(u1, u3), 10), "\n")
cat("u2・u3 =", round(dot_product(u2, u3), 10), "\n")
cat("→ すべての内積が0になり、互いに直交していることが確認できました。\n")


# --- 最終ステップ: 正規化 (長さを1にする) ---
cat("\n--- 最終ステップ: 正規化 ---\n")

# ノルム(ベクトルの長さ)を計算する関数
norm_vec <- function(v) {
  sqrt(sum(v^2))
}

e1 <- u1 / norm_vec(u1)
e2 <- u2 / norm_vec(u2)
e3 <- u3 / norm_vec(u3)

cat("正規直交ベクトル e1:\n")
print(e1)
cat("正規直交ベクトル e2:\n")
print(e2)
cat("正規直交ベクトル e3:\n")
print(e3)

# これらの正規直交ベクトルを列とする行列Qを作成
Q <- cbind(e1, e2, e3)
cat("\n作成された行列 Q (列がe1, e2, e3):\n")
print(Q)

# --- 確認2: 行列Qが直交行列になっているか? ---
cat("\n--- 確認2: Qが直交行列か? (Q^T * Q = I) ---\n")
QtQ <- t(Q) %*% Q
print(round(QtQ, 10))
cat("→ 単位行列となり、Qが直交行列であることが確認できました。\n")

# --- QR分解との比較 ---
cat("\n--- QR分解の結果との比較 ---\n")
Q_qr <- qr.Q(qr(V))
cat("Rのqr()関数で得られた直交行列:\n")
print(Q_qr)
cat("→ 自作したQと(1列目の符号は異なっていますが)同じ基底を張っていることがわかります。\n")
# ※QR分解のアルゴリズムによっては、ベクトルの符号が逆になることがありますが、
#   張られる空間や直交性は同じです。
元のベクトル (行列Vの列):
     v1 v2 v3
[1,]  1  1  0
[2,]  1  0  1
[3,]  0  1  1

--- 直交化のプロセス ---
u1 (v1をそのまま使用):
[1] 1 1 0

u2 (v2 - proj_u1(v2)):
[1]  0.5 -0.5  1.0

u3 (v3 - proj_u1(v3) - proj_u2(v3)):
[1] -0.6666667  0.6666667  0.6666667

--- 確認1: u1, u2, u3の直交性の確認 (内積が0か?) ---
u1・u2 = 0 
u1・u3 = 0 
u2・u3 = 0 
→ すべての内積が0になり、互いに直交していることが確認できました。

--- 最終ステップ: 正規化 ---
正規直交ベクトル e1:
[1] 0.7071068 0.7071068 0.0000000
正規直交ベクトル e2:
[1]  0.4082483 -0.4082483  0.8164966
正規直交ベクトル e3:
[1] -0.5773503  0.5773503  0.5773503

作成された行列 Q (列がe1, e2, e3):
            e1         e2         e3
[1,] 0.7071068  0.4082483 -0.5773503
[2,] 0.7071068 -0.4082483  0.5773503
[3,] 0.0000000  0.8164966  0.5773503

--- 確認2: Qが直交行列か? (Q^T * Q = I) ---
   e1 e2 e3
e1  1  0  0
e2  0  1  0
e3  0  0  1
→ 単位行列となり、Qが直交行列であることが確認できました。

--- QR分解の結果との比較 ---
Rのqr()関数で得られた直交行列:
           [,1]       [,2]       [,3]
[1,] -0.7071068  0.4082483 -0.5773503
[2,] -0.7071068 -0.4082483  0.5773503
[3,]  0.0000000  0.8164966  0.5773503
→ 自作したQと(1列目の符号は異なっていますが)同じ基底を張っていることがわかります。

実行結果と解説

このコードを実行すると、まず3つのベクトル v1, v2, v3 が定義されます。これらは互いに直交していません。 次に、シュミットの直交化法のアルゴリズムに従い、u1, u2, u3 が順番に計算されます。u2v2 から u1 成分を取り除いたもの、u3v3 から u1u2 の成分を取り除いたものです。

確認1では、得られた u1, u2, u3 の内積を計算し、それらがすべて0になる(=互いに直交する)ことを確認します。

最終ステップでは、直交ベクトル u1, u2, u3 をそれぞれの長さで割り、正規直交ベクトル e1, e2, e3 を得ます。

最後に、これらの正規直交ベクトルを列に持つ行列 Q を作り、t(Q) %*% Q を計算すると単位行列になることから、シュミットの直交化法によって直交行列が正しく生成できたことが確認できます。QR分解との比較でも、同じ結果が得られることがわかります。

以上です。