A/Bテストの設計
「オフラインでNDCGが上がった。だが本当にユーザーに受け入れられるかは別問題だ。」
田中VPoEが言う。
「推薦システムの真価はオンラインで測る。A/Bテストで仮説を検証し、データで意思決定する。これがプロダクト改善の基本だ。」
A/Bテストの基本設計
| 要素 | 設計内容 |
|---|---|
| 仮説 | ハイブリッド推薦は人気ランキングよりCTRが高い |
| 指標 | 主指標: CTR、副指標: CVR、客単価 |
| 対照群 | A: 人気ランキング(現行) |
| 実験群 | B: ハイブリッド推薦(新規) |
| サンプルサイズ | 各群5,000ユーザー |
| 期間 | 2週間 |
| 有意水準 | 5%(p < 0.05) |
サンプルサイズの計算
from scipy import stats
import numpy as np
def calculate_sample_size(baseline_rate, min_effect, alpha=0.05, power=0.8):
"""必要サンプルサイズを計算"""
# 帰無仮説: p1 = p2
# 対立仮説: p1 != p2
p1 = baseline_rate
p2 = baseline_rate * (1 + min_effect)
# 効果量(Cohen's h)
h = 2 * (np.arcsin(np.sqrt(p1)) - np.arcsin(np.sqrt(p2)))
# Z値
z_alpha = stats.norm.ppf(1 - alpha / 2)
z_beta = stats.norm.ppf(power)
# 各群のサンプルサイズ
n = ((z_alpha + z_beta) / h) ** 2
return int(np.ceil(n))
# CTR 3% に対して 10%の相対的改善(3.3%)を検出
n = calculate_sample_size(
baseline_rate=0.03,
min_effect=0.10, # 10%の相対的改善
)
print(f"必要サンプルサイズ(各群): {n:,}")
A/Bテスト実行フレームワーク
class ABTestManager:
"""A/Bテストの管理"""
def __init__(self, test_config):
self.config = test_config
self.assignments = {}
self.results = {'A': [], 'B': []}
def assign_user(self, user_id):
"""ユーザーをグループに割り当て"""
if user_id not in self.assignments:
# ハッシュベースの決定的割り当て
hash_val = hash(f"{user_id}_{self.config['test_id']}") % 100
if hash_val < self.config['traffic_split']:
self.assignments[user_id] = 'B' # 実験群
else:
self.assignments[user_id] = 'A' # 対照群
return self.assignments[user_id]
def record_event(self, user_id, event_type, metadata=None):
"""イベントを記録"""
group = self.assignments.get(user_id)
if group:
self.results[group].append({
'user_id': user_id,
'event': event_type,
'metadata': metadata or {},
'timestamp': datetime.now().isoformat(),
})
def analyze(self):
"""結果を分析"""
metrics_a = self._compute_metrics(self.results['A'])
metrics_b = self._compute_metrics(self.results['B'])
# 統計的検定
stat_test = self._run_statistical_test(metrics_a, metrics_b)
return {
'group_a': metrics_a,
'group_b': metrics_b,
'statistical_test': stat_test,
'recommendation': self._make_recommendation(stat_test),
}
def _compute_metrics(self, events):
"""グループのメトリクスを計算"""
users = set(e['user_id'] for e in events)
impressions = [e for e in events if e['event'] == 'impression']
clicks = [e for e in events if e['event'] == 'click']
purchases = [e for e in events if e['event'] == 'purchase']
ctr = len(clicks) / len(impressions) if impressions else 0
cvr = len(purchases) / len(clicks) if clicks else 0
return {
'n_users': len(users),
'impressions': len(impressions),
'clicks': len(clicks),
'purchases': len(purchases),
'ctr': round(ctr, 4),
'cvr': round(cvr, 4),
}
def _run_statistical_test(self, metrics_a, metrics_b):
"""カイ二乗検定"""
from scipy.stats import chi2_contingency
table = np.array([
[metrics_a['clicks'], metrics_a['impressions'] - metrics_a['clicks']],
[metrics_b['clicks'], metrics_b['impressions'] - metrics_b['clicks']],
])
chi2, p_value, _, _ = chi2_contingency(table)
return {
'chi2': round(chi2, 4),
'p_value': round(p_value, 6),
'significant': p_value < 0.05,
}
def _make_recommendation(self, stat_test):
if stat_test['significant']:
return "統計的に有意な差あり。実験群のデプロイを推奨"
else:
return "有意差なし。テスト期間の延長または仮説の見直しを推奨"
まとめ
| 項目 | ポイント |
|---|---|
| 仮説設定 | 明確な指標と最小検出効果を定義 |
| サンプルサイズ | 検出力分析で必要数を事前計算 |
| 割り当て | ハッシュベースで決定的に割り当て |
| 統計検定 | p値に基づく意思決定 |
チェックリスト
- A/Bテストの基本設計要素を列挙できる
- サンプルサイズの計算方法を理解した
- ユーザー割り当ての仕組みを説明できる
- 結果の統計的検定と意思決定の流れを理解した
次のステップへ
A/Bテストの設計を学んだ。次は演習で推薦エージェントを構築しよう。
推定読了時間: 30分