この記事についてClaude(Anthropic)との共同編集により作成されました。
要約
- Kaggleの「ROGII - Wellbore Geology Prediction」で private 8.313 / 231位 / 6191チーム = 銀メダル。公開リーダーボードでは397位だったので、最終順位は166位ぶん上がった
- 公開ノートブックをフォークしたものが自分の公開ベスト(6.364)だったが、提出枠をひとつも使わずに測った検算用のCVが「これは9.8相当だ」と言ったので、締切1週間前に自作モデルへ張り替えた
- 結果はフォークが private 9.512、自作が private 8.313。検算の予測は 0.3ftの悲観側で当たった
- 教訓は「公開リーダーボードを信じるな」ではなく、その指標がその改善を測れるだけの分解能を持っているかを先に判定しろ、という話
- エージェントをループで回しながら進めるやり方でコンペに出たのは今回が初めて。「もう改善のしようがありません」と言われてから踏み込んだUNetが、結果的に勝敗を分けた
はじめに
2026年8月5日に締め切られたKaggleのコンペ「ROGII - Wellbore Geology Prediction」で、銀メダルを取った1。
| 項目 | 値 |
|---|---|
| private LB | 8.313(RMSE、小さいほど良い) |
| 最終順位 | 231位 / 6191チーム(上位3.73%) |
| メダル | 🥈 銀 |
| public LB | 6.364 → 397位 |
| public → private の変動 | 166位の上昇 |
| private 1位 | 5.639 |
| 参加期間 | 実質約1ヶ月 |
「1ヶ月で銀」と書くと格好はつくが、実際には5月末に一度参戦して6月に全敗し、いったん撤退している。そこを省くと再現性のない武勇伝になるので、最初から順番に書く。
この記事の主張はひとつだけだ。
公開リーダーボードのスコアは「測定値」ではなく「小さな標本から引いた1回のくじ」である。それが本物かどうかは、提出枠を使わずに自分で測れる。
何のコンペだったのか
石油・ガスの掘削の話だ。地面に対して横向きに曲げて掘っていく水平井という井戸があり、そのドリルの先端がいま地層のどの高さにいるのかを当てる、という問題だった。
- 掘りながらリアルタイムで測れるのは、GR(ガンマ線)ログという1本の曲線だけ
- ただし同じ油田には、過去に真下に向かって掘ったtypewell(基準井)がある。こちらは深さごとのGRの値が全部わかっている
- したがって、いま掘っている水平井のGR曲線を、typewellのGR曲線に系列として整列させれば、自分がいまどの地層の高さにいるかが逆算できる
要するに、イントロだけ聴いて曲のどこを再生しているか当てるゲームだ。ただし再生速度は一定ではなく、ノイズも乗っていて、途中で早送りされることもある。
評価指標は地層基準の垂直深度(TVT)のRMSE。小さいほど良い。
単位はfeetで、以降このコンペのスコアはすべて ft で書く。石油・ガスの掘削は北米が中心なので、データもそのままフィート表記で来る。1 ft は約0.30mなので、最終的に取れた 8.313 は約2.5m、何もしないベースラインの 15.883 は約4.8mだと思っておくとスケール感がつかめる。
初期に確定させておくべきだった構造的な事実がふたつある。
- 予測すべき区間の84.8%が単一の地層の中にあり、87.7%の井戸は層の境界を一度も跨がない。層の厚み116.5ftに対してTVTの変動は中央値26.4ft
- つまりこれは地層を当てる問題ではなく、分厚い1枚の層の内側で位置を決める問題だった
- 何もしない(掘削開始点の値をそのまま保持する)ベースラインが 15.883。これが床
第0幕|6月、全敗して撤退した
参戦したのは5月27日。データを読んで指標を確定させ、6月5日にまとめて実験を回した。結果がこれだ。
| 施策 | LB |
|---|---|
| 何もしない(const-hold) | 15.883 |
| LightGBMで「開始点からのズレ量」を回帰 | 21.05 ❌ |
| ズレを ±10ft でクリップ | 18.49 ❌ |
| 暴れの主犯と疑った累積Z特徴を除外 | 24.61 ❌(最悪) |
| 公開手法の粒子フィルタを移植 | 9.272 ✅ 初の床超え |
面白いのは、クリップ量に対してスコアがきれいに単調だったことだ。クリップ0(=何もしない)が15.88、±10ftが18.49、素のズレ予測が21.05、除外が24.61。つまり「ズレの予測は、どの大きさで入れても差し引きで有害」という、珍しくきれいな否定的結果だった。
問題は、手元のOOF(交差検証で得た検証スコア)は全部の変種で床を超えていたことだ。ここで「このコンペではCVが当てにならない」という信念を刷り込まれる。これが後で効いてくる。
そして6月中旬、ドメインへの興味が続かず撤退した。「地質はわけがわからん、ARC系のコンペをやろう」という気分だった。
第1幕|7月12日、再開してフォークで銀圏へ
7月に入って、近い締切でめぼしいコンペが他に見当たらなかった。一度「わけがわからん」で降りたはずのここに、仕方なく戻ることにする。
締切は8月5日。残り3週間。この期間で一から作るのは無理なので、公開されている最先端のノートブックに頼ることにした。「コードコンペの本質はフォークだ」みたいな戦略があったわけではなく、残り時間から逆算した消去法である。
| 日付 | 施策 | LB |
|---|---|---|
| 07-12 | 公開フロンティアの複製 V1 | 7.134(銅圏) |
| 07-14 | 別系統の私的複製 | 7.067(銀ライン初突破) |
| 07-16 | 自前の平滑化フィルタを候補プールに1本追加 | 7.050 |
| 07-17 | 自前移植した空間補正(offset-krige) | 6.878(自己ベスト) |
ここで踏んだ罠を書いておく。公開ノートブックの報告値は再現しない。
報告値7.039のノートブックを複製したら7.067、報告値7.016のものは7.123。非再現が3例連続で、差は最大で+0.107あった。上位1000チームが0.1ftの中にひしめいている状況なので、この差は順位にすると数百位ぶんになる。
「報告値を追いかける」だけの動き方は、この非再現ノイズに埋没して終わる。
第2幕|7月18日、仮説を1提出で殺す
3日でトップ層のスコアが 6.9 から 5.x に、およそ1点落ちた。
「誰かが、公開されている学習済みパッケージの予測をそのまま出しているのでは?」という仮説を立てた。もしそうなら、追いかけても意味がない。
そこで、その純粋な予測だけを1枚提出して天井を測った。
結果は LB 20.067。仮説は完全に反証された。誰もそんなことはしていない。
崩落の真因はこの時点ではわからずじまいだったが、この提出は元が取れている。間違った方向に1週間使うのを防いだからだ。提出枠1枚で仮説を殺せるなら、その提出は安い。
その後の7月19日〜21日は全滅期間だった。形状のグラフト(悪化)、近傍探索による基準面補正(8.351に崩壊)、井戸ごとの補正量プローブ(相関ほぼゼロ)。
さらに、自己ベストの6.878が「上振れくじ」だったことも判明する。同じカーネルを5回提出したら平均6.919、標準偏差0.032。6.878はその分布の下側の当たりくじだった。ちなみに公開リーダーボードは 6.20〜6.60 の帯に1312チーム(全体の22%)が単峰で詰まっており、その帯の標準偏差は0.0642。再実行のばらつきとほぼ一致する。あの帯はスコアの分布ではなくノイズの分布そのものだった。
第3幕|7月22日、天井を測る(最大の損失)
ようやくオラクル梯子を測った。「理想的にはどこまで行けるのか」を段階的に並べた表である。
| 参照点 | RMSE |
|---|---|
| 何もしない | 15.910 |
| 当時の自分 | 6.878 |
| 直線オラクル(井戸ごとのオフセットと傾きを完璧に当てる) | 6.6 |
| 当時の首位 | 5.26 |
| 平滑面オラクル | 3.9 |
これが第3週。今回いちばんの損失はここだった。
この表が初週にあれば、「オフセット補正の路線は6.6で頭打ち、形状を扱うモデルが要る」と即座にわかったはずだ。3週間かけて到達した結論が、この表そのものだった。
しかもオラクル梯子の計算は数時間で終わる。参加期間が短かったことより、その短い期間の中で天井の測定を後回しにしたことのほうが効いていた。
同じ7月23日、公開されていた別のノートブックをフォークし、内部の設定プロファイルを固定して提出したところ LB 6.364。作者の報告値6.491を上回り、順位はおよそ108位=銀圏に入った。
このとき設定プロファイルを全部マッピングして、conservative 7.213 / balanced 6.364 / aggressive 9.216 というきれいなU字を確認している。谷底はbalanced。そして上流の3つのレバーは、いじっても出力バイトが1ビットも変わらない見せかけのパラメータだった。つまりこのフォークには安全にいじれる面がゼロだとわかった。
第4幕|7月24日、「CVは信用できない」が誤りだったと気づく
ここが転換点だ。
6月に刷り込まれた「このコンペではCVが反予測的だ」という信念が、実は壊れた実装と、中身が見えないフォークのエンジンから来た過度な一般化だったのではないか、と疑い始めた。きっかけは、他の参加者が「井戸単位で切った5fold × 5seedのCVで、LBとの差が+0.3で安定している」と書いていたことだった。
そこで、CVハーネスそのものを検証した。
やり方は単純で、答えがわかっている構成をひとつ通すだけだ。何もしないベースラインを自分のハーネスで測ると 15.910。実際のLBは 15.883。
ハーネスは正直だった。
これは今回いちばん安く、いちばん効いた行動だ。「CVが当たらない」と言う前に、床のように答えがわかっている構成を1つ通す。ハーネスが嘘をついているなら、その場で見える。
第5幕|7月25日〜27日、自作モデルを一から作り直す
CVが使えるとわかった以上、公開ノートブック追いをやめて自前の土台を作り直した。
| 日付 | 施策 | 効果 |
|---|---|---|
| 07-25 | 粒子フィルタを、厳密なHMMのforward-backwardアライナに置換 | 11.68 → 9.68 |
| 07-25 | 37特徴のGBDT残差スタックを重ねる | OOF 7.831(自前の最良単体) |
| 07-26 | 温度を変えた4本の等重み平均 | 9.681 → 8.993 |
| 07-27 | 1サンプル=1井戸のUNet系列アライナを追加 | 3者ブレンドで検算CV 7.478 |
「もう改善のしようがありません」と言われた
ここでひとつ書いておきたいことがある。
この作り直しに入る局面で、Claudeははっきり弱音を吐いた。「残り期間では、もう改善のしようがありません」。
言い分は合理的だった。フォーク側には安全にいじれる面がゼロ、自前の土台は当時9.68で、フォークの6.364とは3ft以上の開きがある。残りは1週間ちょっと。まっとうな見積もりである。
それでも「やれるだけやってみようぜ」でUNetの学習に踏み込んだ。結果、UNetは3者ブレンドに −0.246 を出し、しかも後述するとおり3つの成分の中で唯一、隠しテストでOOFより良くなる成分だった。ここで止めていたら、自前系はフォークに勝てていない。
見込みが薄いという見積もりと、やらないという判断は別物である。 残り時間があるうちは回す、というのが今回いちばん報われた「非合理」だった。
バグが効いていた
もうひとつ、副産物が個人的にはいちばん面白かった。
粒子フィルタが妙に強かった理由は、GRの欠損値を0で埋めていたバグに由来する「尤度の緩和」だった。 欠損を0にすると尤度がなまって、結果的に温度を上げたのと同じ効果が出ていた。原理的な温度緩和(温度を変えた4本の平均)で置き換えたら、その効果はきれいに再現できた。
バグが効いていた、という話である。バグを直したら弱くなる実装は世の中に確かに存在する。
第6幕|7月28日、提出枠を使わずに「そのスコアは本物か」を測る
ここが記事の中心だ。
このとき自分のベストは public 6.364 のフォークだった。でもこれは自分が書いたものではない。privateで崩れないという保証がどこにもない。
そこで、フォークの中に作者自身が書いていたCVの機構を取り出し、リークを潰して(同じ井戸の候補を参照している箇所と、探索空間で自分自身を参照している箇所の2箇所)、本番と等価なブレンド構成のまま学習用の井戸120本で回した。
結果はこうなった。
| 項目 | 値 |
|---|---|
| フォークのリークなしCV | 9.825(9.802〜9.837) |
| public LB | 6.364 |
| 差 | −3.46ft |
判定基準は、上位に入っていた参加者がディスカッションに書いていた言葉を借りた。「LBがCVより3ft良ければ、それは公開LBへの過剰適合だ」。この基準はCVを回す前に登録してある(CV ≥ 8.4 なら過剰適合と判定する、という形で)。
ブートストラップで見ると、「フォークは正直だ」という仮説の棄却確率は0.9998。ついでに、同じ120井の上で自分のスタック(8.550)がフォーク(9.825)に勝っていた。
そしてこれを、提出枠ゼロで出している。Kaggleのカーネル上でCVを回しただけだ(1井あたり312秒)。
公開ノートブックをフォークして戦うなら、そのフォークのリークなしCVを一度は測るべきだ。 提出枠を1枚も消費せずに、「自分がいま乗っている船が沈むかどうか」がわかる。
第7幕|7月29日〜8月1日、劣化係数と最終2枠
ここからは検算CVを主指標にして、自前系を詰めた。
いちばん効いたレバーがgap補正である。
3つの成分(DPアライナ / GBDTスタック / UNet)を1本ずつ単体で提出して、「手元のOOFではこの値なのに、隠しテストではこの値になる」という劣化係数 c を実測した。
| 成分 | OOF単体 | 実LB単体 | c |
|---|---|---|---|
| DPアライナ | 8.993 | 10.751 | 1.20 |
| GBDTスタック | 7.831 | 8.704 | 1.11 |
| UNet | 10.749 | 9.987 | 0.93 |
成分ごとに劣化の向きが違う。 DPは2割悪化するのに、UNetはむしろOOFより良くなる。
ということは、OOFの上で解いたブレンド重みは、DPを過大評価してUNetを過小評価しているはずだ。cで誤差をスケールし直して重みを解き直すと、予測利得 −0.137 に対して実測 −0.149。LBは 8.358 → 8.209 → 8.163 と動いた。
最終提出は2枠ある。分岐が確率的に残っている以上、両方に1枚ずつ置いた。
| 構成 | public | |
|---|---|---|
| ① | フォーク(未改変) | 6.364 |
| ② | 自前3者ブレンド + gap補正 | 8.163 |
Kaggleの最終提出は「良いほうが採点される」ので、フォークが正直だった世界線でも、過剰適合だった世界線でも、どちらでもメダルが残る形にした。
答え合わせ

| public | private | 差 | |
|---|---|---|---|
| ① フォーク | 6.364 | 9.512 | +3.148 |
| ② 自前(採点されたほう) | 8.163 | 8.313 | +0.150 |
private 8.313 / 231位 / 6191チーム = 銀メダル。 publicでは397位だったので、166位ぶん上がったことになる。
わかったことを並べる。
- フォークのリークなしCV 9.802〜9.837 は、private 9.512 を0.3ftの悲観側で当てた。 提出枠ゼロで測った値が、実測とほぼ一致した
- 自前の検算系9本の public→private の差は +0.059 ± 0.173。フォーク系は +2.821。桁が違う。ハーネスは最後まで正直だった
- フォーク系のうち同系統の25本は、publicでは 6.364〜7.354 に散っていたのに、privateでは平均 9.609 / 標準偏差 0.477 に潰れた。publicでの0.1〜0.5ftの差は、privateではほぼ情報を持っていなかった
- フォーク単独ならメダル圏外だった
- public 1位(4.608)はprivateで6.653=28位。崩落はコンペ全体の規模で起きていた
図の橙がフォーク系、青が自作モデル系だ。橙が左上に固まっていて、青が対角線に乗っている。この1枚で話は終わっている。
なお、フォーク元のノートブックには感謝しかない。6.364は最後まで自分のpublicベストだったし、最終枠の保険としてもきちんと機能した。publicへの過剰適合はこのコンペの構造(テストがおよそ200井しかない)の話であって、誰かの落ち度ではない。
効いた道具、3つ
① CVハーネスを、答えがわかっている点で校正する
何もしないベースラインのように答えがわかっている構成をひとつ通す。自分の場合はCV 15.910 / 実LB 15.883 で、ハーネスの正直さがその場で確認できた。
「CVが当たらない」と言う前に、まずこれをやる。数時間で終わる。
② 他人のノートブックを、提出枠を使わずに検算する
フォークの中のCV機構を取り出して、リークだけ潰して回す。「公開LBがCVより3ft良ければ過剰適合」という基準は、今回きれいに機能した。
③ 成分ごとの「隠しテストでの劣化係数」を測ってから重みを解く
単体提出を1本ずつ使って、c = LB単体 ÷ OOF単体 を実測する。成分ごとに劣化の向きが違う(今回はDP 1.20 / スタック 1.11 / UNet 0.93)。OOFの上で解いた重みは、そのぶん確実にズレている。
そしてこの構造は privateでもそのまま保存された(1.24 / 1.09 / 0.97)。今回いちばん頑健だった知見である。
唯一の判断ミス
きれいな話ばかり書いてもフェアではないので、はっきりした失敗をひとつ。
「帯別重み」(予測のズレの大きさで領域を切って、領域ごとに別のブレンド重みを使う)について、publicが「分割を増やすほど悪化する」ときれいな単調性を見せたので、締切4日前に帯別系を全部降ろした。
| 構成 | 検算CV | public | private |
|---|---|---|---|
| 一律 | 7.5259 | 8.209 | 8.331 |
| 帯別1分割 | — | 8.241 | 8.512 |
| 帯別5帯 | 7.4378 | 8.309 | 8.215 ← 全提出中の最良 |
| 一律(採用したもの) | — | 8.163 | 8.313 |
privateでは逆だった。 検算CVの順序のほうが保存されていた。取り逃した量は0.098ft(約3cm)、順位にしておよそ19位ぶんである(銀ラインまで79位の余裕があったのでメダル色は変わらないが)。
そして最悪なことに、その4日前に、自分で正しい但し書きを書いていた。
帯別の利得の81%は、全体の行の0.99%に乗っている。公開LBはおよそ50井なので期待発火は0.78本。したがって公開LBは帯別の可否を原理的に判定できない。判断材料は検算CVのみ。
それを、翌々日にpublicの3点がきれいに並んでいるのを見て、自分で上書きしたわけだ。
ここから出る一般則
CVとLBが衝突したら、まず「そのLBがその改善を測れるのか」を判定する。
- 利得がどの行・どの井戸に何%乗っているかを分解する
- publicの井戸数を掛けて期待発火数を出す
- 1を割ったら、publicの差はノイズとして扱う
そして、もうひとつ。
過去の自分が書いた「この指標では測れない」という但し書きは、新しい証拠が来ない限り無効化されない。
publicの単調な3点は新しい証拠のように見える。でも、そもそも分解能の外にあるなら、単調に見えること自体が偶然である。
注意しておきたいこと
「public LBを信じるな」という結論に丸めたくないので、補足しておく。
- privateもまた、ひとつの標本である。 privateはおよそ150井の上での実測で、0.098ftの差が母集団で必ず同じ符号になるとは限らない
- 今回publicが役に立たなかったのは、publicがおよそ50井しかなく、かつ効く場面が疎な改善だったからだ。publicが十分に大きければ話は逆になる
- したがって主張は「publicを無視しろ」ではなく、その指標がその改善を測れるかを先に判定しろ、である
おわりに
1ヶ月前まで「地質とか、わけわからんからやらんでいいや」と思っていたコンペである。実際6月には一度全敗して撤退している。
そこから銀まで来られたのは、上手いモデルを引いたからではなく、測り方の道具をひとつずつ足していったからだった。天井を測る、ハーネスを校正する、他人の船を検算する、成分ごとの劣化を測る。どれも派手ではないし、どれも数時間で終わる。
心残り
強いて心残りを挙げるなら、データ領域を選り好みしすぎたことだ。
6月に降りた理由は「地質のドメインに興味が続かない」だった。でも実際に戻ってみたら、この問題の本質は地質の知識ではなく系列の整列で、ドメイン知識を要求される場面はほとんどなかった。降りた理由は、中身をちゃんと見たうえでの判断ではなかったということになる。
7月12日ではなく6月中に戻れていたら、天井の測定もCVハーネスの校正ももっと早く終わっていたはずで、結果は少し違っていたと思う。
ループエンジニアリングで出た初めてのコンペだった
もうひとつ。今回はエージェントをループで回しながら実験を進めるやり方(ループエンジニアリング)でコンペに出た、初めての回だった。
タスクを分解して積み、実験を回し、結果をwikiに書き戻して次を決める、というサイクルを1ヶ月まわし続けたわけだが、そのぶん試行の本数は明らかに増えた。この記事に書いた「効かなかったもの」の大半は、手で回していたら試す前に諦めていたはずのものである。
一方で、Claudeの見積もりをそのまま採用すると止まってしまう場面がある(UNetの件)とか、きれいに並んだ3点を見せられると人間のほうが判断を上書きしてしまう(帯別の件)といった、運用側の癖もはっきり見えた。このへんは今後の資産になったと思う。
そして、いちばん報われた判断はこれだった。
分岐が本質的に不確実なら、両方に1枚ずつ置く。
最終2枠は「良いほうが採点される」ので、フォークが正直だった世界線と過剰適合だった世界線に1枚ずつ置いた。結果は後者で、フォーク単独ならメダル圏外。逆に前者だったならフォークが拾っていた。どちらに転んでも銀以上が残る構造を、締切の1週間前に作れたのが決め手だったと思う。
書ききれなかったネタ(合成データで事前学習しても実データの汎化が動かない話、アンサンブルの床はモデル数Nではなく誤差相関ρで決まる話)は、また別途書く。
参考リンク
- ROGII - Wellbore Geology Prediction(Kaggle) https://www.kaggle.com/competitions/rogii-wellbore-geology-prediction
- Kaggle Progression System(メダル授与基準) https://www.kaggle.com/progression