定理
n 個の確率変数 X1,⋯,Xn が
- 互いに独立している
- 平均 μ、分散 σ2 の同一の確率分布(正規分布でなくて良い)に従う
とき、これらの平均
ˉX=X1+⋯+Xn=1nn∑i=1Xnを新しい確率変数とみなすと、n を無限大に発散させたとき、ˉX の確率分布は平均 μ、分散 σ2n の正規分布に収束する。
言い換えると、標本数 n が十分に大きい場合、母集団の分布がどんな形であっても、標本平均 ˉX の分布は正規分布へと近づいていく。
証明
平均・分散の証明
ˉX の平均 E(ˉX)=μ、分散 V(ˉX)=σ2n であることは、一般的な期待値と分散の性質
E(aX)=aE(X) E(X+Y)=E(X)+E(Y) V(aX)=a2V(X) V(X+Y)=V(X)+V(Y)+2Cov(X,Y)(ここで Cov は共分散であり、X,Y が互いに独立ならゼロ)
を用いて、以下のように計算して示せる。
E(ˉX)=E(1nn∑i=1Xi)=1nn∑i=1E(Xi)=1nn∑i=1μ=1nnμ=μ V(ˉX)=V(1nn∑i=1Xi)=1n2n∑i=1V(Xi)=1n2n∑i=1σ2=1n2nσ2=σ2n正規分布に従うことの証明
ˉX が正規分布に従う ⟺ ˉX を標準化した確率変数 Z が標準正規分布に従う
なので、n→∞ のときに Z が標準正規分布に従うことを証明すれば良い。
それには、モーメント母関数(積率母関数)が標準正規分布のものに収束することを示せば十分。
前節で計算した平均値と分散の値を用いて ˉX を標準化すると、
Z≡ˉX−μσ/√n=√nσ(1nn∑i=1Xi−μ)=1σ√n(n∑i=1Xi−nμ)=1σ√n(n∑i=1(Xi−μ))ここで Xi を標準化した確率変数
Zi≡Xi−μσを導入すれば、
Z=1√nn∑i=1ZiZ のモーメント母関数は
MZ(t)=E(exp(tZ))=E(exp(t√nn∑i=1Zi))=E(n∏i=1exp(t√nZi))=n∏i=1E(exp(t√nZi))=n∏i=1MZi(t√n)よって、Z のモーメント母関数は各変数 Z1,⋯,Zn のモーメント母関数の積で表現できる。
Z1,⋯,Zn は全て同じ確率分布に従うから、モーメント母関数も等しく、
MZ(t)=(MZk(t√n))nとなる(k は 1〜n のどれでも良い)。
MZk(t√n) をマクローリン展開すると、
MZk(t√n)=MXk(0)+M′Zk(0)1!(t√n)+M″Zk(0)2!(t√n)2+M‴Zk(0)3!(t√n)3+⋯ここで、
MZk(t√n)=∫∞−∞exp(tZk√n)f(Zk)dZkであるから、
MZk(0)=∫∞−∞f(Zk)dZk=1(任意の確率密度関数の全区間積分は1)
また、Zk は標準化されており、モーメント母関数の性質と分散・期待値の公式から、
M′Zk(0)=E(Zk)=0 M″Zk(0)=E(Z2k)=V(Zk)+E(Zk)2=1+02=1これらをマクローリン展開の式に代入し、t√n の3次以上の式を O((t√n)3) でまとめて表すと、
MZk(t√n)=1+t22n+O((t√n)3)これを MZ(t) の式に代入すれば、
MZ(t)=(MZk(t√n))n=(1+t22n+O((t√n)3))n⟶exp(t22)(n→∞)最後の式は標準正規分布のモーメント母関数に一致する(証明終了)。
実験
- 色々な確率分布から n 件の標本を繰り返し抽出して、標本平均の度数分布表を描画
- 標本数 n を大きくしていき、中心極限定理が成り立つ(= 標本数 n が大きいほど標本平均の分布が正規分布に近づく)ことを確認
一様分布
ベータ分布
α=9,β=3 の場合:
α=2,β=2 の場合:
カイ二乗分布
自由度 k=2 の場合:
自由度 k=5 の場合:
一次関数に従う分布
ここでは、0≤x≤a の範囲で一次関数 f(x)=cx 型の分布に従う母集団を考え、a=1 の場合を描画した。
確率密度関数 f(x) の全区間積分(直角三角形の面積)は1になる必要があるので、三角形の高さ(x=a における f(x) の値)は 2a
したがって、この確率密度関数(直角三角形の斜辺)の傾き c=2a2 となるから、
f(x)={2a2x(0≤x≤a)0(x<0,a<x)母集団の期待値 μ は
μ=E(x)=∫a0xf(x)dx=2a2∫a0x2dx=2a2[x33]a0=23a同様に
E(x2)=∫a0x2f(x)dx=2a2∫a0x3dx=2a2[x44]a0=12a2であるから、母集団の分散 σ2 は
σ2=V(x)=E(x2)−E(x)2=12a2−49a2=118a2二次関数に従う分布
ここでは、−a≤x≤a の範囲で二次関数 f(x)=cx2 型の分布に従う母集団を考え、a=1 の場合を描画した。
確率密度関数 f(x) の全区間積分は1になる必要があるので、
1=∫a−af(x)dx=c∫a−ax2dx=c[x33]a−a=23ca3よって分布関数の係数 c=32a3 となるから、
f(x)={32a3x2(−a≤x≤a)0(x<−a,a<x)母集団の期待値 μ は
μ=E(x)=∫a−axf(x)dx=32a3∫a−ax3dx=32a3[x44]a−a=38a3(a4−a4)=0同様に
E(x2)=∫a−ax2f(x)dx=32a3∫a−ax4dx=32a3[x55]a−a=310a3(a5−(−a5))=35a2であるから、母集団の分散 σ2 は
σ2=V(x)=E(x2)−E(x)2=35a2−02=35a2Appendix: 実験に使った Python コード
import numpy as np | |
import math | |
from matplotlib import pyplot as plt | |
from abc import ABC, abstractmethod | |
def ln_factorial(n): | |
"""階乗の log を計算するための補助関数""" | |
ret = 0 | |
for i in range(1, n+1): | |
ret += np.log(i) | |
return ret | |
def gauss(x, mu, sigma): | |
"""x を引数として平均 mu, 標準偏差 sigma の正規分布の確率密度関数を計算""" | |
ret = np.exp(- (x-mu)**2 * 0.5 / sigma**2) | |
ret /= np.sqrt(2.0 * np.pi) * sigma | |
return ret | |
class Distribution(ABC): | |
"""一般的な確率分布を表すクラス""" | |
@abstractmethod | |
def get_name(self): | |
pass | |
@abstractmethod | |
def calc_dist_func_xy(self): | |
"""確率密度関数描画のための x, y を返す""" | |
pass | |
@abstractmethod | |
def get_average_of_samples(self, n): | |
"""n 件の標本をランダムに抽出し、平均値を返す""" | |
pass | |
def get_mu(self): | |
return self.__mu | |
def get_sigma(self): | |
return self.__sigma | |
def set_mu(self, mu): | |
self.__mu = mu | |
def set_sigma(self, sigma): | |
self.__sigma = sigma | |
class UniformDist(Distribution): | |
"""一様分布""" | |
def __init__(self, a, b): | |
self.set_mu((a + b) * 0.5) | |
variance = (b-a)**2/12.0 | |
self.set_sigma(np.sqrt(variance)) | |
self.__a = a | |
self.__b = b | |
def get_name(self): | |
return 'Uniform Distribution (a=,b=)'.format(self.__a, self.__b) | |
def calc_dist_func_xy(self): | |
w = self.__b - self.__a | |
x = np.linspace(self.__a-w, self.__b+w, 300) | |
y = np.where((self.__a <= x) & (x <= self.__b), 1.0/w, 0) | |
return x, y | |
def get_average_of_samples(self, n): | |
samples = np.random.rand(n) * (self.__b - self.__a) + self.__a | |
return samples.mean() | |
class BetaDist(Distribution): | |
"""ベータ分布""" | |
def __init__(self, a, b): | |
self.set_mu(a/(a+b)) | |
variance = a*b/(a+b+1)/(a+b)**2 | |
self.set_sigma(np.sqrt(variance)) | |
self.__a = a | |
self.__b = b | |
def get_name(self): | |
return r'Beta Distribution (α=,β=)'.format(self.__a, self.__b) | |
def calc_dist_func_xy(self): | |
def beta(p): | |
ret = np.zeros(p.shape) | |
for i in range(len(p)): | |
if 0 < p[i] < 1: | |
log_beta = (self.__a-1) * np.log(p[i]) + (self.__b-1) * np.log(1-p[i]) + ln_factorial(self.__a+self.__b-1) - ln_factorial(self.__a-1) - ln_factorial(self.__b-1) | |
ret[i] = np.exp(log_beta) | |
return ret | |
x = np.linspace(0, 1.0, 100) | |
y = beta(x) | |
return x, y | |
def get_average_of_samples(self, n): | |
samples = np.random.beta(self.__a, self.__b, n) | |
return samples.mean() | |
class ChiSquaredDist(Distribution): | |
"""カイ二乗分布""" | |
def __init__(self, k): | |
self.set_mu(k) | |
variance = 2 * k | |
self.set_sigma(np.sqrt(variance)) | |
self.__k = k | |
def get_name(self): | |
return r'χ2 Distribution (k=)'.format(self.__k) | |
def calc_dist_func_xy(self): | |
x = np.linspace(0, 20.0, 100) | |
y = np.exp(-x*0.5) / (2**(self.__k*0.5)) / math.gamma(self.__k*0.5) * np.power(x, self.__k*0.5-1) | |
return x, y | |
def get_average_of_samples(self, n): | |
samples = np.random.chisquare(self.__k, n) | |
return samples.mean() | |
class MyLinearDist(Distribution): | |
"""一次関数型の分布""" | |
def __init__(self, a): | |
self.set_mu(a * 2.0 / 3.0) | |
variance = a * a / 18.0 | |
self.set_sigma(np.sqrt(variance)) | |
self.__x_max = a | |
self.__c = 2.0 / (a*a) # 直線の傾き | |
self.__y_max = 2.0 / a # 分布内の y の最大値 | |
def get_name(self): | |
return 'Self-Made Linear Distribution' | |
def calc_dist_func_xy(self): | |
x = np.linspace(0-self.__x_max*0.5, self.__x_max*1.5, 100) | |
y = np.where((0 <= x) & (x <= self.__x_max), self.__c * x, 0) | |
return x, y | |
def get_average_of_samples(self, n): | |
samples = [] | |
while len(samples) < n: | |
x_tmp = np.random.rand() * self.__x_max | |
y_tmp = np.random.rand() * self.__y_max | |
if y_tmp <= self.__c * x_tmp: | |
samples.append(x_tmp) | |
return np.mean(samples) | |
class MyQuadraticDist(Distribution): | |
"""二次関数型の分布""" | |
def __init__(self, a): | |
self.set_mu(0) | |
variance = a * a * 3.0 / 5.0 | |
self.set_sigma(np.sqrt(variance)) | |
self.__x_max = a | |
self.__c = 3.0 / (2.0*a*a*a) # 二次関数の係数 | |
self.__y_max = 3.0 / (2.0*a) # 分布内の y の最大値 | |
def get_name(self): | |
return 'Self-Made Quadratic Distribution' | |
def calc_dist_func_xy(self): | |
x = np.linspace(-self.__x_max*1.5, self.__x_max*1.5, 100) | |
y = np.where((-self.__x_max <= x) & (x <= self.__x_max), x*x*self.__c, 0) | |
return x, y | |
def get_average_of_samples(self, n): | |
samples = [] | |
while len(samples) < n: | |
x_tmp = (np.random.rand()*2-1.0) * self.__x_max | |
y_tmp = np.random.rand() * self.__y_max | |
if y_tmp <= self.__c * x_tmp * x_tmp: | |
samples.append(x_tmp) | |
return np.mean(samples) | |
def func(dist): | |
""" | |
与えられた確率分布について色々な標本サイズで標本平均を繰り返し計算し、 | |
標本平均の分布が正規分布に近づくことをグラフで確認する | |
""" | |
T = 100000 # n個の標本を抽出して平均を取る操作を何度繰り返すか | |
n_samples = [2, 4, 8, 32, 128] # 標本サイズ | |
plt.figure(figsize=(10, 6)) | |
plt.subplots_adjust(wspace=0.3, hspace=0.4) | |
# 母集団の確率密度関数を描画 | |
plt.subplot(2, 3, 1) | |
x, y = dist.calc_dist_func_xy() | |
plt.title('Distribution Function') | |
plt.grid() | |
plt.fill_between(x, np.zeros(x.size), y, facecolor='red', alpha=0.3) | |
plt.plot(x, y, color='black') | |
# 標本を繰り返し抽出して平均値を記録・ヒストグラム描画 | |
for i in range(len(n_samples)): | |
n = n_samples[i] | |
sample_mean = np.empty(T, dtype='float') | |
for t in range(T): | |
sample_mean[t] = dist.get_average_of_samples(n) | |
# ヒストグラム描画のための階級幅を計算 | |
mean_max = sample_mean.max() | |
mean_min = sample_mean.min() | |
bins = np.linspace(mean_min, mean_max, 50) | |
# ヒストグラムを描画 | |
plt.subplot(2, 3, i+2) | |
plt.hist(sample_mean, bins=bins, density=True) | |
# 中心極限定理により収束が期待される正規分布を描画 | |
mu = dist.get_mu() | |
sigma = dist.get_sigma() / np.sqrt(n) | |
x_norm = np.linspace(mean_min-(mean_max-mean_min)*0.2, mean_max+(mean_max-mean_min)*0.2, 100) | |
norm = gauss(x_norm, mu=mu, sigma=sigma) | |
plt.title(r'nsample='.format(n)) | |
plt.plot(x_norm, norm, lw=1.0, color='red', label=r'N(μ,σ2/)'.format(n)) | |
plt.legend() | |
plt.suptitle('Sample Mean of ' + dist.get_name()) | |
plt.show() | |
func(UniformDist(0, 6.0)) | |
func(BetaDist(9, 3)) | |
func(BetaDist(2, 2)) | |
func(ChiSquaredDist(2)) | |
func(ChiSquaredDist(5)) | |
func(MyLinearDist(1.0)) | |
func(MyQuadraticDist(1.0)) |