kaedeasset記事 · Notes霧走町 流氷に眠る

見ない組を 225 回見た。holdout を「見た回数」で数えるのをやめた理由

We looked at the unseen set 225 times: why we stopped counting holdout use by views

 D21-4-T42-holdout-usage-count・技術・日本語入力でプレイする仕組み

確かめ方adaptive data analysisreusable holdout (Thresholdout)holdout settest set reuseclaude自然言語処理

照合を直すときに使ってよい問い(dev)と、使ってはいけない問い(holdout)を分けています。
holdout は 60 問。
8 月 19 日に作り直してから今日まで、225 回参照しました。
そのうち「結果を見て直した」と記録した回は 0 回です。
そして、参照の回数で使い切りを決める規則は、8 月 20 日に自分で外しました。

この回は、holdout の中身を開かずに書いています。
中身も、通った割合も書きません。
書くと、読んだ人ではなく、この回を書いている側に漏れるからです。

この回の位置と、前後の回#

区切り: T34〜T49 確かめ方(16 回のうち 9 回目)
先に読むとよい回: T38(今の出力を写して固定する)
次に読む回: T43(答えの写し込みを見つける)

名前 — adaptive data analysis#

正式名
adaptive data analysis(適応的なデータ分析)と、その対処の reusable holdout
アルゴリズムの名前
Thresholdout
日本語
再利用できる holdout
初出
Cynthia Dwork, Vitaly Feldman, Moritz Hardt, Toniann Pitassi, Omer Reingold, Aaron Roth, "The reusable holdout: Preserving validity in adaptive data analysis", Science 349(6248), 2015
関連
Avrim Blum と Moritz Hardt の "The Ladder: A Reliable Leaderboard for Machine Learning Competitions"(ICML 2015)、Rebecca Roelofs ほか "A Meta-Analysis of Overfitting in Machine Learning"(NeurIPS 2019)

adaptive data analysis とは#

holdout は、作る側が見ない組です。
dev で調整し、最後に holdout で 1 回だけ測る。
holdout の数字は、まだ見ていないデータでの成績の見積もりになります。

統計の保証は、「holdout を使う問いを、holdout を見る前に決めた」ことが前提です。
ところが実際の分析は、前の結果を見て次の手を決めます。
holdout の数字を見て、少し直して、もう一度測る。
これを繰り返すと、直接 holdout を見て合わせたつもりがなくても、決定を通じて holdout の情報が分析に漏れます。
何度も使った holdout は、もう holdout ではなく、2 つ目の dev になります。
Dwork らは、この状況を adaptive data analysis と呼び、正面から扱いました。

対処が reusable holdout です。
アルゴリズムの Thresholdout は、おおよそこう動きます。

  1. 分析者は、ある量の値を訓練側(dev)と holdout の両方で求めたい。
  2. 両者の差が、閾値にノイズを足した値より小さければ、dev の値をそのまま返す。holdout については何も教えない。
  3. 差が大きいときだけ、holdout の値にノイズを足して返す。この「差が大きかった」回数に予算がある。

holdout が話すのは「dev とずれているとき」だけで、しかも正確には話さない。
差分プライバシーの考えで、漏れる情報の量を抑えます。
同じ漏れの量で、素朴に使うよりずっと多くの回数、holdout を使い回せる、という結果です。

同じ系統に、コンペのリーダーボードを守る Ladder(Blum と Hardt)があります。
前の最良より有意に良いときだけ、表示する点を更新します。

一方で、実際の影響は小さかったという測定もあります。
Roelofs らは 100 を超える Kaggle のコンペを調べ、test の再利用による大きな過適合の証拠はほとんど見つからなかった、と報告しました。
ただし Kaggle の test は大きく、参加者は互いに独立です。
うちは 60 問で、適応する主体は 1 つ(同じ作り手と、同じ開発の AI)です。
理屈の上では、過適合しやすい側にいます。

うちで起きたこと(経緯) — adaptive data analysis#

最初の日から踏んでいました。

2026-08-03、照合器を初めて書いた日です。
dev で十分に通るところまで直してから holdout を測ると、基準に届きませんでした。
そこで言い換えの辞書の作り方を直し、もう一度 holdout を測って、基準を越えました。
当時の commit には「holdout の文面を辞書に写す誘惑を、辞書の設計だけで我慢した」とあります。
文面は写していません。
それでも、holdout の結果を見て直し、同じ holdout で測り直したことには変わりません。
これが、名前のある失敗そのものです。

その後、ループ(Scripts/adv-loop.ps1)が holdout を毎周回していました。
毎周、結果を見て、次の手を決めていたことになります。

2026-08-17 に、自分たちの方法を既存の名前で呼び直す監査をしました(named-methods-audit)。
そこで、この使い方が holdout reuse にあたると分かりました。

2026-08-18 に直しました。

  • 毎周のループから holdout を外し、節目のテストサマリ(Scripts/adv-test-summary.ps1)だけが回す。
  • 回すたびに、日付・commit・結果・「見て直したか(actedOn)」を 1 行ずつ追記する。
  • 参照が 10 回を超えるか、見て直したら、その組は使い切り。退避して、中身を見ずに新しく起こす。

Thresholdout は採りませんでした。
当日の commit の理由はこうです。
保証を得るには、検出したい差と同じくらいのノイズが要る。
60 問では 1 問が 1.7 ポイントで、ノイズが信号を飲む。
道具の大きさが、問題の大きさに合っていない。

2026-08-19、初代の参照が 9 回になりました。
初代を、参照の記録ごと holdout-archive/2026-08-19/ へ退避しました。
記録を引き継ぐと、新しい組が最初から 9 回見たことになるからです。
第 2 代の 60 問は、初代を見ずに、言い換えの表からの機械生成もせずに、手で書き起こしました。

2026-08-20、参照回数の上限を外しました。

なぜ上限を外したか#

目録(runs/suite.json)の _budget に、理由が 3 つ残っています。

一つ目、数えている単位が違う。
漏れるのは「見た回数」ではなく「見た結果として下した決定」です。
その単位は、actedOn としてすでに記録している。
当時の参照の記録は 6 行で、6 行とも actedOn は false。
つまり、本来の目的では一度も発動していないのに、回数だけで使い切りが迫っていました。

二つ目、判定の粒度が対象に届いていない。
60 問なので、1 問がおよそ 1.7 ポイントです。
検出したいのは数問ぶんのずれで、物差しの目盛りとほとんど同じ大きさでした。
1 問の揺れで合否が入れ替わりうる物差しで、「何回まで見てよいか」を細かく決めても意味が薄い、という判断です。

三つ目、回帰テストが「回すたびに自分を減らすコマンド」になっていた。
テストサマリは、確かめるたびに 1 回参照します。
上限が 10 なら、10 回回せば、作品が何も悪くなくても FAIL になります。
直しても消えない赤です。
回帰テストを回すのをためらう装置になっていました。

いまの判定はこうです(adv-test-summary.ps1 から)。

$usage = @(Get-AdvHoldoutUsage $DataDir $g)
$spent = $usage.Count
$acted = @($usage | Where-Object { $_.actedOn }).Count
...
Add-AdvHoldoutUsage $DataDir $g $Head7 "$(Get-AdvJsonField $gj 'pass')/$(Get-AdvJsonField $gj 'total')" ([int](Get-AdvJsonField $gj 'negFp'))
$spent++
...
if ($acted -gt 0 -or ($g.Budget -and $spent -gt $g.Budget)) {
$why = if ($acted -gt 0) { "この組を見て直したことがある(actedOn $acted 件)" } else { "参照 $spent 回 > 予算 $($g.Budget)" }
Row 'FAIL' Red "$($g.Name) 消費" "$why — $($g.Dir) を golden/holdout-archive/<日付>/ へ退避し、コーパスを見ずに新しく起こすこと"
$RegParts += 'FAIL'
}

目録から budget の欄を消したので、$g.Budget は空です。
右側の条件は偽になり、actedOn だけが残ります。
判定のコードは変えずに、設定から 1 行消しただけで規則が変わりました。
緩めたことが git の差分に出る形です。

記録の 1 行はこういう形です(acc の欄の値は伏せます)。

{"date":"2026-10-26","commit":"2c7a5966","acc":"…/60","negFp":0,"actedOn":false}

actedOn は機械では決まりません。
記録を書く関数は、いつも false で書きます。
「この結果を見て直したか」は、直した側が自分で true に書き換える、という運用です。

今日の数(10/29)#

第 2 代の参照の記録を、日付と commit と actedOn の欄だけ読んで数えました。

  • 参照 225 回(2026-08-19〜10-26)
  • 異なる commit 133
  • actedOn が true の行 0
  • 月ごと 8 月 36 回、9 月 87 回、10 月 102 回
  • 参照のあった日 28 日。最も多い日は 9 月 22 日の 25 回
  • 上限を外した 8 月 20 日より前が 6 回、その日以後が 219 回

初代は 9 回参照し、actedOn は 0 でした。

名前どおりでない所 — adaptive data analysis#

reusable holdout とは、ほぼ別のものです。

Thresholdout は、holdout が返す情報そのものを絞ります。
うちは、holdout の結果を毎回そのまま返しています。
テストサマリは、通った数を画面にもファイルにも出します。
守っているのは、「holdout を見て照合を直さない」という作り手の規則と、それを破ったら組を捨てる、という使い切りの規則だけです。

これは、封印した探り(使い切りのプローブ)と同じ運用で、教科書の holdout の使い方に近い。
Dwork らの言葉で言えば、適応を防いでいるのではなく、適応したと分かった組を捨てている、です。

そして、題にした「見た回数」は、いまは数えるだけで、何も決めていません。
Dwork らの指摘をそのまま受ければ、問題は回数ではなく、見た結果が次の決定に入ったかどうかです。
その点ではうちの単位(actedOn)の方が筋は通っています。
ただし、actedOn を書くのは、決定を下した本人です。
「見たが、直したのは別の理由だ」と本人が思えば、false のままです。
225 回のどこかで、数字を見たことが次の commit の判断に入っていなかったとは、記録からは言えません。

文書が古いまま#

named-methods-audit の §5-A と、holdout の予算の手引き(adv-holdout-budget.md)は、まだ「上限は 10」と書いています。
アーキテクチャの文書の holdout の行も、「予算超過で使い切り」のままです。
ソースと目録は 8 月 20 日に変わっていて、文書だけが 8 月 18 日の状態でした。
同じ数を 2 か所に書くと片方が古くなる、という形を、ちょうど上限を外した日の commit 自身が書いています。
その commit が、文書の側を直し漏らしていました。

弱点と、採らなかった案 — adaptive data analysis#

  • 60 問は小さい。1 問で 1.7 ポイント動きます。通った割合の小さな変化は、ほとんど何も言いません。
  • 第 2 代を書いたのは、その日 1 日、言い換えの辞書を触っていた同じ作り手です。字面は写していなくても、発想は写ります。辞書との字面の近さは lint(L4)が見ますが、発想の近さは誰も見ていません。
  • actedOn は自己申告です。

採らなかった案です。

  • Thresholdout を入れる。上に書いたとおり、60 問ではノイズが信号を飲みます。入れるなら、まず問いを増やすことが先です。
  • 参照の上限を戻す。回すたびに減る赤は、回帰テストを回さない理由になります。それは、見張りが本体の実行を止める形です。
  • holdout を回帰テストから外し、明示したときだけ見る。8 月 19 日に一度入れましたが、別の変更と同じ commit に入れたため、そちらを差し戻したときに一緒に消えました。手引きには「入れ直すなら単独の commit で」とあり、まだ入れ直していません。

検索語#

adaptive data analysis
reusable holdout Thresholdout
holdout reuse overfitting
Dwork Feldman Hardt 2015 Science

聞き込みの言い方は、遊ぶ人の数だけあります。こちらが一度も見ていない言い方で、『霧走町 流氷に眠る』の第一章をブラウザで入力してみてください。プロフィールのリンクから遊べます。

#claude #自然言語処理