Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
TMVA_SOFIE_Models.py
Go to the documentation of this file.
1### \file
2### \ingroup tutorial_ml
3### \notebook -nodraw
4### Example of inference with SOFIE using a set of models trained with PyTorch
5### and exported to ONNX.
6### This tutorial shows how to store several models in a single header file and
7### the weights in a ROOT binary file.
8### The models are then evaluated using the RDataFrame
9###
10### \macro_code
11### \macro_output
12### \author Lorenzo Moneta
13
14import inspect
15import os
16
17import numpy as np
18import ROOT
19import torch
20import torch.nn as nn
21
22## generate and train PyTorch models with different architectures
23
24
25def CreateModel(nlayers=4, nunits=64):
26 layers = []
27 ninputs = 7
28 for i in range(nlayers):
29 layers += [nn.Linear(ninputs, nunits), nn.ReLU()]
30 ninputs = nunits
31 layers += [nn.Linear(ninputs, 1), nn.Sigmoid()]
32 model = nn.Sequential(*layers)
33 print(model)
34 return model
35
36
37def TrainModel(model, x, y, epochs=5, batch_size=50):
38 x = torch.from_numpy(x)
39 y = torch.from_numpy(y)
40 criterion = nn.BCELoss()
42 nbatches = x.shape[0] // batch_size
43 for epoch in range(epochs):
44 perm = torch.randperm(x.shape[0])
45 running_loss = 0.0
46 for i in range(nbatches):
47 idx = perm[i * batch_size : (i + 1) * batch_size]
49 loss = criterion(model(x[idx]), y[idx])
52 running_loss += loss.item()
53 print(f"Epoch {epoch + 1}/{epochs} - average loss: {running_loss / nbatches:.4f}")
54
55
56def ExportModel(model, modelName):
57 # need to evaluate the model before exporting to ONNX
58 # and to provide a dummy input tensor to set the input model shape
59 # (the batch size is fixed to 1 for the SOFIE inference)
61
62 modelFile = modelName + ".onnx"
63 dummy_x = torch.randn(1, 7)
64 model(dummy_x)
65
66 # check for torch.onnx.export parameters
67 def filtered_kwargs(func, **candidate_kwargs):
68 sig = inspect.signature(func)
69 return {k: v for k, v in candidate_kwargs.items() if k in sig.parameters}
70
71 kwargs = filtered_kwargs(
73 input_names=["input"],
74 output_names=["output"],
75 external_data=False, # may not exist
76 dynamo=True, # may not exist
77 )
78 print("calling torch.onnx.export with parameters", kwargs)
79
80 torch.onnx.export(model, dummy_x, modelFile, **kwargs)
81
82 print("model exported to ONNX as", modelFile)
83 return modelFile
84
85
86def PrepareData():
87 # get the input data
88 inputFile = str(ROOT.gROOT.GetTutorialDir()) + "/machine_learning/data/Higgs_data.root"
89
90 df1 = ROOT.RDataFrame("sig_tree", inputFile)
91 sigData = df1.AsNumpy(columns=["m_jj", "m_jjj", "m_lv", "m_jlv", "m_bb", "m_wbb", "m_wwbb"])
92 # print(sigData)
93
94 # stack all the 7 numpy array in a single array (nevents x nvars)
95 xsig = np.column_stack(list(sigData.values()))
96 data_sig_size = xsig.shape[0]
97 print("size of data", data_sig_size)
98
99 # make SOFIE inference on background data
100 df2 = ROOT.RDataFrame("bkg_tree", inputFile)
101 bkgData = df2.AsNumpy(columns=["m_jj", "m_jjj", "m_lv", "m_jlv", "m_bb", "m_wbb", "m_wwbb"])
102 xbkg = np.column_stack(list(bkgData.values()))
103 data_bkg_size = xbkg.shape[0]
104
105 ysig = np.ones(data_sig_size)
106 ybkg = np.zeros(data_bkg_size)
107 inputs_data = np.concatenate((xsig, xbkg), axis=0).astype(np.float32)
108 inputs_targets = np.concatenate((ysig, ybkg), axis=0).astype(np.float32)
109
110 # split data in training and test data
111 rng = np.random.default_rng(1234)
113 ntrain = inputs_data.shape[0] // 2
114
115 x_train = inputs_data[idx[:ntrain]]
116 y_train = inputs_targets[idx[:ntrain]].reshape(-1, 1)
117 x_test = inputs_data[idx[ntrain:]]
118 y_test = inputs_targets[idx[ntrain:]].reshape(-1, 1)
119
120 return x_train, y_train, x_test, y_test
121
122
123def TrainModels(x_train, y_train, modelNames):
124 # train the models with PyTorch and export them to ONNX
125 modelFiles = []
126 for modelName in modelNames:
127 model = CreateModel(4, 64)
128 TrainModel(model, x_train, y_train)
129 modelFiles.append(ExportModel(model, modelName))
130 return modelFiles
131
132
133### run the models
134
135x_train, y_train, x_test, y_test = PrepareData()
136
137## create models and train them
138
139# All three models use the same small architecture (4 layers of 64 units) to
140# keep the tutorial runtime under control, whatever their names suggest.
141modelNames = ["Higgs_Model_4L_50", "Higgs_Model_4L_200", "Higgs_Model_2L_500"]
142model1, model2, model3 = TrainModels(x_train, y_train, modelNames)
143
144# evaluate with SOFIE the 3 trained models
145
146
147def GenerateModelCode(modelFile, generatedHeaderFile):
149 model = parser.Parse(modelFile)
150
151 print("Generating inference code for the ONNX model from ", modelFile, "in the header ", generatedHeaderFile)
152 # Generating inference code using a ROOT binary file
154 # add option to append to the same file the generated headers (pass True for append flag)
155 model.OutputGenerated(generatedHeaderFile, True)
156 # model.PrintGenerated()
157 return generatedHeaderFile
158
159
160generatedHeaderFile = "Higgs_Model.hxx"
161# need to remove existing header file since we are appending on same one
162if os.path.exists(generatedHeaderFile):
163 print("removing existing file", generatedHeaderFile)
164 os.remove(generatedHeaderFile)
165
166weightFile = "Higgs_Model.root"
167if os.path.exists(weightFile):
168 print("removing existing file", weightFile)
169 os.remove(weightFile)
170
171GenerateModelCode(model1, generatedHeaderFile)
172GenerateModelCode(model2, generatedHeaderFile)
173GenerateModelCode(model3, generatedHeaderFile)
174
175# compile the generated code
176
177ROOT.gInterpreter.Declare('#include "' + generatedHeaderFile + '"')
178
179
180# run the inference on the test data
181session1 = ROOT.TMVA_SOFIE_Higgs_Model_4L_50.Session("Higgs_Model.root")
182session2 = ROOT.TMVA_SOFIE_Higgs_Model_4L_200.Session("Higgs_Model.root")
183session3 = ROOT.TMVA_SOFIE_Higgs_Model_2L_500.Session("Higgs_Model.root")
184
185hs1 = ROOT.TH1D("hs1", "Signal result 4L 50", 100, 0, 1)
186hs2 = ROOT.TH1D("hs2", "Signal result 4L 200", 100, 0, 1)
187hs3 = ROOT.TH1D("hs3", "Signal result 2L 500", 100, 0, 1)
188
189hb1 = ROOT.TH1D("hb1", "Background result 4L 50", 100, 0, 1)
190hb2 = ROOT.TH1D("hb2", "Background result 4L 200", 100, 0, 1)
191hb3 = ROOT.TH1D("hb3", "Background result 2L 500", 100, 0, 1)
192
193
194def EvalModel(session, x):
195 result = session.infer(x)
196 return result[0]
197
198
199for i in range(0, x_test.shape[0]):
200 result1 = EvalModel(session1, x_test[i, :])
201 result2 = EvalModel(session2, x_test[i, :])
202 result3 = EvalModel(session3, x_test[i, :])
203 if y_test[i] == 1:
204 hs1.Fill(result1)
205 hs2.Fill(result2)
206 hs3.Fill(result3)
207 else:
208 hb1.Fill(result1)
209 hb2.Fill(result2)
210 hb3.Fill(result3)
211
212
213def PlotHistos(hs, hb):
214 hs.SetLineColor("kRed")
215 hb.SetLineColor("kBlue")
216 hs.Draw()
217 hb.Draw("same")
218
219
220c1 = ROOT.TCanvas()
221c1.Divide(1, 3)
222c1.cd(1)
223PlotHistos(hs1, hb1)
224c1.cd(2)
225PlotHistos(hs2, hb2)
226c1.cd(3)
227PlotHistos(hs3, hb3)
228c1.Draw()
229
230## draw also ROC curves
231
232
233def GetContent(h):
234 n = h.GetNbinsX()
235 x = ROOT.std.vector["float"](n)
236 w = ROOT.std.vector["float"](n)
237 for i in range(0, n):
238 x[i] = h.GetBinCenter(i + 1)
239 w[i] = h.GetBinContent(i + 1)
240 return x, w
241
242
243def MakeROCCurve(hs, hb):
244 xs, ws = GetContent(hs)
245 xb, wb = GetContent(hb)
246 roc = ROOT.TMVA.ROCCurve(xs, xb, ws, wb)
247 print("ROC integral for ", hs.GetName(), roc.GetROCIntegral())
248 curve = roc.GetROCCurve()
250 return roc, curve
251
252
253c2 = ROOT.TCanvas()
254
255r1, curve1 = MakeROCCurve(hs1, hb1)
257curve1.Draw("AC")
258
259r2, curve2 = MakeROCCurve(hs2, hb2)
260curve2.SetLineColor("kBlue")
261curve2.Draw("C")
262
263r3, curve3 = MakeROCCurve(hs3, hb3)
264curve3.SetLineColor("kGreen")
265curve3.Draw("C")
266
267c2.Draw()
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 ,...