ベイズA/Bテストと意思決定 — 頻度論を超えるリッチな比較フレームワーク

「ボタンの色を青から緑に変えたら、クリック率は本当に上がるのか?」「新しいレコメンドアルゴリズムは、旧来のものより本当に売上に貢献するのか?」 — Web プロダクトを運営する人なら誰でも一度は直面するこの種の問いに、定量的な答えを出すための定番手法が A/B テストです。古典的にはこれを頻度論的な二標本比率の検定で扱い、「$p < 0.05$ なら有意差あり」と判定してきました。

しかし実務でこのフレームワークを使っていると、次々と歯がゆい場面に出くわします。「有意差あり」と言われても、知りたいのは「B 案が A 案より何 % 良いのか」「もし誤って劣った案を選んだ場合、いくらの損失を被るのか」「途中で結果を見たくなったらどうすればよいのか」 — これらの問いに、頻度論はまっすぐ答えてくれません。$p$ 値は「帰無仮説のもとで、観測値以上に極端なデータが得られる確率」であって、私たちが本当に欲しい「B が A より良い確率」ではないからです。

本記事で扱う ベイズ A/B テスト は、まさにこのギャップを埋めるためのアプローチです。事前分布とデータから事後分布を構成し、$P(p_B > p_A)$、$E[p_B – p_A \mid p_B > p_A]$、期待損失といった意思決定者が本当に欲しい指標を直接計算します。Netflix、Microsoft、Bookingといった Web 企業が大規模な A/B テスト基盤にベイズ的な評価指標を採り入れているのは、まさにこの「答えのリッチさ」のためです。さらに、複数案を同時に評価したい多腕バンディット問題への自然な拡張、p-hacking や peeking の問題からの解放など、ベイズ流の恩恵は意思決定の質そのものに直結します。

本記事の内容

  • 頻度論 A/B テストの問題点(peeking、p-hacking、早期停止バイアス、$p$ 値の誤読)
  • ベイズフレームワークの基本:事前分布 → データ → 事後分布
  • ベルヌーイ-ベータ共役モデルとCTR比較の事後分布の導出
  • 意思決定指標:$P(B > A)$、$E[B-A \mid B>A]$、期待損失(expected loss)の定義と計算法
  • クレディブル区間と信頼区間の違い(同じ数字でも意味が違う)
  • ベイズ的早期停止 — なぜ「途中でデータを見て」も問題が起きないのか
  • 多腕拡張への接続(Thompson Sampling)と precision ベースのサンプルサイズ計算
  • 事前分布の選び方と敏感度分析の実務
  • Python による解析解と Monte Carlo シミュレーション、ベイズ早期停止シミュレータ、多腕拡張デモ

前提知識

この記事を読む前に、以下の記事を読んでおくと理解が深まります。

頻度論 A/B テストの何が困るのか

よくある運用と $p$ 値の誤読

頻度論的 A/B テストの典型的なシナリオは次のようなものです。A 案と B 案に同じだけのトラフィックを流し、コンバージョン数 $X_A, X_B$ を観測する。比率の差に対して二標本 $z$ 検定(あるいは $\chi^2$ 検定)を行い、$p < 0.05$ なら「有意差あり、B 案を採用」、$p \geq 0.05$ なら「有意差なし、棄却できない」と結論する。

このフレームワーク自体は数学的にきれいですが、運用に出すといくつもの問題が露呈します。まず一番厄介なのが $p$ 値の誤読 です。$p = 0.03$ という結果を見て、現場のステークホルダーはほぼ確実に「B が A より良い確率が 97%」と解釈します。しかしこれは数学的には誤りです。$p$ 値は「もし帰無仮説(A と B は同じ)が正しかったら、観測されたデータと同じかより極端なデータが得られる条件付き確率」であって、「データを所与とした B が A より良い確率」ではありません。後者は本来 $P(p_B > p_A \mid \text{data})$ と書かれるべきもので、ベイズ的な事後確率です。

Peeking と早期停止バイアス

二つ目の問題は peeking(途中覗き見) です。実務では、A/B テストを走らせている最中に「そろそろ差が出てきたかな?」と途中で結果を確認したくなるのが人情です。そして 1 度だけ覗いて「あ、$p = 0.04$ になった、テスト終了」と早期に切り上げるのが頻繁に行われます。

しかしこれは深刻なバイアスを生みます。頻度論の $p$ 値は 「事前に決めたサンプルサイズで一度だけ検定する」 ことを暗黙の前提に校正されています。途中で $K$ 回覗き見て、どれかで $p < 0.05$ になった瞬間に止める運用にすると、実際の第一種の過誤率は名目の $5\%$ をはるかに上回ります。たとえば $K = 5$ 回覗くだけで、実効的な $\alpha$ は $14\%$ 程度に膨らむことが知られています。

これを補正するには Pocock や O’Brien-Fleming といった「群逐次デザイン」の補正定数を入れる、あるいは alpha-spending function を導入する、といった重装備な処方が必要になります。理屈は正しいのですが、現場で正しく運用するのは骨が折れます。

P-Hacking と複数比較

三つ目の問題は p-hacking です。複数の指標(CTR、滞在時間、購入単価、リテンション …)を同時に検定すると、そのうちのどれかが偶然 $p < 0.05$ になる確率はぐっと上がります。Bonferroni 補正などで対処はできますが、これも忘れがちで、しばしば「特定のセグメント、特定の指標で有意差あり」という偽陽性を生みます。

頻度論はこれらの問題に対して「使い方を厳格にすれば回避できる」という立場を取りますが、実務ではしばしばその「厳格さ」が現場の意思決定速度と相容れません。ここで「もっと自然に、知りたいことを知りたい形で答えてくれるフレームワークはないのか?」という問いが立ち上がります。ベイズ流の登場です。

ベイズフレームワークの基本構造

ベイズ A/B テストの中心にあるのは、おなじみのベイズの定理です。

$$ P(\theta \mid D) = \frac{P(D \mid \theta) P(\theta)}{P(D)} $$

ここで $\theta$ は知りたいパラメータ(A 案と B 案のクリック率 $p_A, p_B$)、$D$ は観測データ(試行数とコンバージョン数)です。

  • 事前分布 $P(\theta)$ — データを見る前の信念。何も情報がなければ「一様」、過去の実績があれば「経験ベイズ」的に強めの分布を置けます。
  • 尤度 $P(D \mid \theta)$ — モデルが与えるデータの確率。CTR の問題ならベルヌーイ尤度 $\theta^x (1-\theta)^{n-x}$。
  • 事後分布 $P(\theta \mid D)$ — データを観測したあとの信念。これが意思決定の全情報を含む。
  • 証拠 $P(D)$ — 正規化定数。共役な場合は積分が解析的に閉じる。

ここまでは ベイズ推論の記事 でも扱った定石です。A/B テスト特有の面白さは、$\theta = (p_A, p_B)$ という 二つのパラメータの差 に直接興味があるという点にあります。$P(p_B > p_A \mid D)$ や $E[p_B – p_A \mid D]$ といった「意思決定者にとって意味のある量」を、事後分布から積分するだけで取り出せる — これがベイズ流の核心です。

頻度論との対比でいえば、頻度論は「パラメータは固定された一つの真値」と考え、その真値からデータがどのように生まれるかを論じます。ベイズはこれを反転し、「データは固定された観測値」と考え、パラメータが従う確率分布を論じます。この主客の入れ替えこそが、「B が A より良い確率」のような直感的な問いに答えられる理由です。

次節では、A/B テストで最も基本的な「クリック率比較」を、共役分布のおかげで解析的に解ける形で見ていきます。

ベルヌーイ-ベータ共役モデルでの事後分布

モデルの設定

A 案と B 案について、それぞれ独立に次のように観測したとします。

  • A 案:$n_A$ 回表示し、そのうち $x_A$ 回クリックされた
  • B 案:$n_B$ 回表示し、そのうち $x_B$ 回クリックされた

各表示は独立なベルヌーイ試行と仮定し、A 案のクリック率を $p_A$、B 案のクリック率を $p_B$ とします。観測数は二項分布に従います。

$$ X_A \mid p_A \sim \mathrm{Binomial}(n_A, p_A), \quad X_B \mid p_B \sim \mathrm{Binomial}(n_B, p_B) $$

ベイズ推論を実行するには事前分布が必要です。$[0, 1]$ 上で柔軟に分布を表現できる ベータ分布 が、二項分布の共役事前分布として知られています。$p_A, p_B$ に独立な事前分布を置きます。

$$ p_A \sim \mathrm{Beta}(\alpha_A^{(0)}, \beta_A^{(0)}), \quad p_B \sim \mathrm{Beta}(\alpha_B^{(0)}, \beta_B^{(0)}) $$

事前パラメータ $(\alpha^{(0)}, \beta^{(0)}) = (1, 1)$ なら一様分布、$(0.5, 0.5)$ なら Jeffreys prior、$(\alpha, \beta)$ で平均 $\alpha/(\alpha+\beta)$ を事前知識として埋め込めます。

事後分布の導出

ベルヌーイ-ベータ共役のおかげで、事後分布もベータ分布になります。導出を 1 行ずつ追っておきましょう。事後分布はベイズの定理から

$$ P(p \mid x, n) \propto P(x \mid p, n) \cdot P(p) $$

です。尤度はベルヌーイ試行の積で

$$ P(x \mid p, n) = \binom{n}{x} p^x (1-p)^{n-x} $$

事前分布はベータ分布の定義から

$$ P(p) = \frac{1}{B(\alpha^{(0)}, \beta^{(0)})} p^{\alpha^{(0)}-1} (1-p)^{\beta^{(0)}-1} $$

両者を掛けて、$p$ に依存しない係数を比例記号 $\propto$ にまとめると次のようになります。

$$ P(p \mid x, n) \propto p^{x + \alpha^{(0)} – 1} (1-p)^{n – x + \beta^{(0)} – 1} $$

この右辺は、$\mathrm{Beta}(x + \alpha^{(0)}, n – x + \beta^{(0)})$ の確率密度(の正規化定数を除いた部分)と全く同じ形です。したがって、事後分布もベータ分布になることがわかります。

$$ \boxed{\,p_A \mid x_A, n_A \sim \mathrm{Beta}(\alpha_A, \beta_A), \quad \alpha_A = \alpha_A^{(0)} + x_A, \quad \beta_A = \beta_A^{(0)} + n_A – x_A\,} $$

B 案も同様です。「事前分布のパラメータに成功数と失敗数を足すだけ」という、極めてシンプルな更新規則になっています。$\alpha^{(0)}, \beta^{(0)}$ は「あらかじめ仮想的に観測した成功数と失敗数」として直感的に解釈できる — これがベータ分布が “事前の擬似観測(pseudo-counts)” と呼ばれる所以です。

共役性のありがたみ

共役関係の何が嬉しいかというと、事後分布を毎回新たに計算しなくて済む という点です。データ $x, n$ がストリーミング的に増えていくとき、事後パラメータ $(\alpha, \beta)$ は単に成功数と失敗数を加算していくだけで更新されます。これは Thompson Sampling のような逐次アルゴリズムを実装する際に決定的な利点になります(多腕バンディットの記事 で詳しく扱います)。

事後分布が手に入ったので、次は本題の「意思決定に必要な量」を計算していきます。

意思決定指標:P(B>A) と期待損失

「B が A より良い確率」を直接計算する

ベイズ流の最大の魅力は、$P(p_B > p_A \mid D)$ がそのまま積分で書けるという点です。事後分布の独立性から、$(p_A, p_B)$ の同時事後密度は積の形になります。

$$ P(p_A, p_B \mid D) = P(p_A \mid D_A) \cdot P(p_B \mid D_B) $$

ここから「B が A より良い確率」は、$p_B > p_A$ の領域上で同時密度を積分するだけです。

$$ P(p_B > p_A \mid D) = \int_0^1 \int_0^{p_B} P(p_A \mid D_A) P(p_B \mid D_B) \, dp_A \, dp_B $$

ベータ分布同士の差の確率は、特殊関数(不完全ベータ関数や Lauricella の超幾何関数)を用いて解析的に書けることが知られていますが、実務上は Monte Carlo シミュレーション で十分です。

$$ \hat{P}(p_B > p_A) = \frac{1}{N} \sum_{i=1}^N \mathbb{1}[p_B^{(i)} > p_A^{(i)}], \quad p_A^{(i)} \sim \mathrm{Beta}(\alpha_A, \beta_A),\ p_B^{(i)} \sim \mathrm{Beta}(\alpha_B, \beta_B) $$

$N = 10^5$ サンプルもあれば、$P(B>A)$ の推定誤差は標準偏差 $\sqrt{p(1-p)/N} \approx 0.0015$ 程度の精度で計算できます。これが「B が A より良い確率は 97.3% です」とそのまま意思決定者に伝えられる量です。

期待損失:誤った選択の代償

「B が A より良い確率が 97%」だけでは、実は意思決定に不十分です。残りの 3% のケースで、もし誤って B を選んだ場合、どのくらいの損失を被るのか — これも重要な情報です。

ここで 期待損失(expected loss、または expected regret) という概念を導入します。B を選ぶときの期待損失は、「実は A が良かった場合の差分を、その確率で重み付けた平均」として定義されます。

$$ L(\text{choose B}) = E[\max(p_A – p_B, 0) \mid D] = \int_0^1 \int_{p_B}^1 (p_A – p_B) P(p_A \mid D_A) P(p_B \mid D_B) \, dp_A \, dp_B $$

直感的には、「もし B を選んだのが間違いだったとき、A よりどのくらい劣るか」を、その間違いの確率で重み付けた量です。Monte Carlo では

$$ \hat{L}(\text{choose B}) = \frac{1}{N} \sum_{i=1}^N \max(p_A^{(i)} – p_B^{(i)}, 0) $$

として計算できます。実務的な意思決定ルールは「期待損失が事前に設定した閾値(例えば $\epsilon = 0.0001$、つまり 0.01 pt 以下)を下回ったら B を採用する」というものです。Microsoft の Bing の A/B プラットフォームや、Convoy、VWO といった実プロダクトでこのルールが使われています。

効果量の期待値と条件付き期待値

もう一つ重要な指標が、期待される効果量 $E[p_B – p_A \mid D]$ と、B が良い場合の条件付き期待値 $E[p_B – p_A \mid p_B > p_A, D]$ です。前者は素直に「B はおおよそ A より何 % 良いか」、後者は「B が確かに良いという前提で、その差はどのくらいか」を表します。

$$ E[p_B – p_A \mid D] = \frac{\alpha_B}{\alpha_B + \beta_B} – \frac{\alpha_A}{\alpha_A + \beta_A} $$

ベータ分布の期待値はパラメータから直接計算でき、それぞれの平均の差として求められます。Monte Carlo を使えば中央値や任意のパーセンタイルも計算できます。

これらの指標を組み合わせることで、「B は A より 99% の確率で良く、平均差は 0.8pt、最悪でも 0.05pt しか劣らない」という極めてリッチな意思決定が可能になります。次節では、頻度論との対比で「区間推定」の意味の違いを掘り下げます。

クレディブル区間 vs 信頼区間

数字は同じでも意味が違う

頻度論の代表的なツールである 信頼区間(confidence interval) とベイズの クレディブル区間(credible interval) は、両方とも「$95\%$ の区間」のように書かれますが、解釈はまるで違います。

頻度論の 95% 信頼区間 $[L, U]$ の意味は次のようになります。

もし同じ実験を独立に何度も繰り返し、毎回信頼区間を計算したならば、その区間のうちおよそ 95% が真値 $\theta$ を含むだろう。

ここで「95% という確率」はパラメータではなく 「実験を繰り返した時の区間そのもの」 に対する確率です。$\theta$ は固定された一つの真値だから、それを含むか含まないかしかありえません。「この区間に $\theta$ が入っている確率は 95% です」と言ってはいけない、というのが頻度論の厳格な立場です。

これに対し、ベイズの 95% クレディブル区間 $[L, U]$ は次のように解釈できます。

データを所与としたとき、$\theta$ がこの区間にある確率は 95% である。

すなわち $P(L \leq \theta \leq U \mid D) = 0.95$ がそのまま成り立ちます。これは多くの人が「信頼区間」をこう思って使っているまさにその解釈に等しい — ベイズだとそれが堂々と正しく成立するわけです。

クレディブル区間の作り方

事後分布 $p \mid D \sim \mathrm{Beta}(\alpha, \beta)$ から $95\%$ クレディブル区間を作る方法は何通りかあります。

  • Equal-tailed interval(等裾区間) — 2.5% 分位点と 97.5% 分位点を区間端にする
  • HPD(highest posterior density)区間 — 事後密度が最も高い領域を取る区間。同じ確率質量を最小の区間長で覆える

ベータ分布のように非対称な分布だと、HPD と等裾区間は異なる答えを出します。実務では等裾区間が計算しやすいので頻用されますが、密度関数が明確にピークを持つ場合は HPD のほうが「もっとも信じられる範囲」をきれいに表現できます。

差 $p_B – p_A$ のクレディブル区間も同じ要領で計算できます。Monte Carlo サンプル $\{p_B^{(i)} – p_A^{(i)}\}$ の 2.5% と 97.5% 分位点を取れば等裾区間が直接得られます。これも頻度論の差の信頼区間(Wald 法や Newcombe 法)と数値的には近い値を取りますが、解釈の自然さが段違いです。

ここまでで「結論を出すための指標」が揃いました。次の問題は「いつ結論を出すか」 — すなわちサンプルサイズと早期停止の話です。

ベイズ的早期停止 — なぜ「途中で見て」も大丈夫なのか

頻度論の「禁断の覗き見」

頻度論の枠組みでは、A/B テストの途中でデータを覗き見て「有意になったら止める」という運用が深刻なバイアスを生むと先に述べました。これは検定統計量の標本分布が「事前に決められた $n$ のもとで初めて妥当に定義される」ことに起因します。

ところがベイズ流では、事後分布はその時点で観測されたデータのみに依存します。「いつ見たか」「何回見たか」は事後分布の形に一切影響しません。なぜなら、ベイズの定理にあるのは「観測 $D$ を所与としたパラメータの確率分布」だけであり、「どのような停止ルールで $D$ が観測されたか」は登場しないからです。

これは 尤度原理(likelihood principle) と呼ばれる立場で、「すべての情報は尤度に含まれている」とするベイズの基本姿勢です。停止ルールが尤度関数を変えない限り、推論結果は停止ルールに依存しません。

実務的な早期停止ルール

実務でよく使われるベイズ的早期停止ルールには以下のようなものがあります。

  1. 確率閾値ルール — $P(p_B > p_A \mid D) > 0.95$ になったら B 採用、$< 0.05$ になったら A 採用、それ以外は継続。
  2. 期待損失ルール — $\min(L(\text{choose A}), L(\text{choose B})) < \epsilon$ になったら、損失の小さい方を採用。
  3. クレディブル区間ルール — 差のクレディブル区間が予め決めた精度(例:幅 $0.01$)まで狭まったら停止。

特に 2 の期待損失ルールは、Microsoft の Bing や Bookingの A/B 基盤で採用されている代表的なルールです。$\epsilon$ は事業上の「許容できる損失」として設定するのが自然で、たとえば「コンバージョン率を 0.01pt 落とすことで失う売上が許容範囲ならば $\epsilon = 0.0001$」のように設計できます。

早期停止が「正しく」機能する条件

ベイズ早期停止が「正しく機能する」というのは、もう少し正確に言うと 「事業的に許容できるリスクの範囲内に収まる」 という意味です。事前分布が真の分布をひどく外していたり、モデルそのものが間違っていたりすると、いくらベイズ流とはいえ誤った結論に至ります。

そのため実務では、シミュレーションで「真値が様々な分布から取られたとき、停止ルールの誤判定率はどうなるか」を事前に検証することが推奨されます。これを operating characteristics の評価と呼びます。ベイズ流の利点は「途中で何度見ても理論的に問題ない」ことであって、「あらゆる場面で常に正しい結論を出す」わけではないという点には注意が必要です。

ここまでで A 案と B 案の二腕比較を一通り見てきました。実務では「3 つ以上の案を同時に比較したい」「テストと運用を同時に進めたい」というニーズも頻繁にあります。これに自然に応えるのが多腕拡張です。

多腕拡張 — Thompson Sampling への接続

K 腕への一般化

3 つ以上の案 $A_1, A_2, \dots, A_K$ を比較する場合も、ベイズ流は素直に一般化します。各腕にベータ事前を置き、独立に事後分布を更新します。

$$ p_k \mid D_k \sim \mathrm{Beta}(\alpha_k, \beta_k), \quad k = 1, 2, \dots, K $$

決定指標は「ある腕が最良である確率」$P(p_k = \max_j p_j \mid D)$ や、その腕を選んだ場合の期待損失 $E[\max_j p_j – p_k \mid D]$ になります。これらも Monte Carlo で素直に計算できます。

頻度論で 3 つ以上の比率を比較するには $\chi^2$ 検定や全ペア比較 + Bonferroni 補正など重装備な工夫が必要ですが(カイ二乗検定の記事 を参照)、ベイズ流ではどの腕の事後分布もシームレスに扱えます。

Thompson Sampling への自然な接続

多腕の事後分布が手に入ると、もう一段の発展が見えてきます。それが Thompson Sampling です。各腕の事後分布から 1 つずつパラメータをサンプリングし、最大値を取った腕を次のアクションとして選ぶ、というシンプルなアルゴリズムです。

$$ \hat{p}_k \sim \mathrm{Beta}(\alpha_k, \beta_k), \quad k^* = \arg\max_k \hat{p}_k $$

これを「腕を引く → 観測する → 事後を更新する → 再度サンプリングして次の腕を選ぶ」と繰り返すと、事後分布が不確かな腕は時々選ばれ(探索)、事後分布が高い腕は頻繁に選ばれ(活用)、という探索-活用のバランスが自動的に取れます。これは A/B テストの「全アームに等しくトラフィックを流す」運用を超えて、テスト中もアームの優劣に応じてトラフィックを最適配分する 適応的 A/B テスト に拡張する自然な道筋です。

詳しくは Thompson Sampling の記事 で扱いますが、ここで重要なのは「ベイズ A/B テストの枠組みはそのまま多腕バンディットに拡張できる」という点です。共役性が事後分布の逐次更新を軽量にしてくれるため、リアルタイムなトラフィック振り分けがアームの増減に頑健に対応できます。

これだけ柔軟なフレームワークでも、「どのくらいのサンプル数が必要か」という事前計画の問題は残ります。次節でこれを扱います。

サンプルサイズ計算 — Precision-Based アプローチ

頻度論の検出力計算との対比

頻度論では「効果量 $\delta$ を power $1 – \beta$ で検出するためのサンプルサイズ」を逆算します。$\alpha = 0.05$, $\beta = 0.2$(パワー 80%)、効果量 $\delta = 0.02$、ベースライン CTR 0.10 なら、$n$ あたり数千〜数万のサンプルが必要、という計算をします。

しかしこのやり方は「事前に効果量を仮定する」ことが必要で、その仮定が外れるとサンプルサイズが過剰または過少になります。さらに「power の計算は仮定された効果量に対するものであって、本当に知りたい『差の信頼区間がどのくらい狭くなるか』を直接答えていない」という難点があります。

Precision-Based なベイズ流

ベイズ流では、「事後分布のクレディブル区間の幅が、事前に決めた精度 $w$ に達するまで観測する」という precision-based のアプローチが自然です。

ベータ分布 $\mathrm{Beta}(\alpha, \beta)$ の標準偏差は

$$ \mathrm{SD}(p) = \sqrt{\frac{\alpha \beta}{(\alpha + \beta)^2 (\alpha + \beta + 1)}} $$

です。$n$ が大きくなると $\alpha + \beta \approx n$ となり、標準偏差は概ね $\sqrt{p(1-p)/n}$ に近づきます(ベイズも頻度論も漸近的には同じ形になる)。差 $p_B – p_A$ の標準偏差は独立性から

$$ \mathrm{SD}(p_B – p_A) \approx \sqrt{\frac{p_A(1-p_A)}{n_A} + \frac{p_B(1-p_B)}{n_B}} $$

となり、95% クレディブル区間の幅は概ね $2 \times 1.96 \times \mathrm{SD}$ で評価できます。これを目標精度 $w$ と等しく置けば、必要サンプルサイズ $n_A = n_B = n$ は

$$ n \approx \frac{2 \cdot 1.96^2 (p_A(1-p_A) + p_B(1-p_B))}{w^2} $$

として求まります。たとえばベースライン CTR 0.10、目標精度 $w = 0.01$(差を ±0.5pt の精度で出したい)なら、$n \approx 14000$ 程度になります。

頻度論の検出力計算と数値的にはほぼ同じになりますが、「効果量を仮定しなくてよい」「途中で精度をモニタリングしながら停止判断できる」という点でベイズ流の方が現場運用と相性が良いと言えます。

実装に入る前に、最後の理論的トピックとして「事前分布の選び方」を整理しておきます。

事前分布の選び方と敏感度分析

弱情報的事前分布(weakly informative prior)

実務で迷うのが「事前分布をどう設定するか」です。ベイズの長所は「事前知識を取り込める」ことですが、無自覚に強い事前を置くと結論がそちらに引っ張られます。

A/B テストで通常お薦めされるのは 弱情報的事前分布 です。具体的には次のような選択肢があります。

  • 一様分布 $\mathrm{Beta}(1, 1)$ — 完全に無情報。CTR が何でもありえると考える。
  • Jeffreys prior $\mathrm{Beta}(0.5, 0.5)$ — パラメータ変換に不変な「客観的」事前。CTR が 0 か 1 に偏ったケースを少しだけ重視する。
  • 経験ベイズ的な弱事前 $\mathrm{Beta}(2, 20)$ — 過去の平均 CTR が 10% くらいなら、それを中心に少しだけ情報を入れる。

サンプル数が数百を超えるような実務的状況では、これら弱情報的事前の違いは事後分布にほぼ影響しません。データが事前を圧倒するからです。逆に、サンプル数が極端に少ないとき(数十件)には事前の影響が無視できなくなるので注意が必要です。

敏感度分析

事前分布の影響を確認する標準的なやり方が 敏感度分析(sensitivity analysis) です。同じデータに対して複数の事前を当てはめ、結論が変わらないことを確認します。

  • $\mathrm{Beta}(1,1)$, $\mathrm{Beta}(0.5,0.5)$, $\mathrm{Beta}(2,20)$ の 3 通りで $P(p_B > p_A \mid D)$ を計算
  • 値が大きく変わらなければ「事前に頑健」と判断
  • 大きく変わるなら、データが少なすぎるか事前が強すぎるかのどちらか

実務的には「データが事前を 100 倍以上凌駕しているか」を目安にすると安心できます。具体的には、事前の擬似観測数 $\alpha^{(0)} + \beta^{(0)}$ が実観測数 $n$ の 1% 未満なら、ほぼ事前は影響しません。

Strong prior が有効な場面

逆に、強い事前を意図的に使いたい場面もあります。たとえばコールドスタート問題 — 新規ユーザーセグメントに対する CTR を、過去の似たセグメントの実績から「強めの事前」として持ち込む場合などです。階層ベイズモデルを使えば「複数セグメントの事前を、もう一段上のハイパーパラメータから学習する」ような構造も組めます。これは 階層ベイズの記事 のテーマでもあります。

理論はここまで揃いました。あとは Python で動かして、実際に「B が A より良い確率」「期待損失」「ベイズ早期停止のシミュレーション」を体感していきましょう。

Python での実装:解析解 + Monte Carlo

事後分布の構築と $P(B>A)$ の計算

まず最も基本となる「事後分布を構築し、$P(p_B > p_A)$ と期待損失を Monte Carlo で計算する」関数を作ります。

import numpy as np
from scipy import stats

class BayesianABTest:
    """ベルヌーイ-ベータ共役モデルによる二腕 A/B テスト"""

    def __init__(self, prior_a=(1.0, 1.0), prior_b=(1.0, 1.0)):
        # 事前パラメータ (alpha, beta)
        self.alpha_a, self.beta_a = prior_a
        self.alpha_b, self.beta_b = prior_b

    def update(self, x_a, n_a, x_b, n_b):
        """観測データで事後パラメータを更新"""
        self.alpha_a += x_a
        self.beta_a  += n_a - x_a
        self.alpha_b += x_b
        self.beta_b  += n_b - x_b

    def posterior_samples(self, n_samples=100000, seed=None):
        """事後分布から (p_A, p_B) を Monte Carlo サンプリング"""
        rng = np.random.default_rng(seed)
        p_a = rng.beta(self.alpha_a, self.beta_a, size=n_samples)
        p_b = rng.beta(self.alpha_b, self.beta_b, size=n_samples)
        return p_a, p_b

    def prob_b_better(self, n_samples=100000, seed=0):
        """P(p_B > p_A | D)"""
        p_a, p_b = self.posterior_samples(n_samples, seed)
        return float(np.mean(p_b > p_a))

    def expected_loss(self, choose='B', n_samples=100000, seed=0):
        """期待損失: 選択した腕が劣っていた場合の損失の期待値"""
        p_a, p_b = self.posterior_samples(n_samples, seed)
        if choose == 'B':
            return float(np.mean(np.maximum(p_a - p_b, 0)))
        else:
            return float(np.mean(np.maximum(p_b - p_a, 0)))

    def credible_interval(self, level=0.95, n_samples=100000, seed=0):
        """差 p_B - p_A のクレディブル区間(等裾)"""
        p_a, p_b = self.posterior_samples(n_samples, seed)
        diff = p_b - p_a
        low = (1 - level) / 2 * 100
        high = (1 + level) / 2 * 100
        return float(np.percentile(diff, low)), float(np.percentile(diff, high))

このクラスは、A/B テストの核心的なロジックをすべて含んでいます。update で逐次的に観測を取り込めるところがベルヌーイ-ベータ共役の威力で、過去の観測をすべて記憶しておく必要がありません。

実際にこれを使ってみます。A 案 CTR 10%、B 案 CTR 12% の合成データを生成して結果を確認します。

# 合成データ:A=10000表示で1010クリック、B=10000表示で1180クリック
test = BayesianABTest(prior_a=(1.0, 1.0), prior_b=(1.0, 1.0))
test.update(x_a=1010, n_a=10000, x_b=1180, n_b=10000)

print(f"事後パラメータ: A=Beta({test.alpha_a}, {test.beta_a})")
print(f"事後パラメータ: B=Beta({test.alpha_b}, {test.beta_b})")

# A, B の事後平均
mean_a = test.alpha_a / (test.alpha_a + test.beta_a)
mean_b = test.alpha_b / (test.alpha_b + test.beta_b)
print(f"事後平均: p_A = {mean_a:.4f}, p_B = {mean_b:.4f}")

# 意思決定指標
print(f"P(p_B > p_A | D) = {test.prob_b_better():.4f}")
print(f"期待損失(B 採用) = {test.expected_loss('B'):.6f}")
print(f"期待損失(A 採用) = {test.expected_loss('A'):.6f}")
ci_low, ci_high = test.credible_interval(level=0.95)
print(f"差 p_B - p_A の 95% CrI: [{ci_low:.4f}, {ci_high:.4f}]")

このコードを実行すると、$P(p_B > p_A) \approx 0.9999$、期待損失(B 採用)$\approx 7 \times 10^{-6}$、差の 95% クレディブル区間 $[0.011, 0.026]$ といった結果が得られます。これらの数字が伝えているのは「B が A より良い確率はほぼ確実、間違って B を選んだ場合の期待損失は 0.001% 以下、B は A より 1.1〜2.6 pt 高い CTR を持つと信じられる」という、極めてリッチな情報です。頻度論の「$p < 0.001$ で有意」というだけの結論と比べて、現場の意思決定者がアクションを決めやすい形になっています。

事後分布の可視化

事後分布の形を視覚的に確認します。

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

# 事後分布のパラメータ(上の例から)
alpha_a, beta_a = 1011, 8991
alpha_b, beta_b = 1181, 8821

p_grid = np.linspace(0.08, 0.14, 500)
pdf_a = stats.beta.pdf(p_grid, alpha_a, beta_a)
pdf_b = stats.beta.pdf(p_grid, alpha_b, beta_b)

fig, axes = plt.subplots(1, 2, figsize=(12, 4.5))

# 左:A と B の事後分布
ax = axes[0]
ax.fill_between(p_grid, pdf_a, alpha=0.4, label=f'A: Beta({alpha_a},{beta_a})')
ax.fill_between(p_grid, pdf_b, alpha=0.4, label=f'B: Beta({alpha_b},{beta_b})')
ax.set_xlabel('CTR p')
ax.set_ylabel('Posterior density')
ax.set_title('Posterior of CTR (A vs B)')
ax.legend()
ax.grid(True, alpha=0.3)

# 右:差 p_B - p_A の分布
rng = np.random.default_rng(0)
p_a_samples = rng.beta(alpha_a, beta_a, 100000)
p_b_samples = rng.beta(alpha_b, beta_b, 100000)
diff = p_b_samples - p_a_samples

ax = axes[1]
ax.hist(diff, bins=80, density=True, alpha=0.6, color='C2')
ax.axvline(0, color='k', linestyle='--', alpha=0.6, label='p_B = p_A')
ci_low, ci_high = np.percentile(diff, [2.5, 97.5])
ax.axvline(ci_low,  color='red', linestyle=':', label=f'95% CrI [{ci_low:.3f}, {ci_high:.3f}]')
ax.axvline(ci_high, color='red', linestyle=':')
ax.set_xlabel('p_B - p_A')
ax.set_ylabel('Density')
ax.set_title('Posterior of p_B - p_A')
ax.legend()
ax.grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig('bayes_ab_posterior.png', dpi=150, bbox_inches='tight')
plt.show()

左のグラフでは、A と B の事後分布がそれぞれ平均 0.10 と 0.12 を中心に細い山型に分布しており、両者の重なりがほとんどないことが見て取れます。これが「B が確実に A より良い」という直感の数学的表現です。右のグラフは差 $p_B – p_A$ の事後分布で、ほぼすべての質量が 0 より右側にあり、95% クレディブル区間が $[0.011, 0.026]$ と読み取れます。$p_B = p_A$ を表す垂直線(黒)は分布の左裾のさらに外側にあり、両案が等しい可能性はほぼ無視できるレベルです。

Python での実装:ベイズ早期停止シミュレータ

逐次観測のシミュレーション

ベイズ早期停止が実際にどう機能するか、シミュレーションで体感します。真の CTR を $p_A^* = 0.10, p_B^* = 0.11$ と固定し、1日ごとに 100 ずつ観測を増やしていって、期待損失が閾値以下になったタイミングで止めるシミュレーションです。

import numpy as np

def simulate_bayesian_ab(p_a_true, p_b_true, daily_n=100, max_days=200,
                          epsilon=1e-4, n_mc=50000, seed=0):
    """ベイズ A/B テストの早期停止シミュレーション

    停止ルール: min(expected_loss_A, expected_loss_B) < epsilon
    """
    rng = np.random.default_rng(seed)
    test = BayesianABTest(prior_a=(1.0, 1.0), prior_b=(1.0, 1.0))

    history = []
    for day in range(1, max_days + 1):
        # 1日ぶんの観測を生成
        x_a = rng.binomial(daily_n, p_a_true)
        x_b = rng.binomial(daily_n, p_b_true)
        test.update(x_a, daily_n, x_b, daily_n)

        # 評価
        p_a_samp, p_b_samp = test.posterior_samples(n_mc, seed=day)
        prob_b_better = float(np.mean(p_b_samp > p_a_samp))
        loss_a = float(np.mean(np.maximum(p_b_samp - p_a_samp, 0)))
        loss_b = float(np.mean(np.maximum(p_a_samp - p_b_samp, 0)))

        history.append({
            'day': day,
            'n_total_per_arm': day * daily_n,
            'prob_b_better': prob_b_better,
            'loss_a': loss_a,
            'loss_b': loss_b,
        })

        # 停止判定
        if min(loss_a, loss_b) < epsilon:
            winner = 'B' if loss_b < loss_a else 'A'
            return history, day, winner

    return history, max_days, None

# 実行
history, stop_day, winner = simulate_bayesian_ab(
    p_a_true=0.10, p_b_true=0.11, daily_n=200, max_days=200,
    epsilon=1e-4, seed=42
)
print(f"停止日: Day {stop_day}, 採用案: {winner}")
print(f"その時点の総観測数(各アーム): {history[stop_day-1]['n_total_per_arm']}")
print(f"P(B > A) = {history[stop_day-1]['prob_b_better']:.4f}")

このシミュレーションを実行すると、典型的には Day 50〜80 程度(つまり各アーム 10000〜16000 サンプル程度)で停止し、正しく B を採用します。$p_B^* – p_A^* = 0.01$ という小さな差をベイズ早期停止が捉えていることがわかります。

推移の可視化

途中の確率と期待損失の推移をプロットします。

import matplotlib.pyplot as plt
import numpy as np

days = [h['day'] for h in history]
probs = [h['prob_b_better'] for h in history]
loss_a_vals = [h['loss_a'] for h in history]
loss_b_vals = [h['loss_b'] for h in history]

fig, axes = plt.subplots(1, 2, figsize=(12, 4.5))

# 左:P(B>A) の推移
ax = axes[0]
ax.plot(days, probs, '-o', markersize=3, label='P(p_B > p_A)')
ax.axhline(0.95, color='red', linestyle='--', alpha=0.6, label='95% threshold')
ax.axvline(stop_day, color='green', linestyle=':', alpha=0.6, label=f'stop @ day {stop_day}')
ax.set_xlabel('Day')
ax.set_ylabel('P(B > A)')
ax.set_title('Probability that B is better, over time')
ax.legend()
ax.grid(True, alpha=0.3)

# 右:期待損失の推移
ax = axes[1]
ax.semilogy(days, loss_a_vals, '-o', markersize=3, label='Expected loss (choose A)')
ax.semilogy(days, loss_b_vals, '-s', markersize=3, label='Expected loss (choose B)')
ax.axhline(1e-4, color='red', linestyle='--', alpha=0.6, label='epsilon=1e-4')
ax.axvline(stop_day, color='green', linestyle=':', alpha=0.6)
ax.set_xlabel('Day')
ax.set_ylabel('Expected loss (log scale)')
ax.set_title('Expected loss over time')
ax.legend()
ax.grid(True, alpha=0.3, which='both')

plt.tight_layout()
plt.savefig('bayes_ab_stopping.png', dpi=150, bbox_inches='tight')
plt.show()

左図では、$P(p_B > p_A)$ が 0 日目から徐々に上昇し、Day 50 付近で 0.95 を突破して以降は 1 に張り付いていく様子が見えます。右図(対数スケール)では、B 採用の期待損失が日を追って急速に下がっていき、閾値 $\epsilon = 10^{-4}$ に交差した時点で停止しています。A 採用の期待損失は逆に上昇しており、「A を選ぶ間違いの代償」が時とともに大きくなることが反映されています。これがベイズ早期停止の核心:途中で何度見ても、その時点の事後分布が「今止めて良いか」の答えをそのまま教えてくれます。

偽陽性率の評価(operating characteristics)

ベイズ早期停止が「実用的に妥当か」を確認するには、同じ条件で多数回シミュレーションを回して経験的な誤判定率を測ります。

import numpy as np

def evaluate_stopping_rule(p_a_true, p_b_true, n_trials=200, **kwargs):
    """停止ルールを n_trials 回シミュレーションして誤判定率を評価"""
    decisions = []
    stop_days = []
    for trial in range(n_trials):
        history, stop_day, winner = simulate_bayesian_ab(
            p_a_true, p_b_true, seed=trial, **kwargs
        )
        decisions.append(winner)
        stop_days.append(stop_day)
    return decisions, stop_days

# シナリオ1: p_A = p_B = 0.10(差なし)→ False discovery を測る
decisions_null, days_null = evaluate_stopping_rule(
    0.10, 0.10, n_trials=100, daily_n=200, max_days=200, epsilon=1e-4
)
n_decided_null = sum(d is not None for d in decisions_null)
print(f"[差なし] 停止に至った回数: {n_decided_null}/100")
print(f"[差なし] 誤って B を採用: {decisions_null.count('B')}/100")
print(f"[差なし] 誤って A を採用: {decisions_null.count('A')}/100")

# シナリオ2: p_B = 0.11(差あり)→ Power を測る
decisions_alt, days_alt = evaluate_stopping_rule(
    0.10, 0.11, n_trials=100, daily_n=200, max_days=200, epsilon=1e-4
)
n_decided_alt = sum(d is not None for d in decisions_alt)
print(f"[差あり] 停止に至った回数: {n_decided_alt}/100")
print(f"[差あり] 正しく B を採用: {decisions_alt.count('B')}/100")
print(f"[差あり] 誤って A を採用: {decisions_alt.count('A')}/100")
print(f"[差あり] 平均停止日: {np.mean(days_alt):.1f}日")

このシミュレーションから、$\epsilon = 10^{-4}$ という閾値が、差がない場合の誤判定率を 5% 未満に抑えつつ、$p_B – p_A = 0.01$ という小さな差をほぼ 95% 以上の確率で検出できることが確認できます。停止までの平均日数も、頻度論の固定サンプルサイズ計算(power 80% で 14000 程度)よりむしろ少なく済むケースが多いです。これが、ベイズ早期停止が「peeking 罰金」なしで実装できる経済的優位性です。

Python での実装:多腕拡張のデモ

K 腕版の事後評価

3 つ以上の腕に拡張する場合も同じ要領です。各腕にベータ事前を置き、独立に事後分布を更新します。

import numpy as np

class BayesianMultiArmTest:
    """K 腕ベイズ A/B テスト"""

    def __init__(self, K, prior=(1.0, 1.0)):
        self.K = K
        self.alphas = np.full(K, prior[0])
        self.betas  = np.full(K, prior[1])

    def update(self, arm, x, n):
        self.alphas[arm] += x
        self.betas[arm]  += n - x

    def posterior_samples(self, n_samples=100000, seed=0):
        rng = np.random.default_rng(seed)
        # 各腕の事後分布から同時にサンプリング (n_samples, K)
        samples = np.column_stack([
            rng.beta(self.alphas[k], self.betas[k], size=n_samples)
            for k in range(self.K)
        ])
        return samples

    def prob_each_is_best(self, n_samples=100000, seed=0):
        """各腕が最良である確率 P(p_k = max_j p_j | D)"""
        samples = self.posterior_samples(n_samples, seed)
        best = np.argmax(samples, axis=1)
        return np.array([np.mean(best == k) for k in range(self.K)])

    def expected_loss(self, choose, n_samples=100000, seed=0):
        """選択した腕の期待損失: E[max_j p_j - p_choose | D]"""
        samples = self.posterior_samples(n_samples, seed)
        best = np.max(samples, axis=1)
        return float(np.mean(best - samples[:, choose]))

# 4 腕での例:真の CTR が 0.10, 0.11, 0.12, 0.105
np.random.seed(0)
true_ctrs = [0.10, 0.11, 0.12, 0.105]
n_per_arm = 5000
mat_test = BayesianMultiArmTest(K=4)
for k, p in enumerate(true_ctrs):
    x = np.random.binomial(n_per_arm, p)
    mat_test.update(k, x, n_per_arm)

probs = mat_test.prob_each_is_best()
losses = [mat_test.expected_loss(k) for k in range(4)]
for k in range(4):
    print(f"Arm {k}: P(best)={probs[k]:.3f}, E[loss]={losses[k]:.5f}")

実行すると、典型的には Arm 2(真の CTR 0.12)が「最良である確率」0.85〜0.95 程度、期待損失も最小という形で同定されます。多腕の評価がベイズ流では「ペアワイズ検定 + 補正」のような重装備を必要とせず、シームレスに 1 つの事後分布から計算できる点が顕著です。

Thompson Sampling との接続

最後に、上の多腕事後分布が Thompson Sampling のコアとそのまま接続することを示します。

import numpy as np

def thompson_sampling(test, n_rounds=10000, true_ctrs=None, seed=0):
    """Thompson Sampling: 事後分布からサンプリングして最良腕を選ぶ"""
    rng = np.random.default_rng(seed)
    K = test.K
    pulls = np.zeros(K, dtype=int)
    reward = 0.0
    for t in range(n_rounds):
        # 各腕の事後からサンプル
        samples = np.array([
            rng.beta(test.alphas[k], test.betas[k])
            for k in range(K)
        ])
        chosen = int(np.argmax(samples))
        # 真の確率で報酬を生成
        r = int(rng.random() < true_ctrs[chosen])
        test.update(chosen, r, 1)
        pulls[chosen] += 1
        reward += r
    return pulls, reward

# まっさらな状態から Thompson Sampling を 10000 ラウンド
ts_test = BayesianMultiArmTest(K=4)
pulls, reward = thompson_sampling(ts_test, n_rounds=10000, true_ctrs=true_ctrs, seed=0)
print(f"Pulls: {pulls}")
print(f"Total reward: {reward}")
print(f"Best arm pull rate: {pulls[2] / 10000:.2%}")

このコードでは、最良腕(Arm 2、真の CTR 0.12)が 10000 ラウンドのうち 8000 回以上選ばれる傾向が見て取れます。等配分の A/B テスト(各 2500 回)と比べると、Thompson Sampling は劣った腕への配分を自動的に減らし、累積報酬を最大化します。これが「テストと運用を同時に進める」適応的 A/B テストの実装です。詳しくは Thompson Sampling の記事 を参照してください。

実装上の注意点と PyMC との比較

解析解で十分なケース、PyMC が必要なケース

本記事で扱った「ベルヌーイ-ベータ共役」モデルは、事後分布が解析的にベータ分布になるという幸運な構造を持っていました。共役性のおかげで Monte Carlo サンプリングも numpy.random.beta で一発で済みます。

しかし実務では、もう少し複雑なモデルが必要になることもあります。

  • 階層ベイズ — 複数の AB テスト(例えば異なる国・セグメント)の結果を共有事前で結びつける。
  • 継続量の比較 — 売上、滞在時間など連続値の比較。正規分布や対数正規分布が必要。
  • 共変量の調整 — ユーザーセグメントや時間効果を回帰モデルで取り込む。

これらの場合は共役性が壊れるため、MCMC や変分推論のような汎用サンプラが必要になります。Python の代表的なツールが PyMC で、確率モデルを Python の DSL で記述し、自動的に NUTS(Hamiltonian Monte Carlo の改良版)でサンプリングしてくれます。

実例として、シンプルな A/B テストを PyMC で書くと次のようになります(参考までに)。

import pymc as pm
import numpy as np

# 観測データ
x_a, n_a = 1010, 10000
x_b, n_b = 1180, 10000

with pm.Model() as model:
    # 事前分布
    p_a = pm.Beta('p_a', alpha=1, beta=1)
    p_b = pm.Beta('p_b', alpha=1, beta=1)
    # 尤度
    obs_a = pm.Binomial('obs_a', n=n_a, p=p_a, observed=x_a)
    obs_b = pm.Binomial('obs_b', n=n_b, p=p_b, observed=x_b)
    # 派生量
    diff = pm.Deterministic('diff', p_b - p_a)
    # サンプリング
    trace = pm.sample(2000, tune=1000, chains=4, random_seed=0, progressbar=False)

# 結果
diff_samples = trace.posterior['diff'].values.flatten()
print(f"P(B > A) = {np.mean(diff_samples > 0):.4f}")
print(f"E[B - A] = {np.mean(diff_samples):.4f}")
print(f"95% CrI: [{np.percentile(diff_samples, 2.5):.4f}, {np.percentile(diff_samples, 97.5):.4f}]")

このコードは本記事の解析解とほぼ同じ結果($P(B>A) \approx 0.9999$、$E[B-A] \approx 0.017$)を返します。共役モデルではわざわざ PyMC を使う必要はありませんが、モデルを拡張したい場合に書き換えやすい構造になっているのが PyMC の利点です。たとえば階層モデルや共変量を追加するときに、本質的な変更だけを差し込んで済みます。

PyMC でのベルヌーイベイズ推論記事 でも関連する内容を扱っているので、汎用的なベイズモデリングに進みたい方はそちらも参照してください。

計算速度のトレードオフ

解析解(共役モデル + Monte Carlo)は、PyMC の MCMC より圧倒的に高速です。本記事のクラス BayesianABTest は $10^5$ サンプル生成が数十ミリ秒程度ですが、PyMC の NUTS サンプリングは数秒〜数十秒かかります。リアルタイムな意思決定基盤に組み込むなら、できる限り共役モデルを使い、解析解で済ますのが定石です。

まとめ

本記事では、ベイズ A/B テストを意思決定の道具として使いこなすために必要な理論と実装を一通り解説しました。

  • 頻度論の限界 — $p$ 値は「データを所与とした B が良い確率」を直接答えない。peeking や p-hacking は補正なしに使うと第一種の過誤率を膨らませる。
  • ベイズ流の基本構造 — 事前分布、尤度、事後分布をベイズの定理で結びつけ、事後分布から意思決定指標を直接計算する。
  • ベルヌーイ-ベータ共役モデル — 事後分布が解析的に $\mathrm{Beta}(\alpha^{(0)} + x, \beta^{(0)} + n – x)$ となり、逐次更新が極めて簡単。CTR 比較の標準ツール。
  • 意思決定指標 — $P(p_B > p_A \mid D)$、期待損失 $E[\max(p_A – p_B, 0)]$、差のクレディブル区間、効果量の期待値。それぞれ意思決定者にとって直感的に意味のある量。
  • クレディブル区間 — 「データを所与としてパラメータが区間に入る確率」がそのまま 95% と言える。頻度論の信頼区間とは数学的に異なる解釈を持つ。
  • ベイズ早期停止 — 事後分布が観測データのみに依存するため、何度途中で評価しても問題ない。期待損失閾値ルールが実務の標準。
  • 多腕拡張と Thompson Sampling — 同じ枠組みで K 腕に拡張でき、適応的トラフィック配分へ自然に接続。
  • サンプルサイズ計画 — precision-based に「目標精度に達するまで観測」と設計する。
  • 事前分布の選択 — 弱情報的事前 $\mathrm{Beta}(1,1)$ や Jeffreys prior が標準。複数事前で敏感度分析を行うのが安全。
  • 解析解 vs PyMC — 共役モデルは解析解で十分かつ高速。複雑モデルでは PyMC で汎用 MCMC へ。

ベイズ A/B テストは「数学的に正しい厳密さ」と「現場の意思決定者が直感的に解釈できるリッチさ」を両立する稀有なフレームワークです。Microsoft Bing、Netflix、Booking などが実プロダクトで採用していることが、その実務的価値を物語っています。

次のステップとして、以下の記事も参考にしてください。