Mímisbrunnr知恵の泉

← マーケティングサイエンス 一覧

🎓 レベル:標準 | 重要度:A(必須)

📎 関連:第7章 実験と因果推論 目次 | 前提:A/Bテストの設計と分析

要点(BLUF)

1. 頻度論からベイズへ

A/Bテストの設計と分析pp 値と信頼区間で「偶然か」を判定しました。けれど現場が本当に知りたいのは、たいてい**「結局 B のほうが良いのか、どれくらいの確率で?」「B に切り替えて損する見込みはどれだけか?」**です。pp 値はこれらに直接答えません(pp 値は「効果がないと仮定したときの希少性」であって「B が良い確率」ではない)。

ベイズ A/B は発想を逆にします。データを固定し、未知の反応率 pA,pBp_A,p_B のほうを確率変数として、データを見たあとの分布=事後分布を求めます。事後分布さえ手に入れば、意思決定に必要な量は全部そこから読めます。

flowchart LR
  D["観測データ<br/>(CV数 x, 試行 n)"] --> PA["事後 Beta_A(p_A の分布)"]
  D --> PB["事後 Beta_B(p_B の分布)"]
  PA --> S["両事後から大量サンプリング"]
  PB --> S
  S --> Q1["P(p_B > p_A)"]
  S --> Q2["増分 p_B − p_A の確信区間"]
  S --> Q3["期待損失(意思決定)"]

2. Beta–二項共役(数式)

コンバージョンは「買う/買わない」のベルヌーイ試行で、群の反応率 pp が成功確率です。pp への事前を Beta(α,β)\text{Beta}(\alpha,\beta) とすると、その密度は pα1(1p)β1p^{\alpha-1}(1-p)^{\beta-1} に比例します。nn 試行で xx 件 CV したデータの尤度は二項なので px(1p)nxp^{x}(1-p)^{n-x} に比例。事後はベイズの定理で両者の積に比例し、

pα1(1p)β1事前×px(1p)nx尤度    p(α+x)1(1p)(β+nx)1\underbrace{p^{\alpha-1}(1-p)^{\beta-1}}_{\text{事前}}\times \underbrace{p^{x}(1-p)^{n-x}}_{\text{尤度}} \;\propto\; p^{(\alpha+x)-1}(1-p)^{(\beta+n-x)-1}

これは Beta(α+x, β+nx)\text{Beta}(\alpha+x,\ \beta+n-x) の密度そのもの。事前と事後が同じ Beta 族にとどまるこの性質を共役(conjugacy)と呼びます。だから事後はパラメータの足し算だけで求まり、MCMC のような数値近似は要りません。Beta(1,1)\text{Beta}(1,1)[0,1][0,1] 上の一様分布で、「何も知らない」に近い弱情報事前です。

意思決定量は、両群の事後 BetaA,BetaB\text{Beta}_A,\text{Beta}_B から大量にサンプリングして読みます。

P(pB>pA)1Mm=1M1 ⁣[pB(m)>pA(m)],pA(m)BetaA, pB(m)BetaBP(p_B>p_A)\approx\frac{1}{M}\sum_{m=1}^{M}\mathbf{1}\!\left[p_B^{(m)}>p_A^{(m)}\right],\qquad p_A^{(m)}\sim\text{Beta}_A,\ p_B^{(m)}\sim\text{Beta}_B

増分 pBpAp_B-p_A の事後は差のサンプル {pB(m)pA(m)}\{p_B^{(m)}-p_A^{(m)}\} で、その 2.5/97.52.5/97.5 パーセンタイルが 95% 確信区間です。そして B を選んだときの期待損失

Loss(B)=E ⁣[max(pApB, 0)]\text{Loss}(B)=\mathbb{E}\!\left[\max(p_A-p_B,\ 0)\right]

——B を選んだのに実は A のほうが良かった、その取りこぼし pApBp_A-p_B(負なら 00)の期待値です。これが意思決定の通貨になります。

3. 事後分布から意思決定する(コード)

A/Bテストの設計と分析同じ設定・同じ生成順pA=0.10,pB=0.12p_A=0.10,p_B=0.12、各群 n=6000n=6000)でデータを作り、一様事前 Beta(1,1)\text{Beta}(1,1) の事後から 1010 万サンプルして、P(pB>pA)P(p_B>p_A)・増分の事後平均と 95% 確信区間・両群の期待損失を出します。

import numpy as np

rng = np.random.default_rng(0)
n_A = n_B = 6000
# 07-01 と同じ設定・同じ生成順なので CV数も一致する
conv_A = rng.binomial(1, 0.10, n_A)
conv_B = rng.binomial(1, 0.12, n_B)
x_A, x_B = conv_A.sum(), conv_B.sum()

# 事前 Beta(1,1)(一様)+二項データ → 事後 Beta(1+CV, 1+非CV)
a0 = b0 = 1.0
postA_a, postA_b = a0 + x_A, b0 + (n_A - x_A)
postB_a, postB_b = a0 + x_B, b0 + (n_B - x_B)

# 各事後から10万サンプル(共役なのでMCMC不要)
M = 100_000
sA = rng.beta(postA_a, postA_b, M)
sB = rng.beta(postB_a, postB_b, M)

p_B_better = np.mean(sB > sA)            # P(p_B > p_A)
diff = sB - sA                            # 増分の事後サンプル
ci_lo, ci_hi = np.percentile(diff, [2.5, 97.5])
loss_B = np.mean(np.maximum(sA - sB, 0))  # Bを選ぶときの期待損失
loss_A = np.mean(np.maximum(sB - sA, 0))  # Aを選ぶときの期待損失

print(f"対照A: CV {x_A}/{n_A}  事後平均CVR = {postA_a / (postA_a + postA_b):.4f}")
print(f"介入B: CV {x_B}/{n_B}  事後平均CVR = {postB_a / (postB_a + postB_b):.4f}")
print(f"P(p_B > p_A) = {p_B_better:.4f}")
print(f"増分の事後平均 = {diff.mean():+.4f}")
print(f"増分の95%確信区間 = [{ci_lo:+.4f}, {ci_hi:+.4f}]")
print(f"Bを選ぶ期待損失 = {loss_B:.6f}")
print(f"Aを選ぶ期待損失 = {loss_A:.6f}")

出力:

対照A: CV 569/6000  事後平均CVR = 0.0950
介入B: CV 724/6000  事後平均CVR = 0.1208
P(p_B > p_A) = 1.0000
増分の事後平均 = +0.0258
増分の95%確信区間 = [+0.0147, +0.0369]
Bを選ぶ期待損失 = 0.000000
Aを選ぶ期待損失 = 0.025839

出力の意味:事後平均 CVR は対照 0.09500.0950・介入 0.12080.1208(一様事前なので 1+x2+n\frac{1+x}{2+n}、生データ 0.0948/0.12070.0948/0.1207 をほぼそのまま)。P(pB>pA)=1.0000P(p_B>p_A)=1.0000 は「1010 万サンプルで B が A に負けた回数がゼロ」という意味で、これは「B が優れている確率がほぼ 11」とそのまま読めます——pp 値とはここが決定的に違います(厳密には 0.999998\approx0.999998、A が勝つのは約 4040 万回に1回で、文字どおりの 11 ではありません)。増分の事後平均 +0.0258+0.0258、95% 確信区間 [+0.0147,+0.0369][+0.0147,+0.0369]A/Bテストの設計と分析信頼区間と数値はほぼ一致しますが、意味は別物(後述)。極めつけは期待損失で、B を選ぶ期待損失はほぼ 00(取りこぼしの心配なし)、A を選ぶ期待損失は 0.02580.0258(A に留まると平均 2.62.6 ポイントぶん取りこぼす)。つまり「迷わず B」という意思決定が、確率と損失の言葉で直接出ます。

両群の事後分布と、増分の事後分布を描きます。

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

rng = np.random.default_rng(0)
n_A = n_B = 6000
conv_A = rng.binomial(1, 0.10, n_A)
conv_B = rng.binomial(1, 0.12, n_B)
x_A, x_B = conv_A.sum(), conv_B.sum()
postA = stats.beta(1 + x_A, 1 + n_A - x_A)
postB = stats.beta(1 + x_B, 1 + n_B - x_B)
sA = rng.beta(1 + x_A, 1 + n_A - x_A, 100_000)
sB = rng.beta(1 + x_B, 1 + n_B - x_B, 100_000)
diff = sB - sA

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(9.4, 4.2))
xs = np.linspace(0.085, 0.140, 400)
ax1.plot(xs, postA.pdf(xs), color="C0", lw=2, label="対照A の事後")
ax1.plot(xs, postB.pdf(xs), color="C3", lw=2, label="介入B の事後")
ax1.fill_between(xs, postA.pdf(xs), alpha=0.20, color="C0")
ax1.fill_between(xs, postB.pdf(xs), alpha=0.20, color="C3")
ax1.set_xlabel("コンバージョン率 p"); ax1.set_ylabel("事後密度")
ax1.set_title("各群のCVRの事後分布"); ax1.legend()

ax2.hist(diff, bins=60, color="C2", alpha=0.85, density=True)
ax2.axvline(0, ls="--", color="black", label="差なし")
ax2.axvline(diff.mean(), ls="-", color="C3", lw=2, label=f"事後平均 {diff.mean():+.3f}")
ax2.set_xlabel("増分 p_B − p_A"); ax2.set_ylabel("事後密度")
ax2.set_title("増分の事後分布(ほぼ全域が正)"); ax2.legend()

fig.suptitle("ベイズA/B:事後分布から P(B>A)・増分・期待損失を読む")
fig.tight_layout()
plt.show()

左パネルでは対照 A(青、0.095\approx0.095 中心)と介入 B(赤、0.121\approx0.121 中心)の事後がほとんど重ならず、B が右にずれています。右パネルの増分の事後はほぼ全域が 00 より右で、これが「P(pB>pA)1P(p_B>p_A)\approx1」を絵にしたものです。区間幅が事後の不確実性、00 までの距離が効果の確かさを表します。

4. 事前の影響・のぞき見・意思決定の閾値(コード)

§3 の結論は決定的でしたが、それはデータが十分(各群 60006000)だからです。一様事前 Beta(1,1)\text{Beta}(1,1) なら、これだけデータがあれば事後はデータがほぼ決め、事前は無視できます。問題はまだデータが少ない序盤に“のぞき見”したとき。同じデータを n=300,1000,6000n=300,1000,6000 の途中段階で見て、一様事前と強い事前 Beta(200,1800)\text{Beta}(200,1800)(平均 0.100.10・事前の重み約 20002000 件ぶん)で P(pB>pA)P(p_B>p_A) と期待損失がどう変わるかを見ます。

import numpy as np

rng = np.random.default_rng(0)
n_full = 6000
conv_A = rng.binomial(1, 0.10, n_full)
conv_B = rng.binomial(1, 0.12, n_full)

def decide(n_peek, a0, b0, M=500_000, seed=7):
    xa = conv_A[:n_peek].sum()
    xb = conv_B[:n_peek].sum()
    g = np.random.default_rng(seed)
    sA = g.beta(a0 + xa, b0 + n_peek - xa, M)
    sB = g.beta(a0 + xb, b0 + n_peek - xb, M)
    return xa, xb, np.mean(sB > sA), np.mean(np.maximum(sA - sB, 0))

print("のぞき見:経過途中の n でのベイズ判断(真は B が +0.02 良い)")
for n_peek in (300, 1000, 6000):
    xa, xb, p_u, loss_u = decide(n_peek, 1, 1)        # 一様 Beta(1,1)
    _,  _,  p_s, _      = decide(n_peek, 200, 1800)   # 強い事前 Beta(200,1800)
    print(f"n={n_peek:>4}/群  CVR_A={xa / n_peek:.3f} CVR_B={xb / n_peek:.3f}  "
          f"P(B>A) 一様={p_u:.3f} 強い事前={p_s:.3f}  Bの期待損失(一様)={loss_u:.4f}")

出力:

のぞき見:経過途中の n でのベイズ判断(真は B が +0.02 良い)
n= 300/群  CVR_A=0.140 CVR_B=0.127  P(B>A) 一様=0.317 強い事前=0.423  Bの期待損失(一様)=0.0189
n=1000/群  CVR_A=0.096 CVR_B=0.112  P(B>A) 一様=0.879 強い事前=0.753  Bの期待損失(一様)=0.0008
n=6000/群  CVR_A=0.095 CVR_B=0.121  P(B>A) 一様=1.000 強い事前=1.000  Bの期待損失(一様)=0.0000

出力の意味:3つの教訓が同時に出ています。第一にのぞき見は危険——n=300n=300 では雑音で対照 A のほうが高く見え(CVRA=0.140>CVRB=0.127\text{CVR}_A=0.140>\text{CVR}_B=0.127)、P(pB>pA)P(p_B>p_A) はわずか 0.3170.317。真は B が良いのに、ここで「A が勝っている」と早期停止したら真に優れた B を捨てるところでした。第二に事前の影響——同じ n=300n=300 で強い事前 Beta(200,1800)\text{Beta}(200,1800)P(B>A)P(B>A)0.3170.4230.317\to0.4230.50.5 側に引き戻し、少データの雑音を正則化します(が、n=6000n=6000 では一様・強い事前とも 1.0001.000差は消滅=データが事前を圧倒)。第三に期待損失が意思決定の通貨——B の期待損失は nn とともに 0.01890.00080.00000.0189\to0.0008\to0.0000 と縮みます。「B の期待損失が閾値(例:0.0010.0010.10.1 ポイント)を下回ったら採用」のように閾値で止めれば、のぞき見の害を損失の言葉で制御できます。頻度論ののぞき見が α\alpha を膨らませるのに対し、ベイズは期待損失という連続量で停止規準を設計できるのが実務上の強みです(ただし後述のとおり、ベイズでものぞき見そのものが無害になるわけではありません)。

⚠️ よくある誤解

関連ノート