忍者ブログ
統計、機械学習、AIを学んでいきたいと思います。 お役に立てば幸いです。

【Kaggle挑戦記】Mechanisms of Action (MoA) Prediction:ベースラインモデルの構築と手元検証

前回、データの構造と役割を確認した「Mechanisms of Action (MoA) Prediction」コンペ。今回は、あえて高度な特徴量抽出などの加工は一切行わず、素のデータをそのままLightGBMに投入して手元での基準スコア(ベースライン)を算出しました。手元の環境である Python 3.13.5 で試してみた検証ログと、マルチラベル特有のスコアの読み方、そして実際に使用したコードを整理します。

0. 検証モデルの設計(5-Fold CV)

今回のターゲットは206種類に及ぶマルチラベル構造です。今回は最もシンプルかつ確実なアプローチとして、「206個のターゲットに対して、それぞれ独立したLightGBMの二値分類モデルを5分割交差検証(5-Fold CV)で合計1,030回ループさせて解く」という愚直なパイプラインを構築しました。

前処理としては、LightGBMがエラーを起こさないための最低限の置換(cp_doseの数値化)と、生物学的に正解がすべて「0」と決まっている偽薬データ(cp_type == 'ctl_vehicle')の除外のみを行っています。

1. 実装した交差検証コード

手元での検証(Cross Validation)を厳密に行うために作成したスクリプトの全文です。PandasとLightGBM、そしてscikit-learnを組み合わせてシンプルに記述しています。

import numpy as np
import pandas as pd
from lightgbm import LGBMClassifier
from sklearn.metrics import log_loss
from sklearn.model_selection import KFold
from sklearn.preprocessing import LabelEncoder
import warnings

# 不要な警告ログを非表示にする
warnings.filterwarnings("ignore")

# ==========================================
# 1. データの読み込み
# ==========================================
print("Loading data...")
train_features = pd.read_csv("train_features.csv")
train_targets = pd.read_csv("train_targets_scored.csv")

# ==========================================
# 2. 最低限の前処理(LightGBMが動くためだけ)
# ==========================================
print("Preprocessing...")

# cp_type が 'ctl_vehicle'(偽薬)のデータは、すべての正解が 0 と決まっています。
# ここは混ぜると学習のノイズになるため、純粋な薬のデータ(trt_cp)だけを抽出して学習させます。
train_mask = train_features["cp_type"] == "trt_cp"
X = train_features[train_mask].reset_index(drop=True)
y = train_targets[train_mask].reset_index(drop=True)

# IDと、不要になった cp_type カラムを削除
X = X.drop(columns=["sig_id", "cp_type"])
y = y.drop(columns=["sig_id"])

# 文字列のカテゴリ(cp_dose: D1/D2)を数値(0/1)に変換
le = LabelEncoder()
X["cp_dose"] = le.fit_transform(X["cp_dose"])

# ==========================================
# 3. 交差検証(5-Fold CV)のセットアップ
# ==========================================
kf = KFold(n_splits=5, shuffle=True, random_state=42)

# すべてのターゲット、すべての行の予測値を格納するゼロ行列を用意
oof_preds = np.zeros(y.shape)
target_cols = y.columns

print(f"Dataset shape: {X.shape}")
print(f"Number of targets: {len(target_cols)}")
print("Starting Cross Validation...")

# ==========================================
# 4. 206個のターゲットをループで1つずつ学習
# ==========================================
for idx, col in enumerate(target_cols):
    y_target = y[col]

    # そのターゲットに「1(効き目あり)」が1つもない特殊なケースの例外処理
    if y_target.sum() == 0:
        continue

    # 5分割のクロスバリデーションを実行
    for train_idx, val_idx in kf.split(X, y_target):
        X_train, y_train = X.iloc[train_idx], y_target.iloc[train_idx]
        X_val, y_val = X.iloc[val_idx], y_target.iloc[val_idx]

        # デフォルト設定のままのLightGBM(二値分類)
        model = LGBMClassifier(
            objective="binary",
            random_state=42,
            n_estimators=100,
            learning_rate=0.05,
            verbose=-1,
        )

        model.fit(X_train, y_train)

        # 「1」になる予測確率を対応する位置に格納
        oof_preds[val_idx, idx] = model.predict_proba(X_val)[:, 1]

    # 進行状況を20個ごとに表示
    if (idx + 1) % 20 == 0 or (idx + 1) == len(target_cols):
        print(f" Trained targets: {idx + 1}/{len(target_cols)}")

# ==========================================
# 5. 全体のスコア(Log Loss)を計算
# ==========================================
losses = []
for idx, col in enumerate(target_cols):
    loss = log_loss(y[col], oof_preds[:, idx], labels=[0, 1])
    losses.append(loss)

cv_score = np.mean(losses)
print("\n" + "=" * 30)
print(f"手元での交差検証スコア(CV Score): {cv_score:.5f}")
print("=" * 30)

2. 交差検証の実行ログ

手元の環境(Mac M3、Python 3.13.5)で上記のスクリプトを試してみました。

Loading data...
Preprocessing...
Dataset shape: (21948, 874)
Number of targets: 206
Starting Cross Validation...
 Trained targets: 20/206
 Trained targets: 40/206
 Trained targets: 60/206
 Trained targets: 80/206
 Trained targets: 100/206
 Trained targets: 120/206
 Trained targets: 140/206
 Trained targets: 160/206
 Trained targets: 180/206
 Trained targets: 200/206
 Trained targets: 206/206

==============================
手元での交差検証スコア(CV Score): 0.03933
==============================

3. スコア「0.03933」の正体と、log(対数)がもたらすペナルティ

一見すると「0.039」という数字は非常に低く、モデルが30%くらい予測を外している(正解率70%程度)ように錯覚してしまいます。しかし結論から言うと、予測のハズレ割合(件数)としてはすでに1%未満に抑え込まれています。これこそが本コンペの評価指標であるLog Loss(対数損失)の最大の特徴です。

Log Lossは予測の正誤だけでなく「モデルの自信度合い(確率)」を厳しく採点する指標で、数式にlog(対数)が含まれています。そのため、間違いが大きくなるほどペナルティが倍々ゲーム(指数関数的)に大炎上する仕組みになっています。

  • 「0(効き目なし)」のエリア: 正解の99%以上が0なので、モデルが「99%の確率で0」と安全な予測を出すだけで、ペナルティはほぼ0点になります。ここで大量の貯金を稼いでいます。
  • たまに登場する「1(効き目あり)」のエリア: ここでモデルが「うーん、自信がないから確率5%くらいで1かな…」と弱気に見逃すと、一発で3点〜4点という特大のペナルティ(罰点)を喰らいます。

この「たまにある1を見逃したときの特大ペナルティ」を、2.4万行 × 206個という膨大なデータの海(分母)で薄く平均化した結果、最終的に「0.03933」という極小の数字として残っているのがこのスコアの正体です。つまり、件数としてはほぼ当たっていますが、本物の「1」に対してモデルがまだ自信を持てずにモヤモヤしている状態と言えます。

初期基準(ベースライン):CV Score = 0.03933ここからの戦いは、件数を増やすのではなく、特徴量を使ってモデルに決定的な証拠を教え、「1」のときに自信満々で高い確率を出させる戦いになります。

4. 次なる一手

何一つ工夫をしていない「素のLightGBM」でここまでのベースラインを測定できたため、手元の検証環境(CV)としては100点満点の仕上がりです。ここからは、先述した「投与時間(cp_time)」や「投与量(cp_dose)」を軸にした特徴量生成や、遺伝子データ(g-)と細胞データ(c-)の統計量を組み合わせ、このスコアをどこまで削り落とせるかの本格的な実験フェーズに移行します。



PR