この記事で学べること
この記事で学べること
本記事では、Kaggle回帰コンペティションに初めて取り組む方、またはより体系的なアプローチを学びたい方に向けて、以下の実践的な知識を共有します。
- プロジェクト環境構築(Cookiecutter Data Science)
- AI支援開発ツールの活用(Claude Code)
- ベストプラクティスライブラリの選定基準
- データ品質問題の発見と解決手法
- 効果的な特徴量エンジニアリング実装
- 実験管理システム
参加したコンペティション Kaggle Playground Series S5E9「Predicting the Beats-per-Minute of Songs」
教材として使用したコンペティション: Kaggle Playground Series S5E9「Predicting the Beats-per-Minute of Songs」
- 問題: 楽曲のBPM(テンポ)を予測する回帰問題
- 評価指標: RMSE (Root Mean Squared Error)
- データ: 合成データ(約52万サンプル、9特徴量)
- 到達スコア: LB 26.38534(630位)
プロジェクト環境構築
Cookiecutter Data Scienceによるプロジェクト構造構築
KaggleのPlaygroundシリーズのS5E9 「Predicting the Beats-per-Minute of Songs」に参加しました。
KaggleのPlaygroundシリーズとは?
Cookiecutter Data Scienceテンプレートを使用して、再現性の高いプロジェクト構造を構築しました。
# Cookiecutter Data Scienceのインストールと使用
pip install cookiecutter
cookiecutter https://github.com/drivendata/cookiecutter-data-science
生成されるディレクトリ構造:
├── data/
│ ├── raw/ # 元データ(不変)
│ ├── interim/ # 中間データ
│ └── processed/ # 最終データ
├── models/ # 訓練済みモデル
├── src/ # ソースコード
│ ├── config.py # 設定・パス管理
│ ├── dataset.py # データ処理
│ ├── features.py # 特徴量エンジニアリング
│ └── modeling/ # モデル関連
├── experiments/ # 実験結果管理
├── scripts/ # 実行スクリプト
└── tests/ # テストコード
主要な技術スタック
# requirements.txt の主要ライブラリ
pandas>=2.0.0 # データ処理
numpy>=1.24.0 # 数値計算
scikit-learn>=1.3.0 # 機械学習
lightgbm>=4.0.0 # 勾配ブースティング
catboost>=1.2.0 # アンサンブル用
optuna>=3.3.0 # ハイパーパラメータ最適化
loguru>=0.7.0 # ログ管理
typer>=0.9.0 # CLI構築
ruff>=0.0.291 # コード品質
プロジェクト管理の工夫
Makefileによるタスク自動化:
# よく使うコマンド
make requirements # 依存関係インストール
make data # データ処理実行
make format # コード整形(ruff)
make lint # コード品質チェック
make test # テスト実行
設定の一元管理(src/config.py):
# パス定数の集中管理
DATA_DIR = Path(__file__).resolve().parents[1] / "data"
MODELS_DIR = Path(__file__).resolve().parents[1] / "models"
EXPERIMENTS_DIR = Path(__file__).resolve().parents[1] / "experiments"
# ログ設定(loguru + tqdm統合)
logger.add(sys.stderr, format="{time} {level} {message}")
この環境構築により、実験の再現性とコードの保守性を両立させることができました。
開発環境とベストプラクティス
Claude Code(AI駆動開発)の活用
本プロジェクトでは、Claude Code(Anthropic社のAI開発支援ツール)を積極的に活用しました。
Claude Codeの活用場面:
- プロジェクト構造設計と初期セットアップ
- ベストプラクティスライブラリの選定支援
- コードレビューとリファクタリング提案
- ドキュメント作成の効率化
特に重要だったのは、ベストプラクティスを意識したライブラリ選定です。Claude Codeとの対話を通じて、以下の方針を確立しました:
ライブラリ選定の基準:
- ログ管理:
print()
ではなくloguru
- 構造化ログによる追跡性向上
- tqdmプログレスバーとの統合
- ファイル出力・ローテーション対応
- CLI構築:
argparse
ではなくtyper
- 型ヒントによる安全性
- 自動ドキュメント生成
- 直感的なインターフェース
- コード品質:
flake8
+black
ではなくruff
- 高速な統合リンター・フォーマッター
- isort機能内蔵(インポート自動ソート)
- pyproject.tomlでの一元管理
- パス操作: 文字列結合ではなく
pathlib.Path
- OS非依存のパス操作
- 型安全性
- 可読性向上
実装例(src/config.py):
from pathlib import Path
from loguru import logger
import sys
# pathlib.Pathによる堅牢なパス管理
PROJECT_ROOT = Path(__file__).resolve().parents[1]
DATA_DIR = PROJECT_ROOT / "data"
MODELS_DIR = PROJECT_ROOT / "models"
# loguruによる構造化ログ
logger.remove()
logger.add(
sys.stderr,
format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | {message}",
level="INFO"
)
実装例(src/dataset.py):
import typer
from typing_extensions import Annotated
app = typer.Typer()
@app.command()
def process_data(
input_path: Annotated[Path, typer.Option(help="Input CSV file path")],
output_path: Annotated[Path, typer.Option(help="Output CSV file path")],
verbose: Annotated[bool, typer.Option(help="Enable verbose logging")] = False
):
"""Process raw data and create features."""
logger.info(f"Processing data from {input_path}")
# ... 処理実装
なぜベストプラクティスにこだわったのか
1. 将来の自分への投資
- 3ヶ月後に見直しても理解できるコード
- 他のプロジェクトへの再利用可能性
2. スケーラビリティの確保
- 小規模実験から本番運用まで対応
- チーム開発への拡張性
3. デバッグ効率の向上
- 構造化ログによる問題追跡
- 型ヒントによるバグ早期発見
Claude Codeとの対話を通じて、「動けばいい」ではなく「保守可能で拡張性の高いコード」を目指すことができました。
取り組みの全体像
第0段階: 探索的データ分析(EDA)での最初の壁
コンペ参加当初、最も驚いたのはターゲット変数(BeatsPerMinute)と強く相関する特徴量が存在しなかったことです。
図1: 特徴量間の相関ヒートマップ - ターゲットとの相関が弱い
上図の相関ヒートマップを見ると、BeatsPerMinute
(BPM)と各特徴量の相関係数は以下の通りでした:
- 最も相関が高い特徴量でも 相関係数0.3以下
- 多くの特徴量は相関係数0.1~0.2程度
- 単一特徴量だけでは予測が困難な状況
この発見から、特徴量エンジニアリングによる交互作用の発見が不可欠という方針が定まりました。
図2: BPMのターゲット分布 - 60~180の範囲に分布
第1段階: ベースライン構築(exp01)
まずは基本的なLightGBMモデルでベースラインを確立しました。
初期アプローチ:
- 9個の基本特徴量のみ使用
- 単純な交互作用特徴量追加(乗算・除算)
- 統計的特徴量(平均・標準偏差・範囲)の生成
- 5-fold Cross Validation
相関分析で単一特徴量の限界を知ったため、最初から交互作用特徴量の生成に注力しました。この段階で、約33特徴量を使用した基本的なパイプラインを構築しました。
第2段階: 特徴量拡張と最適化(exp02-exp10)
音楽ジャンル特徴量の追加(exp01_5):
- VocalContent × MoodScore で Ballad系判定
- Energy × RhythmScore で Dance系判定
- 6個のジャンル特徴量を生成
多重共線性除去(exp02):
- 相関係数0.7以上の特徴量を除去
- LB 26.3879達成(初期改善)
次元削減実験(exp03):
- PCA/ICAによる次元削減
- 特徴量を半減(26特徴量)させても性能維持
- LB 26.38834達成
第3段階: Kaggleサンプルコード活用(exp05-exp10)
Kaggle上位ソリューションのサンプルコードを参考に、包括的な特徴量エンジニアリングを実施しました。
包括的交互作用特徴量(TICKET-017):
- 164個の交互作用特徴量生成
- SelectKBestで50特徴量に選択
- L1/L2正則化による過学習抑制
- LB 26.38534達成(当時の最高性能)
様々な特徴量数で実験を繰り返し、5特徴量版・8特徴量版・10特徴量版など、効率性と性能のトレードオフを検証しました。
データ分析における特徴量生成は下記の書籍を参考にしました。
KaggleやSIGNATEなどのデータ分析コンペティションに参加される方は必携の一冊だと思います。
データ品質問題の発見と解決
問題1: データ欠損の発見(TICKET-029)
最初の重要な発見は、処理済みデータで25%ものサンプルが欠落していたことです。
data/processed
: 419,332サンプルdata/raw
: 524,164サンプル(完全版)
解決策: data/raw
から直接特徴量を生成し直すことで、104,832サンプルを復活させました。
結果: LB 26.38713達成(-0.00821改善、新記録)
問題2: 境界値集中問題(TICKET-025)
合成データ特有の問題として、基本9特徴量の7/9で境界値に値が集中していることを発見しました。
境界値集中の例:
InstrumentalScore
: 33.17%が0.000(楽器演奏なし)VocalContent
: 30.33%が0.024(最小値に集中)AcousticQuality
: 16.95%が0.000(音響品質なし)
解決アプローチ:
- 対数変換(log1p)による0値集中の分散化
- ランク正規化による最小値集中の緩和
- Box-Cox/Yeo-Johnson変換による分布形状最適化
結果: CV-LB格差を+0.076→-0.078へ0.154改善
特徴量エンジニアリング戦略
統一特徴量生成システム(TICKET-016)
Train/Testで完全に同一の特徴量セットを生成する標準化手順を確立しました。
生成特徴量構成(67特徴量):
- 基本特徴量: 9個(元データ)
- 交互作用特徴量: 22個(乗算・除算)
- 対数変換特徴量: 36個(log1p + 統計量)
性能実績:
- CV: 26.464 ± 0.006
- LB: 26.38823
- 高い再現性とCV-LB一貫性を確保
実装のポイント: 特徴量エンジニアリング
交互作用特徴量の考え方:
# 1. ドメイン知識に基づく組み合わせ
df["rhythm_energy"] = df["RhythmScore"] * df["Energy"] # ダンス系判定
# 2. ゼロ除算に注意した除算特徴量
def safe_divide(a, b):
return np.where(b != 0, a / b, 0)
# 3. 対数変換で歪んだ分布を正規化
df["log_feature"] = np.log1p(df["skewed_feature"])
重要な3つのポイント:
- ドメイン知識活用: 単なる組み合わせではなく、意味のある特徴量を
- ゼロ除算対策:
safe_divide()
やnp.where()
で安全に - Train/Test一致: 同じ処理を両データに適用
包括的交互作用特徴量(TICKET-017)
Kaggleサンプルコードを参考に、包括的な特徴量エンジニアリングを実施しました。
手法:
- 164個の交互作用特徴量生成
- 特徴量選択(SelectKBest)で50特徴量に削減
- L1/L2正則化による過学習抑制
最高性能: LB 26.38534(exp09_1)
モデル最適化戦略
クロスバリデーション戦略の改善(TICKET-022)
BPM Stratified KFoldの導入により、CV安定性が劇的に向上しました。
改善効果:
- CV標準偏差: 0.063 → 0.005(10.16倍改善)
- CV-LB一貫性の向上
アンサンブルシステム(TICKET-021)
異なる正則化手法を持つモデルの統合による多様性確保を実現しました。
構成:
- LightGBM(L1/L2正則化重視)
- CatBoost(最適化済みパラメータ)
- Optunaによる重み最適化
最適重み: LightGBM 60.1% + CatBoost 39.9%
技術スタック
# 主要ライブラリ
- pandas: データ処理
- numpy: 数値計算
- scikit-learn: 特徴量エンジニアリング・モデル評価
- LightGBM: 主要モデル
- CatBoost: アンサンブル用サブモデル
- Optuna: ハイパーパラメータ最適化
実験管理の工夫
体系的な実験記録システム
全23実験を体系的に管理するため、独自の実験管理システムを構築しました。
1. 実験結果の一元管理(experiment_results.csv)
CSVベースの実験トラッキング:
exp_id,cv_score,lb_score,cv_lb_diff,n_features,notes
exp01,26.47,26.39,-0.08,33,ベースライン
exp02,26.46,26.38,-0.08,40,多重共線性除去
exp09,26.46,26.385,-0.075,75,正則化版(最高性能)
記録すべき最小限の項目:
- 実験ID、CV/LBスコア、CV-LB差
- 特徴量数、簡潔なメモ
実験記録の自動化コンセプト:
def log_experiment(exp_id, cv_scores, lb_score, n_features):
result = {
'exp_id': exp_id,
'cv_score': np.mean(cv_scores),
'lb_score': lb_score,
'cv_lb_diff': np.mean(cv_scores) - lb_score,
'n_features': n_features
}
# CSVに追記
df = pd.read_csv('experiments/experiment_results.csv')
df = pd.concat([df, pd.DataFrame([result])], ignore_index=True)
df.to_csv('experiments/experiment_results.csv', index=False)
2. 個別実験ディレクトリの構造
各実験は独立したディレクトリで管理:
experiments/
├── exp01_baseline_lgb/
│ ├── config.json # 実験設定
│ ├── results.json # 詳細結果
│ ├── submission.csv # 提出ファイル
│ ├── models/ # 訓練済みモデル
│ │ ├── fold_0_model.pkl
│ │ ├── fold_1_model.pkl
│ │ └── ...
│ └── README.md # 実験メモ
├── exp09_1_ticket017_regularized/
│ └── ...
└── experiment_results.csv # 全実験サマリー
config.jsonの設計:
{
"experiment_name": "exp09_1_regularized",
"model_config": {
"model_type": "LightGBM",
"n_estimators": 2000,
"learning_rate": 0.03,
"reg_alpha": 2.0,
"reg_lambda": 2.0
},
"data_config": {
"n_features": 75,
"cv_strategy": "StratifiedKFold",
"cv_folds": 5
}
}
results.jsonの設計:
{
"cross_validation": {
"mean_rmse": 26.4581,
"std_rmse": 0.0813,
"fold_results": [
{"fold": 0, "rmse": 26.4523},
{"fold": 1, "rmse": 26.4639}
]
},
"leaderboard": {
"lb_score": 26.38534,
"cv_lb_diff": -0.07276
}
}
3. 実験比較・分析の自動化
def compare_experiments(exp_ids: List[str]):
"""複数実験の性能比較"""
df = pd.read_csv('experiments/experiment_results.csv')
df_compare = df[df['exp_id'].isin(exp_ids)]
# 性能比較プロット
fig, axes = plt.subplots(1, 2, figsize=(15, 5))
# CV vs LB スコア
axes[0].scatter(df_compare['cv_score'], df_compare['lb_score'])
axes[0].plot([26.35, 26.50], [26.35, 26.50], 'r--', alpha=0.5)
axes[0].set_xlabel('CV Score')
axes[0].set_ylabel('LB Score')
axes[0].set_title('CV vs LB Consistency')
# 特徴量数 vs LB スコア
axes[1].scatter(df_compare['n_features'], df_compare['lb_score'])
axes[1].set_xlabel('Number of Features')
axes[1].set_ylabel('LB Score')
axes[1].set_title('Feature Count vs Performance')
plt.tight_layout()
plt.savefig('experiments/comparison.png')
return df_compare
主要実験の進化:
exp01 (Baseline LightGBM, 33特徴量)
↓ 特徴量拡張・多重共線性除去
exp02-04 (音楽ジャンル・PCA/ICA実験)
↓ Kaggleサンプルコード活用
exp09_1 (正則化版、75特徴量、LB 26.38534) ← 最高性能
↓ データ品質改善
exp20 (完全rawデータ、76特徴量、LB 26.38713)
↓ アンサンブル最適化
exp21-23 (境界値変換+二元アンサンブル)
この実験管理システムにより、23回の実験全てを追跡可能にし、各実験から得られた知見を次の実験に活かすことができました。
実践から学んだデータサイエンスの教訓
教訓1: データ品質分析こそが最優先タスク
問題発見: 25%のデータ欠損と境界値集中問題
多くの初心者は、いきなりモデル構築から始めがちです。しかし、データ品質問題を見逃すと、どれだけモデルを最適化しても性能向上の限界に達します。
実践的アプローチ:
# 1. データ欠損チェック
print(f"Processed: {len(df_processed):,} samples")
print(f"Raw: {len(df_raw):,} samples")
print(f"Missing: {len(df_raw) - len(df_processed):,} samples ({missing_rate:.1%})")
# 2. 境界値集中チェック
for col in df.columns:
value_counts = df[col].value_counts()
top_value_rate = value_counts.iloc[0] / len(df)
if top_value_rate > 0.15: # 15%以上が同一値
print(f"⚠️ {col}: {top_value_rate:.1%}が{value_counts.index[0]}に集中")
得られた成果:
- データ復活: 104,832サンプル(25%)の復元
- 境界値変換: CV-LB格差を0.154改善
教訓2: ベストプラクティスツールへの投資は早期に
よくある失敗: 「動けばいい」でprint()
デバッグ、文字列パス操作、手動ログ管理
推奨アプローチ:
用途 | ❌ 避けるべき | ✅ 推奨 | 理由 |
---|---|---|---|
ログ | print() | loguru | 構造化ログ、ファイル出力、追跡性 |
CLI | argparse | typer | 型安全、自動ドキュメント |
パス | 文字列結合 | pathlib.Path | OS非依存、型安全 |
コード品質 | flake8 +black | ruff | 高速統合リンター |
3ヶ月後の自分への投資: 初期の2時間が、将来の20時間を節約します。
教訓3: 実験管理システムは「作る」もの
失敗例: ノートブックにmodel_v1.pkl
, model_v2_final.pkl
, model_v2_final_FINAL.pkl
…
成功パターン:
- CSV一元管理: 全実験をexperiment_results.csvで追跡
- JSON構造化: config.json(設定)+ results.json(結果)
- 自動記録: 実験終了時に自動でCSV追記
# 実験記録の自動化例
def log_experiment(exp_id, cv_scores, lb_score, config):
result = {
'exp_id': exp_id,
'cv_score': np.mean(cv_scores),
'lb_score': lb_score,
'cv_lb_diff': np.mean(cv_scores) - lb_score,
'n_features': config['n_features'],
'date': datetime.now().strftime('%Y-%m-%d')
}
# CSVに追記
df = pd.read_csv('experiments/experiment_results.csv')
df = pd.concat([df, pd.DataFrame([result])], ignore_index=True)
df.to_csv('experiments/experiment_results.csv', index=False)
効果: 23実験全てを追跡可能、成功・失敗の分析が容易に
教訓4: 特徴量エンジニアリングは「理解」が先
よくある失敗: とにかく組み合わせを増やす → 164特徴量生成
推奨アプローチ:
- EDAで相関分析: ターゲットとの相関が弱い(0.3以下)ことを確認
- ドメイン知識活用: 音楽の特性を理解(リズム×エネルギー = ダンス系)
- 段階的検証: 交互作用 → 対数変換 → 統計量の順で追加
- 特徴量選択: SelectKBestで164→50特徴量に削減
重要: 特徴量を増やすだけでなく、なぜその特徴量が有効かを理解する
教訓5: CV戦略の選択が汎化性能を決める
発見: 通常のKFoldではCV標準偏差0.063、BPM Stratified KFoldでは0.005(10.16倍改善)
適切なCV戦略の選び方:
# ターゲット変数の分布に基づく層化分割
from sklearn.model_selection import StratifiedKFold
# BPMを10個のビンに分割して層化
y_binned = pd.qcut(y, q=10, labels=False, duplicates='drop')
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
for fold, (train_idx, val_idx) in enumerate(skf.split(X, y_binned)):
# 各フォールドでBPMの分布が均等に
...
効果:
- CV安定性の劇的改善
- CV-LB一貫性の向上
- 性能評価の信頼性確保
教訓6: AI開発支援ツールの戦略的活用
Claude Codeの効果的な使い方:
- プロジェクト設計: ディレクトリ構造、ベストプラクティス相談
- コードレビュー: 型ヒント追加、エラーハンドリング改善
- ドキュメント作成: README、実験レポート生成
- ライブラリ選定: 「なぜそれを選ぶか」の根拠確認
重要な姿勢: AIに「答え」を求めるのではなく、「選択肢と理由」を相談する
教訓7: 失敗実験こそが次の成功への糧
効果のなかった実験例:
- リズム周期性特徴量(TICKET-016): 音楽ドメイン知識を活用したが効果なし → 特徴量飽和を学習
- 5特徴量超選択版(exp05_1): 過度な削減でLB劣化 → 適切なバランスの重要性を理解
- 次元削減(PCA/ICA): 26特徴量で性能維持 → 冗長性の存在を確認
学び: 失敗実験を記録・分析することで、なぜうまくいかないかの理解が深まる
まとめ: 初心者が最初にやるべきこと
ステップ1: 環境構築に時間をかける(初日)
# Cookiecutter Data Scienceでプロジェクト構築
cookiecutter https://github.com/drivendata/cookiecutter-data-science
# ベストプラクティスライブラリのインストール
pip install pandas numpy scikit-learn lightgbm
pip install loguru typer ruff # 開発効率化ツール
ステップ2: データ品質を徹底的に分析(2-3日目)
# 必須チェック項目
1. 欠損値・重複・外れ値の確認
2. ターゲット変数の分布確認
3. 特徴量間の相関分析
4. 境界値・集中値の検出
5. Train/Test分布の一致確認
ステップ3: 実験管理システムを構築(3-4日目)
experiments/
├── experiment_results.csv # まずこれを作る
└── exp01_baseline/
├── config.json
└── results.json
ステップ4: ベースライン→段階的改善(5日目以降)
1. シンプルなベースライン(基本特徴量のみ)
2. 特徴量エンジニアリング(1種類ずつ追加・検証)
3. CV戦略改善(Stratified/Group KFold)
4. モデル最適化(Optuna等)
5. アンサンブル(複数モデル統合)
これからKaggleを始める方へのアドバイス
❌ やりがちな失敗:
- いきなり複雑なモデルから始める
- 実験管理をせず、何をやったか忘れる
- ノートブックに全て書いて再現性がない
print()
デバッグで時間を浪費
✅ 推奨アプローチ:
- Cookiecutter Data Scienceで構造化
- ベストプラクティスツール(loguru/typer/ruff)を最初から使う
- 実験記録を自動化(experiment_results.csv)
- AI支援ツール(Claude Code等)で効率化
- 失敗実験も記録し、学びを蓄積
本記事で紹介した主要リソース
プロジェクトテンプレート:
推奨ライブラリ:
AI開発支援:
- Claude Code - Anthropic社のAI開発ツール
教材コンペティション:
最後に
データサイエンスは、「動くコード」を書くだけでなく、「保守可能で再現性のあるシステム」を構築することが重要です。
本記事で紹介した手法を実践することで、Kaggleコンペティションだけでなく、実務のデータサイエンスプロジェクトでも通用するスキルが身につきます。
皆さんのKaggle挑戦を応援しています!
この記事で紹介しているフォルダ構成やconfigなど、Kaggleでsubmitする際の試行錯誤は、下記のKaggle実験管理術を参考にしました。
まだまだ、キレイに実験管理できているとは言えませんが、少しずつ手法を取り入れていきたいです。