Day 023 — 確率の基礎(ベイズの定理・条件付き確率)応用

2026-05-02 白 / Phase 1 コーディング 確率の基礎 | 難易度: ★★☆☆☆

📚 背景知識(読んでから問題へ)

ベイズの定理の実践応用 — スパムフィルターや医療診断への適用


📝 問題

ナイーブベイズ分類器をスクラッチで実装し、以下のテキストデータを分類せよ。

# 訓練データ(スパム/ハム判定)
train_data = [
    ("win free money now", "spam"),
    ("claim your prize today", "spam"),
    ("click here for free gift", "spam"),
    ("meeting at 3pm tomorrow", "ham"),
    ("please review the attached document", "ham"),
    ("are you free for lunch?", "ham"),
    ("free free free money", "spam"),
    ("project update for this week", "ham"),
]

# テストデータ
test_messages = [
    "free money for everyone",
    "review the project update",
]

# 実装要件:
# 1. 各単語の条件付き確率 P(単語|クラス) を計算(ラプラス平滑化適用)
# 2. P(スパム|メッセージ) と P(ハム|メッセージ) を比較して分類
# 3. 各テストメッセージの分類結果と確率スコアを出力

模範解答

問題1

解法:

事前確率:

  • $P(不良品) = 0.02$, $P(良品) = 0.98$

尤度:

  • $P(陽性|不良品) = 0.95$(感度)
  • $P(陽性|良品) = 1 - 0.90 = 0.10$(偽陽性率)

全確率の法則:

$$P(陽性) = P(陽性|不良品) \cdot P(不良品) + P(陽性|良品) \cdot P(良品)$$

$$= 0.95 \times 0.02 + 0.10 \times 0.98 = 0.019 + 0.098 = 0.117$$

ベイズの定理:

$$P(不良品|陽性) = \frac{0.95 \times 0.02}{0.117} = \frac{0.019}{0.117} \approx 0.1624$$

答え: 約16.2%

> 直感との乖離に注意: 感度95%の検査でも、有病率が低い(2%)と「陽性 = 実際に不良品」の確率は16%しかない。これが医療スクリーニングで問題になる「偽陽性の罠」。


問題2

from collections import defaultdict
import math

def train_naive_bayes(train_data):
    """ナイーブベイズ訓練"""
    class_counts = defaultdict(int)
    word_counts = defaultdict(lambda: defaultdict(int))
    vocab = set()

    for text, label in train_data:
        words = text.lower().split()
        class_counts[label] += 1
        for word in words:
            word_counts[label][word] += 1
            vocab.add(word)

    total = sum(class_counts.values())

    # 事前確率 log P(class)
    log_prior = {c: math.log(count / total) for c, count in class_counts.items()}

    # 条件付き確率 log P(word|class)(ラプラス平滑化 α=1)
    log_likelihood = {}
    vocab_size = len(vocab)

    for c in class_counts:
        total_words_in_class = sum(word_counts[c].values())
        log_likelihood[c] = {}
        for word in vocab:
            count = word_counts[c].get(word, 0)
            # ラプラス平滑化: (count + 1) / (total + vocab_size)
            log_likelihood[c][word] = math.log((count + 1) / (total_words_in_class + vocab_size))
        # 未知語のためのデフォルト対数確率
        log_likelihood[c][''] = math.log(1 / (total_words_in_class + vocab_size))

    return log_prior, log_likelihood, vocab

def predict_naive_bayes(message, log_prior, log_likelihood, vocab):
    """分類予測"""
    words = message.lower().split()
    scores = {}

    for c in log_prior:
        score = log_prior[c]
        for word in words:
            if word in vocab:
                score += log_likelihood[c][word]
            else:
                score += log_likelihood[c]['']
        scores[c] = score

    # ソフトマックスで確率に変換
    max_score = max(scores.values())
    exp_scores = {c: math.exp(s - max_score) for c, s in scores.items()}
    total_exp = sum(exp_scores.values())
    probs = {c: v / total_exp for c, v in exp_scores.items()}

    predicted = max(probs, key=probs.get)
    return predicted, probs

# 実行
train_data = [
    ("win free money now", "spam"),
    ("claim your prize today", "spam"),
    ("click here for free gift", "spam"),
    ("meeting at 3pm tomorrow", "ham"),
    ("please review the attached document", "ham"),
    ("are you free for lunch?", "ham"),
    ("free free free money", "spam"),
    ("project update for this week", "ham"),
]

test_messages = [
    "free money for everyone",
    "review the project update",
]

log_prior, log_likelihood, vocab = train_naive_bayes(train_data)

for msg in test_messages:
    pred, probs = predict_naive_bayes(msg, log_prior, log_likelihood, vocab)
    print(f"メッセージ: '{msg}'")
    print(f"  → 予測: {pred} (spam={probs['spam']:.3f}, ham={probs['ham']:.3f})")
    print()

期待出力:

▶ 出力を見る
メッセージ: 'free money for everyone'
  → 予測: spam (spam=0.xxx, ham=0.xxx)

メッセージ: 'review the project update'
  → 予測: ham (spam=0.xxx, ham=0.xxx)

📐 数学・統計の補足(文系向け)

概念説明
事前確率 P(H)データを見る前の信念
尤度 P(E\H)仮説が正しい場合のデータの確率
事後確率 P(H\E)データを見た後の更新された信念
ラプラス平滑化未出現単語の確率が0にならないよう +1 する
対数確率の使用アンダーフロー防止(確率の積 → 対数の和)

🚀 次のステップ

次回テーマ: 統計学基礎(記述統計・分布) — 平均・分散・標準偏差・正規分布の実装と応用


🎯 自己評価

自分の回答

気づき・メモ