Dev Study
← 評価のための統計
レッスン 1 / 10·約 9 分·第22期 評価のための統計

何も変えなくても、数字は動く

リクエストがアプリケーションに届くまで
  1. ブラウザURL入力
  2. DNS名前→IP
  3. ルータ経路選択
  4. ロードバランサ振り分け
  5. Nginx受け口
  6. アプリ処理今ここ
  7. DB保存

このコースの芯は「その差は、何も変えなくても出る大きさではないのか」です。最初に、何も変えないときの数字の動き方を見ます。

まず結論

同じページを利用者2群に見せて何も変えずに比べても(A/A テスト)、率は群ごとに違って出ます。真の率5%・各群1000人なら、相対で20%以上ずれる確率は約31%です。

改善の数字を読む前に「何も変えなかったら、どれくらい動くのか」を知っておく必要があります。入力を固定して消せる揺れと、固定すると比べる意味が消える揺れは、分けて扱います。

? 読む前に予測

真の購入率がどちらも5%の同じページを、各1000人の2群に見せて比べます(何も変えていません)。2群の率が相対で20%以上ずれる確率はどれくらいでしょうか。

当てることが目的ではありません。外した予測ほど記憶に残るので、直感で選んでください。

抽象原理

同じものを見せても、見る人が違う

新しいボタンで購入率が5.4%から5.8%に上がった。そう聞いたら、まず問うべきことがあります。ボタンを変えなかったら、数字はどれくらい動いていたのか。

これを実際に確かめる実験が A/A テストです。Kohavi らは2007年の論文でこう定義しています。

Instead of an A/B test, you exercise the experimentation system, assigning users to one of two groups, but expose them to exactly the same experience.

2群に分けて、両方に全く同じものを見せる。それでも率は違って出ます。ページは同じでも、どちらの群に誰が入ったかが毎回違うからです。買う気の強い人が片方に少し多く入れば、それだけで率は動きます。

A/A テスト: 何も変えていないのに差が出る理由
同じページA と B で中身は全く同じ
利用者をランダムに2群へ
A 群 1000人誰が入るかは毎回違う
B 群 1000人誰が入るかは毎回違う
それぞれ率を集計
率の差何も変えていないのに0にならない
  • ・揺れの出どころはページではなく、群に入った人の顔ぶれ
  • ・真の率5%・各群1000人なら、差の標準偏差は理論上約0.97ポイント(5%に対して約2割)

同じ論文は、A/A で何が起きるべきかも書いています。

the Null hypothesis should be rejected about 5% of the time when a 95% confidence level is used

Null hypothesis(帰無仮説)は、ここでは「何も変わっていない世界」のことです。何も変えていないのに、約5%の回は「統計的に有意」と判定されるのが正常だ、という意味です。

具体実物

真の率5%・各群1000人の A/A を10回

標準ライブラリだけの模型です。両群とも真の率を5%に固定し、1000人ずつ「買ったかどうか」を乱数で決めます。

# L1: A/A test. true rate 5% in both groups, 1000 users each, 10 runs. stdlib only.
import random, sys, math
seed = int(sys.argv[1]) if len(sys.argv) > 1 else 1
rng = random.Random(seed)
print(f"seed={seed}  true 5% both, n=1000 each, 10 runs")
rels = []
for i in range(10):
    a = sum(rng.random() < 0.05 for _ in range(1000))
    b = sum(rng.random() < 0.05 for _ in range(1000))
    rel = (b - a) / a * 100
    rels.append(rel)
    print(f"  run{i}: A={a/10:.1f}% B={b/10:.1f}% diff={(b-a)/10:+.1f}pt rel={rel:+.0f}%")
print(f"  range of rel: {min(rels):+.0f}% .. {max(rels):+.0f}%")
$ python3 l1-aa.py 1(macOS 26.5.2・Python 3.14.6)コマンド出力
seed=1 true 5% both, n=1000 each, 10 runs
run0: A=5.4% B=5.8% diff=+0.4pt rel=+7%
run1: A=5.8% B=5.0% diff=-0.8pt rel=-14%
run2: A=4.8% B=4.2% diff=-0.6pt rel=-12%
run3: A=5.2% B=5.5% diff=+0.3pt rel=+6%
run4: A=5.5% B=6.5% diff=+1.0pt rel=+18%
run5: A=6.0% B=3.7% diff=-2.3pt rel=-38%
run6: A=5.5% B=4.9% diff=-0.6pt rel=-11%
run7: A=4.9% B=5.2% diff=+0.3pt rel=+6%
run8: A=5.1% B=5.1% diff=+0.0pt rel=+0%
run9: A=4.5% B=4.2% diff=-0.3pt rel=-7%
range of rel: -38% .. +18%

冒頭の「5.4%から5.8%」は run0 です。何も変えていません。run4 では +18%、run5 では −38% 動きました。

ただし、この10回は seed=1 の1組にすぎません。seed=3 では最大 +51% が出ました。10回を1組として2000組回すと、各組の最小の中央値は約 −26%、最大の中央値は約 +35% でした。正側に伸びやすいのは、相対差の分母が A だからです。

1回の A/A で相対差が20%以上になる確率を二項分布で厳密に計算すると 0.3095、10%以上なら 0.6122 です。「統計的に有意」と出た割合は、seed=1 と seed=2 でそれぞれ2万回模擬すると5.2%と4.8%でした。どちらも論文の言う約5%です。

✓ ここまでの確認

A/A テストを100回回し、95%の基準で約5回「統計的に有意」と出ました。Kohavi らの定義に照らした読み方として適切なのはどれですか。

抽象原理

揺れには、消してよいものと消せないものがある

2012年の論文で、Kohavi らは A/A をこう評価しています。

The A/A test has been our most useful tool in identifying issues in practical systems. We strongly recommend that every practical system continuously run A/A tests.

有用なのは、揺れの大きさを先に知っておけるからです。2007年の論文も用途を2つ挙げます。検出力の計算に使うばらつきを集めること、そして実験基盤そのものを検査することです。A/A で効果が分かる、とは書いていません。 何も変えていないのですから。

ここで一般化できます。「同じもの」を2回測って数字が違うとき、その間に毎回違う入力が入っています。問うべきは、その入力を固定してよいかです。

  • 固定してよい入力(テストデータ、実行環境など)なら、固定して揺れを消す
  • 固定すると比べる意味が消える入力(誰が群に入るか)なら、揺れの大きさを知ったうえで読む

後者の揺れを件数や設計で縮める方法は、このコースで順に見ていきます。

具体実物

入力を固定すると縮むベンチマーク

対比のために、同じ処理を20回計るベンチマークを作りました。100万要素のリストから値を探す data.index の時間です。random は毎回探す値を乱数で作り直し、fixed は毎回同じ値(500000)を探します。

# L1 contrast: "same benchmark" timed 20 times. mode random = new input each run, fixed = same input.
import random, sys, time, statistics as st
seed = int(sys.argv[1]) if len(sys.argv) > 1 else 1
rng = random.Random(seed)
data = list(range(1_000_000))
def run(target):
    t0 = time.perf_counter(); data.index(target); return (time.perf_counter() - t0) * 1000
for mode in ("random", "fixed"):
    ts = [run(rng.randrange(len(data)) if mode == "random" else 500_000) for _ in range(20)]
    print(f"seed={seed} {mode:6s} min={min(ts):6.2f}ms median={st.median(ts):6.2f}ms max={max(ts):6.2f}ms max/min={max(ts)/min(ts):7.1f}")
$ python3 l1-bench.py 1 を2回(macOS 26.5.2・Python 3.14.6。2回分を連結)コマンド出力
seed=1 random min= 0.16ms median= 2.73ms max= 5.15ms max/min= 32.1
seed=1 fixed min= 2.68ms median= 2.71ms max= 2.75ms max/min= 1.0
seed=1 random min= 0.16ms median= 2.69ms max= 5.03ms max/min= 31.8
seed=1 fixed min= 2.69ms median= 2.72ms max= 2.80ms max/min= 1.0

入力を作り直すと、同じ「ベンチマーク」が 0.16〜5.15 ms に散りました。探す値が先頭に近いか奥にあるかで、仕事の量そのものが違うからです。入力を固定すると 2.68〜2.75 ms に縮みます。スクリプトを10回走らせ直しても、fixed は全回が 2.63〜3.03 ms に収まりました。

それでも完全には一致しません。同じ seed の2回目も少し違います。残りは入力以外の揺れです。この Mac では原因を特定していませんが、周波数の変動などの実行環境が候補で、固定できる場合もあります。なお ms の値は機械ごとに変わります。見るべきは値そのものではなく、random と fixed の幅の違いです。

✓ ここまでの確認

A/A のずれを、ベンチマークと同じく「入力を固定する」で消そうとして、同じ1000人の同じ記録を A 群・B 群として2回集計しました。どうなりますか。

⇄ 2つを見比べる
具体例 A

A/A テスト(真の率5%・各群1000人)

揺れの出どころは、誰がどちらの群に入ったか。これは実験の中身そのものなので固定できない。1回で相対20%以上ずれる確率は約31%。消すのではなく、大きさを知って読む。

何も変えない1回で相対差がこれ以上になる確率(厳密計算)表
相対差 確率
10%以上 0.6122
20%以上 0.3095
30%以上 0.1285
具体例 B

ベンチマーク(data.index を20回)

揺れの大半は、毎回作り直していた入力から来ていた。入力を固定すると 0.16〜5.15 ms が 2.68〜2.75 ms に縮んだ。残った揺れは原因未特定だが、実行環境など固定できる候補がある。

20回の最小〜最大(seed=1、1回目)表
入力 最小〜最大
毎回作り直す 0.16〜5.15 ms
固定する 2.68〜2.75 ms

片方は入力を固定すれば揺れが縮み、もう片方は固定できません。改善を主張する数字を見たとき、この違いから何を最初に確かめるべきでしょうか。

「その数字を、何も変えずにもう一度取ったらどれくらい動くか」を、両方で考えてみてください。

この章のまとめ

  • A/A テストは、2群に全く同じ体験を見せる実験。それでも率は群ごとに違って出る。
  • 揺れの出どころは、ページではなくどちらの群に誰が入ったか。
  • 真の率5%・各群1000人の1回で、相対20%以上ずれる確率は約31%(厳密計算0.3095)。
  • 10回の幅は seed ごとに変わる。1組の幅を一般化しない。
  • 95%の基準で約5%の回は「統計的に有意」と出るのが正常。0回が正常なのではない。
  • 揺れには、入力を固定して消せるもの(ベンチマーク)と、固定すると比べる意味が消えるもの(A/A)がある。後者は大きさを知って読む。

自分の現場で確かめる

読んだだけでは身につきません。手元の環境で実際に見てみてください。

環境:

理解度チェック

読んだ直後は「分かった」と感じやすいものです。実際に解いて確かめてください。

問題 1 / 3正解: 0

Kohavi らは A/A テストの用途として何を挙げていますか。

用語集(3語)
A/A テスト
利用者を2群に分けるが、両方に全く同じ体験を見せる実験。Null Test とも呼ばれる。ばらつきの把握と実験基盤の検査に使う。
何も変わっていない世界
比べている2つに本当は違いが無い、という想定。統計の用語では帰無仮説と呼ぶ。A/A はこの世界を実際に作って回す実験である。
seed(乱数の種)
疑似乱数の出発点。同じ seed なら同じ乱数列が出る。1つの seed の結果は1回分の標本にすぎない。