Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
TMVA_SOFIE_PyTorch_HiggsModel.py
Go to the documentation of this file.
1### \file
2### \ingroup tutorial_ml
3### \notebook -nodraw
4### This macro trains a simple deep neural network on the Higgs dataset with
5### PyTorch, exports the model to ONNX and runs the SOFIE parser on it to
6### generate and compile C++ inference code.
7###
8### The trained model is saved as HiggsModel.onnx and is used as input by
9### other SOFIE tutorials (e.g. TMVA_SOFIE_RDataFrame.C), so this macro needs
10### to be run before them.
11###
12### \macro_code
13### \macro_output
14
15import inspect
16
17import numpy as np
18import ROOT
19import torch
20import torch.nn as nn
21
22
24 # get the input data
25 inputFile = str(ROOT.gROOT.GetTutorialDir()) + "/machine_learning/data/Higgs_data.root"
26
27 df1 = ROOT.RDataFrame("sig_tree", inputFile)
28 sigData = df1.AsNumpy(columns=["m_jj", "m_jjj", "m_lv", "m_jlv", "m_bb", "m_wbb", "m_wwbb"])
29 # print(sigData)
30
31 # stack all the 7 numpy array in a single array (nevents x nvars)
32 xsig = np.column_stack(list(sigData.values()))
33 data_sig_size = xsig.shape[0]
34 print("size of data", data_sig_size)
35
36 # make SOFIE inference on background data
37 df2 = ROOT.RDataFrame("bkg_tree", inputFile)
38 bkgData = df2.AsNumpy(columns=["m_jj", "m_jjj", "m_lv", "m_jlv", "m_bb", "m_wbb", "m_wwbb"])
39 xbkg = np.column_stack(list(bkgData.values()))
40 data_bkg_size = xbkg.shape[0]
41
42 ysig = np.ones(data_sig_size)
43 ybkg = np.zeros(data_bkg_size)
44 inputs_data = np.concatenate((xsig, xbkg), axis=0).astype(np.float32)
45 inputs_targets = np.concatenate((ysig, ybkg), axis=0).astype(np.float32)
46
47 # split data in training and test data
48 rng = np.random.default_rng(1234)
50 ntrain = inputs_data.shape[0] // 2
51
52 x_train = inputs_data[idx[:ntrain]]
53 y_train = inputs_targets[idx[:ntrain]].reshape(-1, 1)
54 x_test = inputs_data[idx[ntrain:]]
55 y_test = inputs_targets[idx[ntrain:]].reshape(-1, 1)
56
57 return x_train, y_train, x_test, y_test
58
59
61 layers = []
62 ninputs = 7
63 for i in range(1, nlayers):
64 layers += [nn.Linear(ninputs, nunits), nn.ReLU()]
65 ninputs = nunits
66 layers += [nn.Linear(ninputs, 1), nn.Sigmoid()]
67 model = nn.Sequential(*layers)
68 print(model)
69 return model
70
71
73 x = torch.from_numpy(x)
74 y = torch.from_numpy(y)
75 criterion = nn.BCELoss()
77 nbatches = x.shape[0] // batch_size
78 for epoch in range(epochs):
79 perm = torch.randperm(x.shape[0])
80 running_loss = 0.0
81 for i in range(nbatches):
82 idx = perm[i * batch_size : (i + 1) * batch_size]
84 loss = criterion(model(x[idx]), y[idx])
87 running_loss += loss.item()
88 print(f"Epoch {epoch + 1}/{epochs} - average loss: {running_loss / nbatches:.4f}")
89
90
92 # need to evaluate the model before exporting to ONNX
93 # and to provide a dummy input tensor to set the input model shape
94 # (the batch size is fixed to 1 for the SOFIE inference)
96
97 modelFile = modelName + ".onnx"
98 dummy_x = torch.randn(1, 7)
99 model(dummy_x)
100
101 # check for torch.onnx.export parameters
102 def filtered_kwargs(func, **candidate_kwargs):
103 sig = inspect.signature(func)
104 return {k: v for k, v in candidate_kwargs.items() if k in sig.parameters}
105
106 kwargs = filtered_kwargs(
108 input_names=["input"],
109 output_names=["output"],
110 external_data=False, # may not exist
111 dynamo=True, # may not exist
112 )
113 print("calling torch.onnx.export with parameters", kwargs)
114
115 torch.onnx.export(model, dummy_x, modelFile, **kwargs)
116
117 print("model exported to ONNX as", modelFile)
118 return modelFile
119
120
122
123 # parse the input ONNX model into an RModel object
125 model = parser.Parse(modelFile)
126
127 # Generating inference code
130
131 modelName = modelFile.replace(".onnx", "")
132 return modelName
133
134
135###################################################################
136## Step 1 : Create and train the model, export it to ONNX
137###################################################################
138
139x_train, y_train, x_test, y_test = PrepareData()
140# validate the exported model on the first test events
141x_check = x_test[:10]
142
143# create dense model with 3 layers of 64 units and train it
144model = CreateModel(3, 64)
145TrainModel(model, x_train, y_train)
147
148# evaluate the trained model on the validation inputs, for comparison with SOFIE
149with torch.no_grad():
151
152###################################################################
153## Step 2 : Parse model and generate inference code with SOFIE
154###################################################################
155
156modelName = GenerateCode(modelFile)
157modelHeaderFile = modelName + ".hxx"
158
159###################################################################
160## Step 3 : Compile the generated C++ model code
161###################################################################
162
163ROOT.gInterpreter.Declare('#include "' + modelHeaderFile + '"')
164
165###################################################################
166## Step 4: Evaluate the model
167###################################################################
168
169# get first the SOFIE session namespace
171session = sofie.Session()
172
173for i in range(x_check.shape[0]):
175 print("input to model is ", x_check[i], "\n\t -> output using SOFIE = ", y[0], " using PyTorch = ", ytorch[i, 0])
176 if abs(y[0] - ytorch[i, 0]) > 0.01:
177 raise RuntimeError("ERROR: Result is different between SOFIE and PyTorch")
178
179print("OK")
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 ,...
TrainModel(model, x, y, epochs=5, batch_size=50)