Download train.py from helloblueai/bleu-xgboost-classifier: direct link, hf CLI and curl.
- Browser
- Download file 6.32 kB
-
https://huggingface.co/helloblueai/bleu-xgboost-classifier/resolve/main/train.py
- Command line
-
hf download hf://helloblueai/bleu-xgboost-classifier/train.py
-
curl -L -o train.py https://huggingface.co/helloblueai/bleu-xgboost-classifier/resolve/main/train.py
6.32 kB
| """Train and score the Bleu.js forest-cover XGBoost classifier. | |
| Protocol, fixed before the test set is scored: | |
| - Data: sklearn.datasets.fetch_covtype (UCI Covertype, full table) | |
| - Labels shifted from 1..7 to 0..6 | |
| - Outer split: stratified 80/20, random_state=42. The 20% is scored once. | |
| - Inner split: stratified 90/10 of the training rows, random_state=42, | |
| used only to pick n_estimators with early stopping. | |
| - Final fit: all training rows, n_estimators = best_iteration + 1, | |
| sample_weight = balanced class weights from the training rows. | |
| - Baselines, same test rows, no retuning: majority class; a depth-3 | |
| XGBoost with 50 trees and no class weights. | |
| """ | |
| import json | |
| from pathlib import Path | |
| import numpy as np | |
| import xgboost as xgb | |
| from sklearn.datasets import fetch_covtype | |
| from sklearn.metrics import ( | |
| accuracy_score, | |
| classification_report, | |
| f1_score, | |
| log_loss, | |
| ) | |
| from sklearn.model_selection import train_test_split | |
| from sklearn.utils.class_weight import compute_sample_weight | |
| OUT = Path(__file__).resolve().parent | |
| RANDOM_STATE = 42 | |
| CLASS_NAMES = [ | |
| "Spruce/Fir", | |
| "Lodgepole Pine", | |
| "Ponderosa Pine", | |
| "Cottonwood/Willow", | |
| "Aspen", | |
| "Douglas-fir", | |
| "Krummholz", | |
| ] | |
| FEATURE_NAMES = [ | |
| "Elevation", | |
| "Aspect", | |
| "Slope", | |
| "Horizontal_Distance_To_Hydrology", | |
| "Vertical_Distance_To_Hydrology", | |
| "Horizontal_Distance_To_Roadways", | |
| "Hillshade_9am", | |
| "Hillshade_Noon", | |
| "Hillshade_3pm", | |
| "Horizontal_Distance_To_Fire_Points", | |
| *[f"Wilderness_Area_{i}" for i in range(4)], | |
| *[f"Soil_Type_{i}" for i in range(40)], | |
| ] | |
| def scores(y_true, pred, proba): | |
| return { | |
| "accuracy": float(accuracy_score(y_true, pred)), | |
| "macro_f1": float(f1_score(y_true, pred, average="macro")), | |
| "weighted_f1": float(f1_score(y_true, pred, average="weighted")), | |
| "log_loss": float(log_loss(y_true, proba, labels=list(range(7)))), | |
| } | |
| def main(): | |
| bunch = fetch_covtype(as_frame=False) | |
| X = np.asarray(bunch.data, dtype=np.float32) | |
| y = np.asarray(bunch.target, dtype=np.int32) - 1 | |
| if X.shape[1] != len(FEATURE_NAMES) or y.min() != 0 or y.max() != 6: | |
| raise SystemExit(f"Unexpected covertype shape {X.shape} labels {y.min()}..{y.max()}") | |
| X_train, X_test, y_train, y_test = train_test_split( | |
| X, y, test_size=0.2, random_state=RANDOM_STATE, stratify=y | |
| ) | |
| X_fit, X_val, y_fit, y_val = train_test_split( | |
| X_train, y_train, test_size=0.1, random_state=RANDOM_STATE, stratify=y_train | |
| ) | |
| common = dict( | |
| max_depth=8, | |
| learning_rate=0.1, | |
| subsample=0.8, | |
| colsample_bytree=0.8, | |
| min_child_weight=5, | |
| reg_lambda=1.0, | |
| tree_method="hist", | |
| objective="multi:softprob", | |
| num_class=7, | |
| random_state=RANDOM_STATE, | |
| n_jobs=-1, | |
| ) | |
| search = xgb.XGBClassifier(n_estimators=400, early_stopping_rounds=20, **common) | |
| search.fit( | |
| X_fit, | |
| y_fit, | |
| sample_weight=compute_sample_weight("balanced", y_fit), | |
| eval_set=[(X_val, y_val)], | |
| verbose=False, | |
| ) | |
| n_trees = int(search.best_iteration) + 1 | |
| model = xgb.XGBClassifier(n_estimators=n_trees, **common) | |
| model.fit( | |
| X_train, | |
| y_train, | |
| sample_weight=compute_sample_weight("balanced", y_train), | |
| verbose=False, | |
| ) | |
| simple = xgb.XGBClassifier( | |
| n_estimators=50, | |
| max_depth=3, | |
| learning_rate=0.1, | |
| tree_method="hist", | |
| objective="multi:softprob", | |
| num_class=7, | |
| random_state=RANDOM_STATE, | |
| n_jobs=-1, | |
| ) | |
| simple.fit(X_train, y_train, verbose=False) | |
| proba = model.predict_proba(X_test) | |
| pred = proba.argmax(axis=1) | |
| simple_proba = simple.predict_proba(X_test) | |
| simple_pred = simple_proba.argmax(axis=1) | |
| majority = int(np.bincount(y_train).argmax()) | |
| majority_pred = np.full_like(y_test, majority) | |
| majority_proba = np.zeros_like(proba) | |
| majority_proba[:, majority] = 1.0 | |
| report = classification_report( | |
| y_test, pred, target_names=CLASS_NAMES, output_dict=True, digits=4 | |
| ) | |
| gain = model.get_booster().get_score(importance_type="gain") | |
| importance = sorted( | |
| ( | |
| {"feature": FEATURE_NAMES[int(k[1:])], "gain": float(v)} | |
| for k, v in gain.items() | |
| ), | |
| key=lambda row: row["gain"], | |
| reverse=True, | |
| ) | |
| payload = { | |
| "dataset": "UCI Covertype", | |
| "source": "sklearn.datasets.fetch_covtype", | |
| "citation": "Blackard, Jock A. Covertype. UCI Machine Learning Repository.", | |
| "n_rows": int(X.shape[0]), | |
| "n_features": int(X.shape[1]), | |
| "class_names": CLASS_NAMES, | |
| "label_encoding": "model class i is UCI cover type i+1", | |
| "split": { | |
| "test_size": 0.2, | |
| "inner_val_size": 0.1, | |
| "random_state": RANDOM_STATE, | |
| "stratify": True, | |
| }, | |
| "n_train": int(len(y_train)), | |
| "n_test": int(len(y_test)), | |
| "n_estimators_selected_on_inner_val": n_trees, | |
| "hyperparameters": {**common, "n_estimators": n_trees, "sample_weight": "balanced"}, | |
| "xgboost_version": xgb.__version__, | |
| "trainer_note": ( | |
| "XGBClassifier. bleujs.ml.HybridTrainer targets binary classification " | |
| "with binary:logistic, so this multiclass model is trained with the " | |
| "same library and a documented multiclass objective." | |
| ), | |
| "test_metrics": scores(y_test, pred, proba), | |
| "simple_xgboost_baseline": scores(y_test, simple_pred, simple_proba), | |
| "majority_baseline": scores(y_test, majority_pred, majority_proba), | |
| "per_class_f1": {name: float(report[name]["f1-score"]) for name in CLASS_NAMES}, | |
| "per_class_support": {name: int(report[name]["support"]) for name in CLASS_NAMES}, | |
| "feature_importance_gain": importance, | |
| } | |
| model.save_model(OUT / "model.json") | |
| (OUT / "metrics.json").write_text(json.dumps(payload, indent=2)) | |
| print(json.dumps({ | |
| "n_trees": n_trees, | |
| "test": payload["test_metrics"], | |
| "simple": payload["simple_xgboost_baseline"], | |
| "majority": payload["majority_baseline"], | |
| "per_class_f1": payload["per_class_f1"], | |
| }, indent=2)) | |
| if __name__ == "__main__": | |
| main() | |