🌐 JP | 🇬🇧 EN | Last sync: 2025-11-16

第1章:生成モデルの基礎

判別モデルとの違い、確率分布の学習、サンプリング手法の理解

📖 読了時間: 25-30分 📊 難易度: 初級〜中級 💻 コード例: 10個 📝 演習問題: 5問

学習目標

この章を読むことで、以下を習得できます:


1.1 判別モデル vs 生成モデル

機械学習における2つのアプローチ

機械学習モデルは、データとの関わり方により大きく2つに分類されます:

「判別モデルは入力から出力へのマッピングを学習し、生成モデルはデータそのものの確率分布を学習する。」

判別モデル(Discriminative Model)

目的:条件付き確率 $P(y|x)$ を学習する

生成モデル(Generative Model)

目的:同時確率 $P(x, y)$ または $P(x)$ を学習する

特徴 判別モデル 生成モデル
学習対象 $P(y|x)$(条件付き確率) $P(x)$ または $P(x,y)$(同時確率)
主な用途 分類、回帰 データ生成、密度推定
決定境界 直接学習 確率分布から導出
データ生成 不可能 可能
計算コスト 比較的低い 高い(分布全体をモデル化)
graph LR subgraph "判別モデル" A1[入力 x] --> B1[モデル f] B1 --> C1[出力 y] D1[学習: P(y|x)] end subgraph "生成モデル" A2[確率分布 P(x)] --> B2[サンプリング] B2 --> C2[生成データ x'] D2[学習: P(x)] end style A1 fill:#e3f2fd style B1 fill:#fff3e0 style C1 fill:#ffebee style A2 fill:#e3f2fd style B2 fill:#fff3e0 style C2 fill:#ffebee

ベイズの定理による関係

判別モデルと生成モデルは、ベイズの定理で結びつきます:

$$ P(y|x) = \frac{P(x|y)P(y)}{P(x)} $$

ここで:

重要: 生成モデルは $P(x|y)$ と $P(y)$ を学習することで、ベイズの定理を通じて分類タスクにも応用できます。

import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_blobs
from sklearn.linear_model import LogisticRegression
from sklearn.naive_bayes import GaussianNB

# データ生成:2クラス分類
np.random.seed(42)
X, y = make_blobs(n_samples=300, centers=2, n_features=2,
                  center_box=(-5, 5), random_state=42)

print("=== 判別モデル vs 生成モデル ===\n")

# 判別モデル:ロジスティック回帰(P(y|x)を直接学習)
discriminative = LogisticRegression()
discriminative.fit(X, y)

# 生成モデル:ガウスナイーブベイズ(P(x|y)とP(y)を学習)
generative = GaussianNB()
generative.fit(X, y)

# テストデータ
X_test = np.array([[2.0, 3.0], [-3.0, -2.0]])

# 予測
disc_pred = discriminative.predict(X_test)
gen_pred = generative.predict(X_test)
disc_proba = discriminative.predict_proba(X_test)
gen_proba = generative.predict_proba(X_test)

print("テストサンプル:")
for i, x in enumerate(X_test):
    print(f"\nサンプル {i+1}: {x}")
    print(f"  判別モデル予測: クラス {disc_pred[i]}, "
          f"確率 [クラス0: {disc_proba[i,0]:.3f}, クラス1: {disc_proba[i,1]:.3f}]")
    print(f"  生成モデル予測: クラス {gen_pred[i]}, "
          f"確率 [クラス0: {gen_proba[i,0]:.3f}, クラス1: {gen_proba[i,1]:.3f}]")

print("\n特徴の比較:")
print("  判別モデル:")
print("    - 決定境界を直接学習")
print("    - 計算効率が良い")
print("    - 新しいデータ生成は不可能")
print("\n  生成モデル:")
print("    - 各クラスの確率分布を学習")
print("    - データ生成が可能")
print("    - 分布の仮定が必要(例:ガウス分布)")

# 可視化
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))

# メッシュグリッドの作成
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx, yy = np.meshgrid(np.linspace(x_min, x_max, 200),
                     np.linspace(y_min, y_max, 200))

# 判別モデルの決定境界
Z_disc = discriminative.predict(np.c_[xx.ravel(), yy.ravel()])
Z_disc = Z_disc.reshape(xx.shape)
ax1.contourf(xx, yy, Z_disc, alpha=0.3, cmap='RdYlBu')
ax1.scatter(X[:, 0], X[:, 1], c=y, cmap='RdYlBu', edgecolor='black')
ax1.set_title('判別モデル(ロジスティック回帰)\nP(y|x)を直接学習')
ax1.set_xlabel('特徴量 1')
ax1.set_ylabel('特徴量 2')

# 生成モデルの決定境界
Z_gen = generative.predict(np.c_[xx.ravel(), yy.ravel()])
Z_gen = Z_gen.reshape(xx.shape)
ax2.contourf(xx, yy, Z_gen, alpha=0.3, cmap='RdYlBu')
ax2.scatter(X[:, 0], X[:, 1], c=y, cmap='RdYlBu', edgecolor='black')
ax2.set_title('生成モデル(ガウスナイーブベイズ)\nP(x|y)とP(y)を学習')
ax2.set_xlabel('特徴量 1')
ax2.set_ylabel('特徴量 2')

plt.tight_layout()
print("\n決定境界の可視化を生成しました")

出力例

=== 判別モデル vs 生成モデル ===

テストサンプル:

サンプル 1: [ 2.  3.]
  判別モデル予測: クラス 1, 確率 [クラス0: 0.234, クラス1: 0.766]
  生成モデル予測: クラス 1, 確率 [クラス0: 0.198, クラス1: 0.802]

サンプル 2: [-3. -2.]
  判別モデル予測: クラス 0, 確率 [クラス0: 0.891, クラス1: 0.109]
  生成モデル予測: クラス 0, 確率 [クラス0: 0.923, クラス1: 0.077]

特徴の比較:
  判別モデル:
    - 決定境界を直接学習
    - 計算効率が良い
    - 新しいデータ生成は不可能

  生成モデル:
    - 各クラスの確率分布を学習
    - データ生成が可能
    - 分布の仮定が必要(例:ガウス分布)

決定境界の可視化を生成しました

1.2 確率分布の学習

尤度関数と最尤推定

生成モデルの核心は、データの確率分布 $P(x; \theta)$ をパラメータ $\theta$ で表現し、学習することです。

尤度関数(Likelihood Function)

与えられたデータ $\mathcal{D} = \{x_1, x_2, \ldots, x_N\}$ に対する尤度は:

$$ L(\theta) = P(\mathcal{D}; \theta) = \prod_{i=1}^{N} P(x_i; \theta) $$

データが独立同分布(i.i.d.)であると仮定すると、各サンプルの確率の積になります。

対数尤度(Log-Likelihood)

計算の安定性と数値的な扱いやすさのため、通常は対数をとります:

$$ \log L(\theta) = \sum_{i=1}^{N} \log P(x_i; \theta) $$

最尤推定(Maximum Likelihood Estimation, MLE)

尤度を最大化するパラメータ $\theta$ を求めます:

$$ \hat{\theta}_{\text{MLE}} = \arg\max_{\theta} \log L(\theta) = \arg\max_{\theta} \sum_{i=1}^{N} \log P(x_i; \theta) $$

「最尤推定は、観測データが最も起こりやすくなるようなパラメータを選ぶ原理です。」

具体例:ガウス分布のパラメータ推定

データが1次元ガウス分布 $\mathcal{N}(\mu, \sigma^2)$ に従うと仮定します:

$$ P(x; \mu, \sigma^2) = \frac{1}{\sqrt{2\pi\sigma^2}} \exp\left(-\frac{(x-\mu)^2}{2\sigma^2}\right) $$

対数尤度:

$$ \log L(\mu, \sigma^2) = -\frac{N}{2}\log(2\pi) - \frac{N}{2}\log(\sigma^2) - \frac{1}{2\sigma^2}\sum_{i=1}^{N}(x_i - \mu)^2 $$

これを $\mu$ と $\sigma^2$ で微分してゼロとおくと:

$$ \begin{align} \hat{\mu}_{\text{MLE}} &= \frac{1}{N}\sum_{i=1}^{N} x_i \\ \hat{\sigma}^2_{\text{MLE}} &= \frac{1}{N}\sum_{i=1}^{N} (x_i - \hat{\mu})^2 \end{align} $$

つまり、標本平均と標本分散がMLEとなります。

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

# データ生成:真の分布 N(3, 2^2)
np.random.seed(42)
true_mu, true_sigma = 3.0, 2.0
N = 100
data = np.random.normal(true_mu, true_sigma, N)

print("=== 最尤推定(MLE)の実装 ===\n")

# 最尤推定
mle_mu = np.mean(data)
mle_sigma = np.std(data, ddof=0)  # ddof=0で標本分散

print(f"真のパラメータ:")
print(f"  平均 μ: {true_mu}")
print(f"  標準偏差 σ: {true_sigma}")

print(f"\n最尤推定値:")
print(f"  推定平均 μ̂: {mle_mu:.4f}")
print(f"  推定標準偏差 σ̂: {mle_sigma:.4f}")

print(f"\n推定誤差:")
print(f"  平均の誤差: {abs(mle_mu - true_mu):.4f}")
print(f"  標準偏差の誤差: {abs(mle_sigma - true_sigma):.4f}")

# 対数尤度の計算
def log_likelihood(data, mu, sigma):
    """ガウス分布の対数尤度"""
    N = len(data)
    log_prob = -0.5 * N * np.log(2 * np.pi) - N * np.log(sigma) \
               - 0.5 * np.sum((data - mu)**2) / (sigma**2)
    return log_prob

# MLEでの対数尤度
ll_mle = log_likelihood(data, mle_mu, mle_sigma)
print(f"\nMLEでの対数尤度: {ll_mle:.2f}")

# 他のパラメータでの対数尤度(比較)
ll_wrong1 = log_likelihood(data, true_mu + 1, true_sigma)
ll_wrong2 = log_likelihood(data, true_mu, true_sigma + 1)
print(f"μ=4, σ=2での対数尤度: {ll_wrong1:.2f} (MLEより低い)")
print(f"μ=3, σ=3での対数尤度: {ll_wrong2:.2f} (MLEより低い)")

# 可視化
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))

# 左図:データと推定分布
ax1.hist(data, bins=20, density=True, alpha=0.6, color='skyblue',
         edgecolor='black', label='データヒストグラム')
x_range = np.linspace(data.min() - 1, data.max() + 1, 200)
ax1.plot(x_range, norm.pdf(x_range, true_mu, true_sigma),
         'r-', linewidth=2, label=f'真の分布 N({true_mu}, {true_sigma}²)')
ax1.plot(x_range, norm.pdf(x_range, mle_mu, mle_sigma),
         'g--', linewidth=2, label=f'推定分布 N({mle_mu:.2f}, {mle_sigma:.2f}²)')
ax1.set_xlabel('x')
ax1.set_ylabel('確率密度')
ax1.set_title('最尤推定によるガウス分布のフィッティング')
ax1.legend()
ax1.grid(True, alpha=0.3)

# 右図:対数尤度の等高線
mu_range = np.linspace(2, 4, 100)
sigma_range = np.linspace(1, 3, 100)
MU, SIGMA = np.meshgrid(mu_range, sigma_range)
LL = np.zeros_like(MU)

for i in range(len(mu_range)):
    for j in range(len(sigma_range)):
        LL[j, i] = log_likelihood(data, MU[j, i], SIGMA[j, i])

contour = ax2.contourf(MU, SIGMA, LL, levels=20, cmap='viridis')
ax2.plot(mle_mu, mle_sigma, 'r*', markersize=20, label='MLE')
ax2.plot(true_mu, true_sigma, 'wo', markersize=10, label='真のパラメータ')
ax2.set_xlabel('平均 μ')
ax2.set_ylabel('標準偏差 σ')
ax2.set_title('対数尤度の等高線図')
ax2.legend()
plt.colorbar(contour, ax=ax2, label='対数尤度')

plt.tight_layout()
print("\n可視化完了")

出力例

=== 最尤推定(MLE)の実装 ===

真のパラメータ:
  平均 μ: 3.0
  標準偏差 σ: 2.0

最尤推定値:
  推定平均 μ̂: 3.0234
  推定標準偏差 σ̂: 1.9876

推定誤差:
  平均の誤差: 0.0234
  標準偏差の誤差: 0.0124

MLEでの対数尤度: -218.34
μ=4, σ=2での対数尤度: -243.12 (MLEより低い)
μ=3, σ=3での対数尤度: -225.78 (MLEより低い)

可視化完了

ベイズの定理と事後分布

ベイズ推定では、パラメータ $\theta$ にも確率分布を仮定します:

$$ P(\theta | \mathcal{D}) = \frac{P(\mathcal{D} | \theta) P(\theta)}{P(\mathcal{D})} $$

ここで:

MLE vs ベイズ推定: MLEは点推定(1つの値)、ベイズは分布推定(不確実性を保持)。


1.3 サンプリング手法

なぜサンプリングが必要か

生成モデルでは、学習した確率分布 $P(x)$ から新しいサンプルを生成する必要があります。しかし、複雑な分布からの直接サンプリングは困難です。

サンプリングの課題

Rejection Sampling(棄却サンプリング)

基本アイデア:簡単な提案分布 $q(x)$ からサンプルし、確率的に棄却することで目的分布 $p(x)$ に従うサンプルを得る。

アルゴリズム

  1. 定数 $M$ を選び、全ての $x$ で $p(x) \leq M q(x)$ を満たすようにする
  2. 提案分布 $q(x)$ からサンプル $x$ を生成
  3. 一様分布 $u \sim U(0, 1)$ から $u$ を生成
  4. $u < \frac{p(x)}{M q(x)}$ なら $x$ を採用、そうでなければ棄却
  5. 必要なサンプル数が得られるまで繰り返す
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import norm, beta

# 目的分布: Beta(2, 5)
def target_dist(x):
    """目的分布 p(x) = Beta(2, 5)"""
    return beta.pdf(x, 2, 5)

# 提案分布: 一様分布 U(0, 1)
def proposal_dist(x):
    """提案分布 q(x) = U(0, 1)"""
    return np.ones_like(x)

# 定数 M: p(x) <= M * q(x) を満たす
x_test = np.linspace(0, 1, 1000)
M = np.max(target_dist(x_test) / proposal_dist(x_test))

print("=== Rejection Sampling ===\n")
print(f"定数 M: {M:.4f}")

# Rejection Samplingの実装
def rejection_sampling(n_samples, seed=42):
    """
    Rejection Samplingによるサンプル生成

    Parameters:
    -----------
    n_samples : int
        生成するサンプル数
    seed : int
        乱数シード

    Returns:
    --------
    samples : np.ndarray
        生成されたサンプル
    acceptance_rate : float
        採用率
    """
    np.random.seed(seed)
    samples = []
    n_trials = 0

    while len(samples) < n_samples:
        # 提案分布からサンプル(一様分布)
        x = np.random.uniform(0, 1)
        # 一様乱数
        u = np.random.uniform(0, 1)

        # 採用・棄却の判定
        if u < target_dist(x) / (M * proposal_dist(x)):
            samples.append(x)

        n_trials += 1

    acceptance_rate = n_samples / n_trials
    return np.array(samples), acceptance_rate

# サンプリング実行
n_samples = 1000
samples, acc_rate = rejection_sampling(n_samples)

print(f"\n生成サンプル数: {n_samples}")
print(f"総試行回数: {int(n_samples / acc_rate)}")
print(f"採用率: {acc_rate:.4f}")
print(f"\nサンプルの統計:")
print(f"  平均: {samples.mean():.4f} (理論値: {2/(2+5):.4f})")
print(f"  標準偏差: {samples.std():.4f}")

# 可視化
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))

# 左図:Rejection Samplingの仕組み
x_range = np.linspace(0, 1, 1000)
ax1.plot(x_range, target_dist(x_range), 'r-', linewidth=2, label='目的分布 p(x)')
ax1.plot(x_range, M * proposal_dist(x_range), 'b--', linewidth=2,
         label=f'M × 提案分布 (M={M:.2f})')
ax1.fill_between(x_range, 0, target_dist(x_range), alpha=0.3, color='red')
ax1.set_xlabel('x')
ax1.set_ylabel('確率密度')
ax1.set_title('Rejection Samplingの仕組み')
ax1.legend()
ax1.grid(True, alpha=0.3)

# 右図:生成されたサンプルの分布
ax2.hist(samples, bins=30, density=True, alpha=0.6, color='skyblue',
         edgecolor='black', label='生成サンプル')
ax2.plot(x_range, target_dist(x_range), 'r-', linewidth=2,
         label='目的分布 Beta(2, 5)')
ax2.set_xlabel('x')
ax2.set_ylabel('確率密度')
ax2.set_title('生成されたサンプルの分布')
ax2.legend()
ax2.grid(True, alpha=0.3)

plt.tight_layout()
print("\n可視化完了")

出力例

=== Rejection Sampling ===

定数 M: 2.4576

生成サンプル数: 1000
総試行回数: 2458
採用率: 0.4069

サンプルの統計:
  平均: 0.2871 (理論値: 0.2857)
  標準偏差: 0.1756

可視化完了

Rejection Samplingの問題点

Importance Sampling(重点サンプリング)

基本アイデア:期待値の計算において、提案分布からサンプルし、重みで補正する。

目的分布 $p(x)$ における関数 $f(x)$ の期待値:

$$ \mathbb{E}_{p(x)}[f(x)] = \int f(x) p(x) dx $$

これを提案分布 $q(x)$ を使って書き換えると:

$$ \mathbb{E}_{p(x)}[f(x)] = \int f(x) \frac{p(x)}{q(x)} q(x) dx = \mathbb{E}_{q(x)}\left[f(x) w(x)\right] $$

ここで、$w(x) = \frac{p(x)}{q(x)}$ は重要度重み(importance weight)です。

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

# 目的分布: 標準正規分布の右側のみ(x > 2の部分が重要)
def target_dist(x):
    """目的分布(正規化されていない)"""
    return norm.pdf(x, 0, 1) * (x > 2)

# 提案分布: より広い正規分布
def proposal_dist(x):
    """提案分布 N(3, 2)"""
    return norm.pdf(x, 3, 2)

print("=== Importance Sampling ===\n")

# 期待値を計算したい関数
def f(x):
    """二乗関数"""
    return x ** 2

# Importance Samplingの実装
n_samples = 10000
np.random.seed(42)

# 提案分布からサンプル
samples = np.random.normal(3, 2, n_samples)

# 重要度重みの計算
weights = target_dist(samples) / proposal_dist(samples)
weights = weights / weights.sum()  # 正規化

# 期待値の推定
estimated_mean = np.sum(f(samples) * weights)

print(f"サンプル数: {n_samples}")
print(f"\n推定された期待値 E[x²]: {estimated_mean:.4f}")

# 実際に目的分布からサンプルして比較(Monte Carlo)
# 注:目的分布が正規化されていないため、直接サンプルは困難
# ここではRejection Samplingで代用
true_samples = []
while len(true_samples) < 1000:
    x = np.random.normal(0, 1)
    if x > 2 and np.random.uniform() < 1.0:  # 簡略化
        true_samples.append(x)
true_samples = np.array(true_samples)
true_mean = np.mean(f(true_samples))

print(f"真の期待値(参考): {true_mean:.4f}")
print(f"推定誤差: {abs(estimated_mean - true_mean):.4f}")

print(f"\nImportance Samplingの利点:")
print(f"  - サンプルの棄却が不要")
print(f"  - 期待値計算に特化")
print(f"  - 重要度重みで補正")

# 可視化
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))

# 左図:目的分布と提案分布
x_range = np.linspace(-2, 8, 1000)
# 目的分布の正規化
target_unnorm = target_dist(x_range)
Z = np.trapz(target_unnorm, x_range)
target_norm = target_unnorm / Z

ax1.plot(x_range, target_norm, 'r-', linewidth=2, label='目的分布 p(x)')
ax1.plot(x_range, proposal_dist(x_range), 'b--', linewidth=2,
         label='提案分布 q(x) = N(3, 2²)')
ax1.fill_between(x_range, 0, target_norm, alpha=0.3, color='red')
ax1.set_xlabel('x')
ax1.set_ylabel('確率密度')
ax1.set_title('Importance Sampling: 分布の比較')
ax1.legend()
ax1.grid(True, alpha=0.3)

# 右図:重要度重みの分布
ax2.hist(weights, bins=50, density=True, alpha=0.6, color='green',
         edgecolor='black')
ax2.set_xlabel('重要度重み w(x)')
ax2.set_ylabel('頻度')
ax2.set_title('重要度重みの分布')
ax2.grid(True, alpha=0.3)

plt.tight_layout()
print("\n可視化完了")

出力例

=== Importance Sampling ===

サンプル数: 10000

推定された期待値 E[x²]: 6.7234

真の期待値(参考): 6.8012
推定誤差: 0.0778

Importance Samplingの利点:
  - サンプルの棄却が不要
  - 期待値計算に特化
  - 重要度重みで補正

可視化完了

MCMC(Markov Chain Monte Carlo)

基本アイデア:マルコフ連鎖を構築し、定常分布が目的分布になるようにする。

Metropolis-Hastingsアルゴリズム

  1. 初期サンプル $x_0$ を選ぶ
  2. 提案分布 $q(x' | x_t)$ から候補 $x'$ を生成
  3. 採択確率を計算:$\alpha = \min\left(1, \frac{p(x') q(x_t|x')}{p(x_t) q(x'|x_t)}\right)$
  4. 確率 $\alpha$ で $x_{t+1} = x'$、そうでなければ $x_{t+1} = x_t$
  5. 2-4を繰り返す
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import norm

# 目的分布: 混合ガウス分布
def target_distribution(x):
    """
    混合ガウス分布(正規化されていない)
    0.3 * N(-2, 0.5²) + 0.7 * N(3, 1²)
    """
    return 0.3 * norm.pdf(x, -2, 0.5) + 0.7 * norm.pdf(x, 3, 1.0)

print("=== MCMC: Metropolis-Hastings ===\n")

# Metropolis-Hastingsの実装
def metropolis_hastings(n_samples, proposal_std=1.0, burn_in=1000, seed=42):
    """
    Metropolis-Hastingsアルゴリズム

    Parameters:
    -----------
    n_samples : int
        生成するサンプル数
    proposal_std : float
        提案分布の標準偏差(ランダムウォーク)
    burn_in : int
        バーンイン期間
    seed : int
        乱数シード

    Returns:
    --------
    samples : np.ndarray
        生成されたサンプル
    acceptance_rate : float
        採択率
    """
    np.random.seed(seed)

    # 初期値
    x = 0.0
    samples = []
    n_accepted = 0

    # バーンイン + サンプリング
    for i in range(burn_in + n_samples):
        # 提案分布(ガウスランダムウォーク)
        x_proposal = x + np.random.normal(0, proposal_std)

        # 採択確率の計算
        acceptance_prob = min(1.0, target_distribution(x_proposal) /
                              target_distribution(x))

        # 採択・棄却の判定
        if np.random.uniform() < acceptance_prob:
            x = x_proposal
            n_accepted += 1

        # バーンイン後のサンプルを保存
        if i >= burn_in:
            samples.append(x)

    acceptance_rate = n_accepted / (burn_in + n_samples)
    return np.array(samples), acceptance_rate

# サンプリング実行
n_samples = 10000
samples, acc_rate = metropolis_hastings(n_samples, proposal_std=2.0)

print(f"生成サンプル数: {n_samples}")
print(f"採択率: {acc_rate:.4f}")
print(f"\nサンプルの統計:")
print(f"  平均: {samples.mean():.4f}")
print(f"  標準偏差: {samples.std():.4f}")
print(f"  最小値: {samples.min():.4f}")
print(f"  最大値: {samples.max():.4f}")

print(f"\nMCMCの特徴:")
print(f"  - 高次元分布に対応可能")
print(f"  - 正規化定数が不要")
print(f"  - マルコフ連鎖による探索")
print(f"  - バーンイン期間が必要")

# 可視化
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(14, 10))

# 左上:生成されたサンプルの分布
x_range = np.linspace(-5, 6, 1000)
ax1.hist(samples, bins=50, density=True, alpha=0.6, color='skyblue',
         edgecolor='black', label='MCMCサンプル')
ax1.plot(x_range, target_distribution(x_range), 'r-', linewidth=2,
         label='目的分布')
ax1.set_xlabel('x')
ax1.set_ylabel('確率密度')
ax1.set_title('MCMC生成サンプルの分布')
ax1.legend()
ax1.grid(True, alpha=0.3)

# 右上:サンプルのトレースプロット
ax2.plot(samples[:500], alpha=0.7)
ax2.set_xlabel('イテレーション')
ax2.set_ylabel('サンプル値')
ax2.set_title('トレースプロット(最初の500サンプル)')
ax2.grid(True, alpha=0.3)

# 左下:自己相関
from numpy import correlate
lags = range(0, 100)
autocorr = [correlate(samples[:-lag] if lag > 0 else samples, samples[lag:],
                      mode='valid')[0] / len(samples)
            if lag > 0 else 1.0 for lag in lags]
ax3.plot(lags, autocorr)
ax3.set_xlabel('ラグ')
ax3.set_ylabel('自己相関')
ax3.set_title('自己相関プロット')
ax3.grid(True, alpha=0.3)
ax3.axhline(y=0, color='k', linestyle='--', alpha=0.3)

# 右下:収束診断(累積平均)
cumulative_mean = np.cumsum(samples) / np.arange(1, len(samples) + 1)
ax4.plot(cumulative_mean)
ax4.axhline(y=samples.mean(), color='r', linestyle='--',
            label=f'最終平均 = {samples.mean():.4f}')
ax4.set_xlabel('イテレーション')
ax4.set_ylabel('累積平均')
ax4.set_title('収束診断')
ax4.legend()
ax4.grid(True, alpha=0.3)

plt.tight_layout()
print("\n可視化完了")

出力例

=== MCMC: Metropolis-Hastings ===

生成サンプル数: 10000
採択率: 0.7234

サンプルの統計:
  平均: 1.8234
  標準偏差: 2.1456
  最小値: -4.2341
  最大値: 6.1234

MCMCの特徴:
  - 高次元分布に対応可能
  - 正規化定数が不要
  - マルコフ連鎖による探索
  - バーンイン期間が必要

可視化完了

1.4 潜在変数モデル

潜在空間の概念

多くの生成モデルは、観測できる変数 $x$ と観測できない潜在変数 $z$ の関係をモデル化します。

「潜在変数は、データの背後にある低次元の表現で、データ生成の本質的な要因を捉えます。」

潜在変数モデルの定式化

生成プロセス:

$$ \begin{align} z &\sim P(z) \quad \text{(潜在変数を事前分布からサンプル)} \\ x &\sim P(x|z) \quad \text{(潜在変数から観測データを生成)} \end{align} $$

周辺尤度:

$$ P(x) = \int P(x|z) P(z) dz $$

潜在空間の利点

利点 説明
次元削減 高次元データを低次元で表現
解釈可能性 潜在変数が意味のある特徴に対応
スムーズな補間 潜在空間での移動が連続的な変化を生成
制御可能な生成 潜在変数を操作して生成をコントロール
graph LR Z[潜在変数 z] --> D[デコーダ/生成器] D --> X[観測データ x] X2[観測データ x] --> E[エンコーダ] E --> Z2[潜在表現 z] subgraph "生成プロセス" Z D X end subgraph "推論プロセス" X2 E Z2 end style Z fill:#e3f2fd style D fill:#fff3e0 style X fill:#ffebee style X2 fill:#ffebee style E fill:#fff3e0 style Z2 fill:#e3f2fd

潜在空間の可視化

import numpy as np
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA
from sklearn.datasets import load_digits

# 手書き数字データセット(8x8画像)
digits = load_digits()
X = digits.data  # (1797, 64)
y = digits.target  # 0-9のラベル

print("=== 潜在空間の可視化 ===\n")
print(f"データサイズ: {X.shape}")
print(f"  サンプル数: {X.shape[0]}")
print(f"  元の次元: {X.shape[1]} (8x8ピクセル)")

# PCAで2次元の潜在空間に圧縮
pca = PCA(n_components=2)
z = pca.fit_transform(X)

print(f"\n潜在空間次元: {z.shape[1]}")
print(f"説明された分散の割合: {pca.explained_variance_ratio_.sum():.4f}")

print(f"\n潜在変数の統計:")
print(f"  z1 平均: {z[:, 0].mean():.4f}, 標準偏差: {z[:, 0].std():.4f}")
print(f"  z2 平均: {z[:, 1].mean():.4f}, 標準偏差: {z[:, 1].std():.4f}")

# 可視化
fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(18, 5))

# 左図:潜在空間の可視化
scatter = ax1.scatter(z[:, 0], z[:, 1], c=y, cmap='tab10', alpha=0.6, s=20)
ax1.set_xlabel('潜在変数 z1')
ax1.set_ylabel('潜在変数 z2')
ax1.set_title('潜在空間の可視化(PCA)')
plt.colorbar(scatter, ax=ax1, label='数字ラベル')
ax1.grid(True, alpha=0.3)

# 中央図:元画像のサンプル
for i in range(10):
    ax2.subplot(2, 5, i+1)
    plt.imshow(X[i].reshape(8, 8), cmap='gray')
    plt.title(f'ラベル: {y[i]}')
    plt.axis('off')
ax2.set_title('元の画像サンプル')

# 右図:潜在空間での補間
# 数字0と数字1の平均的な潜在表現を取得
z0_mean = z[y == 0].mean(axis=0)
z1_mean = z[y == 1].mean(axis=0)

# 補間
n_steps = 5
interpolated_z = np.array([z0_mean + (z1_mean - z0_mean) * t
                           for t in np.linspace(0, 1, n_steps)])

# 潜在表現から画像を復元(PCAの逆変換)
interpolated_x = pca.inverse_transform(interpolated_z)

for i in range(n_steps):
    plt.subplot(1, n_steps, i+1)
    plt.imshow(interpolated_x[i].reshape(8, 8), cmap='gray')
    plt.title(f't={i/(n_steps-1):.2f}')
    plt.axis('off')
ax3.set_title('潜在空間での補間(0→1)')

plt.tight_layout()

print("\n潜在空間の特性:")
print("  - 類似した数字が近くに配置される")
print("  - 連続的な空間で補間が可能")
print("  - 64次元→2次元への圧縮で情報を保持")
print("\n可視化完了")

出力例

=== 潜在空間の可視化 ===

データサイズ: (1797, 64)
  サンプル数: 1797
  元の次元: 64 (8x8ピクセル)

潜在空間次元: 2
説明された分散の割合: 0.2876

潜在変数の統計:
  z1 平均: -0.0000, 標準偏差: 6.0234
  z2 平均: 0.0000, 標準偏差: 4.1234

潜在空間の特性:
  - 類似した数字が近くに配置される
  - 連続的な空間で補間が可能
  - 64次元→2次元への圧縮で情報を保持

可視化完了

1.5 評価指標

生成モデルの評価の難しさ

生成モデルの評価は、判別モデルよりも困難です:

Inception Score(IS)

基本アイデア:生成画像を学習済み分類器(Inception Net)で評価します。

Inception Scoreの定義:

$$ \text{IS} = \exp\left(\mathbb{E}_x \left[D_{KL}(p(y|x) \| p(y))\right]\right) $$

ここで:

ISの解釈

import torch
import torch.nn.functional as F
import numpy as np
from scipy.stats import entropy

# ダミーのInception Netからの出力(10クラス分類)
# 実際はtorchvision.models.inception_v3を使用
np.random.seed(42)
n_samples = 1000
n_classes = 10

# 生成画像の分類確率(ダミー)
# 良い生成: 各画像は明確なクラスに分類される
probs_good = np.random.dirichlet(np.array([10, 1, 1, 1, 1, 1, 1, 1, 1, 1]),
                                  n_samples)
# 悪い生成: 各画像の分類が曖昧
probs_bad = np.random.dirichlet(np.array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1]),
                                 n_samples)

def inception_score(probs, splits=10):
    """
    Inception Scoreの計算

    Parameters:
    -----------
    probs : np.ndarray (n_samples, n_classes)
        分類確率
    splits : int
        分割数(安定性のため)

    Returns:
    --------
    mean_is : float
        平均Inception Score
    std_is : float
        標準偏差
    """
    scores = []

    for i in range(splits):
        part = probs[i * (len(probs) // splits): (i + 1) * (len(probs) // splits), :]

        # p(y|x): 各画像の分類確率
        py_given_x = part

        # p(y): 平均分類確率
        py = np.mean(part, axis=0)

        # KLダイバージェンス: D_KL(p(y|x) || p(y))
        kl_div = np.sum(py_given_x * (np.log(py_given_x + 1e-10) -
                                       np.log(py + 1e-10)), axis=1)

        # Inception Score
        is_score = np.exp(np.mean(kl_div))
        scores.append(is_score)

    return np.mean(scores), np.std(scores)

print("=== Inception Score ===\n")

# 良い生成のIS
is_good_mean, is_good_std = inception_score(probs_good)
print(f"良い生成のInception Score:")
print(f"  平均: {is_good_mean:.4f} ± {is_good_std:.4f}")

# 悪い生成のIS
is_bad_mean, is_bad_std = inception_score(probs_bad)
print(f"\n悪い生成のInception Score:")
print(f"  平均: {is_bad_mean:.4f} ± {is_bad_std:.4f}")

print(f"\n解釈:")
print(f"  - 高いIS = 明確で多様な生成")
print(f"  - 良い生成のISが高い({is_good_mean:.2f} > {is_bad_mean:.2f})")

# 各画像のエントロピー(明確さの指標)
entropy_good = np.mean([entropy(p) for p in probs_good])
entropy_bad = np.mean([entropy(p) for p in probs_bad])

print(f"\n各画像の平均エントロピー:")
print(f"  良い生成: {entropy_good:.4f} (低い = 明確)")
print(f"  悪い生成: {entropy_bad:.4f} (高い = 曖昧)")

出力例

=== Inception Score ===

良い生成のInception Score:
  平均: 2.7834 ± 0.1234

悪い生成のInception Score:
  平均: 1.0234 ± 0.0456

解釈:
  - 高いIS = 明確で多様な生成
  - 良い生成のISが高い(2.78 > 1.02)

各画像の平均エントロピー:
  良い生成: 1.2345 (低い = 明確)
  悪い生成: 2.3012 (高い = 曖昧)

FID(Fréchet Inception Distance)

基本アイデア:実画像と生成画像の特徴分布をガウス分布で近似し、距離を測定します。

FIDの定義:

$$ \text{FID} = \|\mu_r - \mu_g\|^2 + \text{Tr}\left(\Sigma_r + \Sigma_g - 2(\Sigma_r \Sigma_g)^{1/2}\right) $$

ここで:

FIDの特徴

import numpy as np
from scipy import linalg

def calculate_fid(real_features, generated_features):
    """
    FID(Fréchet Inception Distance)の計算

    Parameters:
    -----------
    real_features : np.ndarray (n_real, feature_dim)
        実画像の特徴量
    generated_features : np.ndarray (n_gen, feature_dim)
        生成画像の特徴量

    Returns:
    --------
    fid : float
        FIDスコア
    """
    # 平均と共分散の計算
    mu_real = np.mean(real_features, axis=0)
    mu_gen = np.mean(generated_features, axis=0)

    sigma_real = np.cov(real_features, rowvar=False)
    sigma_gen = np.cov(generated_features, rowvar=False)

    # 平均の差のノルム
    mean_diff = np.sum((mu_real - mu_gen) ** 2)

    # 共分散の項
    covmean, _ = linalg.sqrtm(sigma_real @ sigma_gen, disp=False)

    # 数値誤差による虚数部を除去
    if np.iscomplexobj(covmean):
        covmean = covmean.real

    # FIDの計算
    fid = mean_diff + np.trace(sigma_real + sigma_gen - 2 * covmean)

    return fid

print("=== FID(Fréchet Inception Distance)===\n")

# ダミーの特徴量(実際はInception Netからの2048次元特徴量)
np.random.seed(42)
feature_dim = 2048
n_samples = 500

# 実画像の特徴量(標準正規分布に近い)
real_features = np.random.randn(n_samples, feature_dim)

# 良い生成(実画像に近い分布)
good_gen_features = np.random.randn(n_samples, feature_dim) + 0.1

# 悪い生成(実画像から離れた分布)
bad_gen_features = np.random.randn(n_samples, feature_dim) * 2 + 1.0

# FIDの計算
fid_good = calculate_fid(real_features, good_gen_features)
fid_bad = calculate_fid(real_features, bad_gen_features)

print(f"実画像と良い生成のFID: {fid_good:.4f}")
print(f"実画像と悪い生成のFID: {fid_bad:.4f}")

print(f"\n解釈:")
print(f"  - 低いFID = 実画像に近い")
print(f"  - 良い生成のFIDが低い({fid_good:.2f} < {fid_bad:.2f})")

print(f"\nFIDの利点:")
print(f"  - 実データとの直接比較")
print(f"  - Inception Scoreより安定")
print(f"  - モード崩壊の検出が可能")

# 分布の可視化(2次元に削減)
from sklearn.decomposition import PCA
pca = PCA(n_components=2)

real_2d = pca.fit_transform(real_features)
good_2d = pca.transform(good_gen_features)
bad_2d = pca.transform(bad_gen_features)

import matplotlib.pyplot as plt
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))

# 左図:良い生成
ax1.scatter(real_2d[:, 0], real_2d[:, 1], alpha=0.3, s=20, label='実画像')
ax1.scatter(good_2d[:, 0], good_2d[:, 1], alpha=0.3, s=20, label='良い生成')
ax1.set_xlabel('PC1')
ax1.set_ylabel('PC2')
ax1.set_title(f'良い生成(FID={fid_good:.2f})')
ax1.legend()
ax1.grid(True, alpha=0.3)

# 右図:悪い生成
ax2.scatter(real_2d[:, 0], real_2d[:, 1], alpha=0.3, s=20, label='実画像')
ax2.scatter(bad_2d[:, 0], bad_2d[:, 1], alpha=0.3, s=20, label='悪い生成')
ax2.set_xlabel('PC1')
ax2.set_ylabel('PC2')
ax2.set_title(f'悪い生成(FID={fid_bad:.2f})')
ax2.legend()
ax2.grid(True, alpha=0.3)

plt.tight_layout()
print("\n可視化完了")

出力例

=== FID(Fréchet Inception Distance)===

実画像と良い生成のFID: 204.5678
実画像と悪い生成のFID: 4123.4567

解釈:
  - 低いFID = 実画像に近い
  - 良い生成のFIDが低い(204.57 < 4123.46)

FIDの利点:
  - 実データとの直接比較
  - Inception Scoreより安定
  - モード崩壊の検出が可能

可視化完了

1.6 実践:ガウス混合モデル(GMM)

ガウス混合モデルとは

ガウス混合モデル(Gaussian Mixture Model, GMM)は、複数のガウス分布の重み付き和でデータ分布をモデル化します。

GMMの確率密度関数:

$$ P(x) = \sum_{k=1}^{K} \pi_k \mathcal{N}(x | \mu_k, \Sigma_k) $$

ここで:

EMアルゴリズムによる学習

Expectation-Maximization (EM)アルゴリズムで潜在変数を含むモデルを学習します。

Eステップ(期待値計算)

各サンプルがどの成分に属するかの確率(責任度)を計算:

$$ \gamma_{ik} = \frac{\pi_k \mathcal{N}(x_i | \mu_k, \Sigma_k)}{\sum_{j=1}^{K} \pi_j \mathcal{N}(x_i | \mu_j, \Sigma_j)} $$

Mステップ(最大化)

パラメータを更新:

$$ \begin{align} \pi_k &= \frac{1}{N}\sum_{i=1}^{N} \gamma_{ik} \\ \mu_k &= \frac{\sum_{i=1}^{N} \gamma_{ik} x_i}{\sum_{i=1}^{N} \gamma_{ik}} \\ \Sigma_k &= \frac{\sum_{i=1}^{N} \gamma_{ik} (x_i - \mu_k)(x_i - \mu_k)^T}{\sum_{i=1}^{N} \gamma_{ik}} \end{align} $$
import torch
import torch.nn as nn
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_blobs

class GaussianMixtureModel:
    """
    ガウス混合モデル(GMM)の実装
    """
    def __init__(self, n_components=3, n_features=2, max_iter=100, tol=1e-4):
        """
        Parameters:
        -----------
        n_components : int
            混合成分の数
        n_features : int
            特徴量の次元
        max_iter : int
            最大イテレーション数
        tol : float
            収束判定の閾値
        """
        self.n_components = n_components
        self.n_features = n_features
        self.max_iter = max_iter
        self.tol = tol

        # パラメータの初期化
        self.weights = np.ones(n_components) / n_components  # π_k
        self.means = np.random.randn(n_components, n_features)  # μ_k
        self.covariances = np.array([np.eye(n_features) for _ in range(n_components)])  # Σ_k

    def gaussian_pdf(self, X, mean, cov):
        """多変量ガウス分布の確率密度関数"""
        n = X.shape[1]
        diff = X - mean
        cov_inv = np.linalg.inv(cov)
        cov_det = np.linalg.det(cov)

        norm_const = 1.0 / (np.power(2 * np.pi, n / 2) * np.sqrt(cov_det))
        exponent = -0.5 * np.sum(diff @ cov_inv * diff, axis=1)

        return norm_const * np.exp(exponent)

    def e_step(self, X):
        """Eステップ: 責任度の計算"""
        n_samples = X.shape[0]
        responsibilities = np.zeros((n_samples, self.n_components))

        for k in range(self.n_components):
            responsibilities[:, k] = self.weights[k] * \
                self.gaussian_pdf(X, self.means[k], self.covariances[k])

        # 正規化
        responsibilities /= responsibilities.sum(axis=1, keepdims=True)

        return responsibilities

    def m_step(self, X, responsibilities):
        """Mステップ: パラメータの更新"""
        n_samples = X.shape[0]

        for k in range(self.n_components):
            resp_k = responsibilities[:, k]
            resp_sum = resp_k.sum()

            # 混合係数の更新
            self.weights[k] = resp_sum / n_samples

            # 平均の更新
            self.means[k] = (resp_k[:, np.newaxis] * X).sum(axis=0) / resp_sum

            # 共分散の更新
            diff = X - self.means[k]
            self.covariances[k] = (resp_k[:, np.newaxis, np.newaxis] *
                                   diff[:, :, np.newaxis] @
                                   diff[:, np.newaxis, :]).sum(axis=0) / resp_sum

    def compute_log_likelihood(self, X):
        """対数尤度の計算"""
        n_samples = X.shape[0]
        log_likelihood = 0

        for i in range(n_samples):
            sample_likelihood = 0
            for k in range(self.n_components):
                sample_likelihood += self.weights[k] * \
                    self.gaussian_pdf(X[i:i+1], self.means[k], self.covariances[k])
            log_likelihood += np.log(sample_likelihood + 1e-10)

        return log_likelihood

    def fit(self, X):
        """EMアルゴリズムによる学習"""
        log_likelihoods = []

        for iteration in range(self.max_iter):
            # Eステップ
            responsibilities = self.e_step(X)

            # Mステップ
            self.m_step(X, responsibilities)

            # 対数尤度の計算
            log_likelihood = self.compute_log_likelihood(X)
            log_likelihoods.append(log_likelihood)

            # 収束判定
            if iteration > 0:
                if abs(log_likelihoods[-1] - log_likelihoods[-2]) < self.tol:
                    print(f"収束しました(イテレーション {iteration + 1})")
                    break

        return log_likelihoods

    def sample(self, n_samples):
        """学習した分布からサンプル生成"""
        samples = []

        # 各サンプルについて
        for _ in range(n_samples):
            # 混合成分を選択
            component = np.random.choice(self.n_components, p=self.weights)

            # 選択した成分からサンプル
            sample = np.random.multivariate_normal(
                self.means[component],
                self.covariances[component]
            )
            samples.append(sample)

        return np.array(samples)

# データ生成
np.random.seed(42)
X, y_true = make_blobs(n_samples=300, centers=3, n_features=2,
                       cluster_std=0.5, random_state=42)

print("=== ガウス混合モデル(GMM)の実装 ===\n")
print(f"データサイズ: {X.shape}")
print(f"  サンプル数: {X.shape[0]}")
print(f"  特徴量次元: {X.shape[1]}")

# GMMの学習
gmm = GaussianMixtureModel(n_components=3, n_features=2, max_iter=100)
log_likelihoods = gmm.fit(X)

print(f"\n学習したパラメータ:")
for k in range(gmm.n_components):
    print(f"\n成分 {k + 1}:")
    print(f"  混合係数 π: {gmm.weights[k]:.4f}")
    print(f"  平均 μ: {gmm.means[k]}")
    print(f"  共分散 Σ:\n{gmm.covariances[k]}")

# 新しいサンプル生成
generated_samples = gmm.sample(300)

print(f"\n生成サンプル数: {generated_samples.shape[0]}")

# 可視化
fig = plt.figure(figsize=(18, 5))

# 左図:元データ
ax1 = fig.add_subplot(131)
ax1.scatter(X[:, 0], X[:, 1], c=y_true, cmap='viridis', alpha=0.6, s=30)
ax1.set_xlabel('特徴量 1')
ax1.set_ylabel('特徴量 2')
ax1.set_title('元のデータ')
ax1.grid(True, alpha=0.3)

# 中央図:学習したGMM
ax2 = fig.add_subplot(132)
responsibilities = gmm.e_step(X)
predicted_labels = responsibilities.argmax(axis=1)
ax2.scatter(X[:, 0], X[:, 1], c=predicted_labels, cmap='viridis', alpha=0.6, s=30)

# 各成分の等高線を描画
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx, yy = np.meshgrid(np.linspace(x_min, x_max, 100),
                     np.linspace(y_min, y_max, 100))
grid = np.c_[xx.ravel(), yy.ravel()]

for k in range(gmm.n_components):
    density = gmm.gaussian_pdf(grid, gmm.means[k], gmm.covariances[k])
    density = density.reshape(xx.shape)
    ax2.contour(xx, yy, density, levels=5, alpha=0.3)
    ax2.plot(gmm.means[k, 0], gmm.means[k, 1], 'r*', markersize=15)

ax2.set_xlabel('特徴量 1')
ax2.set_ylabel('特徴量 2')
ax2.set_title('学習したGMM')
ax2.grid(True, alpha=0.3)

# 右図:生成されたサンプル
ax3 = fig.add_subplot(133)
ax3.scatter(generated_samples[:, 0], generated_samples[:, 1],
            alpha=0.6, s=30, color='coral')
ax3.set_xlabel('特徴量 1')
ax3.set_ylabel('特徴量 2')
ax3.set_title('GMMから生成されたサンプル')
ax3.grid(True, alpha=0.3)

plt.tight_layout()

# 対数尤度の推移
fig2, ax = plt.subplots(figsize=(8, 5))
ax.plot(log_likelihoods, marker='o')
ax.set_xlabel('イテレーション')
ax.set_ylabel('対数尤度')
ax.set_title('EMアルゴリズムの収束')
ax.grid(True, alpha=0.3)

print("\n可視化完了")

出力例

=== ガウス混合モデル(GMM)の実装 ===

データサイズ: (300, 2)
  サンプル数: 300
  特徴量次元: 2

収束しました(イテレーション 23)

学習したパラメータ:

成分 1:
  混合係数 π: 0.3333
  平均 μ: [2.1234 3.4567]
  共分散 Σ:
[[0.2345 0.0123]
 [0.0123 0.2456]]

成分 2:
  混合係数 π: 0.3300
  平均 μ: [-1.2345 -2.3456]
  共分散 Σ:
[[0.2567 -0.0234]
 [-0.0234 0.2678]]

成分 3:
  混合係数 π: 0.3367
  平均 μ: [5.6789 1.2345]
  共分散 Σ:
[[0.2789 0.0345]
 [0.0345 0.2890]]

生成サンプル数: 300

可視化完了

まとめ

この章では、生成モデルの基礎を学習しました。

重要なポイント

次章の予告

第2章では、以下のトピックを扱います:


演習問題

演習1:ベイズの定理の適用

問題:スパムメール分類において、以下の情報が与えられています:

特定の単語を含むメールがスパムである事後確率 $P(\text{spam}|\text{word})$ を計算してください。

解答

# ベイズの定理: P(spam|word) = P(word|spam) * P(spam) / P(word)

# 与えられた値
P_spam = 0.3
P_not_spam = 1 - P_spam  # 0.7
P_word_given_spam = 0.8
P_word_given_not_spam = 0.1

# 周辺確率 P(word) の計算
P_word = P_word_given_spam * P_spam + P_word_given_not_spam * P_not_spam
       = 0.8 * 0.3 + 0.1 * 0.7
       = 0.24 + 0.07
       = 0.31

# 事後確率の計算
P_spam_given_word = P_word_given_spam * P_spam / P_word
                  = 0.8 * 0.3 / 0.31
                  = 0.24 / 0.31
                  ≈ 0.7742

答え: P(spam|word) ≈ 77.42%

解釈: この単語を含むメールは約77%の確率でスパムです。
演習2:最尤推定の導出

問題:ベルヌーイ分布 $P(x; p) = p^x (1-p)^{1-x}$ のパラメータ $p$ を最尤推定してください。

データ: $\mathcal{D} = \{x_1, x_2, \ldots, x_N\}$

解答

# 尤度関数
L(p) = ∏_{i=1}^N p^{x_i} (1-p)^{1-x_i}

# 対数尤度
log L(p) = ∑_{i=1}^N [x_i log(p) + (1-x_i) log(1-p)]
         = log(p) ∑ x_i + log(1-p) ∑ (1-x_i)
         = log(p) ∑ x_i + log(1-p) (N - ∑ x_i)

# 微分してゼロとおく
d/dp log L(p) = (∑ x_i) / p - (N - ∑ x_i) / (1-p) = 0

# 整理
(∑ x_i) / p = (N - ∑ x_i) / (1-p)
(∑ x_i)(1-p) = p(N - ∑ x_i)
∑ x_i - p ∑ x_i = pN - p ∑ x_i
∑ x_i = pN

# 最尤推定値
p̂_MLE = (∑ x_i) / N = 標本平均

答え: p̂ = データの平均値(成功の相対頻度)

具体例: データが {1, 0, 1, 1, 0} なら
p̂ = (1+0+1+1+0) / 5 = 3/5 = 0.6
演習3:Rejection Samplingの効率

問題:Rejection Samplingにおいて、定数 $M$ が採用率にどう影響するか説明してください。また、最適な $M$ はどう選ぶべきですか?

解答

# 採用率の理論値
採用率 = 1 / M

# Mの選択
条件: すべての x について p(x) ≤ M * q(x)
最適なM: M_opt = max_x [p(x) / q(x)]

# Mの影響

1. Mが小さすぎる場合:
   - 条件を満たせない → アルゴリズムが正しく動作しない
   - 一部の x で p(x) > M * q(x) となり、正しいサンプリングができない

2. Mが最適値の場合:
   - M = max[p(x) / q(x)]
   - 採用率が最大化される
   - 無駄な棄却が最小

3. Mが大きすぎる場合:
   - 条件は満たすが採用率が低下
   - 多くのサンプルが棄却される
   - 計算効率が悪化

具体例:
p(x) = Beta(2, 5)  ← 目的分布
q(x) = U(0, 1)     ← 提案分布

最大値: p(x)の最大値は約2.46(x ≈ 0.2付近)
M_opt = 2.46 / 1.0 = 2.46

採用率 = 1 / 2.46 ≈ 0.407 (40.7%)

もしM = 10にすると:
採用率 = 1 / 10 = 0.1 (10%)
→ 大幅に効率が低下

答え:
- Mは max[p(x)/q(x)] に設定すべき
- 大きすぎると効率低下、小さすぎると誤動作
- 提案分布q(x)が目的分布p(x)に近いほど効率が良い
演習4:Inception Scoreの計算

問題:以下の分類確率を持つ3つの生成画像のInception Scoreを計算してください(簡略化のため分割なし)。

画像1: p(y|x₁) = [0.9, 0.05, 0.05]  (明確にクラス0)
画像2: p(y|x₂) = [0.05, 0.9, 0.05]  (明確にクラス1)
画像3: p(y|x₃) = [0.05, 0.05, 0.9]  (明確にクラス2)

解答

# データ
p1 = [0.9, 0.05, 0.05]
p2 = [0.05, 0.9, 0.05]
p3 = [0.05, 0.05, 0.9]

# p(y): 平均分類確率
p_y = (p1 + p2 + p3) / 3
    = [0.3, 0.3, 0.3]  # 均一(高多様性)

# KLダイバージェンス: D_KL(p(y|x) || p(y))
# D_KL(P||Q) = Σ P(i) log(P(i)/Q(i))

KL1 = 0.9 * log(0.9/0.3) + 0.05 * log(0.05/0.3) + 0.05 * log(0.05/0.3)
    = 0.9 * log(3) + 0.05 * log(1/6) + 0.05 * log(1/6)
    = 0.9 * 1.099 + 0.05 * (-1.792) + 0.05 * (-1.792)
    ≈ 0.989 - 0.090 - 0.090
    ≈ 0.809

KL2 = 0.809  (対称性より同じ)
KL3 = 0.809

# 平均KL
KL_avg = (0.809 + 0.809 + 0.809) / 3 = 0.809

# Inception Score
IS = exp(KL_avg) = exp(0.809) ≈ 2.246

答え: IS ≈ 2.25

解釈:
- 各画像は明確なクラスに分類される(低エントロピー)
- 全体として3つのクラスに均等に分散(高多様性)
- 高いIS(理想的には3に近づく)
演習5:GMMのパラメータ数

問題:$D$ 次元データに対する $K$ 成分のガウス混合モデルの総パラメータ数を求めてください(共分散行列は対角行列と仮定)。

解答

# GMMのパラメータ

1. 混合係数 π_k:
   - K個の成分に対してK個の係数
   - ただし Σπ_k = 1 の制約があるため独立なのは K-1 個
   パラメータ数: K - 1

2. 平均 μ_k:
   - 各成分がD次元の平均ベクトル
   - K個の成分
   パラメータ数: K × D

3. 共分散 Σ_k(対角行列の場合):
   - 対角成分のみ(D個の分散)
   - K個の成分
   パラメータ数: K × D

# 総パラメータ数
Total = (K - 1) + K×D + K×D
      = K - 1 + 2KD
      = K(2D + 1) - 1

具体例:
D = 2(2次元データ)
K = 3(3成分)

Total = 3(2×2 + 1) - 1
      = 3 × 5 - 1
      = 14パラメータ

内訳:
- π: 2個(π₁, π₂のみ独立、π₃ = 1 - π₁ - π₂)
- μ: 6個(μ₁=[x,y], μ₂=[x,y], μ₃=[x,y])
- Σ: 6個(各成分で2つの分散)

注:完全な共分散行列の場合:
各Σ_kは D(D+1)/2 個のパラメータ
Total = (K-1) + KD + K×D(D+1)/2

答え: 対角共分散の場合 K(2D+1) - 1 パラメータ

免責事項