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

覗く回数だけ、偶然の勝者が増える

リクエストがアプリケーションに届くまで
  1. ブラウザURL入力
  2. DNS名前→IP
  3. ルータ経路選択
  4. ロードバランサ振り分け
  5. Nginx受け口
  6. アプリ処理今ここ
  7. 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。同じデータで多くの比較をすると、実力と無関係にどれかが偶然よく見えやすくなります。これを 多重比較 の問題と呼びます。

何も変わっていない世界で、見る回数と偶然の勝者
1回だけ比べる偶然に有意: 約5%
互いに無関係な指標を20本に増やす
1本以上が有意1−0.95²⁰ ≈ 64%
同じ実験を20日間、毎日判定する
最初の有意で止める偽陽性 約25%
20日目に1回だけ判定約5%のまま
  • ・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)。

本教材の模擬実験の出力(スクリプト非掲載、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}")
上のコードを l8-flaky.py として保存し python3 l8-flaky.py 1 で実行(seed 1、各20万回)コマンド出力
seed=1 fail-rate 0.3: attempts total=1 (reruns=0) green 0.7002 exact 1-f^k=0.7000
seed=1 fail-rate 0.3: attempts total=2 (reruns=1) green 0.9091 exact 1-f^k=0.9100
seed=1 fail-rate 0.3: attempts total=3 (reruns=2) green 0.9730 exact 1-f^k=0.9730
seed=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%でした。差を生んでいるのは何ですか。

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

毎日覗いて、最初の有意で止める

何も変えていない A/A でも、20日間毎日判定して最初に有意になった日に止めると、偽陽性は約25%。20日目の1回だけで判定すれば約5%のまま。

模擬実験(各群1日500人、20日、seed 3・2万回)表
判定のしかた 偽陽性
20日目に1回だけ 5.0%
毎日判定し最初の有意で止める 24.4%
具体例 B

落ちたら再実行して、最初の緑で止める

何も直していない flaky なテストでも、失敗率30%で合計3回まで走らせて1回でも通れば緑とすると、緑は97.3%。1回だけなら70%。

1−0.3^k(模擬と一致)表
合計の実行回数 緑になる確率
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 なテストの再実行も同じ形。試した回数を結果と一緒に書く。

自分の現場で確かめる

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

環境:

理解度チェック

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

問題 1 / 3正解: 0

20本の指標に Bonferroni の方法を使い、各指標の基準を 0.05/20=0.0025 にしました。この扱いとして適切なのはどれですか。

用語集(4語)
多重比較
同じデータで多くの比較をすること。比較が多いほど、実力と無関係にどれかが偶然よく見えやすくなる。指標を増やすのも、切り口を増やすのも、途中で何度も判定するのも、比較の回数を増やす。
偽陽性(第1種の誤り)
何も変わっていない世界なのに統計的に有意と出ること。1回の検定で5%の基準なら、その確率は約5%。
Bonferroni の方法
比較が g 回なら各回の基準を α/g にして、全体で偶然に当たる確率を α 以下に抑える方法。各回の基準が厳しくなるぶん、小さな差は見えにくくなる。
覗き止め(early stopping)
途中の結果を見て、有意になった時点で実験を止めること。事前に決めた時点で1回だけ判定する場合より偽陽性が増える。