Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
TMVA_SOFIE_Models.py File Reference

Detailed Description

View in nbviewer Open in SWAN
Example of inference with SOFIE using a set of models trained with PyTorch and exported to ONNX.

This tutorial shows how to store several models in a single header file and the weights in a ROOT binary file. The models are then evaluated using the RDataFrame

import inspect
import os
import numpy as np
import ROOT
import torch
import torch.nn as nn
## generate and train PyTorch models with different architectures
def CreateModel(nlayers=4, nunits=64):
layers = []
ninputs = 7
for i in range(nlayers):
layers += [nn.Linear(ninputs, nunits), nn.ReLU()]
ninputs = nunits
layers += [nn.Linear(ninputs, 1), nn.Sigmoid()]
model = nn.Sequential(*layers)
print(model)
return model
def TrainModel(model, x, y, epochs=5, batch_size=50):
criterion = nn.BCELoss()
nbatches = x.shape[0] // batch_size
for epoch in range(epochs):
running_loss = 0.0
for i in range(nbatches):
idx = perm[i * batch_size : (i + 1) * batch_size]
loss = criterion(model(x[idx]), y[idx])
running_loss += loss.item()
print(f"Epoch {epoch + 1}/{epochs} - average loss: {running_loss / nbatches:.4f}")
def ExportModel(model, modelName):
# need to evaluate the model before exporting to ONNX
# and to provide a dummy input tensor to set the input model shape
# (the batch size is fixed to 1 for the SOFIE inference)
modelFile = modelName + ".onnx"
dummy_x = torch.randn(1, 7)
model(dummy_x)
# check for torch.onnx.export parameters
def filtered_kwargs(func, **candidate_kwargs):
sig = inspect.signature(func)
return {k: v for k, v in candidate_kwargs.items() if k in sig.parameters}
kwargs = filtered_kwargs(
input_names=["input"],
output_names=["output"],
external_data=False, # may not exist
dynamo=True, # may not exist
)
print("calling torch.onnx.export with parameters", kwargs)
torch.onnx.export(model, dummy_x, modelFile, **kwargs)
print("model exported to ONNX as", modelFile)
return modelFile
def PrepareData():
# get the input data
inputFile = str(ROOT.gROOT.GetTutorialDir()) + "/machine_learning/data/Higgs_data.root"
df1 = ROOT.RDataFrame("sig_tree", inputFile)
sigData = df1.AsNumpy(columns=["m_jj", "m_jjj", "m_lv", "m_jlv", "m_bb", "m_wbb", "m_wwbb"])
# print(sigData)
# stack all the 7 numpy array in a single array (nevents x nvars)
data_sig_size = xsig.shape[0]
print("size of data", data_sig_size)
# make SOFIE inference on background data
df2 = ROOT.RDataFrame("bkg_tree", inputFile)
bkgData = df2.AsNumpy(columns=["m_jj", "m_jjj", "m_lv", "m_jlv", "m_bb", "m_wbb", "m_wwbb"])
data_bkg_size = xbkg.shape[0]
ysig = np.ones(data_sig_size)
ybkg = np.zeros(data_bkg_size)
inputs_data = np.concatenate((xsig, xbkg), axis=0).astype(np.float32)
inputs_targets = np.concatenate((ysig, ybkg), axis=0).astype(np.float32)
# split data in training and test data
ntrain = inputs_data.shape[0] // 2
x_train = inputs_data[idx[:ntrain]]
y_train = inputs_targets[idx[:ntrain]].reshape(-1, 1)
x_test = inputs_data[idx[ntrain:]]
y_test = inputs_targets[idx[ntrain:]].reshape(-1, 1)
return x_train, y_train, x_test, y_test
def TrainModels(x_train, y_train, modelNames):
# train the models with PyTorch and export them to ONNX
modelFiles = []
for modelName in modelNames:
model = CreateModel(4, 64)
TrainModel(model, x_train, y_train)
modelFiles.append(ExportModel(model, modelName))
return modelFiles
### run the models
x_train, y_train, x_test, y_test = PrepareData()
## create models and train them
# All three models use the same small architecture (4 layers of 64 units) to
# keep the tutorial runtime under control, whatever their names suggest.
modelNames = ["Higgs_Model_4L_50", "Higgs_Model_4L_200", "Higgs_Model_2L_500"]
model1, model2, model3 = TrainModels(x_train, y_train, modelNames)
# evaluate with SOFIE the 3 trained models
def GenerateModelCode(modelFile, generatedHeaderFile):
model = parser.Parse(modelFile)
print("Generating inference code for the ONNX model from ", modelFile, "in the header ", generatedHeaderFile)
# Generating inference code using a ROOT binary file
# add option to append to the same file the generated headers (pass True for append flag)
model.OutputGenerated(generatedHeaderFile, True)
# model.PrintGenerated()
return generatedHeaderFile
generatedHeaderFile = "Higgs_Model.hxx"
# need to remove existing header file since we are appending on same one
if os.path.exists(generatedHeaderFile):
print("removing existing file", generatedHeaderFile)
os.remove(generatedHeaderFile)
weightFile = "Higgs_Model.root"
if os.path.exists(weightFile):
print("removing existing file", weightFile)
os.remove(weightFile)
GenerateModelCode(model1, generatedHeaderFile)
GenerateModelCode(model2, generatedHeaderFile)
GenerateModelCode(model3, generatedHeaderFile)
# compile the generated code
ROOT.gInterpreter.Declare('#include "' + generatedHeaderFile + '"')
# run the inference on the test data
session1 = ROOT.TMVA_SOFIE_Higgs_Model_4L_50.Session("Higgs_Model.root")
session2 = ROOT.TMVA_SOFIE_Higgs_Model_4L_200.Session("Higgs_Model.root")
session3 = ROOT.TMVA_SOFIE_Higgs_Model_2L_500.Session("Higgs_Model.root")
hs1 = ROOT.TH1D("hs1", "Signal result 4L 50", 100, 0, 1)
hs2 = ROOT.TH1D("hs2", "Signal result 4L 200", 100, 0, 1)
hs3 = ROOT.TH1D("hs3", "Signal result 2L 500", 100, 0, 1)
hb1 = ROOT.TH1D("hb1", "Background result 4L 50", 100, 0, 1)
hb2 = ROOT.TH1D("hb2", "Background result 4L 200", 100, 0, 1)
hb3 = ROOT.TH1D("hb3", "Background result 2L 500", 100, 0, 1)
def EvalModel(session, x):
result = session.infer(x)
return result[0]
for i in range(0, x_test.shape[0]):
result1 = EvalModel(session1, x_test[i, :])
result2 = EvalModel(session2, x_test[i, :])
result3 = EvalModel(session3, x_test[i, :])
if y_test[i] == 1:
hs1.Fill(result1)
hs2.Fill(result2)
hs3.Fill(result3)
else:
hb1.Fill(result1)
hb2.Fill(result2)
hb3.Fill(result3)
def PlotHistos(hs, hb):
hb.SetLineColor("kBlue")
hb.Draw("same")
c1.Divide(1, 3)
PlotHistos(hs1, hb1)
PlotHistos(hs2, hb2)
PlotHistos(hs3, hb3)
## draw also ROC curves
def GetContent(h):
x = ROOT.std.vector["float"](n)
w = ROOT.std.vector["float"](n)
for i in range(0, n):
x[i] = h.GetBinCenter(i + 1)
w[i] = h.GetBinContent(i + 1)
return x, w
def MakeROCCurve(hs, hb):
xs, ws = GetContent(hs)
xb, wb = GetContent(hb)
roc = ROOT.TMVA.ROCCurve(xs, xb, ws, wb)
print("ROC integral for ", hs.GetName(), roc.GetROCIntegral())
curve = roc.GetROCCurve()
return roc, curve
r1, curve1 = MakeROCCurve(hs1, hb1)
r2, curve2 = MakeROCCurve(hs2, hb2)
r3, curve3 = MakeROCCurve(hs3, hb3)
ROOT::Detail::TRangeCast< T, true > TRangeDynCast
TRangeDynCast is an adapter class that allows the typed iteration through a TCollection.
ROOT's RDataFrame offers a modern, high-level interface for analysis of data stored in TTree ,...
size of data 10000
Sequential(
(0): Linear(in_features=7, out_features=64, bias=True)
(1): ReLU()
(2): Linear(in_features=64, out_features=64, bias=True)
(3): ReLU()
(4): Linear(in_features=64, out_features=64, bias=True)
(5): ReLU()
(6): Linear(in_features=64, out_features=64, bias=True)
(7): ReLU()
(8): Linear(in_features=64, out_features=1, bias=True)
(9): Sigmoid()
)
Epoch 1/5 - average loss: 0.6687
Epoch 2/5 - average loss: 0.6408
Epoch 3/5 - average loss: 0.6283
Epoch 4/5 - average loss: 0.6215
Epoch 5/5 - average loss: 0.6150
calling torch.onnx.export with parameters {'input_names': ['input'], 'output_names': ['output'], 'external_data': False, 'dynamo': True}
[torch.onnx] Obtain model graph for `Sequential([...]` with `torch.export.export(..., strict=False)`...
[torch.onnx] Obtain model graph for `Sequential([...]` with `torch.export.export(..., strict=False)`... ✅
[torch.onnx] Run decompositions...
[torch.onnx] Run decompositions... ✅
[torch.onnx] Translate the graph into ONNX...
[torch.onnx] Translate the graph into ONNX... ✅
[torch.onnx] Optimize the ONNX graph...
[torch.onnx] Optimize the ONNX graph... ✅
model exported to ONNX as Higgs_Model_4L_50.onnx
Sequential(
(0): Linear(in_features=7, out_features=64, bias=True)
(1): ReLU()
(2): Linear(in_features=64, out_features=64, bias=True)
(3): ReLU()
(4): Linear(in_features=64, out_features=64, bias=True)
(5): ReLU()
(6): Linear(in_features=64, out_features=64, bias=True)
(7): ReLU()
(8): Linear(in_features=64, out_features=1, bias=True)
(9): Sigmoid()
)
Epoch 1/5 - average loss: 0.6691
Epoch 2/5 - average loss: 0.6431
Epoch 3/5 - average loss: 0.6283
Epoch 4/5 - average loss: 0.6227
Epoch 5/5 - average loss: 0.6178
calling torch.onnx.export with parameters {'input_names': ['input'], 'output_names': ['output'], 'external_data': False, 'dynamo': True}
[torch.onnx] Obtain model graph for `Sequential([...]` with `torch.export.export(..., strict=False)`...
[torch.onnx] Obtain model graph for `Sequential([...]` with `torch.export.export(..., strict=False)`... ✅
[torch.onnx] Run decompositions...
[torch.onnx] Run decompositions... ✅
[torch.onnx] Translate the graph into ONNX...
[torch.onnx] Translate the graph into ONNX... ✅
[torch.onnx] Optimize the ONNX graph...
[torch.onnx] Optimize the ONNX graph... ✅
model exported to ONNX as Higgs_Model_4L_200.onnx
Sequential(
(0): Linear(in_features=7, out_features=64, bias=True)
(1): ReLU()
(2): Linear(in_features=64, out_features=64, bias=True)
(3): ReLU()
(4): Linear(in_features=64, out_features=64, bias=True)
(5): ReLU()
(6): Linear(in_features=64, out_features=64, bias=True)
(7): ReLU()
(8): Linear(in_features=64, out_features=1, bias=True)
(9): Sigmoid()
)
Epoch 1/5 - average loss: 0.6721
Epoch 2/5 - average loss: 0.6410
Epoch 3/5 - average loss: 0.6317
Epoch 4/5 - average loss: 0.6216
Epoch 5/5 - average loss: 0.6137
calling torch.onnx.export with parameters {'input_names': ['input'], 'output_names': ['output'], 'external_data': False, 'dynamo': True}
[torch.onnx] Obtain model graph for `Sequential([...]` with `torch.export.export(..., strict=False)`...
[torch.onnx] Obtain model graph for `Sequential([...]` with `torch.export.export(..., strict=False)`... ✅
[torch.onnx] Run decompositions...
[torch.onnx] Run decompositions... ✅
[torch.onnx] Translate the graph into ONNX...
[torch.onnx] Translate the graph into ONNX... ✅
[torch.onnx] Optimize the ONNX graph...
[torch.onnx] Optimize the ONNX graph... ✅
model exported to ONNX as Higgs_Model_2L_500.onnx
Generating inference code for the ONNX model from Higgs_Model_4L_50.onnx in the header Higgs_Model.hxx
Generating inference code for the ONNX model from Higgs_Model_4L_200.onnx in the header Higgs_Model.hxx
Generating inference code for the ONNX model from Higgs_Model_2L_500.onnx in the header Higgs_Model.hxx
ROC integral for hs1 0.7373348431513707
ROC integral for hs2 0.7315016389230002
ROC integral for hs3 0.7254055845906716
Author
Lorenzo Moneta

Definition in file TMVA_SOFIE_Models.py.