第1幕:導入
1-1. 自己紹介 & ジレンマ
筆者はバックエンドエンジニアだが、今の業務はマネジメントが中心で、普段コードを書くことはほとんどない。かつてはAPIの設計やDBのチューニングに明け暮れていたが、今はスプレッドシートと1on1が主戦場だ。機械学習には以前から興味があって、理論をかじったことはある。ただ「興味がある」と「実際にやっている」の間には、ご存知の通り深くて暗い溝がある。
Kaggleは、はるか昔にTitanicを軽く触った程度だ。チュートリアル通りにやって提出して、「へえ、こういう世界か」と思って、それきりになっていた。いつかしっかり参加してみたい——その「いつか」が何年も塩漬けになっていた。
マネジメント業務と生活の中で、まとまったコーディングの時間を確保するのは現実的に難しい。特徴量エンジニアリングを試行錯誤して、モデルのハイパーパラメータを回して、CVスコアを睨みながらアンサンブルを組む——やりたい気持ちはあるのに、キーボードに向かう時間が捻出できない。典型的な「やりたいけど手が動かない」状態だ。
そこで、コードを1行も書かずにKaggleに挑戦してみることにした。
1-2. ルール設定と武器の紹介
「コードを書かない」というのは比喩ではない。文字通り、Pythonのコードを自分の手で1行も書かないというルールを設定した。では何を使うのか。
武器は Claude Code Web だ。AnthropicがClaude Codeのブラウザ版として提供しているAIコーディングエージェントで、GitHubリポジトリに直接接続できる。筆者がやることは自然言語で指示を出すだけ。コードの生成、編集、コミット、プッシュまですべてClaude Code Webが実行する。筆者のキーボードが叩くのは日本語の指示文だけだ。
選んだコンペは Playground Series S6E3 — Predict Customer Churn。テレコム(通信事業者)の顧客が解約するかどうかを予測する二値分類タスクで、評価指標はROC AUCである。Playground Seriesを選んだのには理由がある。Kaggleが定期的に開催しているこのシリーズは合成データが使われており、データの取り扱いに神経を使う必要が少ない。参加のハードルが低く、それでいてモデリングの基本を一通り経験できる。「コードを書かない」という制約をつけて挑むには、ちょうどいい実験場だと考えた。
1-3. プロジェクトセットアップ(3月22日)
最初の一手は拍子抜けするほどシンプルだった。Claude Code Webに向かって「Kaggleのこのコンペに参加したい」と伝え、コンペのURLを貼った。それだけだ。
すると、Claude Code Webが動き始めた。コンペの概要を把握し、データの構造を確認し、パイプラインの構築に取りかかる。数分後にはリポジトリに最初のファイル群が生成されていた。train_and_predict.py(前処理からモデル学習、予測、提出ファイル生成までを1ファイルに集約したパイプライン)、kernel-metadata.json(Kaggle Kernel用の設定ファイル)、requirements.txt。プロジェクトの骨格が、指示を出してから驚くほど短い時間で出来上がった。
ただし、最初から完璧に動いたわけではない。Kaggle Kernelにpushしてみると、即座にエラーが出た。ここで筆者がやったことは「エラーが出た。直して」とClaude Code Webに伝えることだけだ。エラーログを貼り、原因の特定と修正をAIに委ねる。修正されたコードをpush、またエラー、また「直して」。この往復を3コミット繰り返したところで、パイプラインは無事に動くようになった。
このラリーが面白かった。筆者がやっているのは「動かない」「直して」という極めて雑なフィードバックだけなのに、AIがエラーの原因を読み取り、修正し、コミットしてくれる。プロジェクトがゼロから立ち上がっていく過程を、筆者はほとんど観客席から眺めているような感覚だった。
1-4. デプロイパイプライン整備(3月22〜23日)
パイプラインが動くようになった次の瞬間、バックエンドエンジニアとしての本能が顔を出した。「これ、毎回手動でKaggleにpushするの面倒じゃないか?」
そこでClaude Code Webにこう伝えた。「pushしたら自動でKaggleに提出されるようにしたい」と。
生成されたのは2つの仕組みだ。ひとつはrun.sh、ローカルでの実行から提出までを一発で回すシェルスクリプト。もうひとつは.github/workflows/kaggle-submit.yml、GitHub Actionsのワークフローファイルだ。特定のブランチにpushすると、CIがKaggle Kernelを自動的にpushしてくれる。つまり、筆者がgit pushするだけで、Kaggleへの提出まで全自動で走る。
この仕組みが整った瞬間、「ああ、これは自分のフィールドだ」と感じた。CI/CDパイプラインを組んで、pushをトリガーにデプロイを自動化する。バックエンドエンジニアが日常的にやっていることと、やっていることの構造がまったく同じなのだ。ただし今回デプロイされるのはWebアプリではなく、機械学習モデルの予測結果である。
こうして、プロジェクトの土台が整った。リポジトリがあり、パイプラインがあり、CI/CDがある。あとはモデルを改善していくだけだ。そしてその改善も、筆者はコードを書かずにやっていくことになる。
第2幕:展開 ── 試行錯誤の記録
2-1. GitHub Actions の無料枠を使い切った話
最初にやらかした失敗談から始めよう。
筆者は「Kaggle Kernelの実行完了を検知して自動で結果を取得する」ワークフローをGitHub Actionsに組んでいた。仕組みは単純で、kaggle kernels status を60秒ごとにポーリングして、完了したら出力をダウンロードする、というものだ。
一見スマートに見えるこのアプローチ、致命的な問題があった。Kaggle Kernelの実行には10分から30分かかる。つまり、1回の提出で最大30分間、GitHub Actionsのランナーがただ sleep 60 を繰り返しながら待機し続けるのだ。
「タイムアウトが足りないのかな」と思って120分、240分、300分と延長していった。冷静に考えればわかるが、これは傷口を広げているだけだった。GitHub Actionsの無料枠は月2,000分。1日数回提出を繰り返していたら、あっという間に蒸発した。
対策はシンプルだった。GitHub ActionsにはKernel Pushだけをやらせて、完了待機は一切しない。ステータス確認は手動(後述するがスマホからポチポチ確認する)に切り替えた。
ここでの学びは明確だ。AIに「自動化して」と丸投げすると、技術的には正しいがコスト面の判断が抜け落ちた設計が返ってくる。AIは無料枠の残量を気にしてくれない。人間がガードレールを引くべきポイントは確実に存在する。
2-2. Kaggle情報のコンテキスト化
当初、筆者はClaude Codeに「スコアを改善して」とだけ指示していた。返ってくるのは、このコンペのデータ特性を無視した一般論的な提案ばかり。当然だ。コンテキストがなければ、AIは汎用的なアドバイスしか出せない。
そこで、コンペに関する情報を構造化ドキュメントとしてリポジトリに配置した。
- DATASET.md: 全20カラムの定義、合成データであること、解約率26-27%の不均衡
- METRIC.md: ROC AUCの数式と最適化の方向性
- COMPETITION.md: ルール、提出フォーマット、外部データの利用可否
- CLAUDE.md / AGENTS.md: 開発ガイド、コマンド一覧、CV設定の固定ルールなど
効果は劇的だった。「このデータは合成データだからオリジナルデータセットの特性を参考にすべき」「解約率27%なので scale_pos_weight の調整が有効」といった、コンペ固有の文脈を踏まえた提案が返ってくるようになった。
筆者はこれを「プロンプトエンジニアリング」ではなく「ナレッジエンジニアリング」と呼んでいる。その場限りのプロンプトを工夫するのではなく、プロジェクトに永続的な知識基盤を構築する。リポジトリにいるすべてのAIエージェントが、同じコンテキストを共有できる。
2-3. Skills を構築したが、Claude Code Webでは使えなかった
筆者は .claude/skills/ ディレクトリに4つのカスタムスキルを作成した。スコア分析、モデル改善提案、提出ファイル検証、Kaggle提出の自動化。Claude Code CLIならスラッシュコマンドで一発起動できる設計だ。
ところが、筆者がメインで使っていたのはClaude Code Web。そしてWeb版ではスラッシュコマンドとしてスキルを呼び出す機能が対応していなかった。
リポジトリに置いたスキル定義ファイルは「コンテキストとして読まれる」という形では間接的に機能する。しかし、想定していた「コマンド一発で定型作業を実行」という使い方はできなかった。作り込みに費やした時間を思うと、事前に確認しておくべきだった。CLIとWebで機能差がある点は、これからClaude Codeを使い始める人にはぜひ覚えておいてほしい。
2-4. Kaggle Notebook分析の自動化
Kaggleコンペで上位を狙うなら、公開Notebookのチェックは欠かせない。筆者は analyze_notebooks.py というスクリプトを作り、上位公開NotebookのTOP10を自動取得して自分のコードとの差分を分析するようにした。
これもGitHub Actionsのワークフローとして動かしている。手動トリガーか週次スケジュールで実行し、「みんなどんなモデル使ってるの?」「自分が見落としている特徴量エンジニアリングはある?」をAIに調べさせる。その分析結果を次の改善指示のインプットにする、というサイクルだ。
自分でNotebookを一つ一つ読むのと比べると、圧倒的に効率がいい。特に複数コンペを並行して追いかけたい場合、この仕組みは重宝する。
2-5. データ取得とDEBUGモード
Claude Code Webの環境にはKaggle CLIがインストールされていない。つまりコンペデータのダウンロードすら直接できない。ここでもGitHub Actionsが頼みの綱で、download-data.yml というワークフローでデータをダウンロードし、リポジトリにcommitする形をとった。
さらに、Claude Code Webのマシンスペックではフルデータ(約60万行)の学習が厳しい。そこでDEBUGモードを設けた。学習データの10%をStratifiedサンプリングした train_debug.csv をリポジトリに同梱し、環境変数 DEBUG=1 で3fold・100estimatorsの軽量実行ができるようにした。
役割分担はこうだ。パイプラインの検証や特徴量の実験はDEBUGモードで高速に回す。本番のフル学習とスコア計測はKaggle Kernelに委譲する。Claude Code Webはあくまで「設計と検証の場」であり、「本番計算の場」ではない。この割り切りができてから、開発効率が一気に上がった。
2-6. Claude Code Web のハマりどころまとめ
ここまでの試行錯誤を踏まえ、Claude Code WebでKaggleに取り組む際のハマりどころを整理しておく。
| ハマりポイント | 詳細 | 対策 |
|---|---|---|
| Skillsが使えない | スラッシュコマンドがWeb版では未対応 | コンテキストドキュメントとして間接活用 |
| Kaggle CLIがない | データ取得・Kernel Push・提出が直接できない | 全てGitHub Actions経由で実行 |
| セッションの継続性 | 長時間作業でセッションが切れ、コンテキストが失われる | CLAUDE.mdにプロジェクト状態を常に記録 |
| マシンスペックの制約 | フルデータ60万行の学習が重い | DEBUGモード(10%サンプル)で検証、フル学習はKaggle Kernel |
対処の共通パターンは2つに集約される。「重い処理はGitHub Actionsに逃がす」 と 「状態はドキュメントに書く」 だ。Claude Code Webそのものの制約を嘆くより、外部サービスとの組み合わせで補完する発想が大事だと感じた。
2-7. スマホからKaggle参戦
最後に、想定外に良かったポイントを一つ。
Claude Code Webはブラウザベースなので、スマホからも普通にアクセスできる。筆者はベッドの中、移動中の電車、ちょっとした待ち時間にスマホからKaggleの作業をしていた。「特徴量にこれを追加して」「CatBoostのパラメータをこう変えて」といった指示を自然言語で送り、GitHub Actionsの実行状況やKaggle Kernelのステータスもスマホから確認する。
そもそも筆者がこのスタイルを始めた動機は「時間がない」だった。フルタイムの仕事をしながらKaggleに参戦する時間をどう捻出するか。その答えが「コードを書かない」ことだったわけだが、副産物としてスマホだけで完結するワークフローが手に入った。コードを書くならPCが必要だ。でもAIへの指示なら、フリック入力で十分事足りる。
隙間時間の5分で「前回の実験結果を分析して、次に試すべきことを3つ提案して」と送っておく。次にスマホを開いたときには提案が届いている。その中から良さそうなものを選んで「じゃあ1番目を実装して」と返す。この非同期的なやり取りが、スマホというデバイスと驚くほど相性が良かった。
第3幕:結末 -- ベースラインの壁と、10日間の記録
ベースライン構築(3/22-25)
コンペ参戦から数日で、筆者とClaude Codeの共同作業は驚くほど速く形になっていった。
最初の一手はXGBoost単体モデル。だがすぐに「1モデルで勝負する時代ではない」とClaude Codeが提案し、5モデルアンサンブルへと進化した。Target Encodingを組み込み、3種のブレンド手法(Optimized Blend、AUC-Power Blend、Rank Blend)を実装し、後処理まで含めた本格的なMLパイプラインが完成した。
CV AUC: 0.916336。
この数字が、後に筆者を苦しめることになるとは、この時点では知る由もなかった。
完成したパイプラインは1,009行。単一ファイル train_and_predict.py にすべてのロジックが収まっている。前処理、特徴量エンジニアリング、モデル学習、アンサンブル、後処理、提出ファイル生成まで一気通貫だ。1行たりとも自分では書いていないが、構造は筆者が理解し、承認したものだ。
モデル変更・特徴量調整の試行錯誤(3/26-31)
ここからが本当の戦いだった。
LightGBM、期待と現実
「XGBoostだけでは多様性が足りない。LightGBMを追加しよう」
理にかなった提案だった。GBDTの異なる実装を混ぜればアンサンブルの多様性が増す。教科書通りの戦略だ。
結果は無残だった。5バージョンにわたって試行したが、ブレンド最適化をかけるたびにLightGBMの重みはほぼゼロに収束する。CV AUCは0.910-0.912。XGBoost単体より明確に劣る。このデータセットとは相性が悪い -- そう判断して廃止した。
CatBoostとOptunaの不安定性
LightGBMの代わりにCatBoostを投入した。こちらはXGBoostと異なる特性を持ち、ブレンドに貢献してくれそうだった。
問題はOptunaによるハイパーパラメータチューニングだ。同じ探索空間で実行するたびに±0.0004のブレが出る。Kaggleの小数点第5位を争う世界で、0.0004は無視できないノイズだ。改善なのかただの乱数の揺らぎなのか、区別がつかない。
解決策として、Optunaでチューニングしたモデルと固定ハイパーパラメータのモデルを併用する構成に落ち着いた。安定性と探索のバランスを取る、ある意味で妥協的だが実用的な判断だった。
XGBoost DART -- 理論と計算コストの狭間
DARTブースティング(Dropout付きの勾配ブースティング)も試した。理論的には過学習を抑制できるはずだ。だが60万行のデータセットでは1foldの学習に30分以上かかる。5foldで数モデル回すことを考えると、実験のイテレーション速度が致命的に落ちる。廃止。
エンコーディング方式の堂々巡り
frequency encodingやgroupby統計量をグローバルで計算するか、fold内で計算するか。Target Encodingを別CVで行うか、fold内で行うか。リーケージの観点からはfold内が正しい。だがスコアへの影響は±0.0001以内。何度やり直しても差がない。
正直に言えば、ここで数バージョン費やしたのは無駄だった。理論的な正しさとスコアへの寄与は別物だということを、身をもって学んだ。
Pseudo Labeling -- 最後の希望
テストデータの高確信度サンプル(37.6%)を疑似ラベル付きで学習データに追加するPseudo Labeling。コンペでは定番のテクニックだ。
6モデル中、改善したのはわずか1つ。しかも+0.000013。誤差の範囲ですらない。廃止。
スコア推移
| Version | 日付 | CV AUC | 主な変更 |
|---|---|---|---|
| RUN001 | 3/25 | 0.916336 | XGB×4, FE, TE, 3種ブレンド(ベースライン) |
| V46 | 3/31 | 0.915771 | encoding fold内化, LGBM/CB追加 |
| V47 | 3/31 | 0.915756 | LGBM Optuna修正 |
| V48 | 3/31 | 0.915736 | freq/groupbyグローバル, TE fold内 |
| V49 | 3/31 | 0.915635 | TE別CVに戻す |
| V50 | 3/31 | 0.915734 | 特徴量復元 |
| V52 | 3/31 | 0.915782 | XGB×5+CB×3, LGBM廃止, PL廃止 |
見ての通り、ベースラインの0.916336を一度も超えられていない。改善のつもりで手を入れるたびに微妙に悪化する。V52でようやく「無駄なものを削ぎ落とす」方向に舵を切り、XGBoost×5 + CatBoost×3 + Blend-of-Blendsという最終構成に到達した。
最終CV AUC: 0.915782。ベースラインより0.0006低い。
これが現実だ。足し算で良くなるとは限らない。引き算が正解だったのかもしれないし、そもそもベースラインが偶然良かっただけかもしれない。Kaggleの小数点第4位の世界は、確信を持てることのほうが少ない。
数字で振り返る10日間
| 指標 | 数値 |
|---|---|
| 期間 | 10日間(3/22〜3/31) |
| 総コミット | 61 |
| PR数 | 19 |
| パイプライン | 1,009行(単一ファイル) |
| GitHub Actionsワークフロー | 3本 |
| カスタムSkill | 4つ |
| コンテキストドキュメント | 5ファイル |
| 最終CV AUC | 0.915782 |
| 自分で書いたコード | 0行 |
61コミット、19PR。10日間で割れば1日6コミット、2日に1回PR。フルタイムの開発者なら普通のペースだが、筆者は本業の傍ら、通勤電車やランチ休憩の隙間時間でこれをやっていた。コードを1行も書かずに。
得られた知見
技術面
- encoding方式はスコアにほぼ影響しない: グローバル vs fold内、±0.0001以内。理論的な正しさとスコアは別物
- LightGBMはデータセットとの相性がある: 万能ではない。5回試して駄目なら見切りをつける判断が重要
- Optunaの不安定性は実害がある: ±0.0004のブレは、小数点第5位を争うコンペでは致命的。固定HPとの併用が実用的な解
- Pseudo Labelingは銀の弾丸ではない: データセットによっては全く効かない
AI活用面
ここが本記事の核心だ。
「コードを書かない」は「何もしない」ではない。
筆者がやったことを列挙する。CLAUDE.mdを始めとするコンテキストドキュメントの設計と継続的な更新。実験の方向性の決定(「次はCatBoostを試そう」「LightGBMは廃止しよう」)。GitHub Actionsのワークフロー設計。CVスコアの解釈と、改善が本物かノイズかの判断。コスト管理(GitHub Actions無料枠の監視)。
コードの生成はLLMに任せた。だが設計、判断、方向付けはすべて人間の仕事だった。LLMは「次に何を試すべきか」を提案してくれるが、「この実験は打ち切るべきだ」という撤退の判断は筆者がした。コンテキスト設計(CLAUDE.md群)が最も投資対効果の高い作業だったという実感がある。LLMの出力品質は、入力するコンテキストの質で決まる。
もうひとつ。バックエンドエンジニアとしてのスキル -- CI/CD、自動化、構造化、設定ファイルの管理 -- はKaggleでもそのまま活きた。MLの専門知識がなくても、ソフトウェアエンジニアリングの基礎体力があれば、LLMと組んで戦える。
ワークスタイル面
スマホで隙間時間にKaggleに参戦する。朝の通勤電車でClaude Codeに指示を出し、昼休みにCVスコアを確認し、帰りの電車で次の実験方針を考える。このワークスタイルは、忙しい社会人に本当に合っていた。
次の一歩
正直に言えば、上位入賞には程遠いスコアだ。0.915という数字が世界のトップ層に届くかどうか、筆者にはまだわからない。
だが、この10日間で確信したことがある。「MLのコードが書けないからKaggleは無理」という前提は、もう成り立たない。書けないから仕方なくLLMに頼ったのではない。LLMと組むことで、自分の得意領域(設計・自動化・判断)に集中できた。
次は他のコンペにも挑戦してみたい。そして、LLMの提案をより深くレビューできるよう、ML自体の知識も少しずつ身につけていきたい。コードを書けるようになることが目的ではなく、LLMの出力を正しく評価できるようになることが目的だ。
「コードを書かないKaggle参戦」は、エンジニアの新しい選択肢のひとつだと思う。万人に勧めるつもりはないが、少なくとも筆者にとっては、ここから先も続けたいと思えるやり方だった。