LESSON

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分