ブログ

逆関数法を用いたワイブル分布、ロジスティック分布、コーシー分布に基づく乱数の生成

逆関数法は累積分布関数が値域$(0,1)$かつ単調増加の関数であることに基づいて、区間$(0,1)$で生成する一様乱数を累積分布関数と対応させることで乱数生成を行う手法です。当記事では、ワイブル分布、ロジスティック分布、コーシー分布のそれぞれに対して乱数の生成を行いました。

・参考
逆関数法
自然科学の統計学 第$11$章

逆関数法の概要

上記で詳しく取り扱ったが、下図を元に理解すると良い。

基本的には「累積分布関数」の値域に対して「一様乱数」生成し、定義域に対応させることで乱数を生成すると考えればよい。以下、「逆関数法」で取り扱った「指数分布」の例を元に概要の確認を行う。

指数分布と逆関数法

指数分布の累積分布関数を$F(x)$とおくと「逆関数法:指数分布」より、下記が得られる。
$$
\large
\begin{align}
u &= F(x) = 1 – e^{-\lambda x} \\
x &= F^{-1}(u) = -\frac{1}{\lambda} \log{(1-u)}
\end{align}
$$

一様乱数$U \sim \mathrm{Uniform}(0,1)$を上記に基づいて$\displaystyle X=-\frac{1}{\lambda} \log{(1-U)}$のように変換を行うことで指数分布$\mathrm{Ex}(\lambda)$に基づく乱数が得られる。

また、一様乱数$U \sim \mathrm{Uniform}(0,1)$に対して$1-U$の計算を行うことは$U’ = 1-U \sim \mathrm{Uniform}(0,1)$に基づいて$U’$を用いることと同義であるので、$\displaystyle X=-\frac{1}{\lambda} \log{U}$を用いて乱数の生成を行なっても良い。

ここで$U \neq 1,U’ \neq 0$は必須であるので、乱数の生成にあたっては定義域に注意しておく必要がある。

それぞれの確率分布に基づく乱数の生成

ワイブル分布

式の導出

ワイブル分布の確率密度関数を表す式はいくつかあるが、当項では上記で「統計学実践ワークブック」の$19$章を参考にまとめた下記の数式の表記を用いる。

考にまとめた下記の数式の表記を用いる。
$$
\large
\begin{align}
f(t) = \lambda p (\lambda t)^{p-1} e^{-(\lambda t)^p}, \quad t \geq 0
\end{align}
$$

上記に対し、累積分布関数$\displaystyle F(x) = \int_{0}^{x} f(t) dt$の計算を行う。計算にあたっては$\displaystyle \frac{d}{dt} e^{-(\lambda t)^p}$が下記のように計算できることを元に考えれば良い。
$$
\large
\begin{align}
\frac{d}{dt} e^{-(\lambda t)^p} &= e^{-(\lambda t)^p} \frac{d}{dt} (-(\lambda t)^p) \\
&= e^{-(\lambda t)^p} (-p \lambda^{p} t^{p-1}) \\
&= – \lambda p (\lambda t)^{p-1} e^{-(\lambda t)^p}
\end{align}
$$

よって、$\displaystyle F(x) = \int_{0}^{x} f(t) dt$は下記のように計算できる。
$$
\large
\begin{align}
F(x) &= \int_{0}^{x} f(t) dt \\
&= \left[ -e^{-(\lambda t)^p} \right]_{0}^{x} \\
&= 1 – e^{-(\lambda x)^p}
\end{align}
$$

上記に対し$u=F(x)$とおくと、$x=F^{-1}(u)$は下記のように導出できる。
$$
\large
\begin{align}
u &= F(x) \\
&= 1 – e^{-(\lambda x)^p} \\
e^{-(\lambda x)^p} &= 1-u \\
-(\lambda x)^p &= \log{(1-u)} \\
\lambda x &= (-\log{(1-u)})^{\frac{1}{p}} \\
x &= \frac{(-\log{(1-u)})^{\frac{1}{p}}}{\lambda} = F^{-1}(u)
\end{align}
$$

Pythonを用いた乱数生成

まずはワイブル分布の確率密度関数の確認を行う。下記を実行することでワイブル分布の確率密度関数のグラフを作成することができる。

import numpy as np
import matplotlib.pyplot as plt

x = np.arange(0.,2.01,0.01)

lamb = np.array([1., 1., 2., 2., 5.])
p = np.array([5., 10., 5., 10., 5.])

for i in range(lamb.shape[0]):
    y = lamb[i]*p[i]*(lamb[i]*x)**(p[i]-1) * np.e**(-(lamb[i]*x)**p[i])
    plt.plot(x,y,label="lambda: {:.0f}, p: {:.0f}".format(lamb[i],p[i]))

plt.legend()
plt.show()

・実行結果

上記の緑のグラフなどを確認することにより、$x$の増加に伴いハザード関数$h(x)$が増大するIFRであることも確認できる。当記事の主題ではないので詳しくは省略するが、「ワイブル分布の瞬間故障率$h(t)$の計算」で取り扱ったようにワイブル分布はハザード関数の増減を調整できる分布である。

上記の緑の設定である$\lambda = 1, p = 10$に対して、以下では$\displaystyle X = F^{-1}(U) = \frac{(-\log{(1-U)})^{\frac{1}{p}}}{\lambda}$を用いて乱数生成を行う。

import numpy as np
import matplotlib.pyplot as plt

np.random.seed(0)

lamb, p = 1., 10.
x = np.arange(0.,2.01,0.01)
y = lamb*p*(lamb*x)**(p-1) * np.e**(-(lamb*x)**p)

U = np.random.rand(1000)
X = (-np.log(1-U))**(1/p)/lamb

plt.subplot(211)
plt.plot(x,y,color="green")
plt.subplot(212)
plt.hist(X,bins=50,color="limegreen")
plt.xlim([0.,2.])
plt.show()

・実行結果

上記は$1,000$サンプルのヒストグラムの描画を行なったが、概ね確率密度関数に沿って乱数が得られたことが確認できる。また、サンプル数を増やした際の挙動を確認するにあたって以下では$100,000$サンプルでヒストグラムの描画を行なった。

import numpy as np
import matplotlib.pyplot as plt

np.random.seed(0)

lamb, p = 1., 10.
x = np.arange(0.,2.01,0.01)
y = lamb*p*(lamb*x)**(p-1) * np.e**(-(lamb*x)**p)

U = np.random.rand(100000)
X = (-np.log(1-U))**(1/p)/lamb

plt.subplot(211)
plt.plot(x,y,color="green")
plt.subplot(212)
plt.hist(X,bins=50,color="limegreen")
plt.xlim([0.,2.])
plt.show()

・実行結果

ロジスティック分布

式の導出

$$
\large
\begin{align}
f(x) = \frac{\exp(-x)}{(1+\exp(-x))^2}
\end{align}
$$

上記のような確率密度関数$f(x)$を持つ分布をロジスティック分布という。累積分布関数の$F(x)$はロジスティック回帰などに用いられることで取り扱われることが多い一方でロジスティック分布が取り扱われることは少ないように思われるが、「統計検定$1$級対応テキスト」の$2.2.9$節などではロジスティック分布が取り扱われており当項の作成にあたって主に参照した。累積分布関数の$F(x)$は確率密度関数$f(x)$の積分を考えることで、下記のように導出できる。
$$
\large
\begin{align}
F(x) &= \int_{-\infty}^{x} f(t) dt \\
&= \int_{-\infty}^{x} \frac{\exp(-t)}{(1+\exp(-t))^2} dt \\
&= \int_{-\infty}^{x} \frac{-(1+\exp(-t))’}{(1+\exp(-t))^2} dt \\
&= \int_{-\infty}^{x} \left( \frac{1}{1+\exp(-t)} \right)’ dt \\
&= \left[ \frac{1}{1+\exp(-t)} \right]_{-\infty}^{x} \\
&= \frac{1}{1+\exp(-x)} \quad (1)
\end{align}
$$

上記はシグモイド関数という名称でも知られ、ニューラルネットワークなどにも応用される関数であるので重要である。

以下、$(1)$式を$u=F(x)$とおくときの逆関数$u=F^{-1}(x)$の導出を行う。
$$
\large
\begin{align}
u &= F(x) \\
&= \frac{1}{1+\exp(-x)} \\
u(1+\exp(-x)) &= 1 \\
u \exp(-x) &= 1-u \\
\exp(-x) &= \frac{1-u}{u} \\
\exp(x) &= \frac{u}{1-u} \\
x &= \log{\frac{u}{1-u}} = F^{-1}(u)
\end{align}
$$

Pythonを用いた乱数生成

まずはロジスティック分布の確率密度関数の確認を行う。下記を実行することでロジスティック分布の確率密度関数のグラフを作成することができる。

import numpy as np
import matplotlib.pyplot as plt

x = np.arange(-5.,5.02,0.02)
y = np.e**(-x)/(1.+np.e**(-x))**2

plt.plot(x,y)
plt.xlim([-5.,5.])
plt.show()

・実行結果

上記は正規分布に近いので、ロジスティック回帰がプロビット回帰の代用で用いられることが多い。以下、$\displaystyle X = F^{-1}(U) = \log{\frac{U}{1-U}}$を用いて乱数生成を行う。

import numpy as np
import matplotlib.pyplot as plt

np.random.seed(0)

x = np.arange(-5.,5.02,0.02)
y = np.e**(-x)/(1.+np.e**(-x))**2

U = np.random.rand(1000)
X = np.log(U/(1-U))

plt.subplot(211)
plt.plot(x,y,color="green")
plt.subplot(212)
plt.hist(X,bins=50,color="limegreen")
plt.xlim([-5.,5.])
plt.show()

・実行結果

上記は$1,000$サンプルのヒストグラムの描画を行なったが、概ね確率密度関数に沿って乱数が得られたことが確認できる。また、サンプル数を増やした際の挙動を確認するにあたって以下では$100,000$サンプルでヒストグラムの描画を行なった。

import numpy as np
import matplotlib.pyplot as plt

np.random.seed(0)

x = np.arange(-5.,5.02,0.02)
y = np.e**(-x)/(1.+np.e**(-x))**2

U = np.random.rand(100000)
X = np.log(U/(1-U))

plt.subplot(211)
plt.plot(x,y,color="green")
plt.subplot(212)
plt.hist(X,bins=50,color="limegreen")
plt.xlim([-5.,5.])
plt.show()

・実行結果

実行にあたって、plt.histの集計にあたってbins=50を設定した一方で、図ごとに数に差があるのは最大値と最小値が大きいと区間$[-5,5]$内の区切りが少なくなることに起因すると理解すると良い。

コーシー分布

式の導出

「自然科学の統計学」の$2.1$節ではコーシー分布の確率密度関数$f(t)$は下記のように表される。
$$
\large
\begin{align}
f(t) = \frac{1}{\pi} \cdot \frac{1}{1+t^2}
\end{align}
$$

上記に対し、累積分布関数$\displaystyle F(x) = \int_{-\infty}^{x} f(t) dt$の計算を行うことを考える。$F(x)$は下記のように表せる。
$$
\large
\begin{align}
F(x) &= \int_{-\infty}^{x} f(t) dt \\
&= \int_{-\infty}^{x} \frac{1}{\pi} \cdot \frac{1}{1+t^2} dt \\
&= \frac{1}{\pi} \int_{-\infty}^{x} \frac{1}{1+t^2} dt \quad (2)
\end{align}
$$

上記に対し、$t=\tan{\theta}$で置換積分を行うことを考える。ここで$-\infty \leq t \leq x$には$\displaystyle -\frac{\pi}{2} \leq \theta \leq \tan^{-1}(x)$が対応し、$dt$と$d \theta$に関しては下記が成立する。
$$
\large
\begin{align}
\frac{dt}{d \theta} &= \frac{d}{d \theta} \tan{\theta} \\
&= \left( \frac{\sin{\theta}}{\cos{\theta}} \right)’ \\
&= \frac{(\sin{\theta})’\cos{\theta}-\sin{\theta}(\cos{\theta})’}{\cos^2{\theta}} \\
&= \frac{\cos^2{\theta}+\sin^2{\theta}}{\cos^2{\theta}} \\
&= \frac{1}{\cos^2{\theta}}
\end{align}
$$

また、$\displaystyle \frac{1}{1+t^2}$に$t=\tan{\theta}$を代入すると下記のように変形できる。
$$
\large
\begin{align}
\frac{1}{1+t^2} &= \frac{1}{1+\tan^{2}{\theta}} \\
&= \frac{1}{\displaystyle 1 + \frac{\sin^{2}{\theta}}{\cos^{2}{\theta}}} \\
&= \frac{\cos^{2}{\theta}}{\cos^{2}{\theta} + \sin^{2}{\theta}} \\
&= \frac{\cos^{2}{\theta}}{1} \\
&= \cos^{2}{\theta}
\end{align}
$$

ここまでの内容を元に、$(2)$式は下記のように変数の変換を行える。
$$
\large
\begin{align}
F(x) &= \frac{1}{\pi} \int_{-\infty}^{x} \frac{1}{1+t^2} dt \quad (2) \\
&= \frac{1}{\pi} \int_{-\frac{\pi}{2}}^{\tan^{-1}(x)} \frac{1}{1+\tan^2{\theta}} \frac{1}{\cos^2{\theta}} d \theta \\
&= \frac{1}{\pi} \int_{-\frac{\pi}{2}}^{\tan^{-1}(x)} \frac{\cancel{\cos^2{\theta}}}{\cancel{\cos^2{\theta}}} d \theta \\
&= \frac{1}{\pi} \left[ \theta \right]_{-\frac{\pi}{2}}^{\tan^{-1}{(x)}} \\
&= \frac{1}{\pi}\tan^{-1}{(x)} + \frac{\cancel{\pi}}{2 \cancel{\pi}} \\
&= \frac{1}{\pi}\tan^{-1}{(x)} + \frac{1}{2}
\end{align}
$$

上記に対し$u=F(x)$とおくと、$x=F^{-1}(u)$は下記のように導出できる。
$$
\large
\begin{align}
u &= F(x) \\
&= \frac{1}{\pi}\tan^{-1}{(x)} + \frac{1}{2} \\
\frac{1}{\pi}\tan^{-1}{(x)} &= u – \frac{1}{2} \\
\tan^{-1}{(x)} &= \pi \left( u – \frac{1}{2} \right) \\
x &= \tan \left[ \left( u – \frac{1}{2} \right) \pi \right] = F^{-1}(u)
\end{align}
$$

・参考
自由度$\nu=1$の$t$分布:コーシー分布

Pythonを用いた乱数生成

まずはコーシー分布の確率密度関数の確認を行う。下記を実行することでコーシー分布の確率密度関数のグラフを作成することができる。

import numpy as np
import matplotlib.pyplot as plt
from scipy import stats

x = np.arange(-3.,3.01,0.01)
y = 1./(np.pi*(1+x**2))
y_norm = stats.norm.pdf(x)

plt.plot(x,y,label="Cauchy Dist")
plt.plot(x,y_norm,label="Norm Dist")

plt.legend()
plt.show()

・実行結果

上記ではコーシー分布と正規分布の確率密度関数の描画を行なった。両者は概ね同様の形状である一方で、中心から遠い点でもコーシー分布はなかなか$0$に収束せず、このことからコーシー分布は平均などのモーメントが存在しない分布の例に用いられることが多い。

以下では$\displaystyle X = F^{-1}(U) = \tan \left[ \left( U – \frac{1}{2} \right) \pi \right]$を用いて乱数生成を行う。

import numpy as np
import matplotlib.pyplot as plt

np.random.seed(0)

x = np.arange(-3.,3.01,0.01)
y = 1./(np.pi*(1+x**2))

U = np.random.rand(1000)
X = np.tan((U-1./2.)*np.pi)

plt.subplot(211)
plt.plot(x,y,color="green")
plt.subplot(212)
plt.hist(X,bins=10000,color="limegreen")
plt.xlim([-3.,3.])
plt.show()

・実行結果

bins=10000に設定したので実行にあたっては注意が必要

上記の実行にあたって、コーシー分布では最小値・最大値の絶対値が大きくなることからbins=10000で設定を行なった。計算量が多いので、実行にあたっては止まる場合があることに注意が必要である。

以下、サンプル数を$100,000$に増やすのではなく、コーシー分布と正規分布の乱数の最大値・最小値をそれぞれ確認を行う。

正規分布の乱数生成と最大値、最小値

from scipy import stats

np.random.seed(0)

X1 = stats.norm.rvs(size=1000)
X2 = stats.norm.rvs(size=10000000)

print("min X1: {:.2f}, max X1: {:.2f}".format(min(X1), max(X1)))
print("min X2: {:.2f}, max X2: {:.2f}".format(min(X2), max(X2)))

・実行結果

min X1: -2.88, max X1: 3.73
min X2: -5.44, max X2: 5.22

コーシー分布の乱数生成と最大値、最小値

import numpy as np

np.random.seed(0)

U1 = np.random.rand(1000)
U2 = np.random.rand(10000000)

X1 = np.tan((U1-1./2.)*np.pi)
X2 = np.tan((U2-1./2.)*np.pi)

print("min X1: {:.2f}, max X1: {:.2f}".format(min(X1), max(X1)))
print("min X2: {:.2f}, max X2: {:.2f}".format(min(X2), max(X2)))

・実行結果

min X1: -583.02, max X1: 1662.87
min X2: -12647485.80, max X2: 2326381.31

【試験・検定対策用】確率密度関数・確率エレメント・定積分における確率変数の変換の解き方

確率密度関数・確率エレメント・定積分における確率変数の変換にあたっては手順に沿って計算を行えば十分である一方で、公式がわからなくなりがちです。そこで当記事では「ボックス・ミュラー法」の導出を元に難しい点の確認を行なった後になるべく手順に沿って進められるように変数変換の解法に関してフローチャートの形式で取りまとめました。

・参考
ボックス・ミュラー法
ガウス積分
標準演習$100$:確率分布の変数変換

確率変数の変換はなぜ難しいのか

ボックス・ミュラー法の事例

「ボックス・ミュラー法」の「確率密度関数の変数変換」の事例では「ガウス積分」と同様に、$2$次元の正規分布の変数変換を行うが、まずは計算の概要の確認を行う。

ボックス・ミュラー法解説より

ボックス・ミュラー法では上記のような計算を行うが、変数変換のトピックを一通り抑えてもなかなか再現が難しい。$2$次元の変数変換であり、ヤコビアン(ヤコビ行列式)が出てくるのでシンプルな$1$変数の変換のように簡単な計算にならないというのもあるが、「前半では$x,y \to r,\theta$の変数変換である一方で、後半では$r \to s$の変数変換である」ということでわからなくなりやすいのではないかというのが筆者の仮説である。

もう少し詳細を確認するなら、前半では「$x = r \cos \theta$のように変換前を変換後で表す」のに対し、後半では「$s=r^2$のように変換後を変換前で表す」というところがなかなかわかりにくい。

さらに確率密度関数$f(x)$、確率エレメント$f(x)dx$、定積分$\displaystyle \int_{a}^{b} f(x) dx$のように$dx$があるかないかによって解説に用いられる式の用いられ方が変わる場合があるなど、難しいポイントが多い。

よって次節では当節で確認した内容を元に、確率変数の変数変換の解き方をフローチャートに取りまとめた。

変数変換の解き方フローチャート

フローチャートの概要

フローチャートの作成にあたっては、下記の$3$つに着目を行なった。

$1)$ 確率変数の次元 -> $1$次元と$2$次元以上で分類を行なった
$2)$ 変換対象 -> 確率密度関数$f(x)$、確率エレメント$f(x)dx$、定積分$\displaystyle \int_{a}^{b} f(x) dx$で分類を行なった
$3)$ 変換の式 -> 変換前を$x$、変換後を$y$とするとき、$x=g(y)$の変換式 or $y=g(x)$の変換式で分類を行なった

上記の$3$つの視点を元に、下記のようなフローチャートの作成を行なった。

変数変換の解き方フローチャート

フローチャートの詳細

前項で確認を行なったフローチャートは、$3$つの視点に基づいて作成を行なったので、以下、それぞれの視点に関して詳細の確認を行う。

確率変数の次元

フローチャートではまず確率変数の次元について確認を行なったが、$2$次元以上の場合は基本的にヤコビ行列式を用いるので、ヤコビ行列式の定義に基づいて計算を行えば良い。変換前の変数$x_1,…,x_n$に対して変換後を$y_1,…,y_n$とおくとき、ヤコビ行列式$|\det J|$は下記のように表される。
$$
\large
\begin{align}
J &= \left( \begin{array}{ccc} \frac{\partial x_1}{\partial y_1} & \cdots & \frac{\partial x_1}{\partial y_n} \\ \vdots & \ddots & \vdots \\ \frac{\partial x_n}{\partial y_1} & \cdots & \frac{\partial x_n}{\partial y_n} \end{array} \right) \\
|\det J| &= \left| \begin{array}{ccc} \frac{\partial x_1}{\partial y_1} & \cdots & \frac{\partial x_1}{\partial y_n} \\ \vdots & \ddots & \vdots \\ \frac{\partial x_n}{\partial y_1} & \cdots & \frac{\partial x_n}{\partial y_n} \end{array} \right|
\end{align}
$$

上記では$J$の$(i,j)$成分$(J)_{ij}$に関して$\displaystyle (J)_{ij} = \frac{\partial x_i}{\partial y_j}$のように定義を行なったが、$|\det J| = |\det J^{\mathrm{T}}|$であるので$\displaystyle (J)_{ij} = \frac{\partial x_j}{\partial y_i}$のように定義しても結果は変わらないことも抑えておくとよい。ヤコビ行列式は「変換前と変換後の微小区間の面積の対応」を表すが、詳細は下記で取り扱ったので当項では省略する。

変換対象

変換対象は主に確率密度関数$f(x)$、確率エレメント$f(x)dx$、定積分$\displaystyle \int_{a}^{b} f(x) dx$の$3$パターンあるが、特に着目すべきなのが$dx$がつくかどうかである。$1$次元の変数変換における「定積分の保存」に用いられる微分は$\displaystyle \frac{dx}{dy}$か$\displaystyle \frac{dy}{dx}$のどちらかなので、どちらで計算を行なった場合も$dx = \square dy$を用いて$dx$を$dy$で置き換えればよい。

上記は確率エレメント$f(x)dx$と定積分$\displaystyle \int_{a}^{b} f(x) dx$で共通だが、定積分の計算を行う際は「変数の区間」も「変数の変換」に合わせて変えなければいけないことには注意が必要である。

一方で確率密度関数$f(x)$に関して計算を行う際は$dx$が存在しないので、$x$の確率密度関数$f_1(x)$に対して$y$の確率密度関数$f_2(y)$を$\displaystyle f_2(y)=\left|\frac{dx}{dy}\right|f_1(x)$のように変数変換の公式を用いて計算を行えばよい。

変換の式

変数$x$を$y$に変換する際に、それぞれの確率密度関数を$f_1(x)$、$f_2(y)$のようにおく。このとき、$x=g(y)$の形式の変換の式か$y=g(x)$の形式の変換の式かであることは注意する必要がある。

$x=g(y)$の式の場合はそのまま$\displaystyle \left|\frac{dx}{dy}\right|$を計算し、$\displaystyle f_2(y)=\left|\frac{dg(y)}{dy}\right|f_1(x)$を計算すれば良い。一方で、$y=g(x)$の式の場合は$x=g^{-1}(y)$のように$g$の逆関数を計算したのちに$\displaystyle f_2(y)=\left|\frac{dg^{-1}(y)}{dy}\right|f_1(x)$を計算する必要がある。

このように、$dx$が出てこない場合の変数変換のように公式を用いる場合は、「変換前の変数=変換後の変数の関数の式」で表す必要があることに注意が必要ある。

具体的な変数変換の事例とフローチャートの対応

行列式の対数$\ln|{A}|$を$A$で偏微分を行う際の公式の導出

はじめに

ガウス分布の最尤推定では、共分散行列の最尤解を求めるにあたって$\displaystyle \frac{\partial \ln|A|}{\partial A}$を計算する必要があります。本記事では,これが $(A^{-1})^{\mathrm{T}}$ に等しいことを示します。以降、現れる行列は全て正方行列であり、行列式が $0$ ではないとします。本記事の前提知識は、線形代数において、行列式(置換を用いた定義)と余因子展開までです。

流れ

まずは細かい議論を無視し、流れを示します。

STEP 1

$\Delta_{ij}$ を $ D $ 次正方行列 $A$ の第$(i,j)$余因子、$\Delta = (\Delta_{ij}) \mathrm{(} \textit{i,j} \mathrm{ = 1,2,…,D)}$ なる行列と定義すると、$\displaystyle A^{-1} = \frac{\Delta^{\mathrm{T}}}{|A|}$ が成立することを示す。

STEP 2

$D$個の$D$次元列ベクトル $\textit{a}_\rm{1}, \textit{a}_\rm{2},…,\textit{a}_\rm{D}$ を用いて、$\textit{A} \rm{=[} \textit{a}_\rm{1}, \textit{a}_\rm{2},…,\textit{a}_\textrm{D}\textrm{]}$とする。また、$\it{A}$の任意の成分は $x$ に依存するものとする。このとき$|A|$ の$x$ による微分は 次で与えられることを示す。

$$\frac{\partial |\it{A}|}{\partial x} = |\textit{a}^{\prime}_1, \textit{a}_2,…,\textit{a}_D| \rm{+} |\textit{a}_1, \textit{a}^{\prime}_2,…,\textit{a}_D| \rm{+} … \rm{+} |\textit{a}_1, \textit{a}_2,…,\textit{a}^{\prime}_D| $$ (各列ベクトルを $\rm{\it{x}}$ で微分した行列の行列式の和)

特に、$ x = \it{a_{ij}} $ のとき、

$$\frac{\partial |\textit{A}|}{\partial \textit{a}_{ij}} =  |\textit{a}_1, \textit{a}_2,…,\textit{a}^{\prime}_i,…,\textit{a}_D|  = \Delta_{ij} $$

STEP 3

STEP $1$, STEP $2$ を組み合わせる。

$\displaystyle A^{-1} = \frac{\Delta^{\mathrm{T}}}{\it{|A|}}$ を示す

$\it{A}$ の行列式の第 $ j $ 列に関する余因子展開は次で与えられます。

$$\textit{|A|} = \textit{a}_{1j}\Delta_{1j}+\textit{a}_{2j}\Delta_{2j}+…+\textit{a}_{Dj}\Delta_{Dj}$$

ここで、第$k$列に第$j$列を代入した行列を$\textit{A}^{(k \rightarrow j)}$とします。もちろん$\textit{A}^{(j \rightarrow j)}=A$です。$k \not= j \Rightarrow |\textit{A}^{(k \rightarrow j)}|\rm{= 0}$が成り立ちます。(同じ列があるため)

あえて$|\textit{A}^{(k \rightarrow j)}|$の第$k$列($\textit{a}_j$により値が置き換えられている)に関する余因子展開を考えます。

$\textit{|A|} = \textit{a}_{1k}\Delta_{1k}+\textit{a}_{2k}\Delta_{2k}+…+\textit{a}_{Dk}\Delta_{Dk}$の各$\textit{a}_{mk}を\textit{a}_{mj}$ で置き換えれば良いです。したがって、

$$\textit{|A|} = \textit{a}_{1j}\Delta_{1k}+\textit{a}_{2j}\Delta_{2k}+…+\textit{a}_{Dj}\Delta_{Dk} = 0 $$

以上をまとめると、

$$|\textit{A}^{(k \rightarrow j)}|  = \left\{ \begin{array}{ll} |\textit{A}| & (k=j) \\ 0 & (k \not = j)\end{array} \right.$$

$|\textit{A}^{(k \rightarrow j)}| (k,j = 1,2,…,D)$ を並べれば、対角成分が $ |\it{A}| $ で、それ以外は $0$ という行列が作れます。さらに、$$\textit{|A|} = \textit{a}_{1j}\Delta_{1k}+\textit{a}_{2j}\Delta_{2k}+…+\textit{a}_{Dj}\Delta_{Dk} $$という式を考えると、$\textit{A}$と$\Delta$の積で書けます。

行列の文字で書けば、$A\Delta^{\mathrm{T}}=|A|E$ (E: 単位行列)です。また, $\Delta^{\mathrm{T}}A=|A|E$ も成り立つので、$\displaystyle A^{-1} = \frac{\Delta^{\mathrm{T}}}{\it{|A|}}$ を得ます。

行列式の微分を求める

行列 $A$ の行列式は以下で定義されます.

$$\displaystyle |A| = \sum_{\sigma}sgn(\sigma)a_{\sigma(1)1}a_{\sigma(2)2}…a_{\sigma(D)D}$$

$\sigma$は置換、和は$D$文字の任意の置換を走ります。$\sigma(j)$は文字 j の置換による像です。行列式は以下の記事でも解説されています。

一旦、行列 $A$ の全ての成分は変数 $x$ に依存すると仮定しましょう。$x$ で微分します。

$\displaystyle\frac{\partial |\textit{A}|}{\partial x} = \frac{\partial }{\partial x}\sum_{\sigma}sgn(\sigma)a_{\sigma(1)1}a_{\sigma(2)2}…a_{\sigma(D)D}$

$ = \sum_{\sigma}sgn(\sigma)(a^{\prime}_{\sigma(1)1}a_{\sigma(2)2}…a_{\sigma(D)D}+a_{\sigma(1)1}a^{\prime}_{\sigma(2)2}…a_{\sigma(D)D}+…+a_{\sigma(1)1}a_{\sigma(2)2}…a^{\prime}_{\sigma(D)D})$

一部取り出してみます。

$\displaystyle \sum_{\sigma}sgn(\sigma)(a_{\sigma(1)1}a_{\sigma(2)2}…a^{\prime}_{\sigma(j)j}…a_{\sigma(D)D})$

これは行列 $A$ の第 $j$ 列を $x$ で微分したものに置き換えたものの行列式です。従って、$\displaystyle \frac{\partial |\textit{A}|}{\partial x} = |\textit{a}^{\prime}_1, \textit{a}_2,…,\textit{a}_D| \rm{+} |\textit{a}_1, \textit{a}^{\prime}_2,…,\textit{a}_D| \rm{+} … \rm{+} |\textit{a}_1, \textit{a}_2,…,\textit{a}^{\prime}_D|$(各列ベクトルを$\rm{\textit{x}}$ で微分した行列の行列式の和)を得ます。

次に、行列 $A$ の全ての成分は互いに依存しないことを仮定し、$\displaystyle \frac{\partial |\it{A}|}{\partial \textit{a}_{ij}}$ を考えます。第 $j$列以外は$\textit{a}_{ij}$ で微分すれば零ベクトルになるため無視できます.第 $j$列を $\it{a}_{ij}$ で微分すれば、第 $i$ 行だけが $1$ で、それ以外は $0$ です。例えば $(i,j)=(2,1)$ とすると、以下のような行列式を考えることになります。

\begin{vmatrix}
0 & \cdots & a_{1j} & \cdots & a_{1D}\\
1 & \cdots & a_{2j} & \cdots & a_{2D}\\
\vdots & \ddots & & & \vdots \\
0 & & a_{ij} & & a_{iD} \\
\vdots & & & \ddots & \vdots \\
0 & \cdots & a_{Dj} & \cdots & a_{DD}
\end{vmatrix}

余因子展開により、この行列式は $\Delta_{21}$ に等しく、他の $(i,j)$ でも同様です。したがって、$\displaystyle \frac{\partial |\it{A}|}{\partial \it{a_{ij}}}=\Delta_{ij}$ , まとめて $\displaystyle \frac{\partial |\it{A}|}{\partial \it{A}}=\Delta$が得られます。

仕上げ

$\displaystyle \frac{\partial \rm{ ln}|\it{A}|}{\partial A}=\frac{\Delta}{|A|}$ に対して、$\displaystyle A^{-1} = \frac{\Delta^{\mathrm{T}}}{\it{|A|}} $の両辺を転置したものを代入すれば $\displaystyle \frac{\partial \rm{ ln}|\it{A}|}{\partial A} = (A^{-1})^{\mathrm{T}}$ を得ます。

以上です!

参考

・$n$次正方行列の行列式の定義・計算・解釈
https://www.hello-statisticians.com/explain-books-cat/matrix_determinants1.html
・パターン認識と機械学習 Appendix.C

ボックス・ミュラー法(Box-Muller’s method)を用いた正規乱数の生成

当記事では一様乱数から正規乱数の生成を行うボックス・ミュラー法(Box-Muller’s method)の原理に関して取り扱いました。ボックス・ミュラー法はガウス積分を行う際と同様の考え方を用いるので、ガウス積分も合わせて抑えておくと良いと思います。
https://www.hello-statisticians.com/explain-terms-cat/gaussian_integral1.html

一様乱数の生成などの他の乱数生成の手法に関しては下記で取りまとめを行いましたので、こちらも合わせてご確認ください。
https://www.hello-statisticians.com/explain-terms-cat/random_sampling1.html

「自然科学の統計学」の$11.3$節を参考に作成を行いました。

ボックス・ミュラー法の概要と導出

乱数生成にあたって用いる式

$$
\large
\begin{align}
X &= \sqrt{-2 \log{U_{1}}} \cdot \cos{(2 \pi U_{2})} \quad (1) \\
Y &= \sqrt{-2 \log{U_{1}}} \cdot \sin{(2 \pi U_{2})} \quad (2)
\end{align}
$$

一様乱数$U_1 \sim \mathrm{Uniform}(0,1), U_2 \sim \mathrm{Uniform}(0,1)$に対し、上記の変換によって得られる$X, Y$は互いに独立に標準正規分布$\mathcal{N}(0,1)$に従う。

よって、$2$つの一様乱数を生成したのちに、$(1)$式、$(2)$式を用いることで標準正規分布$\mathcal{N}(0,1)$に従う乱数を生成できる。この手法をボックス・ミュラー法という。

確率密度関数の変数変換

当項では前項で取り扱ったボックス・ミュラー法を表す$(1)$式$(2)$式が適切な手法であることの確認を行う。以下では$X \sim \mathcal{N}(0,1), Y \sim \mathcal{N}(0,1)$のとき、同時確率分布$f(x,y)$の微小領域の確率$P(x \leq X \leq x+dx,y \leq Y \leq y+dy)$に対し、「ガウス積分」と同様の変数変換を考えることで$(1)$式$(2)$式を導出する。ここで$P(x \leq X \leq x+dx,y \leq Y \leq y+dy)$は確率エレメントということもある。

$X \sim \mathcal{N}(0,1), Y \sim \mathcal{N}(0,1)$より、確率エレメント$P(x \leq X \leq x+dx,y \leq Y \leq y+dy) = f(x,y)dxdy$は下記のように表すことができる。
$$
\large
\begin{align}
f(x,y)dxdy &= f(x)f(y)dxdy \\
&= \frac{1}{\sqrt{2 \pi}} e^{-\frac{x^2}{2}} \cdot \frac{1}{\sqrt{2 \pi}} e^{-\frac{y^2}{2}} dx dy \quad (3)
\end{align}
$$

上記の$(3)$式に対して下記のような変換を行うことを考える。
$$
\large
\begin{align}
x &= r \cos{\theta} \quad (4) \\
y &= r \sin{\theta} \quad (5)
\end{align}
$$

このとき$x,y \to r,\theta$の変換にあたってのヤコビ行列$J$・ヤコビ行列式$|\det J|$は下記のように表される。
$$
\large
\begin{align}
J &= \left(\begin{array}{cc} \frac{\partial x}{\partial r} & \frac{\partial x}{\partial \theta} \\ \frac{\partial y}{\partial r} & \frac{\partial y}{\partial \theta} \end{array} \right) \\
&= \left(\begin{array}{cc} \cos{\theta} & -r \sin{\theta} \\ \sin{\theta} & r \cos{\theta} \end{array} \right) \\
|\det J| &= \left| \begin{array}{cc} \cos{\theta} & -r \sin{\theta} \\ \sin{\theta} & r \cos{\theta} \end{array} \right| \\
&= r \cos^{2}{\theta} + r \sin^{2}{\theta} \\
&= r
\end{align}
$$

よって、$(3)$式は$(4),(5)$式を用いて下記のように変数変換を行うことができる。
$$
\large
\begin{align}
f(x,y)dxdy &= \frac{1}{\sqrt{2 \pi}} e^{-\frac{x^2}{2}} \cdot \frac{1}{\sqrt{2 \pi}} e^{-\frac{y^2}{2}} dx dy \quad (3) \\
&= \frac{1}{2 \pi} e^{-\frac{(r \cos{\theta})^2}{2}} e^{-\frac{(r \sin{\theta})^2}{2}} r dr d \theta \\
&= \frac{1}{2 \pi} e^{-\frac{r^2}{2}} r dr d \theta \quad (6)
\end{align}
$$

上記の$(6)$式に対し、$s=r^2$で変数変換を行うことを考える。このとき下記が成立する。
$$
\large
\begin{align}
\frac{ds}{dr} &= 2r \\
r dr &= \frac{1}{2} ds
\end{align}
$$

$(6)$式は下記のように変数変換を行える。
$$
\large
\begin{align}
\frac{1}{2 \pi} e^{-\frac{r^2}{2}} r dr d \theta &= \frac{1}{2 \pi} e^{-\frac{s}{2}} \frac{1}{2} ds d \theta \\
&= \frac{1}{2 \pi} d \theta \cdot \frac{1}{2} e^{-\frac{s}{2}} ds
\end{align}
$$

ここで上記の$\displaystyle \frac{1}{2 \pi}$は一様分布$\mathrm{Uniform}(0,2 \pi)$の確率密度関数、$\displaystyle \frac{1}{2} e^{-\frac{s}{2}}$は指数分布$\displaystyle \mathrm{Ex} \left( \frac{1}{2} \right)$の確率密度関数に対応する。よって$\displaystyle \Theta \sim \mathrm{Uniform}(0,2 \pi),R^2 \sim \mathrm{Ex} \left( \frac{1}{2} \right)$であるとき、下記の$X,Y$は標準正規分布$\mathcal{N}(0,1)$に従う。
$$
\large
\begin{align}
X &= R \cos{\Theta} \\
Y &= R \sin{\Theta}
\end{align}
$$

ここで上記の乱数$\Theta$を乱数$U_2 \sim \mathrm{Uniform}(0,1)$から得るには$\Theta = 2 \pi U_2$を計算すれば良い。また、乱数$R$を$U_1 \sim \mathrm{Uniform}(0,1)$から得るには$R=\sqrt{-2 \log{U_{1}}}$を計算すればよい。

$R=\sqrt{-2 \log{U_{1}}}$は$\displaystyle R^2 \sim \mathrm{Ex} \left( \frac{1}{2} \right)$であることから逆関数法によって得られる。詳細は次項で取り扱う。

逆関数法と指数分布

逆関数法の原理に関しては上記で詳しく取り扱ったのでここでは省略するが、下記の図を元に考えると良いので図だけ抜粋を行なった。

以下、当項では指数分布の累積分布関数$F(x)$を計算し、$R = \sqrt{F^{-1}(U_1)}$に関して計算を行う。パラメータ$\lambda$の指数分布$\mathrm{Ex}(\lambda)$の累積分布関数$F(x)$は下記のように計算できる。
$$
\large
\begin{align}
F(x) &= \int_{0}^{x} \lambda e^{-\lambda t} dt \\
&= \left[ – \frac{\cancel{\lambda}}{\cancel{\lambda}} e^{-\lambda t} \right]_{0}^{x} \\
&= \left[ – e^{-\lambda t} \right]_{0}^{x} \\
&= – e^{-\lambda x} + e^{0} \\
&= 1 – e^{-\lambda x}
\end{align}
$$

よって$u=F(x)$のとき$x=F^{-1}(u)$は下記のように計算できる。
$$
\large
\begin{align}
u &= F(x) \\
u &= 1 – e^{-\lambda x} \\
e^{-\lambda x} &= 1-u \\
-\lambda x &= \log{(1-u)} \\
x &= – \frac{1}{\lambda} \log{(1-u)} \\
F^{-1}(u) &= – \frac{1}{\lambda} \log{(1-u)}
\end{align}
$$

上記の$1-u$は$1$から$0$の一様分布を表すが、$u$で置き換えても$0$から$1$の一様分布に変わるだけなので$1-u$を$u$で置き換えることができる。ここで$\displaystyle \lambda = \frac{1}{2}$を代入すると下記が得られる。
$$
\large
\begin{align}
F^{-1}(u) &= -\frac{1}{\lambda} \log{(1-u)} \\
F^{-1}(u) &= -2 \log{u}
\end{align}
$$

上記より$R = \sqrt{F^{-1}(U_1)} = \sqrt{- 2 \log{U_1}}$によって乱数$R$を生成することができると考えられる。

ボックス・ミュラー法に基づくPythonを用いた正規乱数の生成

標準正規分布$\mathcal{N}(0,1)$に従う乱数の生成

一様乱数は「M系列」などに基づいて作成できるが、以下では簡易化にあたってnp.random.randで代用する。

import numpy as np
import matplotlib.pyplot as plt

np.random.seed(0)

n = 1000
U1, U2 = np.random.rand(n), np.random.rand(n)

X = np.sqrt(-2*np.log(U1)) * np.cos(2*np.pi*U2)
Y = np.sqrt(-2*np.log(U1)) * np.sin(2*np.pi*U2)

samples = np.zeros(n*2)
samples[:n] = X
samples[n:] = Y

plt.hist(samples,bins=50,color="limegreen")
plt.show()

・実行結果

上記では$2000$サンプルの生成を行なったが、$200,000$サンプルまで増やすとどのように変わるかに関しても確認を行う。

np.random.seed(0)

n = 100000
U1, U2 = np.random.rand(n), np.random.rand(n)

X = np.sqrt(-2*np.log(U1)) * np.cos(2*np.pi*U2)
Y = np.sqrt(-2*np.log(U1)) * np.sin(2*np.pi*U2)

samples = np.zeros(n*2)
samples[:n] = X
samples[n:] = Y

plt.hist(samples,bins=50,color="limegreen")
plt.show()

・実行結果

上記のように$200,000$サンプルまで増やすことで正規分布の概形まで得られることが確認できる。

また、下記を実行することで$2$次元ヒストグラムを描くことができる。

np.random.seed(0)

n = 100000
U1, U2 = np.random.rand(n), np.random.rand(n)

X = np.sqrt(-2*np.log(U1)) * np.cos(2*np.pi*U2)
Y = np.sqrt(-2*np.log(U1)) * np.sin(2*np.pi*U2)

plt.hist2d(X,Y,bins=50)
plt.show()

・実行結果

基底関数の集合に対応するカーネル関数の計算とPythonを用いたグラフの作成

カーネル法(Kernel methods)の理解にあたって、計算処理の概要が把握しにくいように思われたので、Pythonプログラムの作成を通して図などの再現を行います。当記事では「パターン認識と機械学習」の下巻の$6.2$節の「カーネル関数の構成」を元に、図$6.1$の再現をPythonを用いて行いました。

英語版と翻訳版で図に相違がありましたが、翻訳版の図の再現を目標に作成を行いました。

また、$(\mathrm{o.xx})$の形式の式番号は「パターン認識と機械学習」の式番号に対応させました。

・参考
演習の解答:パターン認識と機械学習

前提の確認

カーネル関数の数式

$$
\large
\begin{align}
k(x,x’) = \boldsymbol{\phi}(x)^{\mathrm{T}}\boldsymbol{\phi}(x’) = \sum_{i=1}^{M} \phi_{i}(x)\phi_{i}(x’) \quad (6.10)
\end{align}
$$

図$6.1$は、上記で表した$(6.10)$式に基づいて作成される。ここで$(6.10)$の$x$と$x’$はスカラー、$\phi_{i}(x)$はスカラー関数である一方で、$\boldsymbol{\phi}(x)$はベクトルを表すことに注意が必要である。ここでスカラー$x$に対して$\boldsymbol{\phi}(x)$がベクトルを表すというのは、$x,x^2,…,x^M$のように多項式を考えるなど、表現方法がいくつかある。また、$x’$は$x$と対になる点であり、カーネル関数は$x$と$x’$の類似度を主に表すと大まかに掴んでおくとよい。

具体的には多項式を元に$x,…,x^{M}$のように基底関数を考えると、$(6.10)$式のカーネル関数は下記のように表せる。
$$
\large
\begin{align}
k(x,z) &= \boldsymbol{\phi}(x)^{\mathrm{T}}\boldsymbol{\phi}(z) \\
&= \left(\begin{array}{ccccc} x & x^{2} & \cdots & x^{M-1} & x^{M} \end{array} \right) \left(\begin{array}{c} z \\ z^{2} \\ \vdots \\ z^{M-1} \\ z^{M} \end{array} \right) \\
&= \sum_{m=1}^{M} x^{m}z^{m}
\end{align}
$$

$x’$の$M$乗を$(x’)^{M}$表すと表記が難しくなるので上記では$x’$ではなく$z$を用いた。上記では$x$と$z$がスカラーであるのに対して、$\boldsymbol{\phi}(x),\boldsymbol{\phi}(z)$がベクトルであることが確認できる。

基底関数

Pythonを用いたカーネル関数の計算

当節ではPythonを用いたカーネル関数の計算を行う。当節では$(6.10)$式の解釈が行いやすいように$x$がスカラーである前提で関数の作成を行なったことで計算効率が良くないので、次節で計算の簡略化に関して取り扱う。

基底関数が多項式関数である場合

当項では基底関数が多項式関数である場合のカーネル関数の計算を行う。よって図$6.1$の左の図の再現を行う。

まずは基底関数に関して図示を行う。

import numpy as np
import matplotlib.pyplot as plt

def basis_poly(x,n):
    res = np.zeros(n)
    res[0] = x
    for i in range(1,n):
        res[i] = res[i-1]*x
    return res

n = 21
x = np.arange(-1.,1.01,0.01)

phi = np.zeros([x.shape[0],n])
for i in range(x.shape[0]):
    phi[i,:] = basis_poly(x[i],n)

for i in range(n):
    plt.plot(x,phi[:,i])

plt.xlim([-1.25,1.25])
plt.ylim([-1.25,1.25])
plt.show()

・実行結果

「パターン認識と機械学習」の図$6.1$の左上に対応

上記のように表した基底関数を元に、下記を実行することで$x’=-0.5$のときのカーネル関数の計算を行える。

k = np.zeros(x.shape[0])

for i in range(x.shape[0]):
    k[i] = np.dot(basis_poly(x[i],n),basis_poly(-0.5,n))

plt.plot(x,k)

plt.xlim([-1.25,1.25])
plt.ylim([-0.4,1.1])
plt.show()

・実行結果

「パターン認識と機械学習」の図$6.1$の左下に対応

基底関数がガウス分布である場合

ガウス分布に基づく基底関数に関して図示を行う。

import numpy as np
import matplotlib.pyplot as plt
from scipy import stats

def basis_norm(x,odd_n,sigma):
    res = np.zeros(odd_n)
    for i in range(odd_n):
        res[i] = stats.norm.pdf(x,loc=2*i/np.float(odd_n-1)-1.,scale=sigma)
    return res

odd_n = 21
sigma = 0.2
x = np.arange(-1.,1.01,0.01)

phi = np.zeros([x.shape[0],odd_n])
for i in range(x.shape[0]):
    phi[i,:] = basis_norm(x[i],odd_n,sigma)

for i in range(odd_n):
    plt.plot(x,phi[:,i])

plt.xlim([-1.25,1.25])
plt.show()

・実行結果

「パターン認識と機械学習」の図$6.1$の中央上に対応

上記のように表した基底関数を元に、下記を実行することで$x’=0$のときのカーネル関数の計算を行える。

k = np.zeros(x.shape[0])

for i in range(x.shape[0]):
    k[i] = np.dot(basis_norm(x[i],odd_n,sigma),basis_norm(0.,odd_n,sigma))

plt.plot(x,k)
plt.xlim([-1.25,1.25])
plt.show()

・実行結果

「パターン認識と機械学習」の図$6.1$の中央下に対応

基底がシグモイド関数である場合

シグモイド関数に基づく基底関数に関して図示を行う。

import numpy as np
import matplotlib.pyplot as plt

def basis_sigmoid(x,odd_n,slope):
    res = np.zeros(odd_n)
    for i in range(odd_n):
        a = 2*i/np.float(odd_n-1)-1.
        res[i] = 1./(1.+np.exp(-slope*(x-a)))
    return res

odd_n = 21
slope = 10.
x = np.arange(-1.,1.01,0.01)

phi = np.zeros([x.shape[0],odd_n])
for i in range(x.shape[0]):
    phi[i,:] = basis_sigmoid(x[i],odd_n,slope)

for i in range(odd_n):
    plt.plot(x,phi[:,i])

plt.xlim([-1.,1.])
plt.show()

・実行結果

「パターン認識と機械学習」の図$6.1$の右上に対応

上記のように表した基底関数を元に、下記を実行することで$x’=0$のときのカーネル関数の計算を行える。

k = np.zeros(x.shape[0])

for i in range(x.shape[0]):
    k[i] = np.dot(basis_sigmoid(x[i],odd_n,slope),basis_sigmoid(0.,odd_n,slope))

plt.plot(x,k)
plt.xlim([-1.25,1.25])
plt.show()

・実行結果

「パターン認識と機械学習」の図$6.1$の右下に対応

処理の高速化

以下、多項式関数に基づいて基底を設定した例に基づいて処理の効率化を行う。

基底に関する処理のベクトル化

前節で作成を行なったbasis_polyはスカラーを受け取る形式でプログラムの作成を行なったが、$x$の値の変化に対してグラフ作成を行う際の処理全体を俯瞰すると効率が良くなかった。以下ではbasis_polyがベクトルを受け取り行列を返すように修正を行う。

import numpy as np
import matplotlib.pyplot as plt

def basis_poly(x,n):
    res = np.zeros([x.shape[0],n])
    res[:,0] = x
    for i in range(1,n):
        res[:,i] = res[:,i-1]*x
    return res

n = 21
x = np.arange(-1.,1.01,0.01)

phi = basis_poly(x,n)

for i in range(n):
    plt.plot(x,phi[:,i])

plt.xlim([-1.25,1.25])
plt.ylim([-1.25,1.25])
plt.show()

・実行結果

上記は前節の結果と同様である。また、カーネル関数は下記のように計算を行える。

k = np.zeros(x.shape[0])

for i in range(x.shape[0]):
    k[i] = np.dot(phi[i,:],basis_poly(np.array([-0.5]),n)[0,:])

plt.plot(x,k)

plt.xlim([-1.25,1.25])
plt.ylim([-0.4,1.1])
plt.show()

・実行結果

上記の結果も前節の結果と同様である。また、上記の計算にあたってbasis_poly(np.array([-0.5]),n)[0,:]のようにスカラーを入力してベクトルを出力すれば十分な場合にbasis_polyはやや冗長であるので、basis_poly内で場合分けすると良い。

一方で、ここで取り扱ったのは処理の流れの把握用のプログラムであるので、あえてbasis_poly内のプログラムを増やさずに外部からの制御を行う形式で作成を行なった。

LU分解を用いた連立方程式の解法と逆行列の導出の仕組みとPythonでのプログラミング

当記事ではLU分解を用いた「$1$次連立方程式の解法」と「逆行列」の導出の仕組みに関して詳しく確認し、確認した式を元にPythonでプログラムを作成します。特に逆行列は行列を扱う上で様々なところで用いられるので、仕組みの概要とプログラムの対応を理解しておくと良いと思います。

・参考
Wikipedia LU分解

仕組みの確認

LU分解の仕組みとPythonを用いたプログラミング

上記で詳しく取り扱った。下記で定義するLU_decompositionは当記事の実装で用いるので、抜粋を行なった。

import numpy as np

def LU_decomposition(X):
    n = X.shape[0]
    L,U = np.zeros([n,n]), np.zeros([n,n])
    for i in range(n):
        for j in range(n):
            if i==0:
                if j==0:
                    L[i,j] = 1
                U[i,j] = X[i,j]
            elif j==0:
                L[i,j] = X[i,j]/U[0,0]
            elif i==j:
                L[i,j] = 1
                U[i,j] = X[i,j] - np.dot(L[i,:j],U[:i,j])
            elif i>j:
                L[i,j] = (X[i,j] - np.dot(L[i,:j],U[:j,j]))/U[j,j]
            elif j>i:
                U[i,j] = X[i,j] - np.dot(L[i,:i],U[:i,j])
    return L, U

A1 = np.array([[2., 1.], [1., 2.]])
L1, U1 = LU_decomposition(A1)

print(L1)
print(U1)
print(np.dot(L1,U1)-A1)

・実行結果

> print(L1)
[[ 1.   0. ]
 [ 0.5  1. ]]
> print(U1)
[[ 2.   1. ]
 [ 0.   1.5]]
> print(np.dot(L1,U1)-A1)
[[ 0.  0.]
 [ 0.  0.]]

LU分解を用いた連立方程式の解法

$n$元$1$次連立方程式は$n$次正方行列の$A$と$n$次元ベクトル$x$と$b$を用いて$Ax=b$のように行列表記することができる。$Ax=b$は下記のように行列表記できる。
$$
\large
\begin{align}
Ax &= b \\
\left(\begin{array}{ccc} a_{11} & \cdots & a_{1n} \\ \vdots & \ddots & \vdots \\ a_{n1} & \cdots & a_{nn} \end{array} \right) \left(\begin{array}{c} x_{1} \\ \vdots \\ x_{n} \end{array} \right) &= \left(\begin{array}{c} b_{1} \\ \vdots \\ b_{n} \end{array} \right)
\end{align}
$$

ここで上記の式では$a_{11}, x_{1}, b_{1}$のように$3$種類の文字を用いたが、「連立方程式を解く」という視点で考えるときは$a_{ij}$と$b_{k}$には何らかの数字が入り、これらの値に対応する$x_{1},…,x_{n}$を導出するというのが一般的な連立方程式を解く流れである。

さて、連立方程式を解くにあたっては、$Ax=b$より$x=A^{-1}b$を用いるというのが一般的な流れであるが、逆行列の公式を用いることのできる$2 \times 2$行列以外は逆行列$A^{-1}$の計算が必要である。ここでは$A^{-1}$が得られていない場合のLU分解を用いた解法に関して確認を行う。

$A=LU$のようにLU分解を行うことができるとき、$Ax=b$は$LUx=b$と表すことができる。ここで$Ux=y$とおくと、$Ly=b$と$Ux=y$のように二段階で連立方程式を解き、$x$を求めることもできる。この計算は$L, U$が三角行列であることによりシンプルに計算を行うことができる。

以下、$Ly=b$と$Ux=y$に関してそれぞれ確認を行う。

$Ly=b$を$y$に関して解く

$$
\large
\begin{align}
b &= Ly \\
\left(\begin{array}{c} b_{1} \\ b_{2} \\ b_{3} \\ \vdots \end{array} \right) &= \left(\begin{array}{cccc} 1 & 0 & 0 & \cdots \\ l_{21} & 1 & 0 & \cdots \\ l_{31} & l_{32} & 1 & \cdots \\ \vdots & \vdots & \vdots & \ddots \end{array} \right) \left(\begin{array}{c} y_{1} \\ y_{2} \\ y_{3} \\ \vdots \end{array} \right) \\
&= \left(\begin{array}{c} y_{1} \\ l_{21}y_{1} + y_{2} \\ l_{31}y_{1} + l_{32}y_{2} + y_{3} \\ \vdots \end{array} \right)
\end{align}
$$

上記を元に$i=1$と$i>1$に場合分けし、$y_{i}$に関して方程式を解くと下記のように表せる。
・$i=1$
$$
\large
\begin{align}
b_{1} &= y_{1} \\
y_{1} &= b_{1}
\end{align}
$$

・$i>1$
$$
\large
\begin{align}
b_{i} &= y_{i} + \sum_{k=1}^{i-1} l_{ik}y_{k} \\
y_{i} &= b_{i} – \sum_{k=1}^{i-1} l_{ik}y_{k}
\end{align}
$$

上記を$i=1$から$n$まで昇順に計算することでベクトル$y$の各要素の値が得られる。

$Ux=y$を$x$に関して解く

$$
\large
\begin{align}
y &= Ux \\
\left(\begin{array}{c} \vdots \\ y_{n-2} \\ y_{n-1} \ y_{n} \end{array} \right) &= \left(\begin{array}{cccc} \ddots & \vdots & \vdots & \vdots \\ \cdots & u_{n-2,n-2} & u_{n-2,n-1} & u_{n-2,n} \\ \cdots & 0 & u_{n-1,n-1} & u_{n-1,n} \\ \cdots & 0 & 0 & u_{nn} \end{array} \right) \left(\begin{array}{c} \vdots \\ x_{n-2} \\ x_{n-1} \ x_{n} \end{array} \right) \\
&= \left(\begin{array}{c} \vdots \\ u_{n-2,n-2}x_{n-2} + u_{n-2,n-1}x_{n-1} + u_{n-2,n}x_{n} \\ u_{n-1,n-1}x_{n-1} + u_{n-1,n}x_{n} \\ u_{nn}x_{n} \end{array} \right)
\end{align}
$$

上記を元に$i=n$と$i<n$に場合分けし、$y_{i}$に関して方程式を解くと下記のように表せる。
・$i=n$
$$
\large
\begin{align}
y_{n} &= u_{nn}x_{n} \\
x_{1} &= \frac{y_{n}}{u_{nn}}
\end{align}
$$

・$i<n$
$$
\large
\begin{align}
y_{i} &= u_{i,n}x_{i} + \sum_{k=1}^{n-i} u_{i,n-k+1}x_{n-k+1} \\
x_{i} &= \frac{\displaystyle y_{i} – \sum_{k=1}^{n-i} u_{i,n-k+1}x_{n-k+1}}{u_{i,n}}
\end{align}
$$

上記を$i=n$から$1$まで降順に計算することでベクトル$x$の各要素の値が得られる。

LU分解を用いた逆行列の導出

$n$次単位行列$E_{n}$を考え、その$i$列を$e_{i}$とおくとき、前項のようにLU分解などを用いることで$Ax_{i}=e_{i}$を解くことで$x_{i}$に関して解ける。ここで$e_{1},…,e_{n}$に関してそれぞれ$x_{1},…,x_{n}$を解くとこの結果は下記のようにまとめることができる。
$$
\large
\begin{align}
A \left(\begin{array}{ccc} x_{1} & \cdots & x_{n} \end{array} \right) &= \left(\begin{array}{ccc} e_{1} & \cdots & e_{n} \end{array} \right) \\
AX &= E_{n}
\end{align}
$$

ここで上記の両辺の左から$A^{-1}$をかけることで$A^{-1}=X$が得られる。よって、$b = e_{1},…,e_{n}$に対して前項の連立方程式の解法を用いることで逆行列が得られる。

Pythonを用いたプログラムの作成

連立方程式の解の計算

前節の「LU分解を用いた連立方程式の解法」の内容に基づいて、下記のように連立方程式を解くことができる。

def solve_equation(A,b):
    n = A.shape[0]
    L, U = LU_decomposition(A)
    x, y = np.zeros(n), np.zeros(n)
    for i in range(n):
        if i==0:
            y[i] = b[i]
        if i>0:
            y[i] = b[i] - np.dot(L[i,:i],y[:i])
    for i in range(n):
        if i==0:
            x[n-1-i] = y[n-1-i]/U[n-1-i,n-1-i]
        if i>0:
            x[n-1-i] = (y[n-1-i] - np.dot(U[n-1-i,(n-i):],x[(n-i):]))/U[n-1-i,n-1-i]
    return x, y

A1 = np.array([[2., 1.], [1., 2.]])
b1 = np.array([3., 3.])

x, _ = solve_equation(A1,b1)
print(x)

・実行結果

> print(x)
[ 1.  1.]

$2x+y=3, x+2y=3$の連立方程式の解が$x=1,y=1$であることから計算結果は妥当であると考えることができる。

逆行列の計算

前節の「LU分解を用いた逆行列の導出」の内容に基づいて、下記のように逆行列を計算することができる。

def calc_inv_mat(A):
    n = A.shape[0]
    E = np.eye(n)
    A_inv = np.zeros([n,n])
    for i in range(n):
        A_inv[:,i], _ = solve_equation(A,E[:,i])
    return A_inv

A1 = np.array([[2., 1.], [1., 2.]])
A1_inv = calc_inv_mat(A1)
print(A1_inv)

・実行結果

> print(A1_inv)
[[ 0.66666667 -0.33333333]
 [-0.33333333  0.66666667]]

$2$次正方行列の逆行列の公式を用いることで上記の計算結果が妥当であることが確かめられる。下記では同様に$3$次正方行列の逆行列の計算結果の確認も行なった。

A2 = np.array([[2., 1., 5.], [1., 6., 2.], [7., 2., 1.]])
A2_inv = calc_inv_mat(A2)

print(A2_inv)
print(np.dot(A2,A2_inv))
print("error: {:.3f}".format(np.linalg.norm(np.dot(A2,A2_inv)-np.eye(3))))

・実行結果

> print(A2_inv)
[[-0.01092896 -0.04918033  0.15300546]
 [-0.07103825  0.18032787 -0.00546448]
 [ 0.21857923 -0.01639344 -0.06010929]]
> print(np.dot(A2,A2_inv))
[[  1.00000000e+00  -6.93889390e-18   0.00000000e+00]
 [  1.11022302e-16   1.00000000e+00   0.00000000e+00]
 [  6.93889390e-16  -9.02056208e-17   1.00000000e+00]]
> print("error: {:.3f}".format(np.linalg.norm(np.dot(A2,A2_inv)-np.eye(3))))
error: 0.000

上記の誤差の計算にはフロベニウスノルムを用いたが、$0$に近い値が得られることから結果が概ね妥当であることが確認できる。

LU分解(LU decomposition)の原理の理解とPythonを用いた実装

当記事ではLU分解(LU decomposition)の原理の理解とPythonを用いた実装に関して取り扱います。LU分解は方程式を順に解く手法であり、手計算を行うと大変なのでPythonなどで計算を行うとスムーズですが、$L$や$U$に関して解くところが複雑であるのでなるべくわかりやすく取りまとめました。

・参考
Wikipedia LU分解

LU分解の原理

行列$L$と$U$の定義

以下、$n$次正方行列の$A$を下記のような下三角行列$L$と上三角行列$U$を用いて$A=LU$のように分解を行うことを考える。$L$はLower、$U$はUpperにそれぞれ対応すると考えれば良い。
$$
\large
\begin{align}
L &= \left(\begin{array}{cccc} 1 & 0 & 0 & \cdots \\ l_{21} & 1 & 0 & \cdots \\ l_{31} & l_{32} & 1 & \cdots \\ \vdots & \vdots & \vdots & \ddots \end{array} \right) \\
U &= \left(\begin{array}{cccc} u_{11} & u_{12} & u_{13} & \cdots \\ 0 & u_{22} & u_{23} & \cdots \\ 0 & 0 & u_{33} & \cdots \\ \vdots & \vdots & \vdots & \ddots \end{array} \right)
\end{align}
$$

ここで上記の$L$の対角成分を$1$、$U$の対角成分を$u_{ii}$とおいたことに注意が必要である。また、行列$A$は下記のように表す。
$$
\large
\begin{align}
A &= \left(\begin{array}{cccc} a_{11} & a_{12} & a_{13} & \cdots \\ a_{21} & a_{22} & a_{23} & \cdots \\ a_{31} & a_{32} & a_{33} & \cdots \\ \vdots & \vdots & \vdots & \ddots \end{array} \right)
\end{align}
$$

下三角行列の$L$と上三角行列の要素の導出

前項で行なった$A, L, U$の定義を元に$A=LU$を要素表記で計算を行う。
$$
\large
\begin{align}
& A = LU \\
& \left(\begin{array}{cccc} a_{11} & a_{12} & a_{13} & \cdots \\ a_{21} & a_{22} & a_{23} & \cdots \\ a_{31} & a_{32} & a_{33} & \cdots \\ \vdots & \vdots & \vdots & \ddots \end{array} \right) = \left(\begin{array}{cccc} 1 & 0 & 0 & \cdots \\ l_{21} & 1 & 0 & \cdots \\ l_{31} & l_{32} & 1 & \cdots \\ \vdots & \vdots & \vdots & \ddots \end{array} \right) \left(\begin{array}{cccc} u_{11} & u_{12} & u_{13} & \cdots \\ 0 & u_{22} & u_{23} & \cdots \\ 0 & 0 & u_{33} & \cdots \\ \vdots & \vdots & \vdots & \ddots \end{array} \right) \\
&= \left(\begin{array}{cccc} u_{11} & u_{12} & u_{13} & \cdots \\ l_{21}u_{11} & l_{21}u_{12}+u_{22} & l_{21}u_{13}+u_{23} & \cdots \\ l_{31}u_{11} & l_{31}u_{12}+l_{32}u_{22} & l_{31}u_{13}+l_{32}u_{23}+u_{33} & \cdots \\ \vdots & \vdots & \vdots & \ddots \end{array} \right)
\end{align}
$$

上記の結果を元に、$a_{ij}=(LU)_{ij}$や$l_{ij}, u_{ij}$は下記のように$i$と$j$に関して場合分けして考えることができる。
・$i=1, j=1$
$$
\large
\begin{align}
a_{11} &= u_{11} \\
l_{11} &= 1 \\
u_{11} &= a_{11}
\end{align}
$$

・$i=1, j>1$
$$
\large
\begin{align}
a_{1j} &= u_{1j} \\
l_{1j} &= 0 \\
u_{1j} &= a_{1j}
\end{align}
$$

・$i>1, j=1$
$$
\large
\begin{align}
a_{i1} &= l_{i1} u_{11} \\
l_{i1} &= \frac{a_{i1}}{u_{11}} \\
u_{i1} &= 0
\end{align}
$$

・$i=j>1$
$$
\large
\begin{align}
a_{ii} &= \sum_{k=1}^{i-1} (l_{ik}u_{ki}) + u_{ii} \\
l_{ii} &= 1 \\
u_{ii} &= a_{ii} – \sum_{k=1}^{i-1} (l_{ik}u_{ki})
\end{align}
$$

・$i>j>1$
$$
\large
\begin{align}
a_{ij} &= \sum_{k=1}^{j-1} (l_{ik}u_{kj}) + l_{ij}u_{jj} \\
l_{ij} &= \frac{\displaystyle a_{ij} – \sum_{k=1}^{j-1} l_{ik}u_{kj}}{u_{jj}} \\
u_{ij} &= 0
\end{align}
$$

・$j>i>1$
$$
\large
\begin{align}
a_{ij} &= \sum_{k=1}^{i-1} (l_{ik}u_{kj}) + u_{ij} \\
l_{11} &= 0 \\
u_{11} &= a_{ij} – \sum_{k=1}^{i-1} l_{ik}u_{kj}
\end{align}
$$

上記の$l_{ij}, u_{ij}$の計算にあたって、$L, U$の要素が用いられるが、どれも「$i$行より上の行」か「$i$行かつ$j$列より左の列」の要素が対応するので、$i$に関する繰り返し処理の中に$j$に関する繰り返し処理を行うことで$L, U$の要素を用いることができる。

ここで$L, U$の要素に関して式から消去を行うことも本来的には可能だが、式変形が複雑であるので、次節ではここで取り扱ったように$L, U$の要素を徐々に確定する計算処理を用いる。

LU分解の実装

LU分解の実装は下記のように行うことができる。

import numpy as np

def LU_decomposition(X):
    n = X.shape[0]
    L,U = np.zeros([n,n]), np.zeros([n,n])
    for i in range(n):
        for j in range(n):
            if i==0:
                if j==0:
                    L[i,j] = 1
                U[i,j] = X[i,j]
            elif j==0:
                L[i,j] = X[i,j]/U[0,0]
            elif i==j:
                L[i,j] = 1
                U[i,j] = X[i,j] - np.dot(L[i,:j],U[:i,j])
            elif i>j:
                L[i,j] = (X[i,j] - np.dot(L[i,:j],U[:j,j]))/U[j,j]
            elif j>i:
                U[i,j] = X[i,j] - np.dot(L[i,:i],U[:i,j])
    return L, U

A1 = np.array([[2., 1.], [1., 2.]])
L1, U1 = LU_decomposition(A1)

print(L1)
print(U1)
print(np.dot(L1,U1))

・実行結果

> print(L1)
[[ 1.   0. ]
 [ 0.5  1. ]]
> print(U1)
[[ 2.   1. ]
 [ 0.   1.5]]
> print(np.dot(L1,U1))
[[ 2.  1.]
 [ 1.  2.]]

上記では$2$次正方行列に関して取り扱ったが、より複雑な例に関しても確認するにあたって、下記では$5$次正方行列に関してLU分解を行う。

A2 = np.array([[2., 1., 2., 4., 6.], [1., 2., 3., 2., 1.], [5., 1., 2, 3., 4.], [6., 1., 2., 3, 5.], [3., 1., 2., 6., 3.]])
L2, U2 = LU_decomposition(A2)

print(L2)
print(U2)
print(np.dot(L2,U2)-A2)

・実行結果

> print(L2)
[[ 1.          0.          0.          0.          0.        ]
 [ 0.5         1.          0.          0.          0.        ]
 [ 2.5        -1.          1.          0.          0.        ]
 [ 3.         -1.33333333  1.33333333  1.          0.        ]
 [ 1.5        -0.33333333  0.33333333  7.          1.        ]]
> print(U2)
[[  2.           1.           2.           4.           6.        ]
 [  0.           1.5          2.           0.          -2.        ]
 [  0.           0.          -1.          -7.         -13.        ]
 [  0.           0.           0.           0.33333333   1.66666667]
 [  0.           0.           0.           0.         -14.        ]]
> print(np.dot(L2,U2)-A2)
[[ 0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.]]

上記のLは下三角行列、Uは上三角行列であることがそれぞれ確認できる。また、print(np.dot(L2,U2)-A2)では$LU$によって$A$が復元できたかについて確認を行なった。全ての要素が0であることが確認できることから、適切に行列の分解が行えたことが確認できる。

べき乗法・逆反復法の概要とQR法・逆反復法を用いた固有値・固有ベクトルの数値計算

QR法と逆反復法を組み合わせることで固有値・固有ベクトルの近似計算を行うことができます。QR法は別記事で取り扱ったので、当記事ではべき乗法や逆反復法に関して取りまとめ、QR法で得られた固有値を元に逆反復法を用いて固有ベクトルの近似計算を行います。

・参考
数値計算講義ノート$6$ 固有値問題の解法

逆反復法の理解

べき乗法

べき乗法は$n$次正方行列$A$の最大固有値と最大固有値に対応する固有ベクトルを計算する手法である。べき乗法の仕組みを理解するにあたっては、$Ax_{i} = \lambda_{i} x_{i}$の式と任意の大きさ$1$のベクトル$y$が大きさ$1$の固有ベクトル$x_i$を用いて下記のように表されることに基づいて考えればよい。
$$
\large
\begin{align}
y &= c_1 x_1 + \cdots c_n x_n \\
&= \sum_{i=1}^{n} c_i x_i
\end{align}
$$

上記に対して$Ay$を計算すると以下のように計算できる。
$$
\large
\begin{align}
Ay &= A(c_1 x_1 + \cdots c_n x_n) \\
&= c_1 A x_1 + \cdots + c_n A x_n \\
&= c_1 \lambda_1 x_1 + \cdots + c_n \lambda_n x_n
\end{align}
$$

上記に基づいて、$A^{k}y$は下記のように計算できる。
$$
\large
\begin{align}
A^{k}y &= A^{k-1} (c_1 \lambda_1 x_1 + \cdots + c_n \lambda_n x_n) \\
&= A^{k-2} (c_1 \lambda_1^2 x_1 + \cdots + c_n \lambda_n^2 x_n) \\
&= \cdots \\
&= A (c_1 \lambda_1^{k-1} x_1 + \cdots + c_n \lambda_n^{k-1} x_n) \\
&= c_1 \lambda_1^k x_1 + \cdots + c_n \lambda_n^k x_n \quad (1)
\end{align}
$$

ここで$|\lambda_1|>|\lambda_2|>\cdots|\lambda_{n}|$が成立すると考える場合、ベクトル$A^{k}y$は最大固有値$\lambda_1$に対応する$x_1$の方向に収束すると考えられる。このことは$\displaystyle \lim_{k \to \infty} \frac{A^{k}y}{|A^{k}y|}$を下記のように考えることで示せる。
$$
\large
\begin{align}
\lim_{k \to \infty} \frac{A^{k}y}{|A^{k}y|} &= \lim_{k \to \infty} \frac{c_1 \lambda_1^k x_1 + \cdots + c_n \lambda_n^k x_n}{(c_1^2 \lambda_1^{2k} + \cdots + c_n^2 \lambda_n^{2k})^{1/2}} \\
&= \lim_{k \to \infty} \frac{c_1 x_1 + \cdots + c_n (\lambda_n/\lambda_1)^k x_n}{(c_1^2 + \cdots + c_n^2 (\lambda_n/\lambda_1)^{2k})^{1/2}} \\
&= \frac{c_1 x_1}{c_1} = x_1 \quad (2)
\end{align}
$$

上記の計算にあたっては「$|x_i|=1$かつ$x_i$が固有ベクトルである $\iff$ $x_i$が正規直交基底である」ことに基づいて計算を行なった。ここまでの内容に基づいて下記のような繰り返し演算で最大固有値$\lambda_{1}$に対応する固有ベクトル$x_1$を求めることができる。
$$
\large
\begin{align}
y_0 &= \frac{u}{|u|} \\
y_{k+1} &= \frac{Ay_k}{|Ay_k|} \\
\lim_{k \to \infty} y_{k} &= x_1
\end{align}
$$

上記では$\displaystyle y_{k+1} = \frac{Ay_k}{|Ay_k|}$のように繰り返しごとに$|Ay_k|$で割り算を行うが、ベクトルの方向が変わらないことから$(2)$式と同じ結果が得られる。

逆反復法

固有値・固有ベクトルに関する$Ax = \lambda x$の式は下記のように変形を行うことができる。
$$
\large
\begin{align}
Ax &= \lambda x \\
A^{-1}Ax &= \lambda A^{-1} x \\
A^{-1} x &= \frac{1}{\lambda} x
\end{align}
$$

上記より$A^{-1}$の固有値$1/\lambda$に対する固有ベクトルが$x$であると考えられる。この式にべき乗法を適用すると$1/\lambda_{i}$が最大、すなわち$\lambda_{i}$が最小になる固有値に対応する固有ベクトルが得られる。

ここでQR法などによって$A$の固有値の近似値$\hat{\lambda}_{i}$が得られているとき、$A-\hat{\lambda}_{i}I$に関して下記が成立する。
$$
\large
\begin{align}
(A-\hat{\lambda}_{i}I) x_i &= Ax_i – \hat{\lambda}_{i} x_{i} \\
&= (\lambda_{i} – \hat{\lambda}_{i}) x_{i} \\
(A-\hat{\lambda}_{i}I)^{-1} x_{i} &= \frac{1}{\lambda_{i} – \hat{\lambda}_{i}} x_{i}
\end{align}
$$

上記にべき乗法を適用すると、$\hat{\lambda}_{i}$に対応する固有ベクトルを得ることができる。上記は漸化式を用いて下記のように表記できる。
$$
\large
\begin{align}
y_0 &= \frac{u}{|u|} \\
y_{k+1} &= \frac{(A-\hat{\lambda}_{i}I)^{-1}y_k}{|(A-\hat{\lambda}_{i}I)^{-1}y_k|} \\
\lim_{k \to \infty} y_{k} &= x_i
\end{align}
$$

上記の逆行列の計算はLU分解を元に行うことができる。詳細は下記で取り扱った。

また、LU分解を用いた「逆行列」の計算は「連立方程式の解」の計算に基づいて計算を行う。よって、下記のように連立方程式の解を解くことによって$x_i$を計算することもできる。
$$
\large
\begin{align}
y_0 &= \frac{u}{|u|} \\
(A-\hat{\lambda}_{i}I)z_{k+1} &= y_{k}, \quad y_{k+1} = \frac{z_{k}}{|z_{k+1}|} \\
\lim_{k \to \infty} y_{k} &= x_i
\end{align}
$$

上記の計算は$y_{k}$が既知のとき、連立方程式の$(A-\hat{\lambda}_{i}I)z_{k+1} = y_{k}$を$z_{k+1}$に関して解くにあたって、LU分解などを用いることを意味する。詳しくは「LU分解を用いた連立方程式の解法」で取りまとめた。

固有値・固有ベクトルの計算

べき乗法を用いた最大固有値と対応する固有ベクトルの計算

行列$\displaystyle A = \left(\begin{array}{cc} 2 & 1 \\ 1 & 2 \end{array} \right)$に対してべき乗法を適用すると下記のように計算を行うことができる。

import numpy as np

A = np.array([[2., 1.], [1., 2.]])
u = np.array([2., 1.])

y_k = u
for i in range(10):
    y_k = np.dot(A,y_k)/np.linalg.norm(np.dot(A,y_k))
    print(y_k)

・実行結果

[ 0.78086881  0.62469505]
[ 0.73279349  0.6804511 ]
[ 0.71578195  0.69832385]
[ 0.71001067  0.70419091]
[ 0.70807608  0.70613615]
[ 0.70743003  0.70678338]
[ 0.70721455  0.706999  ]
[ 0.70714271  0.70707086]
[ 0.70711876  0.70709481]
[ 0.70711077  0.70710279]

上記はベクトル$y_{k}$の初期値を$\displaystyle y_{0} = \left(\begin{array}{c} 2 \\ 1 \end{array} \right)$とおき、繰り返し計算を行なったが、$y_{0}$が他の値でも収束することを以下、確認する。

import numpy as np

A = np.array([[2., 1.], [1., 2.]])
u = np.array([[2., 1.], [1., 2.], [1., 0.], [2., 2.], [-1., 2]])

for i in range(u.shape[0]):
    y_k = u[i,:]
    for j in range(10):
        y_k = np.dot(A,y_k)/np.linalg.norm(np.dot(A,y_k))
    print("・v_0: {}".format(u[i,:]))
    print("eigen value: {:.1f}".format(np.dot(y_k,np.dot(A,y_k))))
    print("eigen vector: {}".format(y_k))

・実行結果

・v_0: [ 2.  1.]
eigen value: 3.0
eigen vector: [ 0.70711077  0.70710279]
・v_0: [ 1.  2.]
eigen value: 3.0
eigen vector: [ 0.70710279  0.70711077]
・v_0: [ 1.  0.]
eigen value: 3.0
eigen vector: [ 0.70711876  0.70709481]
・v_0: [ 2.  2.]
eigen value: 3.0
eigen vector: [ 0.70710678  0.70710678]
・v_0: [-1.  2.]
eigen value: 3.0
eigen vector: [ 0.70707086  0.70714271]

大きさ$1$の固有ベクトル$y_k$に対応する固有値が$\lambda = y_{k}^{\mathrm{T}}Ay_{k}$で求められることを用いて、上記では固有値の計算も合わせて行なった。$\lambda = y_{k}^{\mathrm{T}}A y_{k}$の式は$Ay_{k} = \lambda y_{k}$の両辺に左から$y_{k}^{\mathrm{T}}$をかけることによって下記のように導出できる。
$$
\large
\begin{align}
Ay_{k} &= \lambda y_{k} \\
y_{k}^{\mathrm{T}} A y_{k} &= \lambda y_{k}^{\mathrm{T}} y_{k} \\
&= \lambda
\end{align}
$$

QR法を用いた固有値の計算

詳細は上記でまとめたが、QR法を用いて固有値は下記のように計算できる。

import numpy as np

def QR_gram(X):
    U, Q, R = np.zeros([X.shape[0],X.shape[0]]), np.zeros([X.shape[0],X.shape[0]]), np.zeros([X.shape[0],X.shape[1]])
    U[:,0] = X[:,0]
    Q[:,0] = U[:,0]/np.linalg.norm(U[:,0])
    R[0,0] = np.dot(X[:,0],U[:,0]/np.linalg.norm(U[:,0]))
    for i in range(1,X.shape[0]):
       U[:,i] = X[:,i]
       for j in range(i):
           U[:,i] += -(np.dot(X[:,i],U[:,j])/np.dot(U[:,j],U[:,j]))*U[:,j]
           R[j,i] = np.dot(X[:,i],U[:,j]/np.linalg.norm(U[:,j]))
       Q[:,i] = U[:,i]/np.linalg.norm(U[:,i])
       R[i,i] = np.dot(X[:,i],U[:,i]/np.linalg.norm(U[:,i]))
    return Q, R

A = np.array([[2., 1.], [1., 2.]])
Q, R = QR_gram(A)

A_k = A
Q_k, Q_k_inv = Q, Q.T
for i in range(10):
    Q_k, _ = QR_gram(A_k)
    Q_k_inv = Q_k.T
    A_k = np.dot(Q_k_inv,np.dot(A_k,Q_k))
    
Lambda_A = A_k
print("lambda_1: {:.1f}".format(Lambda_A[0,0]))
print("lambda_2: {:.1f}".format(Lambda_A[1,1]))

・実行結果

> print("lambda_1: {:.1f}".format(Lambda_A[0,0]))
lambda_1: 3.0
> print("lambda_2: {:.1f}".format(Lambda_A[1,1]))
lambda_2: 1.0

逆反復法を用いた固有ベクトルの計算

前項の「QR法」の計算結果を元に、下記のように逆反復法の計算を行うことができる。

lamb = np.array([Lambda_A[0,0], Lambda_A[1,1]])
n = lamb.shape[0]
u = np.array([1., 0.])

for i in range(n):
    A_ = A - lamb[i]*np.eye(n)
    inv_A_ = np.linalg.inv(A_)
    y_k = u/np.linalg.norm(u)
    for j in range(10):
        y_k = np.dot(inv_A_,y_k)/np.linalg.norm(np.dot(inv_A_,y_k))
    print("・eigen value: {:.1f}".format(lamb[i]))
    print("eigen vector: {}".format(y_k))

・実行結果

・eigen value: 3.0
eigen vector: [ 0.70710678  0.70710678]
・eigen value: 1.0
eigen vector: [ 0.70710678 -0.70710678]

上記の計算にあたっては簡易化にあたってnp.linalg.invを用いたが、逆行列の計算に関しては下記で取りまとめたLU分解などを用いることで行うことができる。

グラム・シュミットの正規直交化に基づくQR分解とQR法を用いた固有値の近似計算の概要と実装

当記事ではQR分解(QR decomposition)に基づくQR法を用いた固有値の近似計算の概要とPythonを用いた実装に関して取り扱います。QR分解に関してはいくつか手法がありますが、当記事ではグラム・シュミットの正規直交化法に基づくQR分解の手順を主に確認しました。

・参考
Wikipedia QR分解
Wikipedia QR法

手順の確認

グラム・シュミットの正規直交化法に基づくQR分解

上記で取り扱ったグラム・シュミットの正規直交化法では、線型独立な$\vec{a}_{1}, \cdots, \vec{a}_{n}$に対して下記のように正規直交基底の$\vec{e}_{1}, \cdots, \vec{e}_{n}$の計算を行なった。
$$
\large
\begin{align}
\vec{u}_{1} &= \vec{a}_{1}, & \vec{e}_{1} = \frac{\vec{u}_{1}}{|\vec{u}_{1}|} \\
\vec{u}_{2} &= \vec{a}_{2} – \frac{\vec{a}_{1} \cdot \vec{a}_{2}}{|\vec{a}_{1}|^2} \vec{a}_{1}, & \vec{e}_{2} = \frac{\vec{u}_{2}}{|\vec{u}_{2}|} \\
\vec{u}_{3} &= \vec{a}_{3} – \frac{\vec{a}_{1} \cdot \vec{a}_{3}}{|\vec{a}_{1}|^2} \vec{a}_{1} – \frac{\vec{a}_{2} \cdot \vec{a}_{3}}{|\vec{a}_{2}|^2} \vec{a}_{2}, & \vec{e}_{3} = \frac{\vec{u}_{3}}{|\vec{u}_{3}|} \\
\vdots \\
\vec{u}_{n} &= \vec{a}_{n} – \sum_{k=1}^{n-1} \frac{\vec{a}_{k} \cdot \vec{a}_{n}}{|\vec{a}_{k}|^2} \vec{a}_{k}, & \vec{e}_{n} = \frac{\vec{u}_{n}}{|\vec{u}_{n}|}
\end{align}
$$

ここで上図に基づいて考えることで$\vec{a}_{k} \cdot \vec{e}_{k} = |\vec{u}_{k}|$が成立することを元にベクトル$\vec{a}_{k}$を$\vec{e}_{1}, \cdots, \vec{e}_{n}$を用いて下記のように表すことを考えることができる。
$$
\large
\begin{align}
\vec{a}_{1} &= (\vec{a}_{1} \cdot \vec{e}_{1}) \vec{e}_{1} \\
\vec{a}_{2} &= (\vec{e}_{1} \cdot \vec{a}_{2}) \vec{e}_{1} + (\vec{a}_{2} \cdot \vec{e}_{2}) \vec{e}_{2} \\
\vec{a}_{3} &= (\vec{e}_{1} \cdot \vec{a}_{3}) \vec{e}_{1} + (\vec{e}_{2} \cdot \vec{a}_{3}) \vec{e}_{2} + (\vec{a}_{3} \cdot \vec{e}_{3}) \vec{e}_{3} \\
\vdots \\
\vec{a}_{n} &= \sum_{k=1}^{n} (\vec{e}_{k} \cdot \vec{a}_{n}) \vec{e}_{k}
\end{align}
$$

上記は下記のような行列計算にまとめることができる。
$$
\large
\begin{align}
\left(\begin{array}{ccc} \vec{a}_{1} & \cdots & \vec{a}_{n} \end{array} \right) &= \left(\begin{array}{ccc} (\vec{a}_{1} \cdot \vec{e}_{1}) \vec{e}_{1} & \cdots & \displaystyle \sum_{k=1}^{n} (\vec{e}_{k} \cdot \vec{a}_{n}) \vec{e}_{k} \end{array} \right) \\
&= \left(\begin{array}{ccc} \vec{e}_{1} & \cdots & \vec{e}_{n} \end{array} \right) \left(\begin{array}{ccc} (\vec{a}_{1} \cdot \vec{e}_{1}) & (\vec{e}_{1} \cdot \vec{a}_{2}) & (\vec{e}_{1} \cdot \vec{a}_{3}) \\ 0 & (\vec{a}_{2} \cdot \vec{e}_{2}) & (\vec{e}_{2} \cdot \vec{a}_{3}) \\ 0 & 0 & (\vec{a}_{3} \cdot \vec{e}_{3}) \end{array} \right) \\
A &= QR
\end{align}
$$

上記の$Q$は直交行列、$R$は上三角行列であることがここまでの議論により確認できる。

QR分解とQR法

QR分解では$A=QR$のように行列$A$を直交行列$Q$と上三角行列$R$に分解を行う。ここで$A=QR$より、$R=Q^{-1}A=Q^{\mathrm{T}}A$である。ここで下記のように繰り返し手順を定める。
$$
\large
\begin{align}
A_{1} &= A \\
R_{k}Q_{k} &= A_{k} \\
A_{k+1} &= R_{k}Q_{k} = Q^{\mathrm{T}}_{k}A_{k}Q_{k}
\end{align}
$$

上記の計算を行うことで$A_{n}$の対角成分より下の要素が$0$に収束し、計算によって固有値が変わらないことから$A_{n}$の対角成分が固有値に一致する。この手法をQR法という。

Pythonを用いた計算例

QR分解の実装

グラム・シュミットの正規直交化に基づくQR分解は下記を実行することで行うことができる。計算例はデバッグもかねて「QR分解 Wikipedia」と同様な例を用いた。

import numpy as np

def QR_gram(X):
    U, Q, R = np.zeros([X.shape[0],X.shape[0]]), np.zeros([X.shape[0],X.shape[0]]), np.zeros([X.shape[0],X.shape[1]])
    U[:,0] = X[:,0]
    Q[:,0] = U[:,0]/np.linalg.norm(U[:,0])
    R[0,0] = np.dot(X[:,0],U[:,0]/np.linalg.norm(U[:,0]))
    for i in range(1,X.shape[0]):
       U[:,i] = X[:,i]
       for j in range(i):
           U[:,i] += -(np.dot(X[:,i],U[:,j])/np.dot(U[:,j],U[:,j]))*U[:,j]
           R[j,i] = np.dot(X[:,i],U[:,j]/np.linalg.norm(U[:,j]))
       Q[:,i] = U[:,i]/np.linalg.norm(U[:,i])
       R[i,i] = np.dot(X[:,i],U[:,i]/np.linalg.norm(U[:,i]))
    return Q, R

A = np.array([[12., -51., 4.], [6., 167., -68.], [-4., 24., -41]])
Q, R = QR_gram(A)

print("・Q:")
print(Q)
print("・R:")
print(R)
print("・re-calculated A=QR:")
print(np.dot(Q,R))
print("error: {}".format(np.linalg.norm(A-np.dot(Q,R))))

・実行結果

・Q
[[ 0.85714286 -0.39428571 -0.33142857]
 [ 0.42857143  0.90285714  0.03428571]
 [-0.28571429  0.17142857 -0.94285714]]
・R
[[  14.   21.  -14.]
 [   0.  175.  -70.]
 [   0.    0.   35.]]
・re-calculated A=QR:
[[  12.  -51.    4.]
 [   6.  167.  -68.]
 [  -4.   24.  -41.]]
 error: 1.76298382482e-14

$2 \times 2$正方行列における固有値の計算

下記を実行することで$2 \times 2$正方行列$\displaystyle A = \left(\begin{array}{cc} 2 & 1 \\ 1 & 2 \end{array} \right)$に対して固有値の計算を行うことができる。

A = np.array([[2., 1.], [1., 2.]])
Q, R = QR_gram(A)

A_k = A
Q_k, Q_k_inv = Q, Q.T
for i in range(5):
    Q_k, _ = QR_gram(A_k)
    Q_k_inv = Q_k.T
    A_k = np.dot(Q_k_inv,np.dot(A_k,Q_k))
    print("・STEP: {:.0f}".format(i+1))
    print(A_k)

・実行結果

・STEP: 1
[[ 2.8  0.6]
 [ 0.6  1.2]]
・STEP: 2
[[ 2.97560976  0.2195122 ]
 [ 0.2195122   1.02439024]]
・STEP: 3
[[ 2.99726027  0.0739726 ]
 [ 0.0739726   1.00273973]]
・STEP: 4
[[ 2.99969521  0.0246876 ]
 [ 0.0246876   1.00030479]]
・STEP: 5
[[ 2.99996613  0.00823031]
 [ 0.00823031  1.00003387]]

$\displaystyle A = \left(\begin{array}{cc} 2 & 1 \\ 1 & 2 \end{array} \right)$の固有値は$\lambda=3,1$なので、上記は概ね妥当な結果が得られていると考えることができる。

正射影の式から理解するグラム・シュミット(Gram–Schmidt)の正規直交化法

グラム・シュミットの正規直交化法(Gram–Schmidt orthonormalization)は線型独立な有限個のベクトルで構成される部分空間と同様の部分空間を持つ正規直交系を作り出す手法です。当記事では正射影の式を元にグラム・シュミットの正規直交化法の仕組みに関して取りまとめました。

・参考
Wikipedia グラム・シュミットの正規直交化法

前提の理解

正射影の公式とその導出

上記のように$\vec{a}, \vec{b}$を考える。このとき$\vec{a}$の$\vec{b}$への正射影を$\vec{x}$と定めると、$\vec{x}$は下記のように表すことができる。
$$
\large
\begin{align}
\vec{x} = \frac{\vec{a} \cdot \vec{b}}{|\vec{b}|^2} \vec{b} \quad (1)
\end{align}
$$

以下、$(1)$式のように$\vec{x}$が示せることの確認を行う。

導出にあたっては、ベクトル$\vec{x}$を$\vec{b}$と平行な単位ベクトルの定数倍であることに基づいて考える。ここで$\vec{b}$と平行な単位ベクトルを$\vec{e}$とおき、$\vec{x}=k\vec{e}$と定めると、$\vec{e}$と$k$は下記のように表せる。
$$
\large
\begin{align}
\vec{e} &= \frac{\vec{b}}{|\vec{b}|} \quad (2) \\
k &= |\vec{a}|\cos{\theta} \quad (3) \\
&= \frac{|\vec{a}||\vec{b}|\cos{\theta}}{|\vec{b}|} = \frac{\vec{a} \cdot \vec{b}}{|\vec{b}|}
\end{align}
$$

ここで$\vec{x}=k\vec{e}$に$(2)$式、$(3)$式を代入することで、下記のように$(1)$式を導出することができる。
$$
\large
\begin{align}
\vec{x} &= k \vec{e} \\
&= \frac{\vec{a} \cdot \vec{b}}{|\vec{b}|} \frac{\vec{b}}{|\vec{b}|} \\
&= \frac{\vec{a} \cdot \vec{b}}{|\vec{b}|^2} \vec{b}
\end{align}
$$

線型独立と部分空間

グラム・シュミットの正規直交化法

$2$次元平面におけるグラム・シュミットの正規直交化法

グラム・シュミットの正規直交化法は、上図のように線型独立である$\vec{a}_{1}, \vec{a}_{2}$が与えられた際に正規直交基底の$\vec{e}_{1}, \vec{e}_{2}$を得る手法である。

1) $\vec{a}_{1}$に平行な単位ベクトルを$\vec{e}_{1}$とおく。
2) $\vec{e}_{1}$に垂直なベクトル$\vec{u}_{2}$を$\vec{u}_{2}=\vec{a}_{2}-\vec{x}$を計算することで得る。ここで$\vec{x}$は$\vec{a}_{2}$から$\vec{a}_{1}$への正射影であるので、前節の「正射影の公式」を用いることで計算できる。
3) $\vec{u}_{2}$と平行な単位ベクトルを$\vec{e}_{2}$とおく。

上記に基づいて、$\vec{e}_{1}, \vec{e}_{2}$はそれぞれ下記のように計算できる。
$$
\large
\begin{align}
\vec{e}_{1} &= \frac{\vec{a}_{1}}{|\vec{a}_{1}|} \\
\vec{e}_{2} &= \frac{\vec{u}_{2}}{|\vec{u}_{2}|} \\
\vec{u}_{2} &= \vec{a}_{2} \, – \, \vec{x} = \vec{a}_{2} \, – \, \frac{\vec{a}_{1} \cdot \vec{a}_{2}}{|\vec{a}_{1}|^2} \vec{a}_{1}
\end{align}
$$

$n$次元空間におけるグラム・シュミットの正規直交化法

$n$次元空間におけるグラム・シュミットの正規直交化法は$2$次元平面と同様に考えることができるが、$\vec{e}_{3}$以降は累積を考慮する必要があることに注意しなければならない。具体的には$\vec{e}_{1}, \vec{e}_{2}, \cdots \vec{e}_{n}$は下記のように計算できる。
$$
\large
\begin{align}
\vec{u}_{1} &= \vec{a}_{1}, & \vec{e}_{1} = \frac{\vec{u}_{1}}{|\vec{u}_{1}|} \\
\vec{u}_{2} &= \vec{a}_{2} \, – \, \frac{\vec{a}_{1} \cdot \vec{a}_{2}}{|\vec{a}_{1}|^2} \vec{a}_{1}, & \vec{e}_{2} = \frac{\vec{u}_{2}}{|\vec{u}_{2}|} \\
\vec{u}_{3} &= \vec{a}_{3} \, – \, \frac{\vec{a}_{1} \cdot \vec{a}_{3}}{|\vec{a}_{1}|^2} \vec{a}_{1} \, – \, \frac{\vec{a}_{2} \cdot \vec{a}_{3}}{|\vec{a}_{2}|^2} \vec{a}_{2}, & \vec{e}_{3} = \frac{\vec{u}_{3}}{|\vec{u}_{3}|} \\
\vdots \\
\vec{u}_{n} &= \vec{a}_{n} \, – \, \sum_{k=1}^{n-1} \frac{\vec{a}_{k} \cdot \vec{a}_{n}}{|\vec{a}_{k}|^2} \vec{a}_{k}, & \vec{e}_{n} = \frac{\vec{u}_{n}}{|\vec{u}_{n}|}
\end{align}
$$