概要¶
スコア関数¶
分布からランダムに得られた個のサンプルをとする。
の同時確率関数(尤度)ををと表す。
対数尤度の導関数
を スコア関数 (score function)という。
フィッシャー情報量¶
スコア関数の2乗の期待値
を フィッシャー情報量 という。
TODO: 導出を追う
と書けるらしいので(?)
Source
import numpy as np
np.random.seed(0)
n = 1000
alpha = 1.1 # 知りたいパラメータ
scale = 1 # 今回は既知とする
x = np.random.gamma(shape=alpha, scale=scale, size=n)
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=[4,2])
ax.hist(x, bins=30)
ax.set(xlabel="Value", ylabel="Frequency")
fig.show()
ガンマ分布のshapeパラメータは、のとき指数分布と一致する。
データが指数分布()に従っているのか、かガンマ分布()なのか検定する(ガンマ分布のscaleパラメータは既知とする)
最尤推定量について以下の仮説に対する検定を行う
最尤推定量を求める
from scipy.stats import gamma
def log_likelihood(alpha):
return np.sum(gamma.logpdf(x, a=alpha, scale=scale))
def neg_log_likelihood(alpha, scale, data):
# scipyはminimize_scalar しかないので、対数尤度ではなく負の対数尤度にする
return -np.sum(gamma.logpdf(data, a=alpha, scale=scale))
from scipy.optimize import minimize_scalar
result = minimize_scalar(neg_log_likelihood, args=(scale, x), bounds=(0.01, 10), method='bounded')
alpha_hat = result.x
# 最尤推定量
print(f"{alpha_hat=:.3f}")alpha_hat=1.075
という関係があるので、対数尤度の2階微分でフィッシャー情報量を求める
import numdifftools as nd
grad = nd.Derivative(log_likelihood, n=1)
hess = nd.Derivative(log_likelihood, n=2)
print(f"{grad(alpha_hat)=}, {hess(alpha_hat)=}")grad(alpha_hat)=array(0.0002217), hess(alpha_hat)=array(-1480.84211289)
なぜフィッシャー情報量で割るのか → 対数尤度のピークが鋭ければは大きな値をとるためらしい
Source
fig, axes = plt.subplots(figsize=[6, 6], nrows=4, sharex=True)
alphas = np.linspace(0.01, 3, 100)
axes[0].plot(alphas, [log_likelihood(a) for a in alphas])
axes[0].axvline(alpha_hat, color="darkorange")
axes[0].text(alpha_hat, log_likelihood(alpha_hat)*2, r" $\hat{\alpha}$", color="darkorange")
axes[0].set(ylabel=r"log likelihood $\ell(\alpha)$",
title=fr"log likelihood and derivatives (true $\alpha$ = {alpha})")
scores = [grad(a).tolist() for a in alphas]
axes[1].plot(alphas, scores)
axes[1].set(ylabel=r"$\ell'(\alpha)$")
infos = [hess(a).tolist() for a in alphas]
axes[2].plot(alphas, infos)
axes[2].set(ylabel=r"$\ell''(\alpha)$")
test_stats = [(abs(score)/np.sqrt(-info)) for score, info in zip(scores, infos)]
axes[3].plot(alphas, test_stats)
axes[3].set(xlabel=r"$\alpha$", ylabel=r"$\frac{|\ell'(\alpha)|}{ \sqrt{-\ell''(\alpha)}}$")
fig.show()/usr/local/lib/python3.10/site-packages/numpy/lib/nanfunctions.py:1563: RuntimeWarning: All-NaN slice encountered
return function_base._ureduce(a,
/usr/local/lib/python3.10/site-packages/numdifftools/limits.py:150: UserWarning: All-NaN slice encountered
warnings.warn(str(msg))

の値が大きいので標準正規分布に従っているのか不安だが、概ねそれっぽい値になってる
とする(とする)という帰無仮説のもとでの検定統計量を計算
# 検定統計量
alpha_0 = 1
I = -hess(alpha_0) # I = E[-l''(θ)]
score = abs(grad(alpha_0)) / np.sqrt(I)
print(f"|l'(α0)| / √I(α0) = {score:.3f}")|l'(α0)| / √I(α0) = 2.894
これは標準正規分布上では次のような値になる
Source
from scipy.stats import norm
fig, ax = plt.subplots(figsize=[4, 2])
# 検定統計量が従う標準正規分布
x_for_plot = np.linspace(-3, 3, 100)
z = norm.pdf(x_for_plot, loc=0, scale=1)
ax.plot(x_for_plot, z)
clevel = 0.05 / 2
for a in [clevel, (1 - clevel)]:
ppf = norm.ppf(a)
ax.axvline(x=ppf, color="steelblue", linestyle="--")
if ppf < 0:
ax.text(ppf - 0.1, norm.pdf(ppf) + 0.01, "$Z_{a/2}$", color="steelblue", horizontalalignment="right")
else:
ax.text(ppf + 0.1, norm.pdf(ppf) + 0.01, "$Z_{1-a/2}$", color="steelblue", horizontalalignment="left")
ax.set(xlabel="x", ylabel="density", title=f"standard normal distribution $N(\mu=0, \sigma=1)$")
# 検定統計量
ax.axvline(score, color="darkorange")
ax.text(score, 0.3, f"{score=:.3f}", color="darkorange", horizontalalignment="right")
fig.show()
from scipy.stats import norm
pvalue = 2 * (1 - norm.cdf(score))
pvalue0.0038054754122531786