Rで 線形代数:コレスキー分解 を試みます。
1. コレスキー分解の説明
コレスキー分解とは、対称かつ正定値な行列 A
を、ある下三角行列 L
とその転置行列 t(L)
(これは上三角行列になります)の積に分解することです。
A = L t(L)
それぞれの行列の性質は以下の通りです。
行列 A
(分解対象の行列)
コレスキー分解が可能な行列は、以下の2つの条件を両方満たす必要があります。
- 対称行列 (Symmetric Matrix): 転置しても元の行列と等しい行列 (
A = t(A)
)。つまり、a_ij = a_ji
が成り立ちます。 - 正定値行列 (Positive-definite Matrix):
- 任意のゼロではないベクトル
x
に対して、t(x)Ax > 0
が常に成り立つ行列。 - 「すべての固有値が正である」、「すべての主小行列式が正である」行列です。
- 正定値行列は常に対称です(より正確には、エルミート行列ですが、実数行列の範囲では対称行列と同じです)。
- 任意のゼロではないベクトル
行列 L
(下三角行列)
- LU分解の
L
とは異なり、コレスキー分解のL
は対角成分が1であるとは限りません。L
の対角成分は正の実数になります。
L = [l_11, 0, 0 ]
[l_21, l_22, 0 ]
[l_31, l_32, l_33 ]
t(L)
(上三角行列)
-
L
の転置行列であるため、上三角行列になります。
t(L) = [l_11, l_21, l_31]
[ 0, l_22, l_32]
[ 0, 0, l_33]
コレスキー分解の考え方
対称正定値行列 A
を A = L * t(L)
と分解する操作となり、t(L)
は L
の転置ですので、実質的に「一つの行列 L
を見つける」操作と言えます。
A = L t(L)
の式を行列の要素で書き下してみると、L
の各要素を計算する公式を導き出すことができます。 例えば、3x3
行列の場合、A = L t(L)
は以下のようになります。
[a_11, a_12, a_13] [l_11, 0, 0] [l_11, l_21, l_31]
[a_21, a_22, a_23] = [l_21, l_22, 0] [ 0, l_22, l_32]
[a_31, a_32, a_33] [l_31, l_32, l_33] [ 0, 0, l_33]
右辺を計算し、左辺の a_ij
と比較することで、l_11
, l_21
, l_22
, … の順に l_ij
を次々と計算していくことができます。
-
a_11 = l_11²
→l_11 = sqrt(a_11)
-
a_21 = l_21 * l_11
→l_21 = a_21 / l_11
-
a_22 = l_21² + l_22²
→l_22 = sqrt(a_22 - l_21²)
- … と続きます。
この計算過程で、根号(sqrt
)の中身が負になると計算ができません。行列 A
が正定値であるという条件は、この根号の中身が常に正であることを保証してくれます。
2. R言語による実装
以下に、コレスキー分解を実装したRコードを示します。 前提条件(対称性、正定値性)のチェックも行っています。
# コレスキー分解を実装する関数
<- function(A) {
fun_cholesky <- nrow(A)
n
# --- 前提条件のチェック ---
# 1. 正方行列か?
if (ncol(A) != n) {
stop("入力は正方行列である必要があります。")
}# 2. 対称行列か? (isSymmetricは誤差を考慮してくれる)
if (!isSymmetric(A, tol = 1e-10)) {
stop("入力された行列は対称行列ではありません。")
}
# Lを初期化 (ゼロ行列で)
<- matrix(0, n, n)
L
# --- コレスキー分解の計算 ---
# Lの要素を左上の要素から順に計算していく
for (i in 1:n) {
for (j in 1:i) {
# Lの対角要素 (j == i) の場合
if (j == i) {
# A[i,i] から、それまでに計算したLのi行目の要素の二乗和を引く
<- 0
sum_k if (i > 1) {
<- sum(L[i, 1:(i - 1)]^2)
sum_k
}
# 平方根の中身を計算
<- A[i, i] - sum_k
val_under_sqrt
# 3. 正定値性のチェック (平方根の中身が負にならないか)
if (val_under_sqrt < 0) {
stop("入力された行列は正定値ではありません。")
}
<- sqrt(val_under_sqrt)
L[i, i] else { # Lの非対角要素 (j < i) の場合
}
# A[i,j] から、Lのi行目とj行目の内積を引く
<- 0
sum_k if (j > 1) {
<- sum(L[i, 1:(j - 1)] * L[j, 1:(j - 1)])
sum_k
}
<- (A[i, j] - sum_k) / L[j, j]
L[i, j]
}
}
}
return(L)
}
# ---- テスト ----
# テスト用の対称正定値行列を作成
<- matrix(c(
A 4, 12, -16,
12, 37, -43,
-16, -43, 98
nrow = 3, byrow = TRUE)
),
cat("--- コレスキー分解のテスト ---\n")
cat("元の行列 A:\n")
print(A)
# 実装でコレスキー分解を実行
cat("\n--- 実装による結果 ---\n")
<- fun_cholesky(A)
L cat("L (実装):\n")
print(L)
# 検算: L %*% t(L) が A に戻るか確認
<- L %*% t(L)
A_reconstructed cat("\n検算: L %*% t(L) の結果(Aに復元されるか):\n")
print(A_reconstructed)
cat("AとL %*% t(L)は等しいか:", all.equal(A, A_reconstructed), "\n")
# 参考: Rの組み込み関数によるコレスキー分解
cat("\n\n--- Rの組み込み関数 chol() による結果 ---\n")
# chol() はデフォルトで「上三角行列」を返すので、転置してLにする
<- t(chol(A))
L_builtin cat("L (組み込み関数):\n")
print(L_builtin)
# --- 最終的な比較 ---
cat("\n\n--- 最終比較 ---\n")
cat("実装Lと組み込み関数のLは等しいか:", all.equal(L, L_builtin), "\n")
--- コレスキー分解のテスト ---
元の行列 A:
[,1] [,2] [,3]
[1,] 4 12 -16
[2,] 12 37 -43
[3,] -16 -43 98
--- 実装による結果 ---
L (実装):
[,1] [,2] [,3]
[1,] 2 0 0
[2,] 6 1 0
[3,] -8 5 3
検算: L %*% t(L) の結果(Aに復元されるか):
[,1] [,2] [,3]
[1,] 4 12 -16
[2,] 12 37 -43
[3,] -16 -43 98
AとL %*% t(L)は等しいか: TRUE
--- Rの組み込み関数 chol() による結果 ---
L (組み込み関数):
[,1] [,2] [,3]
[1,] 2 0 0
[2,] 6 1 0
[3,] -8 5 3
--- 最終比較 ---
実装Lと組み込み関数のLは等しいか: TRUE
以上です。