- ブラウザURL入力
- DNS名前→IP
- ルータ経路選択
- ロードバランサ振り分け
- Nginx受け口
- アプリ処理今ここ
- DB保存
差が何も変えなくても出る大きさではないかを問うてきました。今回は、1回なら5%の偶然が、見る回数を増やすとどこまで膨らむかを数えます。
まず結論
1回の比較なら、何も変わっていなくても統計的に有意と出るのは約5%です。ところが見る回数を増やすと、どれか1回が当たる確率は回数とともに上がります。互いに無関係な20本の指標なら 1−0.95²⁰ で約64%です。
毎日結果を覗いて、最初に有意になった日に止める規則でも同じことが起きます。効くのは覗くことではなく、覗いた結果で止めどきを決めることです。
何も変えていない A/A 実験で、互いに無関係な指標を20本、それぞれ5%の基準で検定します。1本以上が統計的に有意と出る確率はおよそどれでしょうか。
当てることが目的ではありません。外した予測ほど記憶に残るので、直感で選んでください。
1回なら5%、k回なら 1−0.95^k
何も変わっていない世界でも、5%の基準で1回検定すれば、約5%の確率で統計的に有意と出ます。これは基準の定義そのものです。
では比較を増やすとどうなるか。1回が外れる(有意にならない)確率は0.95です。互いに無関係な k 回がすべて外れる確率は 0.95 の k 乗で、1回以上当たる確率はその残り 1−0.95^k になります。「少なくとも1個」を余事象で数える、サイコロの問題と同じ形です。
k が5なら0.226、10なら0.401、20なら0.642。同じデータで多くの比較をすると、実力と無関係にどれかが偶然よく見えやすくなります。これを 多重比較 の問題と呼びます。
- ・64%は指標が互いに無関係という仮定の値。実際の指標は関連し合うので低めに出うる
- ・約25%と約5%は各群1日500人・真の率は両群5%の模擬実験(3000回ずつ seed 1〜13 で、約25%は24〜26%、約5%は4.6〜5.6%で揺れる)
何も変えない A/A で、20本の指標を見る
模擬実験で確かめます。両群とも真の率5%、各群2000人、互いに無関係な指標20本を毎回5%の基準で検定し、これを seed 3 で2万回繰り返しました(Python 3.14.6)。
python 3.14.6 seed=3 R=20000(a) 20 metrics A/A n=2000/arm: >=1 significant 64.0% mean #sig 1.00 theory 1-0.95^20=64.151% single-metric FPR 4.987%(b) 20 daily looks 500/arm/day: stop-at-first-significance FPR 24.4%; only final look 5.0%first-significant day histogram: [1009, 607, 490, 403, 294, 247, 215, 191, 170, 156, 149, 142, 118, 119, 109, 109, 88, 95, 83, 80]
1本ずつ見れば偶然の有意は4.987%で、基準どおりです。それでも 1本以上が有意だった回は64.0%、理論値64.151%とほぼ一致します。3000回ずつ seed 1・2 で回すと64.6%と66.2%で、試行回数が少ないとこの程度は揺れるので、言えるのは「約64%」までです。
同じ行の mean #sig 1.00 は平均の本数で、別の量です。平均が約1本になるのは指標が独立でなくても成り立ちます。Dirty Dozen は区分けの例で 1 out of 20 segment groups would show a statistically significant change in the metric simply by chance と書き、assuming segment groups are independent という仮定を論文の側で付けています。§5.5 も、多くの指標を同時に追えば、いくつかは判定の境界を下回ると書いています。
20本の指標の A/A を2万回繰り返すと、有意と出た本数の平均は1.00本、1本以上有意だった割合は64.0%でした。正しい読み方はどれですか。
時間方向に覗くのも、比較を増やすことになる
指標の本数だけではありません。同じ実験を毎日覗き、有意になった日に止めるのも、判定の機会を増やしています。Dirty Dozen §5.6 はこう書きます。
Continuously checking the results and stopping as soon as statistical significance is achieved leads to an increase in Type-I error, or the probability of a false positive.
さきほどの出力の3〜4行目がその模擬です。各群に毎日500人ずつ足し、20日間毎日判定して、最初に有意になった日に止めました。偽陽性は24.4%、同じデータを20日目に1回だけ判定すると 5.0% です。3000回ずつ seed を変えると、前者は24〜26%ほど、後者は4.6〜5.6%で揺れます。
64%まで増えないのは、20回の判定が同じ累積データを見ていて互いに独立でないからです。最初に有意になった日は1日目が1009回、20日目は80回でした。1日目が最も多いのは、1日目の有意はすべて「最初」に数えられるからです。後の日ほど前日までのデータと大半が重なるので、新たに初めて有意になる回は減っていきます。
同じ節は、2週で差が出ずに 延長する のも同じ誤りだとし、stopping early or extending the experiment are both very common mistakes と書きます。
ただし「途中で見るな」は言い過ぎです。同じ節は、ひどい劣化が出たらすぐ止めるために、監視は essentially a requirement だと書きます。対処として挙げるのは、決めた時点でだけ出荷を判断する、覗いた回数に合わせてp値を調整する、途中で止めてよいベイズの枠組み、の3つです。Johari らは、常に監視しても成り立つp値(always valid)を提案しています。その動機として、補正なしで覗けば Even with 10,000 samples ... Type I error can easily increase fivefold と書いています。この「5倍」は論文の条件での主張で、上の約25%とは設定が別です。
落ちたテストを、緑になるまで再実行する
同じ形は CI にもあります。flaky なテスト、つまりコードを変えなくても時々落ちるテストを、落ちたら再実行し、どこかで1回通れば緑とする運用です。
1回の失敗率を30%と置いた模擬です(本コースで置いた仮定)。合計 k 回まで走らせ、1回でも通れば緑とします。
import random, sys
seed=int(sys.argv[1]); rng=random.Random(seed); R=200000; f=0.30
for attempts in (1,2,3,4):
g=sum(any(rng.random()>=f for _ in range(attempts)) for _ in range(R))
print(f"seed={seed} fail-rate {f}: attempts total={attempts} (reruns={attempts-1}) green {g/R:.4f} exact 1-f^k={1-f**attempts:.4f}")
seed=1 fail-rate 0.3: attempts total=1 (reruns=0) green 0.7002 exact 1-f^k=0.7000seed=1 fail-rate 0.3: attempts total=2 (reruns=1) green 0.9091 exact 1-f^k=0.9100seed=1 fail-rate 0.3: attempts total=3 (reruns=2) green 0.9730 exact 1-f^k=0.9730seed=1 fail-rate 0.3: attempts total=4 (reruns=3) green 0.9921 exact 1-f^k=0.9919
合計3回(再実行2回)まで許すと、緑は97.3% です。テストの中身は何も直っていません。赤になるのは全回落ちたときだけで、その確率 0.3^k が回数とともに縮みます。20本の指標で「1本以上有意」が増えたのと同じ、1−(外れる確率)^k の形です。
0〜150ms の一様乱数の遅れで、100ms を超えると落ちるテスト(第16期で扱う例)なら、落ちる確率は約3分の1です。これを合計3回までの再実行で包むと、緑は 1−(1/3)³≈96% になります。
20日間毎日覗き、最初に有意になった日に止める規則では偽陽性が約25%、20日目に1回だけ見ると5.0%でした。差を生んでいるのは何ですか。
毎日覗いて、最初の有意で止める
何も変えていない A/A でも、20日間毎日判定して最初に有意になった日に止めると、偽陽性は約25%。20日目の1回だけで判定すれば約5%のまま。
判定のしかた 偽陽性20日目に1回だけ 5.0%毎日判定し最初の有意で止める 24.4%
落ちたら再実行して、最初の緑で止める
何も直していない flaky なテストでも、失敗率30%で合計3回まで走らせて1回でも通れば緑とすると、緑は97.3%。1回だけなら70%。
合計の実行回数 緑になる確率1回 70.0%3回 97.3%
統計の実験と CI の運用。見ている数字は違いますが、偶然の当たりを増やしている共通の規則は何でしょうか。
「いつ止めるか」を、何が決めているかを両方で探してください。
この章のまとめ
- 5%の基準で1回比べれば、何も変わっていなくても約5%は有意と出る。互いに無関係な k 回なら 1回以上当たる確率は 1−0.95^k、20回で約64%。
- 「平均1本当たる」と「1本以上当たる確率64%」は 別の量。
- 毎日覗いて 最初の有意で止めると 約25%。20日目に1回だけ判定すれば約5%。効いているのは覗くことより、結果で止めどきを決めること。延長も同じ誤り。
- 監視をやめる必要はない。判定日を先に決める・p値を調整する・途中で止めてよい方法を使う。Bonferroni は全体を抑える代わりに小さな差を見えにくくする。
- flaky なテストの再実行も同じ形。試した回数を結果と一緒に書く。
自分の現場で確かめる
読んだだけでは身につきません。手元の環境で実際に見てみてください。
理解度チェック
読んだ直後は「分かった」と感じやすいものです。実際に解いて確かめてください。
20本の指標に Bonferroni の方法を使い、各指標の基準を 0.05/20=0.0025 にしました。この扱いとして適切なのはどれですか。
用語集(4語)
- 多重比較
- 同じデータで多くの比較をすること。比較が多いほど、実力と無関係にどれかが偶然よく見えやすくなる。指標を増やすのも、切り口を増やすのも、途中で何度も判定するのも、比較の回数を増やす。
- 偽陽性(第1種の誤り)
- 何も変わっていない世界なのに統計的に有意と出ること。1回の検定で5%の基準なら、その確率は約5%。
- Bonferroni の方法
- 比較が g 回なら各回の基準を α/g にして、全体で偶然に当たる確率を α 以下に抑える方法。各回の基準が厳しくなるぶん、小さな差は見えにくくなる。
- 覗き止め(early stopping)
- 途中の結果を見て、有意になった時点で実験を止めること。事前に決めた時点で1回だけ判定する場合より偽陽性が増える。
出典(一次ソース)
- Dmitriev, Gupta, Kim, Vaz, “A Dirty Dozen: Twelve Common Metric Interpretation Pitfalls in Online Controlled Experiments”, KDD 2017, §5.5・5.6・5.8 ↗
- Johari, Pekelis, Walsh, “Always Valid Inference: Continuous Monitoring of A/B Tests”, arXiv:1512.04922v3 (2019-07-16) ↗
- NIST/SEMATECH e-Handbook of Statistical Methods 7.4.7.3 Bonferroni's method ↗
- Wasserstein & Lazar, “The ASA Statement on p-Values: Context, Process, and Purpose”, The American Statistician 70(2), 2016 ↗