ナイーブベイズスパムフィルタ
最も基本的な条件付き確率の公式を使って、sklearnを使わずに簡単な中国語スパムフィルタを実装します。
このケースを学ぶと、以下が理解できます:ベイズの定理 + 「各単語が独立」という仮定 = 実用可能なテキスト分類器。
生活からの導入
メールボックスのスパムフィルタ
あなたのメールボックスには毎日100通のメールが届きます。プログラムは新しいメールがスパムかどうかをどう自動判断するのでしょうか?プログラムのやり方:過去のメールから、スパムメールに「無料」「当選」などの単語が出現する確率、および通常メールに「会議」「プロジェクト」などの単語が出現する確率を統計します。
新しいメールが届いたら、計算します:スパムメールという仮定の下でこれらの単語が同時に出現する確率 vs 通常メールという仮定の下での確率。大きい方に判定します。
直感的理解
ナイーブベイズの重要な単純化—「ナイーブ仮定」:各単語の出現有無は互いに独立であると仮定します。この仮定は現実では成立しませんが(「無料」と「賞品」はよく一緒に出現します)、実用上の効果は驚くほど良いです。
| テクニック | 手法 | 解決する問題 |
|---|---|---|
| 対数確率 | logを使って掛け算を足し算に変換する | 多くの小数を掛け合わせると0になる(数値のアンダーフロー) |
| ラプラス平滑化 | 各単語のカウントを+0ではなく+1にする | トレーニングセットに出現しない単語があると確率が0になる |
Python での実践
実例
from collections import defaultdict
import math
# トレーニングデータ(ラベル付けされた中国語のショートメール8件)
train_data = [
("赢取 免费 奖品 立即 点击", "spam"),
("恭喜 中奖 领取 现金 优惠", "spam"),
("限时 优惠 点击 链接 免费", "spam"),
("贷款 低息 立即 申请 现金", "spam"),
("会议 安排 明天 上午 讨论", "ham"),
("项目 进度 汇报 会议 记录", "ham"),
("周末 聚餐 安排 大家 参加", "ham"),
("报告 数据 分析 项目 完成", "ham"),
]
# 各カテゴリの単語頻度を集計する
class_word_counts = defaultdict(lambda: defaultdict(int))
class_doc_counts = defaultdict(int)
vocab = set()
for text, label in train_data:
class_doc_counts[label] += 1
for word in text.split():
class_word_counts[label][word] += 1
vocab.add(word)
vocab_size = len(vocab)
total_docs = len(train_data)
# ナイーブベイズ分類(対数確率 + ラプラス平滑化)
def predict(text, alpha=1.0):
words = text.split()
scores = {}
for label in class_doc_counts:
log_prob = math.log(class_doc_counts[label] / total_docs)
total_words = sum(class_word_counts[label].values())
for word in words:
word_count = class_word_counts[label][word]
word_prob = (word_count + alpha) / (total_words + alpha * vocab_size)
log_prob += math.log(word_prob)
scores[label] = log_prob
return max(scores, key=scores.get), scores
# テスト
test_emails = [
"免费 领取 现金 奖品",
"明天 会议 项目 汇报",
"限时 点击 优惠 链接",
"周末 大家 一起 讨论 报告",
]
print("サンプル 朴素贝叶斯分类结果:\n")
for email in test_emails:
label, scores = predict(email)
print(f" {email!r:28s} -> 【{label}】")
import math
# トレーニングデータ(ラベル付けされた中国語のショートメール8件)
train_data = [
("赢取 免费 奖品 立即 点击", "spam"),
("恭喜 中奖 领取 现金 优惠", "spam"),
("限时 优惠 点击 链接 免费", "spam"),
("贷款 低息 立即 申请 现金", "spam"),
("会议 安排 明天 上午 讨论", "ham"),
("项目 进度 汇报 会议 记录", "ham"),
("周末 聚餐 安排 大家 参加", "ham"),
("报告 数据 分析 项目 完成", "ham"),
]
# 各カテゴリの単語頻度を集計する
class_word_counts = defaultdict(lambda: defaultdict(int))
class_doc_counts = defaultdict(int)
vocab = set()
for text, label in train_data:
class_doc_counts[label] += 1
for word in text.split():
class_word_counts[label][word] += 1
vocab.add(word)
vocab_size = len(vocab)
total_docs = len(train_data)
# ナイーブベイズ分類(対数確率 + ラプラス平滑化)
def predict(text, alpha=1.0):
words = text.split()
scores = {}
for label in class_doc_counts:
log_prob = math.log(class_doc_counts[label] / total_docs)
total_words = sum(class_word_counts[label].values())
for word in words:
word_count = class_word_counts[label][word]
word_prob = (word_count + alpha) / (total_words + alpha * vocab_size)
log_prob += math.log(word_prob)
scores[label] = log_prob
return max(scores, key=scores.get), scores
# テスト
test_emails = [
"免费 领取 现金 奖品",
"明天 会议 项目 汇报",
"限时 点击 优惠 链接",
"周末 大家 一起 讨论 报告",
]
print("サンプル 朴素贝叶斯分类结果:\n")
for email in test_emails:
label, scores = predict(email)
print(f" {email!r:28s} -> 【{label}】")
サンプル 朴素贝叶斯分类结果: '免费 领取 现金 奖品' -> 【spam】 '明天 会议 项目 汇报' -> 【ham】 '限时 点击 优惠 链接' -> 【spam】 '周末 大家 一起 讨论 报告' -> 【ham】
AIにおける応用シーン
| シーン | 説明 |
|---|---|
| テキスト分類 | スパムフィルタ、ニュース分類、感情分析——ナイーブベイズは古典的なベースライン |
| ドキュメントのトピックモデリング | LDA(Latent Dirichlet Allocation)の数学的基盤 |
| 異常検知 | 新しいサンプルが「正常分布」に属する確率を計算し、低すぎる場合は異常と判定する |