Skip to content

Commit 3200e21

Browse files
committed
[tutorials][ML] Add RDataLoader classification tutorial with XGBoost
1 parent abbaf5b commit 3200e21

3 files changed

Lines changed: 103 additions & 1 deletion

File tree

tutorials/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ if(MSVC AND NOT win_broken_tests)
7878
list(APPEND dataframe_veto machine_learning/ml_dataloader_filters_vectors.py)
7979
list(APPEND dataframe_veto machine_learning/ml_dataloader_Higgs_Classification.py)
8080
list(APPEND dataframe_veto machine_learning/ml_dataloader_resampling.py)
81+
list(APPEND dataframe_veto machine_learning/ml_dataloader_XGBoost.py)
8182
# df036* and df037* seem to trigger OS errors when trying to delete the
8283
# test files created in the tutorials. It is unclear why.
8384
list(APPEND dataframe_veto analysis/dataframe/df036_missingBranches.C)
@@ -132,6 +133,7 @@ if (NOT dataframe)
132133
list(APPEND dataframe_veto machine_learning/ml_dataloader_filters_vectors.py)
133134
list(APPEND dataframe_veto machine_learning/ml_dataloader_resampling.py)
134135
list(APPEND dataframe_veto machine_learning/ml_dataloader_Higgs_Classification.py)
136+
list(APPEND dataframe_veto machine_learning/ml_dataloader_XGBoost.py)
135137
# RooFit tutorials depending on RDataFrame
136138
list(APPEND dataframe_veto
137139
roofit/roofit/rf408_RDataFrameToRooFit.C
@@ -955,12 +957,14 @@ if(pyroot)
955957
file(GLOB requires_xgboost RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
956958
machine_learning/tmva101_Training.py
957959
machine_learning/tmva102_Testing.py # requires tmva101_Training.py
960+
machine_learning/ml_dataloader_XGBoost.py
958961
roofit/roofit/rf618_mixture_models.py
959962
)
960963
file(GLOB requires_sklearn RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
961964
machine_learning/TMVA_SOFIE_Models.py
962965
machine_learning/tmva101_Training.py # uses the xgboost sklearn plugin
963966
machine_learning/tmva102_Testing.py # requires tmva101_Training.py
967+
machine_learning/ml_dataloader_XGBoost.py
964968
roofit/roofit/rf617_simulation_based_inference_multidimensional.py
965969
roofit/roofit/rf618_mixture_models.py # uses the xgboost sklearn plugin
966970
)

tutorials/machine_learning/index.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,5 +137,6 @@
137137
| ml_dataloader_PyTorch.py | Loading batches of events from a ROOT dataset into a basic PyTorch workflow. |
138138
| ml_dataloader_TensorFlow.py | Loading batches of events from a ROOT dataset into a basic TensorFlow workflow. |
139139
| ml_dataloader_Higgs_Classification.py | Loading batches of events from different files for a data-normalization workflow. |
140-
| ml_dataloader_resampling.py | Loading batches of events from an imbalanced ROOT dataset and balancing them. |
140+
| ml_dataloader_resampling.py | Loading batches of events from an imbalanced ROOT dataset and balancing them. |
141+
| ml_dataloader_XGBoost.py | Training classifier models directly from remote ROOT files, shown with an XGBoost example. |
141142

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
## \file
2+
## \ingroup tutorial_ml
3+
## \notebook -nodraw
4+
## This tutorial demonstrates training a classifier model directly
5+
## from remote ROOT data without intermediate preparation steps,
6+
## using XGBoost and the RDataLoader interface.
7+
##
8+
## \macro_code
9+
## \macro_output
10+
##
11+
## \date July 2026
12+
## \author Silia Taider
13+
14+
import ROOT
15+
16+
variables = ["Muon_pt_1", "Muon_pt_2", "Electron_pt_1", "Electron_pt_2"]
17+
18+
19+
def filter_events(df):
20+
"""Reduce initial dataset to only events which shall be used for training"""
21+
return df.Filter("nElectron>=2 && nMuon>=2", "At least two electrons and two muons")
22+
23+
24+
def define_variables(df):
25+
"""Define the variables which shall be used for training"""
26+
return (
27+
df
28+
.Define("Muon_pt_1", "Muon_pt[0]")
29+
.Define("Muon_pt_2", "Muon_pt[1]")
30+
.Define("Electron_pt_1", "Electron_pt[0]")
31+
.Define("Electron_pt_2", "Electron_pt[1]")
32+
)
33+
34+
35+
def prepare_rdf(filename, label_value):
36+
"""Load, filter, define variables, and add label column"""
37+
filepath = "root://eospublic.cern.ch//eos/root-eos/cms_opendata_2012_nanoaod/" + filename
38+
df = ROOT.RDataFrame("Events", filepath)
39+
df = filter_events(df)
40+
df = define_variables(df)
41+
df = df.Define("label", f"{label_value}.0")
42+
return df
43+
44+
45+
def load_data():
46+
"""Load signal and background data"""
47+
rdf_sig = prepare_rdf("SMHiggsToZZTo4L.root", 1)
48+
rdf_bkg = prepare_rdf("ZZTo2e2mu.root", 0)
49+
50+
# Compute class-balancing weights
51+
num_sig = rdf_sig.Count().GetValue()
52+
num_bkg = rdf_bkg.Count().GetValue()
53+
num_all = num_sig + num_bkg
54+
55+
rdf_sig = rdf_sig.Define("weight", f"{num_all}.0/{num_sig}.0")
56+
rdf_bkg = rdf_bkg.Define("weight", f"{num_all}.0/{num_bkg}.0")
57+
58+
loader = ROOT.Experimental.ML.RDataLoader(
59+
[rdf_sig, rdf_bkg],
60+
columns=variables + ["label", "weight"],
61+
target="label",
62+
weights="weight",
63+
batch_size=num_all, # Load all data in one batch
64+
drop_remainder=False,
65+
set_seed=42,
66+
)
67+
68+
# Split into training and testing sets
69+
train, test = loader.train_test_split(test_size=0.5)
70+
71+
# train.as_numpy() and test.as_numpy() return generators of batches.
72+
# Since batch_size=num_all, each split contains exactly one batch;
73+
# next(iter(...)) materializes it into in-memory numpy arrays.
74+
X_train, y_train, w_train = next(iter(train.as_numpy()))
75+
X_test, y_test, w_test = next(iter(test.as_numpy()))
76+
77+
# Flatten target and weights from (n,1) to (n,) as expected by XGBoost
78+
return (X_train, y_train.ravel(), w_train.ravel(), X_test, y_test.ravel(), w_test.ravel())
79+
80+
81+
if __name__ == "__main__":
82+
from sklearn.metrics import roc_auc_score
83+
from xgboost import XGBClassifier
84+
85+
X_train, y_train, w_train, X_test, y_test, w_test = load_data()
86+
87+
print(f"Training events: {X_train.shape[0]}")
88+
print(f"Testing events: {X_test.shape[0]}")
89+
90+
bdt = XGBClassifier(max_depth=3, n_estimators=500)
91+
bdt.fit(X_train, y_train, sample_weight=w_train)
92+
93+
# Evaluate on test set
94+
y_proba = bdt.predict_proba(X_test)[:, 1]
95+
auc = roc_auc_score(y_test, y_proba)
96+
97+
print(f"Training done. ROC AUC: {auc:.4f}")

0 commit comments

Comments
 (0)