diff --git a/tmva/pymva/test/CMakeLists.txt b/tmva/pymva/test/CMakeLists.txt index fb5899fb0fd4f..52014fdd4e253 100644 --- a/tmva/pymva/test/CMakeLists.txt +++ b/tmva/pymva/test/CMakeLists.txt @@ -55,6 +55,10 @@ endif(ROOT_SKLEARN_FOUND) # Enable tests based on available python modules if(ROOT_TORCH_FOUND) + configure_file(generatePyTorchModelClassification.py generatePyTorchModelClassification.py COPYONLY) + configure_file(generatePyTorchModelMulticlass.py generatePyTorchModelMulticlass.py COPYONLY) + configure_file(generatePyTorchModelRegression.py generatePyTorchModelRegression.py COPYONLY) + # Test PyTorch: Binary classification if (ROOT_SKLEARN_FOUND) diff --git a/tmva/sofie/test/generatePyTorchModelClassification.py b/tmva/pymva/test/generatePyTorchModelClassification.py similarity index 77% rename from tmva/sofie/test/generatePyTorchModelClassification.py rename to tmva/pymva/test/generatePyTorchModelClassification.py index c19cfe1bf3c00..42b6e11b43667 100644 --- a/tmva/sofie/test/generatePyTorchModelClassification.py +++ b/tmva/pymva/test/generatePyTorchModelClassification.py @@ -2,11 +2,7 @@ from torch import nn # Define model -model = nn.Sequential( - nn.Linear(4, 64), - nn.ReLU(), - nn.Linear(64, 2), - nn.Softmax(dim=1)) +model = nn.Sequential(nn.Linear(4, 64), nn.ReLU(), nn.Linear(64, 2), nn.Softmax(dim=1)) # Construct loss function and Optimizer. criterion = torch.nn.MSELoss() @@ -33,8 +29,8 @@ def fit(model, train_loader, val_loader, num_epochs, batch_size, optimizer, crit # print train statistics running_train_loss += train_loss.item() - if i % 32 == 31: # print every 32 mini-batches - print(f"[{epoch+1}, {i+1}] train loss: {running_train_loss / 32 :.3f}") + if i % 32 == 31: # print every 32 mini-batches + print(f"[{epoch + 1}, {i + 1}] train loss: {running_train_loss / 32:.3f}") running_train_loss = 0.0 if schedule: @@ -51,15 +47,15 @@ def fit(model, train_loader, val_loader, num_epochs, batch_size, optimizer, crit curr_val = running_val_loss / len(val_loader) if save_best: - if best_val==None: - best_val = curr_val - best_val = save_best(model, curr_val, best_val) + if best_val is None: + best_val = curr_val + best_val = save_best(model, curr_val, best_val) # print val statistics per epoch - print(f"[{epoch+1}] val loss: {curr_val :.3f}") + print(f"[{epoch + 1}] val loss: {curr_val:.3f}") running_val_loss = 0.0 - print(f"Finished Training on {epoch+1} Epochs!") + print(f"Finished Training on {epoch + 1} Epochs!") return model @@ -67,7 +63,7 @@ def fit(model, train_loader, val_loader, num_epochs, batch_size, optimizer, crit def predict(model, test_X, batch_size=32): # Set to eval mode model.eval() - + test_dataset = torch.utils.data.TensorDataset(torch.Tensor(test_X)) test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=batch_size, shuffle=False) @@ -78,7 +74,7 @@ def predict(model, test_X, batch_size=32): outputs = model(X) predictions.append(outputs) preds = torch.cat(predictions) - + return preds.numpy() @@ -86,5 +82,4 @@ def predict(model, test_X, batch_size=32): # Store model to file m = torch.jit.script(model) -torch.jit.save(m,"PyTorchModelClassification.pt") - +torch.jit.save(m, "PyTorchModelClassification.pt") diff --git a/tmva/sofie/test/generatePyTorchModelMulticlass.py b/tmva/pymva/test/generatePyTorchModelMulticlass.py similarity index 78% rename from tmva/sofie/test/generatePyTorchModelMulticlass.py rename to tmva/pymva/test/generatePyTorchModelMulticlass.py index 88b787f48c132..a6fee1f51df4f 100644 --- a/tmva/sofie/test/generatePyTorchModelMulticlass.py +++ b/tmva/pymva/test/generatePyTorchModelMulticlass.py @@ -2,11 +2,7 @@ from torch import nn # Define model -model = nn.Sequential( - nn.Linear(4, 64), - nn.ReLU(), - nn.Linear(64, 4), - nn.Softmax(dim=1)) +model = nn.Sequential(nn.Linear(4, 64), nn.ReLU(), nn.Linear(64, 4), nn.Softmax(dim=1)) # Construct loss function and Optimizer. criterion = nn.CrossEntropyLoss() @@ -34,8 +30,8 @@ def fit(model, train_loader, val_loader, num_epochs, batch_size, optimizer, crit # print train statistics running_train_loss += train_loss.item() - if i % 4 == 3: # print every 4 mini-batches - print(f"[{epoch+1}, {i+1}] train loss: {running_train_loss / 4 :.3f}") + if i % 4 == 3: # print every 4 mini-batches + print(f"[{epoch + 1}, {i + 1}] train loss: {running_train_loss / 4:.3f}") running_train_loss = 0.0 if schedule: @@ -53,15 +49,15 @@ def fit(model, train_loader, val_loader, num_epochs, batch_size, optimizer, crit curr_val = running_val_loss / len(val_loader) if save_best: - if best_val==None: - best_val = curr_val - best_val = save_best(model, curr_val, best_val) + if best_val is None: + best_val = curr_val + best_val = save_best(model, curr_val, best_val) # print val statistics per epoch - print(f"[{epoch+1}] val loss: {curr_val :.3f}") + print(f"[{epoch + 1}] val loss: {curr_val:.3f}") running_val_loss = 0.0 - print(f"Finished Training on {epoch+1} Epochs!") + print(f"Finished Training on {epoch + 1} Epochs!") return model @@ -69,7 +65,7 @@ def fit(model, train_loader, val_loader, num_epochs, batch_size, optimizer, crit def predict(model, test_X, batch_size=32): # Set to eval mode model.eval() - + test_dataset = torch.utils.data.TensorDataset(torch.Tensor(test_X)) test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=batch_size, shuffle=False) @@ -80,7 +76,7 @@ def predict(model, test_X, batch_size=32): outputs = model(X) predictions.append(outputs) preds = torch.cat(predictions) - + return preds.numpy() @@ -88,5 +84,4 @@ def predict(model, test_X, batch_size=32): # Store model to file m = torch.jit.script(model) -torch.jit.save(m,"PyTorchModelMulticlass.pt") - +torch.jit.save(m, "PyTorchModelMulticlass.pt") diff --git a/tmva/sofie/test/generatePyTorchModelRegression.py b/tmva/pymva/test/generatePyTorchModelRegression.py similarity index 78% rename from tmva/sofie/test/generatePyTorchModelRegression.py rename to tmva/pymva/test/generatePyTorchModelRegression.py index 2622d43fc20b0..a7f529914833d 100644 --- a/tmva/sofie/test/generatePyTorchModelRegression.py +++ b/tmva/pymva/test/generatePyTorchModelRegression.py @@ -2,10 +2,7 @@ from torch import nn # Define model -model = nn.Sequential( - nn.Linear(2, 64), - nn.Tanh(), - nn.Linear(64, 1)) +model = nn.Sequential(nn.Linear(2, 64), nn.Tanh(), nn.Linear(64, 1)) # Construct loss function and Optimizer. criterion = torch.nn.MSELoss() @@ -32,8 +29,8 @@ def fit(model, train_loader, val_loader, num_epochs, batch_size, optimizer, crit # print train statistics running_train_loss += train_loss.item() - if i % 32 == 31: # print every 32 mini-batches - print(f"[{epoch+1}, {i+1}] train loss: {running_train_loss / 32 :.3f}") + if i % 32 == 31: # print every 32 mini-batches + print(f"[{epoch + 1}, {i + 1}] train loss: {running_train_loss / 32:.3f}") running_train_loss = 0.0 if schedule: @@ -50,15 +47,15 @@ def fit(model, train_loader, val_loader, num_epochs, batch_size, optimizer, crit curr_val = running_val_loss / len(val_loader) if save_best: - if best_val==None: - best_val = curr_val - best_val = save_best(model, curr_val, best_val) + if best_val is None: + best_val = curr_val + best_val = save_best(model, curr_val, best_val) # print val statistics per epoch - print(f"[{epoch+1}] val loss: {curr_val :.3f}") + print(f"[{epoch + 1}] val loss: {curr_val:.3f}") running_val_loss = 0.0 - print(f"Finished Training on {epoch+1} Epochs!") + print(f"Finished Training on {epoch + 1} Epochs!") return model @@ -66,7 +63,7 @@ def fit(model, train_loader, val_loader, num_epochs, batch_size, optimizer, crit def predict(model, test_X, batch_size=32): # Set to eval mode model.eval() - + test_dataset = torch.utils.data.TensorDataset(torch.Tensor(test_X)) test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=batch_size, shuffle=False) @@ -77,7 +74,7 @@ def predict(model, test_X, batch_size=32): outputs = model(X) predictions.append(outputs) preds = torch.cat(predictions) - + return preds.numpy() @@ -85,4 +82,4 @@ def predict(model, test_X, batch_size=32): # Store model to file m = torch.jit.script(model) -torch.jit.save(m,"PyTorchModelRegression.pt") \ No newline at end of file +torch.jit.save(m, "PyTorchModelRegression.pt") diff --git a/tmva/sofie/test/CMakeLists.txt b/tmva/sofie/test/CMakeLists.txt index fc72127c14d84..2b4b558d3c1e6 100644 --- a/tmva/sofie/test/CMakeLists.txt +++ b/tmva/sofie/test/CMakeLists.txt @@ -145,9 +145,6 @@ endif() # Any features that link against libpython are disabled if built with tpython=OFF if (tpython AND ROOT_TORCH_FOUND AND ROOT_ONNX_FOUND AND BLAS_FOUND AND NOT broken_onnx) - configure_file(generatePyTorchModelClassification.py generatePyTorchModelClassification.py COPYONLY) - configure_file(generatePyTorchModelMulticlass.py generatePyTorchModelMulticlass.py COPYONLY) - configure_file(generatePyTorchModelRegression.py generatePyTorchModelRegression.py COPYONLY) configure_file(generatePyTorchModels.py generatePyTorchModels.py COPYONLY) # Test RModelParser_PyTorch diff --git a/tutorials/CMakeLists.txt b/tutorials/CMakeLists.txt index 4320a5104be84..9d0b23f214f7c 100644 --- a/tutorials/CMakeLists.txt +++ b/tutorials/CMakeLists.txt @@ -345,25 +345,43 @@ else() ROOT_FIND_PYTHON_MODULE(keras) ROOT_FIND_PYTHON_MODULE(sonnet) ROOT_FIND_PYTHON_MODULE(graph_nets) + + # Check if we support the installed Keras version. Otherwise, veto SOFIE + # Keras tutorials. This mirrors the logic in tmva/sofie/test/CMakeLists.txt. + # TODO: make sure we also support the newest Keras + set(unsupported_keras_version "3.5.0") + if (NOT DEFINED ROOT_KERAS_VERSION) + message(WARNING "Keras found, but version unknown — cannot verify compatibility.") + elseif (NOT ROOT_KERAS_VERSION VERSION_LESS ${unsupported_keras_version}) + message(WARNING "Keras version ${ROOT_KERAS_VERSION} is too new for the SOFIE Keras parser (only supports < ${unsupported_keras_version}). Corresponding tutorials will not be tested.") + set(keras_unsupported TRUE) + endif() + if (NOT BLAS_FOUND) list(APPEND tmva_veto machine_learning/TMVA_SOFIE_GNN_Application.C) list(APPEND tmva_veto machine_learning/TMVA_SOFIE_RDataFrame.C) list(APPEND tmva_veto machine_learning/TMVA_SOFIE_RSofieReader.C) endif() - if (NOT tmva-pymva OR NOT ROOT_KERAS_FOUND) + if (NOT tmva-sofie OR NOT ROOT_KERAS_FOUND OR keras_unsupported) list(APPEND tmva_veto machine_learning/TMVA_SOFIE_Keras.C) + list(APPEND tmva_veto machine_learning/TMVA_SOFIE_Models.py) list(APPEND tmva_veto machine_learning/TMVA_SOFIE_Keras_HiggsModel.C) list(APPEND tmva_veto machine_learning/TMVA_SOFIE_RDataFrame.C) list(APPEND tmva_veto machine_learning/TMVA_SOFIE_RDataFrame_JIT.C) list(APPEND tmva_veto machine_learning/TMVA_SOFIE_RSofieReader.C) endif() - if (NOT tmva-pymva OR NOT ROOT_TORCH_FOUND) + if (NOT tmva-pymva) + # These SOFIE tutorials take models trained via PyMVA-PyKeras as input + list(APPEND tmva_veto machine_learning/TMVA_SOFIE_Keras_HiggsModel.C) + list(APPEND tmva_veto machine_learning/TMVA_SOFIE_RDataFrame.C) + list(APPEND tmva_veto machine_learning/TMVA_SOFIE_RDataFrame.py) + list(APPEND tmva_veto machine_learning/TMVA_SOFIE_RSofieReader.C) + endif() + if (NOT tmva-sofie OR NOT ROOT_TORCH_FOUND) list(APPEND tmva_veto machine_learning/TMVA_SOFIE_PyTorch.C) endif() - # The following tutorials use PyMVA functionality - if (NOT tmva-pymva) + if (NOT tmva-sofie) list(APPEND tmva_veto machine_learning/TMVA_SOFIE_RDataFrame.py) - list(APPEND tmva_veto machine_learning/TMVA_SOFIE_Models.py) list(APPEND tmva_veto machine_learning/TMVA_SOFIE_Inference.py) endif() #veto this tutorial since it is added directly diff --git a/tutorials/machine_learning/TMVA_SOFIE_Inference.py b/tutorials/machine_learning/TMVA_SOFIE_Inference.py index ebcdf8199c312..1b150bdbaa4ca 100644 --- a/tutorials/machine_learning/TMVA_SOFIE_Inference.py +++ b/tutorials/machine_learning/TMVA_SOFIE_Inference.py @@ -14,12 +14,8 @@ ### \macro_output ### \author Lorenzo Moneta -import ROOT import numpy as np - - -ROOT.TMVA.PyMethodBase.PyInitialize() - +import ROOT # check if the input file exists modelFile = "Higgs_trained_model.h5" diff --git a/tutorials/machine_learning/TMVA_SOFIE_Keras.C b/tutorials/machine_learning/TMVA_SOFIE_Keras.C index 651e2ed35c50d..b8a4bf5366c71 100644 --- a/tutorials/machine_learning/TMVA_SOFIE_Keras.C +++ b/tutorials/machine_learning/TMVA_SOFIE_Keras.C @@ -39,16 +39,15 @@ model.save('KerasModel.h5')\n"; void TMVA_SOFIE_Keras(const char * modelFile = nullptr, bool printModelInfo = true){ - //Running the Python script to generate Keras .h5 file - TMVA::PyMethodBase::PyInitialize(); + // Running the Python script to generate Keras .h5 file - if (modelFile == nullptr) { - TMacro m; - m.AddLine(pythonSrc); - m.SaveSource("make_keras_model.py"); - gSystem->Exec(TMVA::Python_Executable() + " make_keras_model.py"); - modelFile = "KerasModel.h5"; - } + if (modelFile == nullptr) { + TMacro m; + m.AddLine(pythonSrc); + m.SaveSource("make_keras_model.py"); + gSystem->Exec("python3 make_keras_model.py"); + modelFile = "KerasModel.h5"; + } //Parsing the saved Keras .h5 file into RModel object SOFIE::RModel model = SOFIE::PyKeras::Parse(modelFile); diff --git a/tutorials/machine_learning/TMVA_SOFIE_Models.py b/tutorials/machine_learning/TMVA_SOFIE_Models.py index 469e22940c77c..25d1931870bfe 100644 --- a/tutorials/machine_learning/TMVA_SOFIE_Models.py +++ b/tutorials/machine_learning/TMVA_SOFIE_Models.py @@ -13,20 +13,17 @@ ### \macro_output ### \author Lorenzo Moneta -import ROOT -from os.path import exists - -ROOT.TMVA.PyMethodBase.PyInitialize() - - -## generate and train Keras models with different architectures +import os import numpy as np -from tensorflow.keras.models import Sequential +import ROOT +from sklearn.model_selection import train_test_split from tensorflow.keras.layers import Dense +from tensorflow.keras.models import Sequential from tensorflow.keras.optimizers import Adam -from sklearn.model_selection import train_test_split +## generate and train Keras models with different architectures + def CreateModel(nlayers = 4, nunits = 64): model = Sequential() @@ -103,7 +100,6 @@ def GenerateModelCode(modelFile, generatedHeaderFile): generatedHeaderFile = "Higgs_Model.hxx" #need to remove existing header file since we are appending on same one -import os if (os.path.exists(generatedHeaderFile)): weightFile = "Higgs_Model.root" print("removing existing files", generatedHeaderFile,weightFile) diff --git a/tutorials/machine_learning/TMVA_SOFIE_PyTorch.C b/tutorials/machine_learning/TMVA_SOFIE_PyTorch.C index dbf1ca5dd1a34..1eb5ae9d74b80 100644 --- a/tutorials/machine_learning/TMVA_SOFIE_PyTorch.C +++ b/tutorials/machine_learning/TMVA_SOFIE_PyTorch.C @@ -41,45 +41,45 @@ torch.jit.save(m,'PyTorchModel.pt')\n"; void TMVA_SOFIE_PyTorch(){ - //Running the Python script to generate PyTorch .pt file - TMVA::PyMethodBase::PyInitialize(); + // Running the Python script to generate PyTorch .pt file - TMacro m; - m.AddLine(pythonSrc); - m.SaveSource("make_pytorch_model.py"); - gSystem->Exec(TMVA::Python_Executable() + " make_pytorch_model.py"); + TMacro m; + m.AddLine(pythonSrc); + m.SaveSource("make_pytorch_model.py"); + gSystem->Exec("python3 make_pytorch_model.py"); - //Parsing a PyTorch model requires the shape and data-type of input tensor - //Data-type of input tensor defaults to Float if not specified - std::vector inputTensorShapeSequential{2,32}; - std::vector> inputShapesSequential{inputTensorShapeSequential}; + // Parsing a PyTorch model requires the shape and data-type of input tensor + // Data-type of input tensor defaults to Float if not specified + std::vector inputTensorShapeSequential{2, 32}; + std::vector> inputShapesSequential{inputTensorShapeSequential}; - //Parsing the saved PyTorch .pt file into RModel object - SOFIE::RModel model = SOFIE::PyTorch::Parse("PyTorchModel.pt",inputShapesSequential); + // Parsing the saved PyTorch .pt file into RModel object + SOFIE::RModel model = SOFIE::PyTorch::Parse("PyTorchModel.pt", inputShapesSequential); - //Generating inference code - model.Generate(); - model.OutputGenerated("PyTorchModel.hxx"); + // Generating inference code + model.Generate(); + model.OutputGenerated("PyTorchModel.hxx"); - //Printing required input tensors - std::cout<<"\n\n"; - model.PrintRequiredInputTensors(); + // Printing required input tensors + std::cout << "\n\n"; + model.PrintRequiredInputTensors(); - //Printing initialized tensors (weights) - std::cout<<"\n\n"; - model.PrintInitializedTensors(); + // Printing initialized tensors (weights) + std::cout << "\n\n"; + model.PrintInitializedTensors(); - //Printing intermediate tensors - std::cout<<"\n\n"; - model.PrintIntermediateTensors(); + // Printing intermediate tensors + std::cout << "\n\n"; + model.PrintIntermediateTensors(); - //Checking if tensor already exist in model - std::cout<<"\n\nTensor \"0weight\" already exist: "< tensorShape = model.GetTensorShape("0weight"); - std::cout<<"Shape of tensor \"0weight\": "; - for(auto& it:tensorShape){ - std::cout< tensorShape = model.GetTensorShape("0weight"); + std::cout << "Shape of tensor \"0weight\": "; + for (auto &it : tensorShape) { + std::cout << it << ","; + } std::cout<<"\n\nData type of tensor \"0weight\": "; SOFIE::ETensorType tensorType = model.GetTensorType("0weight"); std::cout<AccessPathName(modelFile.c_str())) { Info("TMVA_SOFIE_RDataFrame","You need to run TMVA_Higgs_Classification.C to generate the Keras trained model");