Skip to content

Commit 0d0b692

Browse files
Student2Jonah Ascoli
authored andcommitted
[tutorials][ML] Add resampling tutorial
1 parent e8df1eb commit 0d0b692

3 files changed

Lines changed: 87 additions & 0 deletions

File tree

tutorials/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ if(MSVC AND NOT win_broken_tests)
7676
list(APPEND dataframe_veto machine_learning/ml_dataloader_TensorFlow.py)
7777
list(APPEND dataframe_veto machine_learning/ml_dataloader_PyTorch.py)
7878
list(APPEND dataframe_veto machine_learning/ml_dataloader_filters_vectors.py)
79+
list(APPEND dataframe_veto machine_learning/ml_dataloader_resampling.py)
7980
# df036* and df037* seem to trigger OS errors when trying to delete the
8081
# test files created in the tutorials. It is unclear why.
8182
list(APPEND dataframe_veto analysis/dataframe/df036_missingBranches.C)
@@ -128,6 +129,7 @@ if (NOT dataframe)
128129
list(APPEND dataframe_veto machine_learning/ml_dataloader_TensorFlow.py)
129130
list(APPEND dataframe_veto machine_learning/ml_dataloader_PyTorch.py)
130131
list(APPEND dataframe_veto machine_learning/ml_dataloader_filters_vectors.py)
132+
list(APPEND dataframe_veto machine_learning/ml_dataloader_resampling.py)
131133
# RooFit tutorials depending on RDataFrame
132134
list(APPEND dataframe_veto
133135
roofit/roofit/rf408_RDataFrameToRooFit.C
@@ -937,6 +939,7 @@ if(pyroot)
937939
file(GLOB requires_torch RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
938940
machine_learning/pytorch/*.py
939941
machine_learning/ml_dataloader_PyTorch.py
942+
machine_learning/ml_dataloader_resampling.py
940943
)
941944
file(GLOB requires_xgboost RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
942945
machine_learning/tmva101_Training.py

tutorials/machine_learning/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,4 +137,5 @@
137137
| ml_dataloader_NumPy.py | Loading batches of events from a ROOT dataset as Python generators of numpy arrays. |
138138
| ml_dataloader_PyTorch.py | Loading batches of events from a ROOT dataset into a basic PyTorch workflow. |
139139
| ml_dataloader_TensorFlow.py | Loading batches of events from a ROOT dataset into a basic TensorFlow workflow. |
140+
| ml_dataloader_resampling.py | Loading batches of events from an imbalanced ROOT dataset and balancing them. |
140141

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
### \file
2+
### \ingroup tutorial_ml
3+
### \notebook -nodraw
4+
### Example of resampling when one class is underrepresented in the dataset.
5+
###
6+
### \macro_code
7+
### \macro_output
8+
### \author Jonah Ascoli
9+
10+
import ROOT
11+
import torch
12+
from tqdm import tqdm
13+
14+
seed = 42
15+
torch.manual_seed(seed)
16+
17+
18+
# Create an imbalanced dataset with two classes, one of which is underrepresented.
19+
# Here, we'll create two files, one with even numbers and one with odd numbers,
20+
# and then merge them to form a dataset with underrepresented odd numbers.
21+
def make_df(b1_expr, num_events):
22+
return ROOT.RDataFrame(num_events).Define("b1", b1_expr).Define("b2", "(int) b1%2")
23+
24+
25+
df_major = make_df("(int) 2 * rdfentry_", 100000)
26+
df_minor = make_df("(int) 2 * rdfentry_ + 1", 1000)
27+
28+
batch_size = 256
29+
num_epochs = 10
30+
31+
loss_fn = torch.nn.BCEWithLogitsLoss()
32+
33+
34+
def train_model(model, optimizer, dataloader):
35+
train, val = dataloader.train_test_split(test_size=0.2)
36+
for _ in tqdm(range(num_epochs), desc="Training"):
37+
model.train()
38+
for X, y in train.as_torch():
39+
optimizer.zero_grad()
40+
loss = loss_fn(model(X), y)
41+
loss.backward()
42+
optimizer.step()
43+
losses = []
44+
for X, y in val.as_torch():
45+
with torch.no_grad():
46+
loss = loss_fn(model(X), y)
47+
losses.append(loss.item())
48+
print(f"Validation Loss: {sum(losses) / len(losses)}")
49+
50+
51+
# First, let's try to create a dataloader without resampling and see how it handles the underrepresented class.
52+
dl = ROOT.Experimental.ML.RDataLoader(
53+
[df_major, df_minor],
54+
batch_size=batch_size,
55+
target="b2",
56+
set_seed=seed,
57+
load_eager=True,
58+
)
59+
60+
basic_model = torch.nn.Linear(1, 1) # Simple linear model for binary classification
61+
basic_optimizer = torch.optim.Adam(basic_model.parameters())
62+
63+
print("Training without resampling:")
64+
train_model(basic_model, basic_optimizer, dl)
65+
66+
# Now, let's try the same thing with oversampling
67+
# Strategy: more batches of the underrepresented class
68+
# Takes more time per epoch, but each epoch is more effective
69+
dl_oversampled = ROOT.Experimental.ML.RDataLoader(
70+
[df_major, df_minor],
71+
batch_size=batch_size,
72+
target="b2",
73+
set_seed=seed,
74+
load_eager=True, # Must be enabled for resampling
75+
sampling_type="oversampling", # Can also be "undersampling"
76+
sampling_ratio=0.1, # ~10% of the data will be from the underrepresented class
77+
)
78+
79+
oversampling_model = torch.nn.Linear(1, 1)
80+
oversampling_optimizer = torch.optim.Adam(oversampling_model.parameters())
81+
82+
print("Training with oversampling:")
83+
train_model(oversampling_model, oversampling_optimizer, dl_oversampled)

0 commit comments

Comments
 (0)