From 391a7b2ac80e18acb2d88b9b6dece6187b697677 Mon Sep 17 00:00:00 2001 From: Silia Taider Date: Fri, 17 Jul 2026 19:44:28 +0200 Subject: [PATCH] [tutorials][ML] Add RDataLoader tutorial for XGBoost --- tutorials/CMakeLists.txt | 2 + tutorials/machine_learning/index.md | 3 +- .../machine_learning/ml_dataloader_XGBoost.py | 96 +++++++++++++++++++ 3 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 tutorials/machine_learning/ml_dataloader_XGBoost.py diff --git a/tutorials/CMakeLists.txt b/tutorials/CMakeLists.txt index 3297527a27e8f..23083e4396ba8 100644 --- a/tutorials/CMakeLists.txt +++ b/tutorials/CMakeLists.txt @@ -78,6 +78,7 @@ if(MSVC AND NOT win_broken_tests) list(APPEND dataframe_veto machine_learning/ml_dataloader_filters_vectors.py) list(APPEND dataframe_veto machine_learning/ml_dataloader_Higgs_Classification.py) list(APPEND dataframe_veto machine_learning/ml_dataloader_resampling.py) + list(APPEND dataframe_veto machine_learning/ml_dataloader_XGBoost.py) # df036* and df037* seem to trigger OS errors when trying to delete the # test files created in the tutorials. It is unclear why. list(APPEND dataframe_veto analysis/dataframe/df036_missingBranches.C) @@ -132,6 +133,7 @@ if (NOT dataframe) list(APPEND dataframe_veto machine_learning/ml_dataloader_filters_vectors.py) list(APPEND dataframe_veto machine_learning/ml_dataloader_resampling.py) list(APPEND dataframe_veto machine_learning/ml_dataloader_Higgs_Classification.py) + list(APPEND dataframe_veto machine_learning/ml_dataloader_XGBoost.py) # RooFit tutorials depending on RDataFrame list(APPEND dataframe_veto roofit/roofit/rf408_RDataFrameToRooFit.C diff --git a/tutorials/machine_learning/index.md b/tutorials/machine_learning/index.md index 242f934e21232..43f7301e8b276 100644 --- a/tutorials/machine_learning/index.md +++ b/tutorials/machine_learning/index.md @@ -137,5 +137,6 @@ | ml_dataloader_PyTorch.py | Loading batches of events from a ROOT dataset into a basic PyTorch workflow. | | ml_dataloader_TensorFlow.py | Loading batches of events from a ROOT dataset into a basic TensorFlow workflow. | | ml_dataloader_Higgs_Classification.py | Loading batches of events from different files for a data-normalization workflow. | -| ml_dataloader_resampling.py | Loading batches of events from an imbalanced ROOT dataset and balancing them. | +| ml_dataloader_resampling.py | Loading batches of events from an imbalanced ROOT dataset and balancing them. | +| ml_dataloader_XGBoost.py | Training XGBoost directly from remote ROOT files. | diff --git a/tutorials/machine_learning/ml_dataloader_XGBoost.py b/tutorials/machine_learning/ml_dataloader_XGBoost.py new file mode 100644 index 0000000000000..8ebb5daf63055 --- /dev/null +++ b/tutorials/machine_learning/ml_dataloader_XGBoost.py @@ -0,0 +1,96 @@ +## \file +## \ingroup tutorial_ml +## \notebook -nodraw +## This tutorial demonstrates XGBoost training using RDataLoader to load data +## directly from ROOT files without intermediate preparation steps. +## This is an alternative implementation to tutorial tmva101_Training.py, +## which stores intermediate files after train/test splitting +## and uses numpy arrays to load data. +## +## \macro_code +## \macro_output +## +## \date July 2026 +## \author Silia Taider + +import ROOT + +variables = ["Muon_pt_1", "Muon_pt_2", "Electron_pt_1", "Electron_pt_2"] + + +def filter_events(df): + """Reduce initial dataset to only events which shall be used for training""" + return df.Filter("nElectron>=2 && nMuon>=2", "At least two electrons and two muons") + + +def define_variables(df): + """Define the variables which shall be used for training""" + return ( + df + .Define("Muon_pt_1", "Muon_pt[0]") + .Define("Muon_pt_2", "Muon_pt[1]") + .Define("Electron_pt_1", "Electron_pt[0]") + .Define("Electron_pt_2", "Electron_pt[1]") + ) + + +def prepare_rdf(filename, label_value): + """Load, filter, define variables, and add label column""" + filepath = "root://eospublic.cern.ch//eos/root-eos/cms_opendata_2012_nanoaod/" + filename + df = ROOT.RDataFrame("Events", filepath) + df = filter_events(df) + df = define_variables(df) + df = df.Define("label", f"{label_value}.0") + return df + + +def load_data(): + """Load signal and background data""" + rdf_sig = prepare_rdf("SMHiggsToZZTo4L.root", 1) + rdf_bkg = prepare_rdf("ZZTo2e2mu.root", 0) + + # Compute class-balancing weights + num_sig = rdf_sig.Count().GetValue() + num_bkg = rdf_bkg.Count().GetValue() + num_all = num_sig + num_bkg + + rdf_sig = rdf_sig.Define("weight", f"{num_all}.0/{num_sig}.0") + rdf_bkg = rdf_bkg.Define("weight", f"{num_all}.0/{num_bkg}.0") + + loader = ROOT.Experimental.ML.RDataLoader( + [rdf_sig, rdf_bkg], + columns=variables + ["label", "weight"], + target="label", + weights="weight", + batch_size=num_all, # Load all data in one batch + drop_remainder=False, + set_seed=42, + ) + + # Split into training and testing sets + train, test = loader.train_test_split(test_size=0.5) + + X_train, y_train, w_train = next(iter(train.as_numpy())) + X_test, y_test, w_test = next(iter(test.as_numpy())) + + # Flatten target and weights from (n,1) to (n,) as expected by XGBoost + return (X_train, y_train.ravel(), w_train.ravel(), X_test, y_test.ravel(), w_test.ravel()) + + +if __name__ == "__main__": + from sklearn.metrics import roc_auc_score + from xgboost import XGBClassifier + + X_train, y_train, w_train, X_test, y_test, w_test = load_data() + + print(f"Training events: {X_train.shape[0]}") + print(f"Testing events: {X_test.shape[0]}") + + bdt = XGBClassifier(max_depth=3, n_estimators=500) + bdt.fit(X_train, y_train, sample_weight=w_train) + + # Evaluate on test set + y_proba = bdt.predict_proba(X_test)[:, 1] + auc = roc_auc_score(y_test, y_proba) + + print(f"Training done. ROC AUC: {auc:.4f}")