不完全Cholesky分解前処理付き共役勾配法
線型連立方程式を解く
線形な連立方程式は,行列
と表されます.
これを数値的に解くことは,次元
例えば,ガウスの消去法では,
線形連立方程式
例えば,極端な例を挙げると,
前処理
したがって, 連立方程式
としておいて, 行列
このような処理のことを,前処理と呼びます.
一方で,前処理も計算を必要とする処理である以上,前処理の計算量は小さいものでないと意味がありません.
そこで,前処理の目指すところは,ざっくり言うと以下の2点と言うことになります.
- 前処理後の線形連立方程式
は解きやすいP_LAP_R \boldsymbol{y} = P_L\boldsymbol{b} - 前処理の計算量は小さい
前処理付き共役勾配法
この記事では,前処理一般について議論するのではなく,前処理を施した共役勾配法について述べます.
すなわち,
- 前処理を施した連立線型方程式,
を解くアルゴリズムの導出.P_L A P_R \boldsymbol{y} = P_L\boldsymbol{b} - 前処理行列
の具体形の紹介.P_L, P_R
について述べます.
解くべき方程式
今回は共役勾配法に対する前処理を考えます.
そのため
となる,行列
この記事では,まず,
その後に
共役勾配法のアルゴリズム
ここで,共役勾配法のアルゴリズムを書いておきます.
これは,
Input:
Set
while
\alpha_j \coloneqq || \boldsymbol{r}_j ||_2^2/\langle A\boldsymbol{p}_j, \boldsymbol{p}_j\rangle_2 \boldsymbol{x}_{j+1} \coloneqq \boldsymbol{x}_j + \alpha_j \boldsymbol{p}_j \boldsymbol{r}_{j+1} \coloneqq \boldsymbol{r}_j - \alpha_j A\boldsymbol{p}_j \beta_{j+1} \coloneqq ||\boldsymbol{r}_{j+1}||_2^2/||\boldsymbol{r}_j||_2^2 \boldsymbol{p}_{j+1} \coloneqq \boldsymbol{r}_{j+1} + \beta_{j+1} \boldsymbol{p}_j j \coloneqq j+1
done
前処理つき共役勾配法のアルゴリズムの導出
まずは,前処理行列
前処理後の方程式,
に対して,上記の共役勾配法を適用すると,
Input:
Set
while
\alpha_j \coloneqq || \boldsymbol{r}_j ||_2^2/\langle S^\mathrm{T}AS \boldsymbol{p}_j, \boldsymbol{p}_j\rangle_2 \boldsymbol{y}_{j+1} \coloneqq \boldsymbol{y}_j + \alpha_j \boldsymbol{p}_j \boldsymbol{r}_{j+1} \coloneqq \boldsymbol{r}_j - \alpha_j S^\mathrm{T}AS \boldsymbol{p}_j \beta_{j+1} \coloneqq ||\boldsymbol{r}_{j+1}||_2^2/||\boldsymbol{r}_j||_2^2 \boldsymbol{p}_{j+1} \coloneqq \boldsymbol{r}_{j+1} + \beta_{j+1} \boldsymbol{p}_j j \coloneqq j+1
done
となる.この反復法によって得られた
とすることで,求めたい解
これをもう少し変形して,直接,解
記号の導入と式変形
まずは,以下のように記号を定義する.
今,
これらを使って,
となることがわかる.したがって,
となる
前処理付き共役勾配法のアルゴリズム
したがって,前処理付き共役勾配法のアルゴリズムは以下のようになる.
(注意:上までは
Input:
Set
while
\alpha_j \coloneqq \langle \boldsymbol{r}_j, \boldsymbol{z}_j \rangle_2/\langle A\boldsymbol{p}_j, \boldsymbol{p}_j \rangle_2 \boldsymbol{x}_{j+1} \coloneqq \boldsymbol{x}_j + \alpha_j \boldsymbol{p}_j \boldsymbol{r}_{j+1} \coloneqq \boldsymbol{r}_j - \alpha_j A\boldsymbol{p}_j \beta_j \coloneqq \langle \boldsymbol{r}_{j+1}, \boldsymbol{z}_{j+1} \rangle_2/\langle \boldsymbol{r}_j, \boldsymbol{z}_j \rangle_2 \boldsymbol{z}_{j+1} \coloneqq P \boldsymbol{r}_{j+1} \boldsymbol{p}_{j+1} \coloneqq \boldsymbol{z}_{j+1} + \beta_{j+1} \boldsymbol{p}_j j \coloneqq j+1
done
前処理行列を決める
ここまでは,前処理行列
ここでは,前処理行列として,どのようなものを選べば良いかを述べ,具体例を挙げます.
最初にも述べましたが,前処理と,線形連立方程式を解くことは表裏一体で,
今回は,共役勾配法に話を絞りましょう.
良い前処理とは
共役勾配法では, 行列
つまり,
よって,前処理を施して,相異なる固有値の個数を小さくできれば,前処理を施して良かったと言えます.
一方で,前処理には時間をさきたくありません.
行列
今回は,
不完全Cholesky分解
前処理の例として, 不完全Cholesky分解を紹介します.
Cholesky分解
その前にCholesky分解について簡単に説明します.
正定値対称行列
ここで,下三角行列
であるものとすると,このような分解は一意である.これをCholesky分解と呼ぶ.
これは,
となって,
となるが,これは,
となって,これによって,
いま,
であるから,結局,
となる.
Cholesky分解のアルゴリズム
Cholesky分解のアルゴリズムは以下のようになる.
Input:
l_{11} = a_{11} - for
toi =2 do;n - for j = 1 to i-1 do;
l_{ij} \coloneqq a_{ij} - for
tok = 1 do;j-1 l_{ij} \coloneqq l_{ij} - l_{ik}l_{jk}
l_{ij} \coloneqq l_{ij}/d_j
d_i \coloneqq a_{ii} - for
tok=1 do;i-1 d_i \coloneqq d_i - l_{ik} l_{ik}
d_i \coloneqq \sqrt{d_i}
- for j = 1 to i-1 do;
不完全Cholesky分解(0埋め)
行列
-
に属する添字の場合には,コレスキー分解の計算を行わず,その要素を\mathrm{M} とする.0 -
に属さない添字の場合には,通常通りコレスキー分解の計算を行い,要素の値を決定する.\mathrm{M}
という手順で,下三角行列
つまり,
しかし,これによって得られる分解,
は,
不完全Cholesky分解前処理つき共役勾配法
不完全コレスキー分解によって得られる行列
すなわち,
とする.
ただし,
また, このような
と選ぶことに対応する.
これはよい前処理か?
これが,確かに良い前処理であると納得するために,
一旦前処理として,不完全でない,完全な,Cholesky分解としてみよう.
つまり,
として,
を前処理行列に選んでみよう.
すると,
であるから,
となる.
もし,Cholesky分解が速やかに実行できて,
前処理行列
連立方程式
と求めることができる.
一方で,Cholesky分解の計算量は小さくない.
これを,不完全Cholesky分解に変えてみよう.
すなわち,
とする.すると,
しかし,それに近いもの,もう少し厳密に言うと,
したがって,不完全Cholesky分解によって,前処理を施すと,よりはやく解を得ることができる.
アルゴリズムを実装
不完全コレスキー分解前処理付き共役勾配法のアルゴリズム
不完全コレスキー分解前処理付き共役勾配法のアルゴリズムをまとめておく.
Input:
Set
while
\alpha_j \coloneqq \langle \boldsymbol{r}_j, \boldsymbol{z}_j \rangle_2/\langle A\boldsymbol{p}_j, \boldsymbol{p}_j \rangle_2 \boldsymbol{x}_{j+1} \coloneqq \boldsymbol{x}_j + \alpha_j \boldsymbol{p}_j \boldsymbol{r}_{j+1} \coloneqq \boldsymbol{r}_j - \alpha_j A \boldsymbol{p}_j \boldsymbol{z}_{j+1} \coloneqq (\tilde{L} \tilde{D} \tilde{L}^\mathrm{T})^{-1} \boldsymbol{r}_{j+1} \beta_{j+1} \coloneqq \langle \boldsymbol{r}_{j+1}, \boldsymbol{z}_{j+1} \rangle_2/\langle \boldsymbol{r}_j, \boldsymbol{z}_j \rangle_2 \boldsymbol{p}_{j+1} \coloneqq \boldsymbol{z}_{j+1} + \beta_{j+1} \boldsymbol{p}_j j \coloneqq j+1
done
ここで,
と2段階で解く.第1式は前進代入,第2式は後退代入で解くことができる.
C++による実装
以下の実装では,独自に実装した,行列演算のテンプレートクラス,matrix<T>
とベクトルの演算を定義した,vector
コンテナの拡張を用いているが,詳細は,githubを参照してください.
前進代入・後退代入
以下は,前進代入と後退代入の実装である.
template <typename T>
vector<T> forward_substitution(const matrix<T> &A, const vector<T> &b)
{
vector<T> x(b);
for (size_t row = 0; row < A.row; row++)
{
for (size_t i = 0; i < row; i++)
{
x[row] -= A[row][i] * x[i];
}
x[row] *= 1 / A[row][row];
}
return x;
}
template <typename T>
vector<T> back_substitution(const matrix<T> &A, const vector<T> &b)
{
vector<T> x(b);
size_t N = b.size();
x[N - 1] = b[N - 1] / A[N - 1][N - 1];
for (int row = N - 2; row >= 0; row--)
{
for (size_t i = row + 1; i < N; i++)
{
x[row] -= A[row][i] * x[i];
}
x[row] *= 1 / A[row][row];
}
return x;
}
不完全Cholesky分解
以下は,不完全Cholesky分解の実装である.
template <typename T>
matrix<T> incomplete_cholesky_factorization(const matrix<T> &A, double precision)
{
matrix<T> L(A.row, A.column, 0);
L[0][0] = A[0][0];
for (size_t i = 1; i < L.row; i++)
{
for (size_t j = 0; j < i; j++)
{
if (fabs(A[i][j]) < precision)
continue;
L[i][j] = A[i][j];
for (size_t k = 0; k < j; k++)
{
L[i][j] -= L[i][k] * L[j][k];
}
L[i][j] *= 1 / L[j][j];
}
L[i][i] = A[i][i];
for (size_t k = 0; k < i; k++)
{
L[i][i] -= L[i][k] * L[i][k];
}
L[i][i] = sqrt(L[i][i]);
}
return L;
}
不完全Cholesky分解前処理つき共役勾配法(ICCG)
以下は,不完全Cholesky分解前処理つき共役勾配法の実装である.
template <typename T>
vector<T> incomplete_cholesky_factorization_conjugate_gradient(const matrix<T>& A, const vector<T>& b, const vector<T> &initial_guess, const T &precision)
{
auto x = initial_guess;
matrix<T> L = incomplete_cholesky_factorization(A);
matrix<T> L_T = L.transpose();
vector<T> r(N);
auto k = A * x;
r = b - (A * x);
vector<T> z(N);
auto w = forward_substitution(L, r);
z = back_substitution(L_T, w);
vector<T> p(z);
for (size_t i = 0; i < N; i++)
{
auto t = A * p;
auto dot_r_z = r * z;
auto alpha = dot_r_z / (t * p);
x += alpha * p;
r -= alpha * t;
if (r * r < precision)
break;
w = forward_substitution(L, r);
z = back_substitution(L_T, w);
auto beta = r * z / dot_r_z;
p = z + beta * p;
}
return x;
}
参考文献
- Wendland, H. (2017). Numerical Linear Algebra: An Introduction (Cambridge Texts in Applied Mathematics). Cambridge: Cambridge University Press. doi:10.1017/9781316544938
- 森 正武, 共立数学講座12 数値解析 (第2版), 共立出版 (2002)
Discussion