AurelPx's picture
Add training script
1c21d3c verified
raw
history blame
17.3 kB
"""
Adult Income Dataset - SOTA Solution
OpenML Task 7592 / data_id=1590
Target: AUC > 0.9300, Accuracy > 0.8756 on 10-fold CV
Method: LightGBM + XGBoost + CatBoost stacking + Feature Engineering + Optuna
"""
import warnings, sys
warnings.filterwarnings("ignore")
import numpy as np
import pandas as pd
from sklearn.datasets import fetch_openml
from sklearn.model_selection import StratifiedKFold
from sklearn.metrics import roc_auc_score, accuracy_score
from sklearn.preprocessing import OrdinalEncoder
from sklearn.linear_model import LogisticRegression
import lightgbm as lgb
import xgboost as xgb
import catboost as cb
import optuna
optuna.logging.set_verbosity(optuna.logging.WARNING)
import time
def log(msg):
print(msg, flush=True)
sys.stdout.flush()
log("=" * 70)
log("ADULT INCOME DATASET - SOTA SOLUTION")
log("OpenML Task 7592 | Target: Acc > 0.8756, AUC > 0.9300")
log("=" * 70)
# ─────────────────────────────────────────────────────────
# 1. CHARGEMENT DONNÉES
# ─────────────────────────────────────────────────────────
log("\n[1/6] Chargement donnΓ©es OpenML (data_id=1590)...")
t0 = time.time()
X, y = fetch_openml(data_id=1590, as_frame=True, return_X_y=True, cache=True)
y_bin = (y == ">50K").astype(int)
log(f" Shape: {X.shape} | Target: {y_bin.sum()} positifs / {len(y_bin)} total ({y_bin.mean():.1%})")
CAT_COLS = ["workclass", "education", "marital-status", "occupation",
"relationship", "race", "sex", "native-country"]
NUM_COLS = ["age", "fnlwgt", "education-num", "capital-gain", "capital-loss", "hours-per-week"]
log("\n EDA:")
for col in CAT_COLS:
log(f" {col:20s}: {X[col].nunique():3d} vals, {X[col].isna().sum():5d} NaN")
for col in NUM_COLS:
log(f" {col:20s}: mean={X[col].mean():.1f}, std={X[col].std():.1f}")
log(f" Chargement: {time.time()-t0:.1f}s")
# ─────────────────────────────────────────────────────────
# 2. FEATURE ENGINEERING
# ─────────────────────────────────────────────────────────
log("\n[2/6] Feature Engineering avancΓ©...")
CAT_COLS = ["workclass", "education", "marital-status", "occupation",
"relationship", "race", "sex", "native-country"]
NUM_COLS = ["age", "fnlwgt", "education-num", "capital-gain", "capital-loss", "hours-per-week"]
def build_features(X, fit_encoder=True, encoder=None):
age = X["age"].astype(float).values
fnlwgt = X["fnlwgt"].astype(float).values
edu_num = X["education-num"].astype(float).values
cap_gain = X["capital-gain"].astype(float).values
cap_loss = X["capital-loss"].astype(float).values
hours = X["hours-per-week"].astype(float).values
X_num = np.column_stack([
age, fnlwgt, edu_num, cap_gain, cap_loss, hours,
np.log1p(cap_gain), np.log1p(cap_loss),
cap_gain - cap_loss,
np.log1p(np.abs(cap_gain - cap_loss)) * np.sign(cap_gain - cap_loss),
((cap_gain > 0) | (cap_loss > 0)).astype(float),
(cap_gain > 0).astype(float), (cap_loss > 0).astype(float),
age ** 2,
pd.cut(age, bins=[0,25,35,45,55,65,100], labels=False).astype(float),
pd.cut(hours, bins=[0,35,40,45,60,100], labels=False).astype(float),
(hours > 40).astype(float),
np.log1p(fnlwgt),
edu_num * age, edu_num * hours
])
X_cat = X[CAT_COLS].astype(str)
if fit_encoder:
encoder = OrdinalEncoder(handle_unknown="use_encoded_value", unknown_value=-1)
X_cat_enc = encoder.fit_transform(X_cat)
else:
X_cat_enc = encoder.transform(X_cat)
return np.hstack([X_num, X_cat_enc]), encoder
def build_cb_features(X):
X_cb = X.copy()
for col in CAT_COLS:
if hasattr(X_cb[col], 'cat'):
X_cb[col] = X_cb[col].cat.add_categories(["Unknown"]).fillna("Unknown").astype(str)
else:
X_cb[col] = X_cb[col].fillna("Unknown").astype(str)
cap_gain = X_cb["capital-gain"].astype(float)
cap_loss = X_cb["capital-loss"].astype(float)
X_cb["cap_gain_log"] = np.log1p(cap_gain)
X_cb["cap_loss_log"] = np.log1p(cap_loss)
X_cb["cap_net"] = cap_gain - cap_loss
X_cb["cap_any"] = ((cap_gain > 0) | (cap_loss > 0)).astype(float)
X_cb["age_bins"] = pd.cut(X_cb["age"].astype(float), bins=[0,25,35,45,55,65,100], labels=False).astype(float)
X_cb["edu_x_age"] = X_cb["education-num"].astype(float) * X_cb["age"].astype(float)
X_cb["fnlwgt_log"] = np.log1p(X_cb["fnlwgt"].astype(float))
return X_cb
X_enc, oe = build_features(X)
X_cb_df = build_cb_features(X)
y_arr = y_bin.values
n = len(y_arr)
log(f" Features LGB/XGB: {X_enc.shape[1]} | CatBoost: {X_cb_df.shape[1]} colonnes")
# ─────────────────────────────────────────────────────────
# 3. BASELINE 3-FOLD (validation rapide architecture)
# ─────────────────────────────────────────────────────────
log("\n[3/6] Baseline 3-fold CV (300 estimators, validation architecture)...")
# Paramètres baseline réduits pour vitesse
LGB_BASE = dict(n_estimators=300, learning_rate=0.05, num_leaves=63,
colsample_bytree=0.8, subsample=0.8, subsample_freq=1,
min_child_samples=20, reg_alpha=0.05, reg_lambda=1.0,
max_depth=8, random_state=42, n_jobs=-1, verbose=-1)
XGB_BASE = dict(n_estimators=300, learning_rate=0.05, max_depth=6,
colsample_bytree=0.8, subsample=0.8, min_child_weight=5,
reg_alpha=0.05, reg_lambda=1.5, eval_metric="logloss",
random_state=42, n_jobs=-1, verbosity=0)
CB_BASE = dict(iterations=300, learning_rate=0.05, depth=8,
cat_features=CAT_COLS, random_seed=42, verbose=0, thread_count=4)
cv3 = StratifiedKFold(n_splits=3, shuffle=True, random_state=42)
oof_lgb_3 = np.zeros(n); oof_xgb_3 = np.zeros(n); oof_cb_3 = np.zeros(n)
for fi, (tr, va) in enumerate(cv3.split(X_enc, y_arr)):
t_fold = time.time()
log(f" Fold {fi+1}/3 LGB...", )
m = lgb.LGBMClassifier(**LGB_BASE); m.fit(X_enc[tr], y_arr[tr])
oof_lgb_3[va] = m.predict_proba(X_enc[va])[:, 1]
log(f" Fold {fi+1}/3 XGB...")
m = xgb.XGBClassifier(**XGB_BASE); m.fit(X_enc[tr], y_arr[tr])
oof_xgb_3[va] = m.predict_proba(X_enc[va])[:, 1]
log(f" Fold {fi+1}/3 CB ...")
m = cb.CatBoostClassifier(**CB_BASE); m.fit(X_cb_df.iloc[tr], y_arr[tr])
oof_cb_3[va] = m.predict_proba(X_cb_df.iloc[va])[:, 1]
avg = (oof_lgb_3[va] + oof_xgb_3[va] + oof_cb_3[va]) / 3
log(f" β†’ Fold {fi+1} done: AUC={roc_auc_score(y_arr[va], avg):.5f} Acc={accuracy_score(y_arr[va], (avg>=0.5).astype(int)):.5f} ({time.time()-t_fold:.0f}s)")
avg_3 = (oof_lgb_3 + oof_xgb_3 + oof_cb_3) / 3
auc_avg_3 = roc_auc_score(y_arr, avg_3)
best_acc_3 = max(accuracy_score(y_arr, (avg_3 >= t).astype(int)) for t in np.arange(0.3, 0.7, 0.005))
log(f"\n BASELINE 3-FOLD: LGB={roc_auc_score(y_arr, oof_lgb_3):.5f} "
f"XGB={roc_auc_score(y_arr, oof_xgb_3):.5f} CB={roc_auc_score(y_arr, oof_cb_3):.5f} "
f"AVG_AUC={auc_avg_3:.5f} BestAcc={best_acc_3:.5f}")
log(f" Target 0.8756: {'βœ… ATTEINT' if best_acc_3 >= 0.8756 else '❌ ' + str(round(best_acc_3,5))}")
# ─────────────────────────────────────────────────────────
# 4. OPTUNA TUNING
# ─────────────────────────────────────────────────────────
log("\n[4/6] Optuna Tuning...")
cv_inner = StratifiedKFold(n_splits=3, shuffle=True, random_state=123)
# LightGBM - 40 trials
log(" Tuning LightGBM (40 trials)...")
def lgb_obj(trial):
p = dict(
n_estimators = trial.suggest_int("n_estimators", 200, 1200),
learning_rate = trial.suggest_float("learning_rate", 0.01, 0.1, log=True),
num_leaves = trial.suggest_int("num_leaves", 31, 127),
max_depth = trial.suggest_int("max_depth", 4, 10),
min_child_samples = trial.suggest_int("min_child_samples", 5, 80),
colsample_bytree= trial.suggest_float("colsample_bytree", 0.5, 1.0),
subsample = trial.suggest_float("subsample", 0.5, 1.0),
subsample_freq = 1,
reg_alpha = trial.suggest_float("reg_alpha", 1e-4, 5.0, log=True),
reg_lambda = trial.suggest_float("reg_lambda", 1e-4, 5.0, log=True),
random_state=42, n_jobs=-1, verbose=-1
)
return np.mean([roc_auc_score(y_arr[va],
lgb.LGBMClassifier(**p).fit(X_enc[tr], y_arr[tr]).predict_proba(X_enc[va])[:,1])
for tr, va in cv_inner.split(X_enc, y_arr)])
st_lgb = optuna.create_study(direction="maximize", sampler=optuna.samplers.TPESampler(seed=42))
st_lgb.optimize(lgb_obj, n_trials=40, show_progress_bar=False)
best_lgb = st_lgb.best_params
log(f" LGB best AUC={st_lgb.best_value:.5f} | {best_lgb}")
# XGBoost - 40 trials
log(" Tuning XGBoost (40 trials)...")
def xgb_obj(trial):
p = dict(
n_estimators = trial.suggest_int("n_estimators", 200, 1200),
learning_rate = trial.suggest_float("learning_rate", 0.01, 0.1, log=True),
max_depth = trial.suggest_int("max_depth", 3, 10),
min_child_weight = trial.suggest_int("min_child_weight", 1, 20),
colsample_bytree = trial.suggest_float("colsample_bytree", 0.5, 1.0),
subsample = trial.suggest_float("subsample", 0.5, 1.0),
gamma = trial.suggest_float("gamma", 0, 3),
reg_alpha = trial.suggest_float("reg_alpha", 1e-4, 5.0, log=True),
reg_lambda = trial.suggest_float("reg_lambda", 1e-4, 5.0, log=True),
eval_metric="logloss", random_state=42, n_jobs=-1, verbosity=0
)
return np.mean([roc_auc_score(y_arr[va],
xgb.XGBClassifier(**p).fit(X_enc[tr], y_arr[tr]).predict_proba(X_enc[va])[:,1])
for tr, va in cv_inner.split(X_enc, y_arr)])
st_xgb = optuna.create_study(direction="maximize", sampler=optuna.samplers.TPESampler(seed=42))
st_xgb.optimize(xgb_obj, n_trials=40, show_progress_bar=False)
best_xgb = st_xgb.best_params
log(f" XGB best AUC={st_xgb.best_value:.5f} | {best_xgb}")
# CatBoost - 25 trials (plus lent)
log(" Tuning CatBoost (25 trials)...")
def cb_obj(trial):
p = dict(
iterations = trial.suggest_int("iterations", 200, 800),
learning_rate = trial.suggest_float("learning_rate", 0.01, 0.1, log=True),
depth = trial.suggest_int("depth", 4, 9),
l2_leaf_reg = trial.suggest_float("l2_leaf_reg", 0.01, 10.0, log=True),
bagging_temperature = trial.suggest_float("bagging_temperature", 0, 3),
random_strength = trial.suggest_float("random_strength", 0, 3),
cat_features=CAT_COLS, random_seed=42, verbose=0, thread_count=4
)
return np.mean([roc_auc_score(y_arr[va],
cb.CatBoostClassifier(**p).fit(X_cb_df.iloc[tr], y_arr[tr]).predict_proba(X_cb_df.iloc[va])[:,1])
for tr, va in cv_inner.split(X_enc, y_arr)])
st_cb = optuna.create_study(direction="maximize", sampler=optuna.samplers.TPESampler(seed=42))
st_cb.optimize(cb_obj, n_trials=25, show_progress_bar=False)
best_cb = st_cb.best_params
log(f" CB best AUC={st_cb.best_value:.5f} | {best_cb}")
# ─────────────────────────────────────────────────────────
# 5. STACKING FINAL 10-FOLD
# ─────────────────────────────────────────────────────────
log("\n[5/6] Stacking Final 10-Fold CV (paramètres Optuna)...")
# Paramètres finaux tunés
lgb_final = {**best_lgb, "random_state": 42, "n_jobs": -1, "verbose": -1}
xgb_final = {**best_xgb, "eval_metric": "logloss", "random_state": 42, "n_jobs": -1, "verbosity": 0}
cb_final = {**best_cb, "cat_features": CAT_COLS, "random_seed": 42, "verbose": 0, "thread_count": 4}
cv10 = StratifiedKFold(n_splits=10, shuffle=True, random_state=42)
oof_lgb = np.zeros(n); oof_xgb = np.zeros(n); oof_cb = np.zeros(n)
fold_aucs = []
for fi, (tr, va) in enumerate(cv10.split(X_enc, y_arr)):
t_f = time.time()
log(f" Fold {fi+1:2d}/10 LGB...")
m_lgb = lgb.LGBMClassifier(**lgb_final); m_lgb.fit(X_enc[tr], y_arr[tr])
oof_lgb[va] = m_lgb.predict_proba(X_enc[va])[:, 1]
log(f" Fold {fi+1:2d}/10 XGB...")
m_xgb = xgb.XGBClassifier(**xgb_final); m_xgb.fit(X_enc[tr], y_arr[tr])
oof_xgb[va] = m_xgb.predict_proba(X_enc[va])[:, 1]
log(f" Fold {fi+1:2d}/10 CB ...")
m_cb = cb.CatBoostClassifier(**cb_final); m_cb.fit(X_cb_df.iloc[tr], y_arr[tr])
oof_cb[va] = m_cb.predict_proba(X_cb_df.iloc[va])[:, 1]
avg = (oof_lgb[va] + oof_xgb[va] + oof_cb[va]) / 3
fold_auc = roc_auc_score(y_arr[va], avg)
fold_aucs.append(fold_auc)
log(f" β†’ Fold {fi+1:2d} done: AUC={fold_auc:.5f} ({time.time()-t_f:.0f}s)")
# ─────────────────────────────────────────────────────────
# 6. RÉSULTATS + META-STACKING
# ─────────────────────────────────────────────────────────
log("\n[6/6] RΓ©sultats finaux...")
auc_lgb = roc_auc_score(y_arr, oof_lgb)
auc_xgb = roc_auc_score(y_arr, oof_xgb)
auc_cb = roc_auc_score(y_arr, oof_cb)
# Moyenne simple + threshold sweep
avg = (oof_lgb + oof_xgb + oof_cb) / 3
auc_avg = roc_auc_score(y_arr, avg)
acc_05 = accuracy_score(y_arr, (avg >= 0.5).astype(int))
best_acc_avg, best_thr_avg = max(
((accuracy_score(y_arr, (avg >= t).astype(int)), t) for t in np.arange(0.3, 0.70, 0.002)),
key=lambda x: x[0])
# Weighted blend grid search
best_auc_w, best_w = 0, (1/3, 1/3, 1/3)
for w1 in np.arange(0.1, 0.7, 0.1):
for w2 in np.arange(0.1, 0.7, 0.1):
w3 = 1.0 - w1 - w2
if w3 <= 0.05: continue
auc = roc_auc_score(y_arr, w1*oof_lgb + w2*oof_xgb + w3*oof_cb)
if auc > best_auc_w:
best_auc_w, best_w = auc, (w1, w2, w3)
wblend = best_w[0]*oof_lgb + best_w[1]*oof_xgb + best_w[2]*oof_cb
best_acc_w = max(accuracy_score(y_arr, (wblend >= t).astype(int)) for t in np.arange(0.3, 0.70, 0.002))
# Meta-stacking LogReg
log(" Meta-stacking LogReg...")
meta_X = np.column_stack([oof_lgb, oof_xgb, oof_cb])
oof_meta = np.zeros(n)
for tr, va in cv10.split(meta_X, y_arr):
lr = LogisticRegression(C=10, max_iter=1000, random_state=42)
lr.fit(meta_X[tr], y_arr[tr])
oof_meta[va] = lr.predict_proba(meta_X[va])[:, 1]
auc_meta = roc_auc_score(y_arr, oof_meta)
best_acc_meta = max(accuracy_score(y_arr, (oof_meta >= t).astype(int)) for t in np.arange(0.3, 0.70, 0.002))
# Meilleurs scores finaux
best_auc_all = max(auc_avg, best_auc_w, auc_meta)
best_acc_all = max(best_acc_avg, best_acc_w, best_acc_meta)
# ─────────────────────────────────────────────────────────
# RAPPORT
# ─────────────────────────────────────────────────────────
log("\n" + "=" * 70)
log("RAPPORT FINAL - ADULT INCOME DATASET")
log("=" * 70)
log("\nπŸ“Š RΓ‰SULTATS 10-FOLD CV:")
log(f" LightGBM seul - AUC: {auc_lgb:.5f}")
log(f" XGBoost seul - AUC: {auc_xgb:.5f}")
log(f" CatBoost seul - AUC: {auc_cb:.5f}")
log(f" Moyenne simple - AUC: {auc_avg:.5f} | Acc@0.5={acc_05:.5f} | Acc@opt={best_acc_avg:.5f} (thr={best_thr_avg:.3f})")
log(f" Poids optim - AUC: {best_auc_w:.5f} | Acc@opt={best_acc_w:.5f} (w={best_w[0]:.1f}/{best_w[1]:.1f}/{best_w[2]:.1f})")
log(f" Meta-LR stack - AUC: {auc_meta:.5f} | Acc@opt={best_acc_meta:.5f}")
log(f"\n AUC fold-by-fold: {[round(x,4) for x in fold_aucs]}")
log(f" MeanΒ±Std: {np.mean(fold_aucs):.5f} Β± {np.std(fold_aucs):.5f}")
log(f"\nπŸ† MEILLEURE: AUC={best_auc_all:.5f} | Acc={best_acc_all:.5f}")
log(f"\n🎯 OBJECTIFS:")
log(f" Accuracy > 0.8756: {'βœ… ATTEINT (' + str(round(best_acc_all,5)) + ')' if best_acc_all > 0.8756 else '❌ ' + str(round(best_acc_all,5))}")
log(f" AUC > 0.9300: {'βœ… ATTEINT (' + str(round(best_auc_all,5)) + ')' if best_auc_all > 0.9300 else '❌ ' + str(round(best_auc_all,5))}")
log(f"\nπŸ“‹ vs OpenML SOTA (AdaBoost 2017: AUC=0.92840 Acc=0.87400):")
log(f" Ξ”AUC: {best_auc_all - 0.92840:+.5f} | Ξ”Acc: {best_acc_all - 0.87400:+.5f}")
log("\n" + "=" * 70)
log("TERMINÉ.")
log("=" * 70)