Pythonプログラミング(ステップ7・補足・ガンマ関数)
このページでは、統計分布などにしばしば登場するガンマ関数について説明する。
積分で定義されるガンマ関数
積分を使って「新しい」関数を定義する例はたくさんある。 例えば、正規分布$\frac{1}{\sqrt{2\pi}} e^{-t^2/2}$ を元に、その累積分布関数 $$ F(x) = \frac{1}{\sqrt{2\pi}} \int_{-\infty}^x e^{-t^2/2} dt $$ を定義できるが、$F(x)$をこれ以上簡単な数式で表すことはできない。
これは、積分範囲を「可動」にして、関数を生成する例であるが、 被積分関数の中に「可動」な変数を入れて、関数を生成することもできる。 その代表的な例がガンマ関数で、以下のように定義される: $$ \Gamma(z) = \int_0^{\infty} t^{z-1} e^{-t} dt $$ 被積分関数の中身の$z$が「可動」箇所で、$t$で積分しても、その部分の自由度が残るため、積分全体としては$z$の関数となる。
階乗の一般化
では、ガンマ関数の値を計算してみよう。 $z=1$の場合は $$ \Gamma(1) = \int_0^{\infty} e^{-t} dt = \left[ -e^{-t} \right]_0^{\infty} = 1 $$ $z=1/2$の場合は、$t = y^2$と変数変換すると $$ \Gamma(1/2) = \int_0^{\infty} t^{-1/2} e^{-t} dt = 2 \int_0^{\infty} e^{-y^2} dy = \sqrt{\pi} $$ と求まる。 その他の一般の $z$ に対しては値を求めることが難しい。
しかしながら、$z$が自然数の場合と、半整数の場合は、具体的な値を得ることができる。 まず、部分積分($e^{-t}$を積分、$t^{z}$を微分)すると、 $$ \begin{eqnarray} \Gamma(z+1) = & \int_0^{\infty} t^{z} e^{-t} dt = \left[ -t^z e^{-t} \right]_0^\infty + \int_0^{\infty} z \, t^{z-1} e^{-t} dt \\ & = z \int_0^{\infty} t^{z-1} e^{-t} dt = z \, \Gamma(z) \end{eqnarray} $$ という関係式(漸化式)が得られる。それを繰り返し適用すると、$n$が自然数の場合 $$ \Gamma(n+1) = n \Gamma(n-1) = n (n-1) \Gamma(n-2) = \cdots = n (n-1) (n-2) \cdots \Gamma(1) = n! $$ となる。すなわち $\Gamma(n+1)$ は $n!$ を与えることがわかった。 同様に、半整数に対しても $$ \Gamma\left( \frac{2n+1}{2}\right) = \frac{2n-1}{2} \frac{2n-3}{2} \cdots \Gamma(1/2) $$ となるので、具体的な値を得ることができる。
このように、ガンマ関数は、階乗を実数、さらには複素数に拡張したものと考えることができる。 すると、自然数$n$について $f(n+1)=n!$ となるような関数は他にあっても構わないように思われる。 事実、例えば $f(x) = \Gamma(x) + \sin(\pi x)$ のようにすれば、いくらでも「階乗もどき」の関数が作れるはずだ。
しかしながら、そのような関数のうち、$\Re(z) \gt 0$の領域で $\log f(z)$ が下に凸であるような複素解析関数は唯一ガンマ関数であることが証明されている (Bohr–Mollerup Theorem)。
ガンマ関数の近似:スターリングの式
統計学や物理学などでは、大きな$n$に対する階乗($n!$)の振る舞いを知る必要性が出てくる。 そこで、$n!$の良い見積を得るために、その積分表示である$\Gamma(n+1)$を使ってみよう。 積分変数を$t = zs$と変換すると $$ \begin{eqnarray} \Gamma(z+1) = \int_0^\infty t^z e^{-t} dt = \int_0^\infty (sz)^z e^{-sz} z ds\\ = z^{z+1} \int_0^\infty s^z e^{-sz} ds = z^{z+1} \int_0^\infty e^{z (\log(s) -s)} ds \end{eqnarray} $$
ここで、$e$の「上」の $z(\log(s)-s)$ は上に凸な形をしており、微分を取ると、$s=1$で最大になることがわかる。 これを、$s=1$の周りでテイラー展開すると $$ z(\log(s)-s) = -z - \frac{z}{2} (s-1)^2 + \cdots $$ である。そこで、 $$ \Gamma(z+1) = z^{z+1} e^{-z} \int_0^\infty e^{- \frac{z}{2} (s-1)^2 + \cdots} ds $$ と書き直して、$z$が正の大きな値の場合の積分の振る舞いを考えてみる。
$z$が十分大きければ、被積分関数は$s=1$の周りで鋭いピークを持つので、その値は以下の式でよく近似できるであろう: $$ \int_0^\infty e^{- \frac{z}{2} (s-1)^2 + \cdots} ds \approx \int_{-\infty}^\infty e^{- \frac{z}{2} (s-1)^2} ds = \sqrt{\frac{2\pi}{z}} $$ ピークの幅が鋭くなれば、「遠く」の様子は関係なくなるであろうから、積分範囲を$-\infty$まで広げても結果は変わらないはずである。 また、展開の高次の項も無視できるとした(本来ならば誤差の評価を行うべきであるが、ここでは割愛)。
$z$の値が大きくなると、指数関数の肩の$z (\log(s)-s)$は幅が狭まり鋭い頂点を持つようになるので、 頂点の辺りだけを放物線で表したとしても、よい近似が得られる。
ここまでをまとめると、スターリングの式として知られる $$ n! = \Gamma(n+1) \approx n^{n+1} e^{-n} \sqrt{\frac{2\pi}{n}} = n^{n+1/2} e^{-n} \sqrt{2\pi} $$ が得られた。
math.gamma( )
Pythonでは、ガンマ関数は数学ライブラリとして用意されており、積分を行わずとも、import math
しておけば、math.gamma( )
で呼び出すことができる。以下に、ガンマ関数の値とスターリングの近似値のそれぞれ対数を計算し、比較してプロットするコードの例を示す。
実行には、matplotlibとNumPyが必要。
# coding: utf-8 import matplotlib.pyplot as plt import numpy as np import math xdata = np.arange(1,10,0.01) n = xdata.shape[0] log_gamma = np.empty(n) log_stirling = np.empty(n) for i in range(n): x = xdata[i] log_gamma[i] = math.log(math.gamma(x+1)) log_stirling[i] = (x+1/2)*math.log(x) - x + 1/2*math.log(2*math.pi) plt.title("GAMMA FUNCTION") plt.plot(xdata,log_gamma, color=(1.0,0,0.0), linewidth=1.0) plt.plot(xdata,log_stirling, color=(0.0,0,1.0), linewidth=1.0) plt.xlim(0,10) plt.xlabel('X') plt.ylabel('LOG GAMMA(X)') plt.grid(True) plt.show()
赤がガンマ関数、青がスターリングの式。 スターリングの式は、$x$の値が比較的小さい領域でも、驚くべき正確さを発揮することがわかる。
ベータ関数
ガンマ関数の親戚で、統計学などでよく登場する二変数の関数にベータ関数がある。 ベータ関数はいろいろな定義(表現)が可能であるが、ひとまず、ガンマ関数との関係 $$ B(x,y) = \frac{\Gamma(x) \Gamma(y)}{\Gamma(x+y)} $$ を押さえておけば、Pythonのmathライブラリを使って、
math.gamma(x)*math.gamma(y)/math.gamma(x+y)
で計算が可能である。