Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions tutorials/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion tutorials/machine_learning/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
| ml_dataloader_XGBoost.py | Training XGBoost directly from remote ROOT files. |
| ml_dataloader_XGBoost.py | Training classifier models directly from remote ROOT files, shown with an XGBoost example. |

The pattern you show generalized very well to other classification algos, so I would make that clear before people ask you to re-write the same tutorial for catboost or sckit-learn algorithms


96 changes: 96 additions & 0 deletions tutorials/machine_learning/ml_dataloader_XGBoost.py
Original file line number Diff line number Diff line change
@@ -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.
Comment on lines +4 to +8

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would not mention tmva101. Cross-referencing between tutorials has a risk to not age well, in particular here because we definitely have to do something about tmva101: it encourages users to store experimental objects like TMVA::Experimental::RBDT in a ROOT file via ROOT.TMVA.Experimental.SaveXGBoost, without explicitly telling you to not do this for production purposes. Yes, the SaveXGBoost interface is clearly experimental, but it doesn't warn the users that the content of the generated file will also be experimental classes.

##
## \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}")
Loading