Loading [MathJax]/jax/output/CommonHTML/jax.js

定理

n 個の確率変数 X1,,Xn

  • 互いに独立している
  • 平均 μ、分散 σ2 の同一の確率分布(正規分布でなくて良い)に従う

とき、これらの平均

ˉX=X1++Xn=1nni=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(1nni=1Xi)=1nni=1E(Xi)=1nni=1μ=1nnμ=μ V(ˉX)=V(1nni=1Xi)=1n2ni=1V(Xi)=1n2ni=1σ2=1n2nσ2=σ2n

正規分布に従うことの証明

ˉX が正規分布に従う ˉX を標準化した確率変数 Z が標準正規分布に従う

なので、n のときに Z が標準正規分布に従うことを証明すれば良い。
それには、モーメント母関数(積率母関数)が標準正規分布のものに収束することを示せば十分。

前節で計算した平均値と分散の値を用いて ˉX を標準化すると、

ZˉXμσ/n=nσ(1nni=1Xiμ)=1σn(ni=1Xinμ)=1σn(ni=1(Xiμ))

ここで Xi を標準化した確率変数

ZiXiμσ

を導入すれば、

Z=1nni=1Zi

Z のモーメント母関数は

MZ(t)=E(exp(tZ))=E(exp(tnni=1Zi))=E(ni=1exp(tnZi))=ni=1E(exp(tnZi))=ni=1MZi(tn)

よって、Z のモーメント母関数は各変数 Z1,,Zn のモーメント母関数の積で表現できる。

Z1,,Zn は全て同じ確率分布に従うから、モーメント母関数も等しく、

MZ(t)=(MZk(tn))n

となる(k は 1〜n のどれでも良い)。

MZk(tn) をマクローリン展開すると、

MZk(tn)=MXk(0)+MZk(0)1!(tn)+MZk(0)2!(tn)2+MZk(0)3!(tn)3+

ここで、

MZk(tn)=exp(tZkn)f(Zk)dZk

であるから、

MZk(0)=f(Zk)dZk=1

(任意の確率密度関数の全区間積分は1)

また、Zk は標準化されており、モーメント母関数の性質と分散・期待値の公式から、

MZk(0)=E(Zk)=0 MZk(0)=E(Z2k)=V(Zk)+E(Zk)2=1+02=1

これらをマクローリン展開の式に代入し、tn の3次以上の式を O((tn)3) でまとめて表すと、

MZk(tn)=1+t22n+O((tn)3)

これを MZ(t) の式に代入すれば、

MZ(t)=(MZk(tn))n=(1+t22n+O((tn)3))nexp(t22)(n)

最後の式は標準正規分布のモーメント母関数に一致する(証明終了)。

実験

  1. 色々な確率分布から n 件の標本を繰り返し抽出して、標本平均の度数分布表を描画
  2. 標本数 n を大きくしていき、中心極限定理が成り立つ(= 標本数 n が大きいほど標本平均の分布が正規分布に近づく)ことを確認

一様分布

UniformDist

ベータ分布

α=9,β=3 の場合:

BetaDist-1

α=2,β=2 の場合:

BetaDist-2

カイ二乗分布

自由度 k=2 の場合:

ChiSquaredDist-1

自由度 k=5 の場合:

ChiSquaredDist-2

一次関数に従う分布

LinearDist

ここでは、0xa の範囲で一次関数 f(x)=cx 型の分布に従う母集団を考え、a=1 の場合を描画した。

確率密度関数 f(x) の全区間積分(直角三角形の面積)は1になる必要があるので、三角形の高さ(x=a における f(x) の値)は 2a

したがって、この確率密度関数(直角三角形の斜辺)の傾き c=2a2 となるから、

f(x)={2a2x(0xa)0(x<0,a<x)

母集団の期待値 μ

μ=E(x)=a0xf(x)dx=2a2a0x2dx=2a2[x33]a0=23a

同様に

E(x2)=a0x2f(x)dx=2a2a0x3dx=2a2[x44]a0=12a2

であるから、母集団の分散 σ2

σ2=V(x)=E(x2)E(x)2=12a249a2=118a2

二次関数に従う分布

QuadraticDist

ここでは、axa の範囲で二次関数 f(x)=cx2 型の分布に従う母集団を考え、a=1 の場合を描画した。

確率密度関数 f(x) の全区間積分は1になる必要があるので、

1=aaf(x)dx=caax2dx=c[x33]aa=23ca3

よって分布関数の係数 c=32a3 となるから、

f(x)={32a3x2(axa)0(x<a,a<x)

母集団の期待値 μ

μ=E(x)=aaxf(x)dx=32a3aax3dx=32a3[x44]aa=38a3(a4a4)=0

同様に

E(x2)=aax2f(x)dx=32a3aax4dx=32a3[x55]aa=310a3(a5(a5))=35a2

であるから、母集団の分散 σ2

σ2=V(x)=E(x2)E(x)2=35a202=35a2

Appendix: 実験に使った 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))