diff --git a/tmva/pymva/inc/TMVA/RModelParser_Keras.h b/tmva/pymva/inc/TMVA/RModelParser_Keras.h index 74c71570057f7..d60eb2041e650 100644 --- a/tmva/pymva/inc/TMVA/RModelParser_Keras.h +++ b/tmva/pymva/inc/TMVA/RModelParser_Keras.h @@ -45,7 +45,9 @@ namespace PyKeras{ /// Parser function for translatng Keras .h5 model into a RModel object. /// Accepts the file location of a Keras model and returns the /// equivalent RModel object. -RModel Parse(std::string filename); +/// One can specify as option a batch size that can be used when the input Keras model +/// has not a defined input batch size : e.g. for input = (input_dim,) +RModel Parse(std::string filename, int batch_size = -1); }//PyKeras }//SOFIE diff --git a/tmva/pymva/src/RModelParser_Keras.cxx b/tmva/pymva/src/RModelParser_Keras.cxx index 1938e29f1dd17..b164eb9c5ea63 100644 --- a/tmva/pymva/src/RModelParser_Keras.cxx +++ b/tmva/pymva/src/RModelParser_Keras.cxx @@ -791,12 +791,15 @@ std::unique_ptr MakeKerasIdentity(PyObject* fLayer) /// For adding the Output Tensor infos, only the names of the model's output /// tensors are extracted and are then passed into `AddOutputTensorNameList()`. /// +/// Provide optionally a batch size that can be used to overwrite the one given by the +/// model. If a batch size is not given 1 is used if the model does not provide a batch size +/// /// Example Usage: /// ~~~ {.cpp} /// using TMVA::Experimental::SOFIE; /// RModel model = PyKeras::Parse("trained_model_dense.h5"); /// ~~~ -RModel Parse(std::string filename){ +RModel Parse(std::string filename, int batch_size){ char sep = '/'; #ifdef _WIN32 @@ -966,8 +969,11 @@ RModel Parse(std::string filename){ // Getting the shape vector from the Tuple object std::vectorfInputShape = GetDataFromTuple(fPInputShapes); if (static_cast(fInputShape[0]) <= 0){ - fInputShape[0] = 1; - std::cout << "Model has not a defined batch size, assume is 1 - input shape : " + fInputShape[0] = std::max(batch_size,1); + std::cout << "Model has not a defined batch size "; + if (batch_size <=0) std::cout << " assume is 1 "; + else std::cout << " use given value of " << batch_size; + std::cout << " - input shape for tensor " << fInputName << " : " << TMVA::Experimental::SOFIE::ConvertShapeToString(fInputShape) << std::endl; } rmodel.AddInputTensorInfo(fInputName, ETensorType::FLOAT, fInputShape); @@ -995,8 +1001,11 @@ RModel Parse(std::string filename){ std::vectorfInputShape = GetDataFromTuple(fInputShapeTuple); if (static_cast(fInputShape[0]) <= 0){ - fInputShape[0] = 1; - std::cout << "Model has not a defined batch size, assume is 1 - input shape for tensor " + fInputShape[0] = std::max(batch_size,1); + std::cout << "Model has not a defined batch size "; + if (batch_size <=0) std::cout << " assume is 1 "; + else std::cout << " use given value of " << batch_size; + std::cout << " - input shape for tensor " << fInputName << " : " << TMVA::Experimental::SOFIE::ConvertShapeToString(fInputShape) << std::endl; } rmodel.AddInputTensorInfo(fInputName, ETensorType::FLOAT, fInputShape); diff --git a/tmva/pymva/test/TestRModelParserKeras.C b/tmva/pymva/test/TestRModelParserKeras.C index f2d0542933bd3..1ed66169488e3 100644 --- a/tmva/pymva/test/TestRModelParserKeras.C +++ b/tmva/pymva/test/TestRModelParserKeras.C @@ -24,6 +24,7 @@ void GenerateModels() { TEST(RModelParser_Keras, SEQUENTIAL) { constexpr float TOLERANCE = DEFAULT_TOLERANCE; + // input is 8 x batch size that is fixed to be 4 std::vector inputSequential = { 0.12107884, 0.89718615, 0.89123899, 0.32197549, 0.17891638, 0.83555135, 0.98680066, 0.14496809, 0.07255503, 0.55386989, 0.6628149 , 0.29843291, @@ -38,7 +39,7 @@ TEST(RModelParser_Keras, SEQUENTIAL) if (gSystem->AccessPathName("KerasModelSequential.h5",kFileExists)) GenerateModels(); - TMVA::Experimental:: RSofieReader r("KerasModelSequential.h5"); + TMVA::Experimental:: RSofieReader r("KerasModelSequential.h5",{{4,8}}); std::vector outputSequential = r.Compute(inputSequential); diff --git a/tmva/sofie/inc/TMVA/RFunction.hxx b/tmva/sofie/inc/TMVA/RFunction.hxx index bd078ee119b4f..1cca39aa7ff3e 100644 --- a/tmva/sofie/inc/TMVA/RFunction.hxx +++ b/tmva/sofie/inc/TMVA/RFunction.hxx @@ -52,8 +52,7 @@ public: std::shared_ptr GetFunctionBlock() { return function_block; } - - std::string GenerateModel(const std::string& filename, long read_pos=0, long block_size=1); + std::string GenerateModel(const std::string& filename, long read_pos = 0, long block_size = -1); std::string Generate(const std::vector& inputPtrs); FunctionTarget GetFunctionTarget() { return fTarget; diff --git a/tmva/sofie/src/RModel.cxx b/tmva/sofie/src/RModel.cxx index fbb1975224f4d..592e9a474690e 100644 --- a/tmva/sofie/src/RModel.cxx +++ b/tmva/sofie/src/RModel.cxx @@ -262,24 +262,37 @@ void RModel::Initialize(int batchSize, bool verbose) { // loop on inputs and see if shape can be full specified // if the batch size is provided it can be used to specify the full shape // Add the full specified tensors in fReadyInputTensors collection - for (auto &input : fInputTensorInfos) { + auto originalInputTensorInfos = fInputTensorInfos; // need to copy because we may delete elements + for (auto &input : originalInputTensorInfos) { + if (verbose) std::cout << "looking at the tensor " << input.first << std::endl; // if a batch size is provided convert batch size // assume is parametrized as "bs" or "batch_size" if (batchSize > 0) { // std::vector shape; // shape.reserve(input.second.shape.size()); - for (auto &d : input.second.shape) { - if (d.isParam && (d.param == "bs" || d.param == "batch_size")) { - d = Dim{static_cast(batchSize)}; + // assume first parameter is teh batch size + if (!input.second.shape.empty()) { + auto & d0 = input.second.shape[0]; + if (d0.isParam) { + if (verbose) std::cout << "Fix the batch size to " << batchSize << std::endl; + d0 = Dim{static_cast(batchSize)}; + } + else { // look for cases that a bs or bath_size is specified in tensor shape + for (auto &d : input.second.shape) { + if (d.isParam && (d.param == "bs" || d.param == "batch_size")) { + d = Dim{static_cast(batchSize)}; + if (verbose) std::cout << "Input shape has bs or batch_size as names. Fix the batch size to " << batchSize << std::endl; + } + } } } } auto shape = ConvertShapeToInt(input.second.shape); if (!shape.empty()) { - // add to the ready input tensor informations - AddInputTensorInfo(input.first, input.second.type, shape); - // remove from the tensor info + // remove from the tensor info old dynamic shape fInputTensorInfos.erase(input.first); + // add to the ready input tensor information the new fixed shape + AddInputTensorInfo(input.first, input.second.type, shape); } // store the parameters of the input tensors else { @@ -633,7 +646,7 @@ void RModel::ReadInitializedTensorsFromFile(long pos) { fGC += " std::ifstream f;\n"; fGC += " f.open(filename);\n"; fGC += " if (!f.is_open()) {\n"; - fGC += " throw std::runtime_error(\"tmva-sofie failed to open file for input weights\");\n"; + fGC += " throw std::runtime_error(\"tmva-sofie failed to open file \" + filename + \" for input weights\");\n"; fGC += " }\n"; if(fIsGNNComponent) { @@ -769,7 +782,7 @@ long RModel::WriteInitializedTensorsToFile(std::string filename) { } if (!f.is_open()) throw - std::runtime_error("tmva-sofie failed to open file for tensor weight data"); + std::runtime_error("tmva-sofie failed to open file " + filename + " for tensor weight data"); for (auto& i: fInitializedTensors) { if (i.second.type() == ETensorType::FLOAT) { size_t length = 1; diff --git a/tmva/tmva/inc/TMVA/RSofieReader.hxx b/tmva/tmva/inc/TMVA/RSofieReader.hxx index b4be9c4c9a87a..312d6fc024d94 100644 --- a/tmva/tmva/inc/TMVA/RSofieReader.hxx +++ b/tmva/tmva/inc/TMVA/RSofieReader.hxx @@ -113,7 +113,12 @@ public: if (gSystem->Load("libPyMVA") < 0) { throw std::runtime_error("RSofieReader: cannot use SOFIE with Keras since libPyMVA is missing"); } - parserCode += "{\nTMVA::Experimental::SOFIE::RModel model = TMVA::Experimental::SOFIE::PyKeras::Parse(\"" + path + "\"); \n"; + // assume batch size is first entry in first input ! + std::string batch_size = "-1"; + if (!inputShapes.empty() && ! inputShapes[0].empty()) + batch_size = std::to_string(inputShapes[0][0]); + parserCode += "{\nTMVA::Experimental::SOFIE::RModel model = TMVA::Experimental::SOFIE::PyKeras::Parse(\"" + path + + "\"," + batch_size + "); \n"; } else if (type == kPt) { // use PyTorch direct parser