Rで 線形代数:LU分解 を試みます。
1. LU分解の説明
LU分解とは、正方行列 A
を、下三角行列 L
(Lower triangular) と上三角行列 U
(Upper triangular) の積に分解することです。
A = LU
それぞれの行列の性質は以下の通りです。
行列 L
(下三角行列)
- 行列の対角成分より右上の要素がすべて0である行列です。
- ドゥーリトル法 による場合、
L
の対角成分はすべて1とします。
L = [ 1, 0, 0 ]
[l_21, 1, 0 ]
[l_31, l_32, 1 ]
行列 U
(上三角行列)
- 行列の対角成分より左下の要素がすべて0である行列です。これはQR分解の
R
行列と同じ形です。
U = [u_11, u_12, u_13]
[ 0, u_22, u_23]
[ 0, 0, u_33]
LU分解の考え方:ガウスの消去法
LU分解は「ガウスの消去法」を利用します。 ガウスの消去法は、行の基本変形(ある行から別の行の定数倍を引く)を繰り返して、行列を上三角行列に変形する操作であり。最終的に得られる上三角行列が、U
行列です。
さらに、L
行列は、ガウスの消去法の各ステップで使った「係数」を記録した行列になります。例えば、2行目から1行目の c
倍を引いた場合、L
行列の (2, 1)
成分に c
を格納します。この操作の記録を並べると、対角成分が1の下三角行列 L
が出来上がります。
ピボット選択と PA = LU
分解
ガウスの消去法を行っていると、対角成分(ピボット)が0になってしまい、割り算ができず計算が止まってしまうことがあります。また、ピボットの値が0に非常に近いと、計算誤差が大きくなり(桁落ち)、数値的に不安定になります。
この問題を解決するのが「ピボット選択 (Pivoting)」です。 これは、計算の各ステップで、対象となる列の要素の中で絶対値が最大のものを選び、その要素がある行と現在の行を入れ替える操作です。
この「行の入れ替え」操作は、置換行列 P
(Permutation matrix) を用いて表現できます。P
は単位行列の行を入れ替えただけの行列です。
ピボット選択を導入したLU分解は、元の A = LU
という形ではなく、以下のようになります。
PA = LU
これは「A
の行を P
に従って並べ替えたものなら、LU分解できる」という意味です。この PA = LU
分解は、A
が正則行列(逆行列を持つ行列)であれば、計算可能です。
2. R言語による実装
以下に、ピボット選択付きの PA = LU
分解を実装したRコードを示します。
# LU分解を実装する関数 (ピボット選択付き PA=LU)
<- function(A) {
fun_lu <- nrow(A)
n
# L, U, P を初期化
<- diag(0, n) # まずはゼロ行列として開始
L <- A # UはAのコピーとして開始
U <- diag(1, n) # Pは単位行列として開始
P
# ガウスの消去法を実行
# kはピボットの列を指す
for (k in 1:(n - 1)) {
# 1. ピボット選択 (Partial Pivoting)
# k列目のk行目以降で絶対値が最大の要素を探す
<- max(abs(U[k:n, k]))
pivot_val
# 最大値がある行のインデックスを取得 (kからの相対位置なのでk-1を足す)
<- which.max(abs(U[k:n, k])) + k - 1
max_row_index
# 2. 行の交換 (U, P, そしてLの計算済み部分)
if (max_row_index != k) {
# Uの行を交換
<- U[k, ]
temp_row_U <- U[max_row_index, ]
U[k, ] <- temp_row_U
U[max_row_index, ]
# Pの行を交換 (行の入れ替え操作を記録)
<- P[k, ]
temp_row_P <- P[max_row_index, ]
P[k, ] <- temp_row_P
P[max_row_index, ]
# Lの交換は少し注意が必要。これまでに計算済みの部分(k列より左)を交換する
if (k > 1) {
<- L[k, 1:(k - 1)]
temp_row_L 1:(k - 1)] <- L[max_row_index, 1:(k - 1)]
L[k, 1:(k - 1)] <- temp_row_L
L[max_row_index,
}
}
# 3. 前進消去
# ピボット行より下の行の、k列目の要素を0にしていく
for (i in (k + 1):n) {
# 消去係数を計算し、Lに格納する
<- U[i, k] / U[k, k]
L[i, k]
# Uの行から、(ピボット行 * 係数) を引く
# これにより U[i, k] がゼロになる
:n] <- U[i, k:n] - L[i, k] * U[k, k:n]
U[i, k
}
}
# Lの対角成分を1にする (ドゥーリトル法)
diag(L) <- 1
# 結果をリストで返す
return(list(P = P, L = L, U = U))
}
# ---- テスト ----
# テスト用の行列を作成
<- matrix(c(
A 1, 1, 1,
2, 2, 5,
4, 6, 8
nrow = 3, byrow = TRUE)
),
cat("--- LU分解のテスト ---\n")
cat("元の行列 A:\n")
print(A)
# 実装でLU分解を実行
<- fun_lu(A)
lu_result
cat("\n--- 実装による結果 ---\n")
cat("P (置換行列):\n")
print(lu_result$P)
cat("\nL (下三角行列):\n")
print(lu_result$L)
cat("\nU (上三角行列):\n")
print(lu_result$U)
# 検算: PA = LU が成り立つか確認
<- lu_result$P %*% A
PA <- lu_result$L %*% lu_result$U
LU
cat("\n--- 検算 ---\n")
cat("P %*% A の結果:\n")
print(PA)
cat("\nL %*% U の結果:\n")
print(LU)
# all.equalで浮動小数点数の誤差を考慮して比較
cat("\nPA と LU は等しいか:", all.equal(PA, LU), "\n")
--- LU分解のテスト ---
元の行列 A:
[,1] [,2] [,3]
[1,] 1 1 1
[2,] 2 2 5
[3,] 4 6 8
--- 実装による結果 ---
P (置換行列):
[,1] [,2] [,3]
[1,] 0 0 1
[2,] 0 1 0
[3,] 1 0 0
L (下三角行列):
[,1] [,2] [,3]
[1,] 1.00 0.0 0
[2,] 0.50 1.0 0
[3,] 0.25 0.5 1
U (上三角行列):
[,1] [,2] [,3]
[1,] 4 6 8.0
[2,] 0 -1 1.0
[3,] 0 0 -1.5
--- 検算 ---
P %*% A の結果:
[,1] [,2] [,3]
[1,] 4 6 8
[2,] 2 2 5
[3,] 1 1 1
L %*% U の結果:
[,1] [,2] [,3]
[1,] 4 6 8
[2,] 2 2 5
[3,] 1 1 1
PA と LU は等しいか: TRUE
3. Rの組み込み関数 lu{Matrix} との比較
<- Matrix::lu(A)
lu_builtin_result <- Matrix::expand(lu_builtin_result)
expanded_lu expanded_lu
$L
3 x 3 Matrix of class "dtrMatrix" (unitriangular)
[,1] [,2] [,3]
[1,] 1.00 . .
[2,] 0.50 1.00 .
[3,] 0.25 0.50 1.00
$U
3 x 3 Matrix of class "dtrMatrix"
[,1] [,2] [,3]
[1,] 4.0 6.0 8.0
[2,] . -1.0 1.0
[3,] . . -1.5
$P
3 x 3 sparse Matrix of class "pMatrix"
[1,] . . |
[2,] . | .
[3,] | . .
# as.matrix()で標準の行列に変換
<- as.matrix(expanded_lu$P)
P_builtin <- as.matrix(expanded_lu$L)
L_builtin <- as.matrix(expanded_lu$U)
U_builtin
cat("P (組み込み関数):\n")
print(P_builtin)
cat("\nL (組み込み関数):\n")
print(L_builtin)
cat("\nU (組み込み関数):\n")
print(U_builtin)
P (組み込み関数):
[,1] [,2] [,3]
[1,] FALSE FALSE TRUE
[2,] FALSE TRUE FALSE
[3,] TRUE FALSE FALSE
L (組み込み関数):
[,1] [,2] [,3]
[1,] 1.00 0.0 0
[2,] 0.50 1.0 0
[3,] 0.25 0.5 1
U (組み込み関数):
[,1] [,2] [,3]
[1,] 4 6 8.0
[2,] 0 -1 1.0
[3,] 0 0 -1.5
本サンプルでは実装と lu{Matrix} の結果は一致しています。
但し、サンプルとした 行列A は 全ての主小行列式が非ゼロ ではありませんので、LU分解の結果は一意ではありません。
行列A のLU分解はピボット選択なしでは不可能ですので、実装と lu{Matrix} の結果が同じであるのは、両者が同じ手順でピボット選択しているからに過ぎません。
以上です。