メインコンテンツへスキップ

4.2.4 統計推論の基礎

最尤推定の尤度曲線図

統計推論 = データから規則を逆に推測する

前の節では、いろいろな確率分布を学びました。でも現実世界では、分布のパラメータは分かりません(たとえば、コインの表が出る確率がいくつか、など)。統計推論とは、観測したデータから分布のパラメータを逆に推測することです。

学習目標

  • 最尤推定(MLE)の直感を理解する——なぜ「確率を最大化する」のか
  • 最大事後推定(MAP)を理解する——事前知識を加える
  • 仮説検定と p 値を理解する(A/Bテストの考え方)
  • Python で MLE を実装する

推論の前に用語を読み解く

統計推論には短い略語が多く出てきます。孤立した定義として暗記するより、1 つの流れとして読むと理解しやすくなります。

用語正式名称 / 意味初学者向けの問い
MLEMaximum Likelihood Estimation、最尤推定観測データを最も起こりやすくするパラメータはどれか?
MAPMaximum A Posteriori、最大事後推定データと事前知識を合わせたとき、最も妥当なパラメータはどれか?
EMExpectation-Maximization、期待値最大化隠れた変数があるとき、どう仮定と更新を交互に行うか?
likelihood尤度そのパラメータが本当なら、このデータはどれくらい自然か?
log-likelihood対数尤度小さな確率の積を、数値的に安定した足し算として扱う方法
prior事前分布 / 事前確率現在のデータを見る前に、何を信じていたか?
posterior事後分布 / 事後確率データと事前知識を合わせた後、判断はどう更新されたか?
p-value帰無仮説のもとでの尾部確率本当に差がないなら、この結果はどれくらい珍しいか?
CIConfidence interval、信頼区間未知の値が入りそうな範囲はどこか?

大事な注意:p 値は 「帰無仮説が正しい確率」ではありません。帰無仮説が正しいと仮定したときに、現在と同じくらい極端な結果が出る確率です。

歴史的背景:MLE と EM はそれぞれどう生まれたのか?

この節には、特に知っておくとよい歴史的な節目が 2 つあります。

節目主要な著者最も重要に解決したこと
1922Maximum Likelihood EstimationRonald Fisher「観測データを最もよく説明するパラメータ」を体系化し、統計学習や損失関数の主流の重要な土台になった
1977EM AlgorithmDempster, Laird, Rubin「潜在変数や欠損情報がある」パラメータ推定問題に、安定した反復フレームワークを与えた

ここでとても大事な区別があります。

  • MLE は、より広い分野 / 原則
  • EM は、ある種の難しい場面で MLE を求めるための代表的な方法

なので、初めてこの節を学ぶ人がまず知るべきなのは次のことです。

MLE は「どのパラメータが本物らしいか」を答え、EM は「問題の中に見えない部分があるとき、どうやって少しずつそのパラメータに近づくか」を答えます。

なぜこの流れは、初学者にとって特に魅力的なのか?

それは、「データから規則を逆に推測する」ことが、まるで事件を解くように感じられるからです。

  • 真実を直接見ているわけではない
  • パラメータも誰も教えてくれない
  • でも、手元にはたくさんの観測の痕跡がある

すると、問題はこう変わります。

  • どんな説明が、この痕跡を一番うまくつなげられるのか?

MLE は「探偵っぽい」と感じさせ、
EM は「見えない箱の中を手探りで進む」ように感じさせます。
だから、統計推論を初めて真剣に学ぶ人は、突然こう思うことがあります。

なるほど、モデルの学習は単に式を計算するだけではなく、段階的に逆算していく作業なんだ。

なぜこの流れが、その後の統計学習でとても重要になったのか?

とても素朴な問いを、すごく分かりやすく説明しているからです。

  • 世界はパラメータをそのまま教えてくれない
  • だから、データから逆向きに推測するべき

MLE の一番魅力的なところは、まさに探偵の仕事に似ていることです。

  • 現場にはたくさんの痕跡が残っている
  • 真実は分からない
  • でも「どの説明が本当に起きたことに一番近いか?」は考えられる

そして EM は、こう言っているようなものです。

  • 現場の情報の一部が、どうしても見えないなら
  • それでも諦めず、まず 1 回仮定して、そこから何度も修正して近づいていこう

だから、この主線が初学者にとって魅力的なのは、

「データから規則を逆に推測する」ことが、段階的で、戦略があり、少しずつ近づけるプロセスとして見えるようになるからです。

まず、とても大事な学習イメージ

この節は、MLE / MAP / p 値 が出てきたところで、急に難しく感じやすいです。
でも、ここで大事なのは、統計推論を統計学の授業のように全部完璧に覚えることではなく、まず次のことを知ることです。

  • データが見えたとき、私たちは何を逆に推測したいのか
  • 「データを最もよく説明する」とは、数学的にどういう意味か
  • なぜこれらの考え方が、最終的に loss、正則化、A/Bテストにそのままつながるのか

まずは全体の地図を作る

前の 2 つの節では「確率をどう定義するか、分布はどんな形か」を学びました。
ここからは次の段階に進みます。

データを手に入れたとき、その背後にあるパラメータや結論をどう逆推測するのか?

統計推論のデータからパラメータへの図

この節で一番大事なのは、用語を覚えることより、まず次の 3 つをつかむことです。

  • MLE:どのパラメータがこのデータを一番よく説明するか
  • MAP:データに加えて、事前の常識も考える
  • 仮説検定:差が見えたとき、それが偶然かどうかを判断する

一、最尤推定(MLE)

直感:どのパラメータがデータを一番よく説明するか?

コインを 1 枚拾いました。公平かどうか分かりません。10 回投げたら、表表裏表表表裏表表表 でした(表 8 回、裏 2 回)。

質問:このコインが表になる確率 p は、どれくらいが最もありそうでしょうか?

直感的には、p ≈ 0.8 です。MLE はこの直感を数式にしたものです——観測されたデータが起こる確率を最大にするパラメータを見つけるのです。

初学者向けの、もっと覚えやすい例え

MLE はまず、「探偵が事件を再現する」作業だと思うと分かりやすいです。

  • すでにいくつかの手がかり(観測データ)を見ている
  • そこから、どんなパラメータ設定ならこの出来事が本当に起きたように見えるかを逆算する

つまり、MLE の中心は「最大化したいから最大化する」のではなく、こういうことです。

目の前のデータを最もよく説明できるパラメータを見つける。

コードで理解する

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

plt.rcParams['font.sans-serif'] = ['Arial Unicode MS']
plt.rcParams['axes.unicode_minus'] = False

# 観測データ:10 回投げて、表 8 回・裏 2 回
n_heads = 8
n_tails = 2
n_total = n_heads + n_tails

# さまざまな p について、このデータが起こる確率(尤度関数)を計算する
p_values = np.linspace(0.01, 0.99, 1000)

# 尤度関数:L(p) = C(n,k) * p^k * (1-p)^(n-k)
# p に依存しない C(n,k) は無視してよい
likelihood = p_values**n_heads * (1 - p_values)**n_tails

# MLE:尤度が最大の p
p_mle = p_values[np.argmax(likelihood)]
print(f"MLE 推定: p = {p_mle:.3f}")

# 可視化
plt.figure(figsize=(10, 5))
plt.plot(p_values, likelihood, color='steelblue', linewidth=2)
plt.axvline(x=p_mle, color='red', linestyle='--', linewidth=2, label=f'MLE: p = {p_mle:.2f}')
plt.fill_between(p_values, likelihood, alpha=0.1, color='steelblue')
plt.xlabel('p(表が出る確率)')
plt.ylabel('尤度 L(p)')
plt.title(f'尤度関数:硬貨を 10 回投げる、表 {n_heads} 回・裏 {n_tails} 回')
plt.legend(fontsize=12)
plt.grid(True, alpha=0.3)
plt.show()

期待される出力:

MLE 推定: p = 0.800

MLE の数学的な直感

MLE の答えは実はとてもシンプルです。p = 表の回数 / 総回数 = 8/10 = 0.8

ただし、MLE の価値は、これがどんな分布にも使える共通の考え方だという点にあります。
つまり、どんな分布でも、同じ発想でパラメータを探せます。

これが AI にとって特に重要な理由

多くの損失関数は、一見すると「最適化している」だけに見えます。
でも、もっと深い見方をすると、実際には次のことをしています。

  • パラメータの組を探す
  • その組が、学習データを一番よく説明するようにする

つまり、MLE は多くの学習目標の共通言語です。

データが増えるほど、推定はより正確になる

# 本当の p = 0.6
rng = np.random.default_rng(seed=42)
true_p = 0.6
n_experiments = [10, 50, 100, 500, 2000]

fig, axes = plt.subplots(1, len(n_experiments), figsize=(20, 4))

for ax, n in zip(axes, n_experiments):
# n 回コインを投げる
heads = rng.binomial(n, true_p)

# 尤度関数
p_vals = np.linspace(0.01, 0.99, 500)
ll = heads * np.log(p_vals) + (n - heads) * np.log(1 - p_vals)
ll = np.exp(ll - ll.max()) # 正規化

p_mle = heads / n
print(f"n={n:4d}, 表={heads:4d}, MLE={p_mle:.3f}")

ax.plot(p_vals, ll, color='steelblue', linewidth=2)
ax.axvline(x=true_p, color='green', linestyle='--', label=f'真の p={true_p}')
ax.axvline(x=p_mle, color='red', linestyle='--', label=f'MLE={p_mle:.3f}')
ax.set_title(f'n = {n}')
ax.set_xlabel('p')
ax.legend(fontsize=8)

plt.suptitle('データが多いほど、MLE はより正確で、より確実になる(曲線が細くなる)', fontsize=13)
plt.tight_layout()
plt.show()

seed=42 の場合の期待される出力:

n=  10, 表=   5, MLE=0.500
n= 50, 表= 31, MLE=0.620
n= 100, 表= 69, MLE=0.690
n= 500, 表= 318, MLE=0.636
n=2000, 表=1212, MLE=0.606

解釈:データが多いほど、尤度関数のピークは細くなり、真の値に近づきます。これが「ビッグデータ」の力です。


二、最大事後推定(MAP)

MLE の問題点

もしコインを 3 回だけ投げて、すべて表だったら、MLE は p = 3/3 = 1.0 と答えます。
つまり、「このコインは永遠に表が出る」と言っていることになります。

これは明らかに不自然です。私たちの常識では、普通のコインの p は 0.5 に近いはずです。

MAP:事前知識を加える

MAP は MLE に「事前知識(prior)」を加えます。つまり、パラメータについての前もっての信念です。

MAP = 尤度 × 事前分布

もっと覚えやすい言い方

MLE が

  • 目の前の証拠だけを見る

だとしたら、MAP はもっとこうです。

  • 目の前の証拠 + もともと持っている世界の常識

だから、AI の中のいろいろな現象を説明するのにとても向いています。

  • なぜ「パラメータを大きくしすぎない」ようにすると安定するのか
  • なぜ正則化は単なるテクニックではなく、ある種の事前仮定なのか
# データ:3 回すべて表
n, k = 3, 3

p_values = np.linspace(0.01, 0.99, 1000)

# 尤度関数
likelihood = p_values**k * (1 - p_values)**(n - k)

# 事前分布:p は 0.5 付近にあると考える(Beta 分布で表現)
prior = stats.beta.pdf(p_values, a=5, b=5) # 0.5 を中心とした事前分布

# 事後分布 ∝ 尤度 × 事前分布
posterior = likelihood * prior
posterior = posterior / np.trapezoid(posterior, p_values) # 正規化

# 最大値を探す
p_mle = p_values[np.argmax(likelihood)]
p_map = p_values[np.argmax(posterior)]

print(f"MLE: p = {p_mle:.3f}")
print(f"MAP: p = {p_map:.3f}")

# 可視化
fig, ax = plt.subplots(figsize=(10, 5))
ax.plot(p_values, likelihood / np.trapezoid(likelihood, p_values),
'--', color='coral', linewidth=2, label='尤度関数')
ax.plot(p_values, prior / np.trapezoid(prior, p_values),
'--', color='green', linewidth=2, label='事前分布')
ax.plot(p_values, posterior, color='steelblue', linewidth=2, label='事後分布')
ax.axvline(x=p_mle, color='coral', linestyle=':', alpha=0.7, label=f'MLE = {p_mle:.2f}')
ax.axvline(x=p_map, color='steelblue', linestyle=':', alpha=0.7, label=f'MAP = {p_map:.2f}')
ax.set_xlabel('p')
ax.set_ylabel('確率密度')
ax.set_title('MLE vs MAP(データが 3 回しかない場合)')
ax.legend()
ax.grid(True, alpha=0.3)
plt.show()

期待される出力:

MLE: p = 0.990
MAP: p = 0.637

解釈

  • MLE は p=1.0 に近い値を出す(少ないデータに引っ張られすぎる)
  • MAP は p≈0.64 を出す(データと事前分布の折衷)
  • データが増えると、MAP と MLE は同じ値に近づく

MLE vs MAP

MLEMAP
事前分布を使う?いいえはい
データが少ないとき過学習しやすいより安定
データが多いときMAP に近づくMLE に近づく
AI での対応通常の学習正則化(例:L2 正則化 = ガウス事前分布)
AI とのつながり

L2 正則化(weight decay とも呼ばれる)は、本質的には MAP です。これは、重みの事前分布が平均 0 の正規分布だと仮定し、重みが大きくなりすぎないようにします。これが、正則化が過学習を防げる理由です。


三、仮説検定と A/Bテスト

日常の場面

Web サイトのボタンの色を変えました(A 版は青、B 版は緑)。すると、B 版のクリック率が 2% 上がりました。

質問:この差は本当にあるのでしょうか? それとも単なるランダムな揺れでしょうか?

仮説検定の考え方

p 値の直感

p 値と帰無仮説分布の図解

p 値 = 真の差がないと仮定したときに、これと同じくらい大きい(またはそれ以上の)差が、偶然だけで出る確率。

  • p 値が小さい(たとえば 0.01)→ 「本当に差がないなら、こんな結果はほとんど起きない」→ 差は本物っぽい
  • p 値が大きい(たとえば 0.3)→ 「本当に差がなくても、こういう結果はよく起きる」→ ただの偶然かもしれない

言い方には注意が必要です。p 値は、対立仮説が正しいことを証明するものではありません。帰無仮説が正しいとしたときに、観測結果がどれくらい珍しいかを示すだけです。実際のプロダクトでは、サンプルサイズ、実験設計、ビジネス上の影響、同時に多数の検定をしていないかも確認する必要があります。

A/Bテストの実践

# A/Bテストをシミュレーションする
rng = np.random.default_rng(seed=2)

# A グループ:青いボタン、真のクリック率 10%
n_a = 1000
clicks_a = rng.binomial(n_a, 0.10)
rate_a = clicks_a / n_a

# B グループ:緑のボタン、真のクリック率 12%(本当に良い)
n_b = 1000
clicks_b = rng.binomial(n_b, 0.12)
rate_b = clicks_b / n_b

print(f"A グループのクリック率: {rate_a:.1%} ({clicks_a}/{n_a})")
print(f"B グループのクリック率: {rate_b:.1%} ({clicks_b}/{n_b})")
print(f"差: {rate_b - rate_a:.1%}")

# z 検定を使う
from scipy.stats import norm

# 合算比率
p_pool = (clicks_a + clicks_b) / (n_a + n_b)
# 標準誤差
se = np.sqrt(p_pool * (1 - p_pool) * (1/n_a + 1/n_b))
# z 統計量
z = (rate_b - rate_a) / se
# p 値(片側)
p_value = 1 - norm.cdf(z)

print(f"\nz 統計量: {z:.3f}")
print(f"p 値: {p_value:.4f}")

if p_value < 0.05:
print("→ p < 0.05 なので、差は有意です!B 版は本当に良いです。")
else:
print("→ p >= 0.05 なので、差は有意ではありません。偶然かもしれません。")

seed=2 の場合の期待される出力:

A グループのクリック率: 10.3% (103/1000)
B グループのクリック率: 12.9% (129/1000)
差: 2.6%

z 統計量: 1.816
p 値: 0.0347
→ p < 0.05 なので、差は有意です!B 版は本当に良いです。

シミュレーションで p 値を理解する

# シミュレーション:A と B に本当に差がない(どちらも 10%)なら、
# どれくらいの差が見えるのか?
rng = np.random.default_rng(seed=2)
n_simulations = 10000
simulated_diffs = []

for _ in range(n_simulations):
# 2 つのグループに同じ 10% の確率を使う
sim_a = rng.binomial(1000, 0.10) / 1000
sim_b = rng.binomial(1000, 0.10) / 1000
simulated_diffs.append(sim_b - sim_a)

simulated_diffs = np.array(simulated_diffs)

# 分布を描く
observed_diff = rate_b - rate_a

plt.figure(figsize=(10, 5))
plt.hist(simulated_diffs, bins=50, density=True, color='steelblue',
edgecolor='white', alpha=0.7, label='帰無仮説下の差の分布')
plt.axvline(x=observed_diff, color='red', linewidth=2, linestyle='--',
label=f'観測された差: {observed_diff:.3f}')

# p 値 = 赤線より右側の面積
p_sim = (simulated_diffs >= observed_diff).mean()
plt.fill_between(np.linspace(observed_diff, 0.08, 100),
0, 30, alpha=0.3, color='red', label=f'p 値 ≈ {p_sim:.4f}')

plt.xlabel('クリック率の差 (B - A)')
plt.ylabel('密度')
plt.title('p 値の直感:観測された差はどれくらい「珍しい」か?')
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()

数値でも確認したい場合は、次を追加してください。

print(f"シミュレーション p 値: {p_sim:.4f}")

seed=2 の場合の期待される出力:

シミュレーション p 値: 0.0262

四、MLE と損失関数の関係

MLE = 交差エントロピーを最小化すること

これはとても重要な関係です——分類問題では、尤度を最大化することは、交差エントロピー損失を最小化することと等価です。

# 二値分類問題
# モデル予測: p_hat = モデルがラベル 1 だと考える確率
# 正解ラベル: y ∈ {0, 1}

# 尤度関数
# L = ∏ p_hat^y * (1-p_hat)^(1-y)

# 対数を取る(対数尤度)
# log L = Σ [y * log(p_hat) + (1-y) * log(1-p_hat)]

# log L を最大化する = -log L を最小化する = 交差エントロピーを最小化する!

# 例
y_true = np.array([1, 0, 1, 1, 0])
p_pred = np.array([0.9, 0.2, 0.8, 0.7, 0.3])

# 交差エントロピー(手計算)
cross_entropy = -np.mean(
y_true * np.log(p_pred) + (1 - y_true) * np.log(1 - p_pred)
)
print(f"交差エントロピー損失: {cross_entropy:.4f}")

# 対数尤度(手計算)
log_likelihood = np.mean(
y_true * np.log(p_pred) + (1 - y_true) * np.log(1 - p_pred)
)
print(f"対数尤度: {log_likelihood:.4f}")
print(f"交差エントロピー = -対数尤度: {-log_likelihood:.4f}")

期待される出力:

交差エントロピー損失: 0.2530
対数尤度: -0.2530
交差エントロピー = -対数尤度: 0.2530
なぜこれが大事なのか?

PyTorch の nn.CrossEntropyLoss()nn.BCELoss() を見たとき、今なら分かるはずです——これらは本質的に最尤推定をしているのです。 損失関数は適当に作られているのではなく、確率論の深い基礎の上にあります。


ここまで学んだら、次の節で何を持っていくべきか?

この節を読んだあと、次に持っていくとよい問いは次の 3 つです。

  1. モデルが分布を予測するなら、「分布と分布の違い」はどう測るのか?
  2. なぜ交差エントロピーは、情報理論の概念にも、学習の損失にも見えるのか?
  3. なぜ KL 散逸は、VAE、RLHF、蒸留で何度も登場するのか?

この 3 つの問いは、自然に次へつながります。

次の内容とのつながり
  • 次の節:情報理論——交差エントロピーを別の角度から理解する
  • 第 5 ステーション:ロジスティック回帰の損失関数は交差エントロピー(MLE 由来)
  • 第 5 ステーション:正則化(L1/L2)の確率的な解釈は MAP
  • 第 6 ステーション:ニューラルネットの学習 = 損失関数を最小化する = MLE/MAP を行う

まとめ

概念直感公式/コード
MLEデータを一番よく説明するパラメータを見つける尤度関数を最大化する
MAPMLE + 事前知識尤度 × 事前分布を最大化する
p 値差がどれくらい「珍しい」か帰無仮説下でその差が起こる確率
A/Bテスト2 つのグループに本当の差があるか比べるscipy.stats
交差エントロピー交差エントロピーを最小化する = MLEnn.CrossEntropyLoss()

ハンズオン演習

演習 1:コイン投げの MLE

コインを 100 回投げて、表が 62 回出たとします。

  1. MLE で p を推定する
  2. 尤度関数を描く
  3. 事前分布が Beta(10, 10) のとき、MAP 推定はいくつか?

参考実装:

n = 100
k = 62
p_vals = np.linspace(0.01, 0.99, 1000)

likelihood = p_vals**k * (1 - p_vals)**(n - k)
p_mle = p_vals[np.argmax(likelihood)]

prior = stats.beta.pdf(p_vals, 10, 10)
posterior = likelihood * prior
posterior = posterior / np.trapezoid(posterior, p_vals)
p_map = p_vals[np.argmax(posterior)]

print(f"MLE 推定: {p_mle:.3f}")
print(f"Beta(10, 10) 事前分布での MAP 推定: {p_map:.3f}")

期待される出力:

MLE 推定: 0.620
Beta(10, 10) 事前分布での MAP 推定: 0.602

演習 2:A/Bテスト

A/Bテストをシミュレーションしてください:A グループ(n=500)の真のコンバージョン率は 8%、B グループ(n=500)の真のコンバージョン率も 8%(差はない)。これを 1000 回実行し、p 値が 0.05 未満になる回数を数えてください(これが「偽陽性率」です。理論上は約 5% のはずです)。

参考実装:

rng = np.random.default_rng(seed=42)
false_positives = 0
n_runs = 1000

for _ in range(n_runs):
clicks_a = rng.binomial(500, 0.08)
clicks_b = rng.binomial(500, 0.08)
rate_a = clicks_a / 500
rate_b = clicks_b / 500

p_pool = (clicks_a + clicks_b) / 1000
se = np.sqrt(p_pool * (1 - p_pool) * (1/500 + 1/500))
if se == 0:
continue

z = (rate_b - rate_a) / se
p_value = 2 * (1 - norm.cdf(abs(z))) # 両側検定
false_positives += p_value < 0.05

print(f"偽陽性率: {false_positives / n_runs:.1%} ({false_positives}/{n_runs})")

seed=42 の場合の期待される出力:

偽陽性率: 3.9% (39/1000)

演習 3:正規分布の MLE 推定

N(5, 2) から 200 個のサンプルを生成し、MLE で平均と標準偏差を推定してください(正規分布の MLE:平均 = 標本平均、標準偏差 = 標本標準偏差)。真の値と比較してみましょう。

参考実装:

rng = np.random.default_rng(seed=42)
samples = rng.normal(5, 2, 200)

mu_hat = samples.mean()
sigma_hat = np.sqrt(((samples - mu_hat) ** 2).mean())

print(f"推定平均: {mu_hat:.3f}(真の平均: 5)")
print(f"推定標準偏差: {sigma_hat:.3f}(真の標準偏差: 2)")

期待される出力:

推定平均: 4.939(真の平均: 5)
推定標準偏差: 1.759(真の標準偏差: 2)