🥈

Kaggle ROGIIコンペ振り返りー192nd Place Solution

に公開

はじめに

Fusicのレオナです。

KaggleのROGII - Wellbore Geology Prediction[1]で、水平井戸のガンマ線(GR)をtypewellへ対応付けるCNN-SDFモデルを実装しました。最終結果は192位、Privateスコア8.143で銀メダルでした。[2]

このコンペでは、当初は行単位の特徴量と後処理を中心に試していました。しかし最終日に方針を変え、井戸1本を1サンプルとする2次元モデルを実装しました。本ブログでは、CNN-SDFの仕組み、評価方法を切り替えた理由、終了後に公開された上位解法との共通点を振り返ります。

結果

構成 partial host-cut OOF[3] Public Private
hold-last 16.682 約15.88 記録なし
GBM 480特徴 12.048 記録なし 記録なし
CNN-SDF single-scale + flip TTA 8.669 6.353 8.339
CNN-SDF multi-scale + flip TTA 8.347 6.232 8.224
0.7×multi-scale TTA + 0.3×GR欠損マスクモデルのTTA 8.145 6.079 8.143

最終アンサンブルはPublic 6.079で100位、Private 8.143で192位でした。1位との差は2.504フィートです。231坑のpartial OOFでは、誤差上位3坑だけで二乗誤差総和(SSE)の39.96%を占めました。

今回の要点は次の3つです。

  1. 行単位の回帰から、井戸全体を扱うCNN-SDFへ切り替えた
  2. 旧ローカル評価とPublic LBの逆転を受け、坑井単位のhost-cut評価へ切り替えた
  3. multi-scale TTAと、GR欠損の扱いが異なるP2を混ぜることで失敗する井戸を分散した

タスクとアプローチ

コンペ概要

項目 内容
タスク 水平井戸GRとtypewell GRを対応付け、True Vertical Thickness(TVT)を予測
予測区間 TVT_inputが欠損している後半区間(全体の約73%)
評価指標 予測区間の全行をまとめたpooled RMSE

1次元の対応付けとしての問題設定

このタスクは、各行を独立に予測する回帰問題というより、2本のGR系列を対応付ける問題です。水平井戸GRをtypewell GRのどこへ重ねるかが決まれば、各MD行に対応するtypewell上のTVT座標も決まります。

入力は掘削位置ごとのMD、座標(X、Y、Z)、GR、既知区間のTVT(TVT_input)です。typewell側にはTVTとGRの対応表があります。水平井戸では最初の約27%だけTVTが既知で、残り約73%が予測区間です。

GRは準周期的なので、複数の位置が同じように一致します。20フィートずれた対応と90フィートずれた対応が同程度に見える区間があり、1つの入力に対して複数の対応候補が残ります。さらに、評価指標は予測区間の全行をまとめたpooled RMSEです。そのため、大きなドリフトを外した少数の井戸が全体スコアを強く左右します。

水平井戸GR、typewell GR、TVT対応、井戸軌跡の例
図1:データセット付属図の例(表示名Well10784)。GR、TVT対応、末尾200フィート、井戸軌跡を示します。

取り組みの時系列

最初の提出は2026年7月25日、締切は8月6日9時(JST)でした。実質的な取り組み期間は約12日です。[4]

時期 取り組み 結果と判断
7月25日 公開NotebookのQ0522とV14を再現 Public 6.428のiaztec V14をベースラインに採用
7月25〜28日 hold-lastとoracleの基準値、入力契約、坑井単位の分割を整備 公開testで見える3坑はtrainと重複しており、検証には使わないと判断
7月末〜8月1日 較正倍率、候補軌道、近傍坑井転写、形状予測、合成データ事前学習を比較 単体でベースラインを超えない案を打ち切り、V14の後処理へ探索を絞る
8月2〜4日 ローカル評価後に提出した施策を棚卸し 改善を予測した5件がPublic LBと逆転し、旧評価器による候補選択を停止
8月5日〜6日朝 井戸全体を1サンプルとするCNN-SDFを実装 partial OOFでmulti-scale TTAとGR欠損マスクモデルの混合を選択

出発点のV14[5]は、粒子フィルタ、物理アンカー、GBMを組み合わせた公開Notebookです。同じPublic評価集合への過学習を避けるため、施策は未知の井戸を分離したローカルCVで選ぶ方針にしました。それでも前半の評価器ではPublic LBとの逆転が続いたため、最終的に坑井単位のhost-cut評価へ切り替えました。

最終手法:CNN-SDF

CNN-SDFへの切り替え

最終日にディスカッションを調べると、Kaggle参加者のTucker Arrants[6]が、井戸全体を1サンプルとするGRベースのモデルで、well-grouped pooled CVが約5だったと報告していました。評価条件を直接比較することはできませんが、自分のtree系partial OOFは12.048でした。この差は、行単位の特徴量設計を続けるより、井戸全体の表現へ移る判断材料になりました。[7]

hengck23はDiscussion[8]で、typewell GRと水平井戸GRから2次元ヒートマップを作り、CNNでsigned distance field(SDF、符号付き距離場)を回帰する公開Notebookを紹介していました。GRログから複数の地層軌道が考えられる問題については、Alyaevらの論文[9]でも、複数の軌道とその確率を同時に予測しています。

CNNには残差U-Netを使いました。井戸全体を画像化する定式化とSDFの教師信号を公開実装から引き継ぎ、近似hold-last prior、列ごとのsoftmax位置推定ヘッド、TVTへ微分可能に復号する損失を追加しました。[10]

水平井戸GRの欠損構造

trainは773坑で、各坑に水平井戸とtypewellのCSVが1つずつ対応します。水平井戸側は5,092,255行、typewell側は1,567,045行です。

観測値としての欠損は、水平井戸側のGRに集中していました。全5,092,255行のうち1,507,972行(29.6%)が欠損し、773坑すべてに1行以上の欠損がありました。坑ごとの欠損率は中央値27.7%で、0.7%から80.1%まで幅があります。typewell GRには欠損がありませんでした。[11]

TVT_inputも74.3%が欠損していますが、こちらは予測対象の正解を隠すタスク上の仕様です。そこで、TVT_inputの欠損とGRの観測欠損を分けて扱い、水平井戸GRは前後値で補間しました。後述するP2では、補間前のGR実測率と、その実測率で減衰させたGR差もモデルへ渡しています。

入力契約の固定

testで利用できる列は、水平井戸側のMD, X, Y, Z, GR, TVT_inputと、typewell側のTVT, GRだけに制限しました。
TVT_inputの最後の非NaN位置をh_psとし、ここより後ろの真値TVTは入力経路に渡しません。
trainでも、将来のTVTは教師信号を作るときだけ参照します。

GRは欠測を前後値で補間し、Savitzky-Golayフィルタ(window 51、degree 2)を適用しました。
軌跡のX、Y、Zはwindow 101で平滑化し、差分からdipとazimuthを計算しました。
角度はsinとcosに分け、境界の不連続を避けました。

井戸全体の384×896画像への変換

1サンプルを1坑分のデータとし、画像の行をtypewell TVT、列を水平井戸MDのブロックに割り当てます。
最終構成は、typewell側384行、既知列128列、未知列768列です。
typewellの行間隔は0.5フィートで、host cut付近のTVTを中心に±96フィートを切り出します。
既知列にはcut直前の区間、未知列にはcut直後の区間を、それぞれS点ずつ平均して並べます。

未知区間の長さが井戸ごとに違うため、1列がまとめるMD行数Sは固定しません。
未知区間の行数をntail、未知列数をH_F=768とすると、次の式でSを決めます。

S = max(s_base, ceil(ntail / H_F))

学習時のs_baseは8〜20点から乱択し、推論時は12点を基準にしました。
実際のSはmaxで拡張されるため、長い井戸では20点を超える場合があります。
各列はS行の平均で作り、範囲外の列には有効マスクを付けます。
データ上ではおおむねSフィートに相当しますが、実装はMD値ではなく配列の行数でblock化しています。

教師信号となるSDFは、各MD列jに対して、typewell行iのTVTから水平井戸の正解TVTまでの符号付き距離を持つ2次元配列です。
この実装では、曲線までの2次元ユークリッド距離ではなく、同じMD列内のTVT方向の差だけを計算します。

sdf[i, j] = clip((h_tvt[j] - t_tvt[i]) / 40.0, -3.0, 3.0)

40フィートをSDFの1単位としているため、正解TVTがtypewell行より40フィート大きければ+1、同じなら0、40フィート小さければ-1になります。
たとえば、あるMD列の正解TVTが11,000フィートの場合、SDFは次の値になります。

typewell行のTVT 正解TVTとの差 SDF
10,960フィート +40フィート +1.0
11,000フィート 0フィート 0.0
11,040フィート -40フィート -1.0

各列をtypewellの行方向に見ると、SDFが正から負へ切り替わるゼロ交差が水平井戸のTVTに対応します。
値は-3から+3へclipするため、120フィート以上離れた位置では一定値になります。

正解行だけを指定する分類の教師信号と異なり、SDFは各行が正解TVTからどちら向きに何フィート離れているかを表します。
そのため、画像全体をMSEの学習信号に使いながら、MD方向に連続するTVT軌道を2次元の場として学習できます。

表現力を確かめるため、評価対象と復号経路が異なる2つの検証を行いました。通常の評価経路で真のSDFをdecode_tvt()へ通した表現床は0.046フィートです。231坑のpartial OOFで±96フィートの窓幅に真値が収まるかを調べた別の検証では、表現床は0.071フィートで、窓外へ真値が出る坑は2坑だけでした。少なくともこの231坑では、窓幅が誤差の主因ではありませんでした。[10:1]

11チャネルのGPU展開

GRや軌跡特徴は1次元ベクトルとして保持し、expand_channels()[B, C, T, H]へ展開します。
最終構成の入力チャネルは次の11個です。

  1. typewell GR
  2. 水平井戸GR
  3. 水平井戸GRとtypewell GRの差
  4. 既知TVTに置いたGaussian ridge
  5. hold-last priorとtypewell行の差
  6. dipのsin
  7. dipのcos
  8. azimuthのsin
  9. azimuthのcos
  10. 既知列のmask
  11. MD方向の正規化距離

GRは(GR - 60) / 40で正規化します。
hold-last priorには、既知区間ではS点ごとのTVTブロック平均を使い、未知区間では最後の既知ブロック平均を繰り返しました。
厳密な「cut行のTVTを固定するhold-last」ではなく、その近似です。
このpriorとtypewell行の差を入力することで、ネットワークへ近似hold-lastからのずれを示します。モデルの出力自体はpriorの残差ではなく、絶対的なSDFになります。

Gaussian ridgeは、priorとtypewell行の差を1.5フィートで割った値を指数関数へ通し、既知列だけに掛けます。

d = (prior[:, None, :] - t_tvt[:, :, None]) / 1.5
hist = torch.exp(-d * d) * known[:, None, :]

既知列はknownチャネルで明示し、GRとGaussian ridgeを文脈情報として入力します。画像範囲外の列とtypewellのpadding行は入力時にゼロ埋めし、SDF損失ではmaskの対象外にしました。分類ヘッドではpadding行をsoftmax前に除外します。
近傍井戸の情報は最終構成には入れていません。
近傍priorは後述の別実験として評価しました。

残差U-Netの2種類の出力

残差U-Netの入力は[B, 11, 384, 896]です。
width 64、encoder depth (2, 2, 2, 3, 3)とし、3×3 Conv、BatchNorm、GELUからなるBlockをskip connectionでつなぎました。
decoderはbilinear upsampleとencoder featureの連結で構成しました。

出力ヘッドは2チャネルです。第1チャネルはtanh×3で範囲を制限したSDF、第2チャネルは各列が対応するtypewell行のlogitsを出すrow head(typewell行分類ヘッド)です。[10:2]

SDFとrow headのTVT復号

推論では、MD列ごとにSDFのゼロ交差をTVTへ戻します。
hard decodeは、予測SDFが行方向に単調であると仮定し、正値の行数からゼロ交差を挟む2行を特定して線形補間します。

学習時には、この処理を微分可能にするため、SDFをフィートへ戻してtau=2.0のsigmoidを適用します。
正のSDFは1、負のSDFは0に近づくため、sigmoidの行方向の総和が正解TVTより小さいtypewell行数の近似になります。
この行数から半行分を引き、0.5フィートを掛けて窓先頭のTVTへ加えると、TVTへ変換できます。

s = sdf[:, 0] * 40.0
frac = torch.sigmoid(s / tau).sum(dim=1)
pred_tvt = t_tvt[:, :1] + 0.5 * (frac - 0.5)

このpred_tvtと真値TVTの二乗誤差を、typewell窓内で表現可能な列について平均しました。
未知列の重みを1、既知列を0.25とし、真値が窓外または窓端8フィート以内にある列は対象外にしました。
SDFのMSEだけでは、最終指標であるTVT RMSEと勾配の向きがずれるためです。

row headには、真のTVTを中心とするσ=3フィートのsoft Gaussian教師分布を作り、typewellのpadding行をsoftmax前にマスクします。
推論ではargmaxの±24行だけを残し、局所的な期待値を取ります。

SDF MSEは全有効列を対象とし、ゼロ交差に近い画素へ最大4倍の重みを与えました。
最終損失は次の組み合わせです。

L = L_sdf
  + 1.0 * L_tvt_mse / 100
  + 1.0 * L_row_ce
  + 1.0 * L_row_tvt_mse / 100

最終提出では、事前に固定していたSDF復号とrow head復号の0.5 blendを使いました。

col_sdf = decode_tvt(sdf, t_tvt)
col_row = soft_row_tvt(row_logits, t_mask, t_tvt)
col_tvt = 0.5 * (col_sdf + col_row)

提出後の保存評価は、SDF単独8.650、row head単独8.928、0.5 blend 8.669でした。結果だけを見ればSDF単独がわずかに良好です。row headを含むrunはSDF-onlyの初期runより良かったものの、widthや既知列幅、学習条件も同時に変えているため、row head単独の効果を測るablationにはなっていません。[12]

CNN-SDFのposterior mapと予測誤差
図2:fold 2のOOF井戸060ab2b8。上段はtypewell行へのlog posteriorで、緑が真値、青がSDFとrow headのblendです。下段は予測区間6,258行の補間後誤差です。

物理と測定過程に沿ったデータ拡張

学習時のbuild_vectors()では、各サンプルの列幅、cut位置、typewell行中心、GRの測定条件を乱択しました。
最終学習runで使った主な設定は次の通りです。

拡張 設定
基準ブロック幅s_base U{8, ..., 20}点(実際のSは前述の式で決定)
typewell軸反転 50%で反転し、TVTとdipの符号も反転
cut shift N(0, 400²)から生成し、−150〜井戸ごとの正側上限へclip
行中心jitter ±40行
GR gain 0.8〜1.2
GR bias -8〜+8
GR noise σ=2.5
typewell depth warp ±3%
GR span blanking 連続区間をmask相当の値で空白化

typewellの上下反転では、t_grの行順を反転し、t_tvtは符号反転と行順反転を同時に行います。
水平井戸側の相対TVTとdipも符号反転し、それに合わせてSDFの教師信号を作ります。
depth warpはtypewell GRの座標だけを変え、SDFの教師信号は変更しません。
各変換後も真値を復号できることをオラクルで確認しました。

学習にはAdamW(lr=3e-4、weight decay=1e-2)とOneCycleLRを使い、batch sizeを8、勾配ノルムの上限を1.0としました。最終モデルはouter fold 0〜2を各240 epochs、EMA decay 0.999で学習しました。[12:1]

評価設計と最終アンサンブル

host cutによるリーク検証

trainデータ上でcutを人工的に前倒しするpseudo-cutでは、未知区間が本番より短くなり、較正段の性能を過大評価します。
そこで、TVT_inputの最後の非NaN行を本番と同じcutとして使い、その後ろの全行を採点しました。

773坑からtrain/test重複3坑を除いた770坑を、坑井単位で10分割しました。
期限内にouter fold 0〜2だけを学習、推論したため、OOF対象は231坑、1,138,663行です。
以降、この評価をpartial host-cut OOFと呼びます。
これは10 fold全体の推定値ではありませんが、hold-lastが16.682になることと、推論経路がtestで利用可能な列だけを読むことを確認しました。

モデル partial host-cut OOF pooled RMSE(10分割中fold 0〜2)
hold-last 16.682
LGBM + CatBoost(480特徴) 12.048
CNN-SDF single-scale + flip TTA 8.669

best.pthは、性能を報告するのと同じvalidation井戸でepochを選んでいるため、8.607という値を得ても最終判定には使いませんでした。同じデータでepoch選択と性能報告を兼ねることによる選択バイアスを避けるため、最後のepochのfinal.pthで得た8.669を判定値としました。[10:3]

行レベルで平均するmulti-scale TTA

単一スケールの推論でも、typewell軸反転の予測を平均します。
multi-scale TTAでは同じ重みをS=10、12、14、16で4回実行します。
列上で平均してはいけません。
理由は、Sが変わると1列に含まれるMD行数も変わり、同じ列番号が同じMD区間を表さないためです。
各スケールでexpand_to_rows()を実行し、元のMD行へ戻してから平均します。
partial OOFでは各井戸に対して、その井戸をheld-outにした1 foldのモデルだけを使います。
一方で、testのPublic/Private提出ではfold 0〜2の3モデルも平均するため、OOFとLBはモデル本数が同一ではありません。

pred_rows = []
for S in (10, 12, 14, 16):
    sdf, row_logits = model(build_vectors(well, s_base=S))
    col_sdf = decode_tvt(sdf, t_tvt)
    col_row = soft_row_tvt(row_logits, t_mask, t_tvt)
    col = 0.5 * (col_sdf + col_row)
    pred_rows.append(expand_to_rows(col[H_H:], S, ntail))
prediction = np.mean(pred_rows, axis=0)

基準ブロック幅s_baseは学習時にも8〜20点で乱択しており、推論時の集合をLB上で探索していません。
partial OOFは8.669から8.347、Publicは6.353から6.232へ下がりました。
ただし、改善量の60.4%を1坑が占めました。
最も改善した1坑を除いても改善は残りましたが、上位2坑を除くと符号が反転し、坑井単位のpaired bootstrap CIも0をまたぎました。pooledでは改善したものの、特定の坑に依存しない改善とは確認できませんでした。[13]

GR欠損マスクモデルの補完性

水平井戸GRの欠損を補間すると、どの値が実測で、どの値が補間値かをモデルから区別できません。そこで、有効なGRだけで各ブロックの平均を計算し、「ブロック内のGR実測率」と「GR差×実測率」の2チャネルを追加したGR欠損マスクモデルを作りました。入力はベースモデルの11チャネルから13チャネルになります。実測行が1つもないブロックでは補間後の平均値を保持しつつ、実測率を0として重み付きGR差を無効化しました。

評価範囲の異なる実験を混同しないよう、結果を表にまとめます。

評価範囲 結果 解釈
fold 0 ベースモデル10.59、欠損マスクモデル11.58 欠損マスクモデル単独では0.99フィート悪化
231坑のpartial OOF multi-scale TTA 8.347、欠損マスクモデルのTTA 9.232、0.7×multi-scale TTA + 0.3×欠損マスクモデルのTTAは8.145 欠損マスクモデル単独は弱いが、混合すると改善
2-fold限定の追加seed実験 欠損マスクモデルは10.03→9.84、アンサンブルは9.036→9.100 欠損マスクモデル自体は改善したが、アンサンブルは悪化

欠損マスクモデルのTTAとmulti-scale TTAの残差相関は0.789で、最終夜に試した候補の中では最も低い値でした。一方で、2つの欠損マスクモデルのseedを平均するとmulti-scale TTAとの相関が0.843へ上がり、補完性が低下しました。単独精度だけでなく、失敗する井戸がどれだけ異なるかがアンサンブルには重要だったと解釈しています。最終提出には、231坑のpartial OOFで選んだ0.7×multi-scale TTA + 0.3×欠損マスクモデルのTTAを採用し、追加seedは使いませんでした。[10:4]

うまくいかなかった試みと学び

近傍井戸priorの検証

近傍情報については、信号の有無、モデルへの入れ方、診断時の自己近傍混入を分けて調べました。

まず、テスト井戸が既存井戸の間に掘られるin-fillであるため、近傍井戸のTVT曲線には利用価値があると考えました。保存した診断では、距離条件を満たす150〜600フィート帯の163坑をpooled集計すると、単純な近傍転写がhold-lastを上回り、ドリフト70フィート超のsubsetでも改善しました。

一方で、近傍TVTと距離を2チャネルとしてCNNへ直接加えたv4dは、fold 0のhost-cut validationでepoch 59時点13.986でした。VM中断のため、231坑のpartial OOFは取得していません。近傍信号そのものが無効というより、使うべき井戸を選ぶ仕組みがないまま入力したため、ノイズとして扱われた可能性があります。

さらに、trainデータ上の点単位k近傍診断では、照会対象と同じ井戸の軌跡点が約4フィート間隔で近傍を埋め、50フィート以内には同一孔の再掘削や再ログも含まれました。近傍情報を使うなら、井戸自身を候補から除外したうえで、posteriorや不確実性を使って利用可否を制御する必要があります。[10:5]

大域探索による候補選択

CNNの誤差が「答えを表現できるのに選べていない」なら、推論後に候補軌道を大量に作って選べるかを試しました。1坑あたり4,444本の線形・二次軌道を生成し、各軌道上のposteriorを集約した値をenergyとしました。このenergyが最小の候補をMAPとして選びます。

候補集合のoracle pooled RMSEは3.18フィートだったため、良い軌道は候補内に含まれていました。
一方で、MAPはenergy最小として選ぶので、真値側のenergyがMAPより高いこと自体は自明です。
問題は差と順位でした。
予測誤差が特に大きかった坑では、真値側候補のenergyがMAPの3〜4倍となり、4,444本中1,498〜3,419位へ落ちていました。
少なくともこの候補familyでは、posterior由来のenergyが良い候補を上位へ並べられていませんでした。

fold 2でCNN-SDF誤差が大きかった20井戸のRMSE
図3:fold 2のOOFでCNN-SDF誤差が大きかった20坑。灰色はhold-last、青はCNN-SDF blendです。fold 2全体は77坑、371,197行で、pooled RMSEは15.21から6.26フィートになりました。

正のtemperature変更はenergy順位を保存するため、この枝を入れ替えられません。
候補数を変えると候補集合自体も変わるため、一般には同じことを言えません。
それでも今回試した4,444候補の範囲では、後処理よりも正しい枝へ質量を置く学習を優先すべきだと判断しました。次の候補は、endpoint driftの補助ヘッドや、大ドリフト井戸へのsample weightです。[14]

ローカル較正段の5連敗

最終モデルを作る前の公開Notebookでは、較正倍率と信頼度に応じて、ベースラインから候補軌道へ予測を移動していました。保存記録で追える逆転は次の5件です。

施策 ローカル評価 Public LB
較正倍率 倍率を上げるほど悪化 6.504→6.460→6.425→6.396と改善
較正スキップ(S13) 0.16改善 7.506
候補除外(REDIRECT) 約1.7改善 7.382
近傍転写(nbr1) 採用基準を通過 7.150
selector(EXP1) 改善方向 8.049

ローカルで改善を予測した5件は、すべてPublic LBで期待どおりになりませんでした。[15]

候補family別の診断では、学習成分を含まないParticle Filter候補(pf)へベースラインから予測を1フィート動かすごとに、RMSEが0.70フィート増える回帰係数が得られました。最終の配備等価スイープでも、同じ係数は1.09〜1.14と最大級でした。旧較正器の搭載モデルは、重複を含むtrain 773坑をすべて学習済みです。そのため、train井戸上のベースラインが真値に近すぎる可能性があります。
一方で、CNN-SDFのpartial OOFは重複3坑を除いた770坑を分割しているため、両者は評価母集団も異なります。

移動量は倍率やprofile強度と交絡し、ペア比較でも完全には消えませんでした。
したがって「候補の中身に関係なく移動距離だけを測っていた」とまでは断定できません。
ただし、少なくともこの評価器では候補familyよりベースラインからの移動量との関連が支配的で、候補選択の基準には使えないと判断しました。[16]

ローカルCVを基準にした理由

PublicとPrivateでは、評価対象の井戸が異なります。Publicへの再提出が安定していることと、Privateの新しい井戸へ一般化することは別の問題です。ディスカッション投稿の要旨を筆者の言葉でまとめると、次の見方が共有されていました。[7:1]

  • Publicは再提出に対して精密ですが、Privateとは母集団が異なります。
  • 小さなA/B差は実行ばらつきと区別しにくいです。
  • PublicからPrivateで順位が大きく変動する可能性があるため、モデル選択ではCVを優先します。

Publicは約52坑、Privateは約148坑と見積もられていました。無変更再提出14回から見たPublicの実行ばらつきはσ≈0.03フィートです。これとは別に、評価井戸の標本数から見たRMSEの標準誤差は、最新の監査メモではPublic約0.78フィート、Private約0.45フィートと整理されています。古いdiscussionメモでは0.78フィートをPrivate側としていましたが、後の監査でラベル誤りと判断しました。そこで、Leaderboardの小差よりも、入力契約、host cut、坑井単位分割を固定したpartial OOFをモデル比較の基準にしました。[17]

75位のwriteupは24提出についてcorr(CV, Private)=0.930corr(Public, Private)=0.404と報告し、CV差が0.1フィート未満の場合だけLBをtie-breakerにする運用を説明しています。[18]

公開された解法

公開された17件のwriteupを対象に、生成AIを整理の補助に使い、各記事の記載を筆者が要約しました。以下、Kaggleユーザー名は敬称略で記載します。[18:1]

順位 記事掲載CV 記事掲載Private[19] 解法の中心
1位 Ruby 4.627 5.639 2D alignment U-Net、Particle Filter、XY近傍特徴
3位 Takoi、tereka 5.288 5.836 HMM、Particle Filter、3種類のNNをsoftmaxで重み付けして混合
6位 k256.dev 5.458 5.984 91本のParticle Filter候補をrow-levelでbagging
7位 Gaopeng Ren、eijixxx 5.203 6.057 HMM、U-Net、合成誤差で学習したrefiner
9位 Anil Ozturk、Ertuğrul Demir 5.28 6.251 whole-well U-Net、HMM、beam search、NNLS
10位 Can 5.719 6.269 物理候補、2D U-Net、LightGBM selector、TCN
11位 IIDA 5.66 6.299 U-Net、同一井戸の既知GR、Particle Filter、GBDT
13位 monnu 5.097 6.319 3画像モデル、coherent decode、1D候補モデル
14位 ktyser 6.252 6.329 階層U-Net、坑単位の選択モデル、seed averaging
18位 yu4u 4.91 6.411 chord座標の画像化、合成データ、異種headのblend
21位 goonew 5.028 6.504 合成候補scorer、forward-backward、再結合層
23位 Jiwei Liuほか 6.067 6.729 distributional U-Net、nested residual、routing
26位 Tucker Arrants 5.038 6.660 stacked U-Net、合成井戸による事前学習
27位 Ryo Takaki 記録なし 6.651 ConvNeXt 2D U-Net、距離prior、Particle Filter
36位 Chris Deotte 5.706 約6.8 2D ResNet、persistent-datum HMM、実測GR再整合
52位 DECEMほか 6.115 7.087 65候補Bank、Typed Crossformer、bounded residual
75位 AyoGG 5.976 7.444 純HMM/DP、multi-basin hedging、5モデル平均

上位解法に共通していたのは、井戸全体を2次元のalignment問題として扱うことと、誤った周期へ飛ばないための仕組みを持つことです。前者にはU-Net系の画像モデル、後者にはParticle Filter、HMM、beam search、coherent decodeなどが使われていました。また、単一予測に賭けず、物理候補や複数モデルを選択モデルや重み付けで組み合わせる解法も多く見られました。

自分のCNN-SDFも、井戸全体を2次元化して列ごとの分布を予測する点では同じ方向です。一方で、chord座標、合成井戸による事前学習、物理候補bank、二段目refinerなどは採用できませんでした。これらが順位差の原因だとは断定できませんが、画像モデルだけで完結させず、座標表現、合成データ、物理候補、二段目モデルまで含めて設計する余地があったことが、今回の自分の学びです。

最後に

今回は、水平井戸GRとtypewell GRの対応を井戸単位の2次元画像で表し、残差U-NetでSDFを予測しました。最終提出ではmulti-scale TTAとGR欠損マスクモデルをアンサンブルし、192位、Private 8.143で銀メダルを獲得しました。

最大の反省は、モデルより先に評価器を疑うべきだったことです。前半は全train坑を学習済みのベースライン上で較正施策を比較し、ローカルとPublic LBの逆転を5回経験しました。評価値の大小だけでなく、その評価器が未知の井戸に対する改善を測れているかを確かめる必要があります。すでに用意していた坑井単位のhost-cut分割を、CNNの評価にもっと早く使っていれば、モデル改善に使える時間を増やせたはずです。

これまでのKaggle参加はお試し程度で、モデル開発の経験も特定のドメインに限られていました。ジュニアデータサイエンティストとして、今回は初めて「メダルを取りに行く」と決め、締切の約2週間前から本格的に取り組みました。限られた期間で銀メダルを獲得できたことを、素直にうれしく感じています。

生成AIの発達によって、アイデアさえあれば、公開実装を読み、動くコードと検証可能な実験へ落とし込むまでの障壁は大きく下がりました。一方で、どの評価を信じ、どの結果を採用するかは、自分で判断し続ける必要があります。それでも、アイデアを持って生成AIと検証を回せば、私のように経験の浅いジュニアでもKaggleメダルを狙える時代になったと実感しています。

脚注
  1. Kaggle, ROGII - Wellbore Geology Prediction, https://www.kaggle.com/competitions/rogii-wellbore-geology-prediction ↩︎

  2. ROGII - Wellbore Geology Prediction, Leaderboard, https://www.kaggle.com/competitions/rogii-wellbore-geology-prediction/leaderboard ↩︎

  3. 773坑からtrain/test重複3坑を除いた770坑を10分割し、outer fold 0〜2だけを実行したpartial OOFです。
    231坑、1,138,663行をrow-pooledで評価しました。 ↩︎

  4. 保存していた提出履歴では、最初の提出は2026年7月25日、競技締切は同年8月6日9時(JST)でした。 ↩︎

  5. iaztec, "Top PF-Config | Branch Conservative | Visuals" (Version 14), https://www.kaggle.com/code/iaztec/top-pf-config-branch-conservative-visuals ↩︎

  6. Tucker Arrantsは本コンペの最終26位の参加者です。
    CVに関する発言はDiscussion #731550で確認できます。
    最終解法は26th Place Solutionで公開されています。 ↩︎

  7. Public評価集合の規模とPublicからPrivateへの変動についてはDiscussion #728477を参照しました。 ↩︎ ↩︎

  8. hengck23, Discussion #699853 ↩︎

  9. Sergey Alyaev, Ahmed H. Elsheikh, "Direct multi-modal inversion of geophysical logs using deep learning", https://arxiv.org/abs/2201.01871 ↩︎

  10. 前処理、入力チャネル、損失、推論条件の数値は、最終提出時に保存した設定と実験ログから転記しました。 ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  11. Kaggle配布trainの水平井戸CSV 773件とtypewell CSV 773件を集計しました。欠損率29.6%は、水平井戸GRの全5,092,255行を分母にしたrow-weighted集計です。予測区間など特定subsetだけを分母にする欠損率とは直接比較できません。坑ごとの欠損率は各CSV内で計算しました。 ↩︎

  12. モデル構成と学習条件は、最終提出時に保存したコードと学習ログから転記しました。 ↩︎ ↩︎

  13. TTAの比較値は、完全に学習から除外したouter fold 0〜2の231坑をrow-pooledで自己評価した結果です。 ↩︎

  14. 大域探索の候補数、oracle RMSE、energy順位は、CNN-SDFが出力したposteriorを使って計測しました。 ↩︎

  15. 5件の逆転は、施策をローカル評価してからPublic LBへ提出した履歴を突き合わせて数えました。 ↩︎

  16. 候補family別の移動ペナルティは、train坑上でbaselineから候補軌道へ予測を動かし、移動量とRMSEの関係を自己計測した結果です。 ↩︎

  17. 評価坑数をPublic約52坑、Private約148坑として、標本数に由来するRMSEの標準誤差をPublic約0.78フィート、Private約0.45フィートと推定しました。古いdiscussionメモでは0.78フィートをPrivate側としていましたが、後の監査でPublic側の値と整理しました。無変更再提出のσ≈0.03フィートは、これとは別の実行ばらつきです。 ↩︎

  18. Ruby, "1st Place Solution", https://www.kaggle.com/competitions/rogii-wellbore-geology-prediction/writeups/1st-place-solution
    Takoi, tereka, "3rd Place Solution", https://www.kaggle.com/competitions/rogii-wellbore-geology-prediction/writeups/3rd-place-solution
    k256.dev, "6th Place - PF, PF, PF, PF, Physics and Row-Level Bagging", https://www.kaggle.com/competitions/rogii-wellbore-geology-prediction/writeups/6th-place-diverse-gr-representations-91-pfs
    Gaopeng Ren, eijixxx, "7th Place Solution", https://www.kaggle.com/competitions/rogii-wellbore-geology-prediction/writeups/7th-place-solution-hmm-unet-agent-is-all-you
    Anil Ozturk, Ertuğrul Demir, "9th Place Solution", https://www.kaggle.com/competitions/rogii-wellbore-geology-prediction/writeups/9th-place-solution
    Can, "10th Place Solution", https://www.kaggle.com/competitions/rogii-wellbore-geology-prediction/writeups/10th-place-solution-a-compass-not-a-map
    IIDA, "11th Place Solution", https://www.kaggle.com/competitions/rogii-wellbore-geology-prediction/writeups/11th-place-solution
    monnu, "13th Place Solution", https://www.kaggle.com/competitions/rogii-wellbore-geology-prediction/writeups/13th-place-solution
    ktyser, "14th Place Solution", https://www.kaggle.com/competitions/rogii-wellbore-geology-prediction/writeups/14th-place-a-hierarchical-u-net-stack-with-seed-a
    yu4u, "18th Place Solution", https://www.kaggle.com/competitions/rogii-wellbore-geology-prediction/writeups/18th-place-solution
    goonew, "21st Place Solution", https://www.kaggle.com/competitions/rogii-wellbore-geology-prediction/writeups/21st-place-solution
    Jiwei Liu, Eduardo Rocha de Andrade, Max Jeblick, dott, "23rd Kaggle Agent Solution", https://www.kaggle.com/competitions/rogii-wellbore-geology-prediction/writeups/23rd-kaggle-agent-solution
    Tucker Arrants, "26th Place Solution", https://www.kaggle.com/competitions/rogii-wellbore-geology-prediction/writeups/26th-place-solution-stacked-unet-synthetic-wel
    Ryo Takaki, "27th Place Solution", https://www.kaggle.com/competitions/rogii-wellbore-geology-prediction/writeups/27th-place-solution
    Chris Deotte, "36th Place - GPT5.6 Sol - Yolo", https://www.kaggle.com/competitions/rogii-wellbore-geology-prediction/writeups/36th-place-gpt5-6-sol-yolo
    DECEM, Dylan Xue, hobezhang, "52nd Place Solution", https://www.kaggle.com/competitions/rogii-wellbore-geology-prediction/writeups/52nd-place-solution-physics-bank-typed-crossfo
    AyoGG, "75th Place: Physics-constrained HMM/DP", https://www.kaggle.com/competitions/rogii-wellbore-geology-prediction/writeups/75th-place-physics-constrained-hmmdp ↩︎ ↩︎

  19. 表のPrivateは各writeupに記載されたモデルまたはsubmissionの値です。
    最終順位を決めたselected submissionと一致しない場合があります。 ↩︎

Fusic 技術ブログ

Discussion