Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
RModelParser_Keras.cxx
Go to the documentation of this file.
1// @(#)root/tmva/pymva $Id$
2// Author: Sanjiban Sengupta 2021
3
4/**********************************************************************************
5 * Project : TMVA - a Root-integrated toolkit for multivariate data analysis *
6 * Package : TMVA *
7 * Function: TMVA::Experimental::SOFIE::PyKeras::Parse *
8 * *
9 * Description: *
10 * Parser function for translating Keras .h5 model to RModel object *
11 * *
12 * Example Usage: *
13 * ~~~ {.cpp} *
14 * using TMVA::Experimental::SOFIE; *
15 * RModel model = PyKeras::Parse("trained_model_dense.h5"); *
16 * ~~~ *
17 * *
18 **********************************************************************************/
19
21
22#include <Python.h>
23
24#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
25#include <numpy/arrayobject.h>
26
27namespace TMVA{
28namespace Experimental{
29namespace SOFIE{
30namespace PyKeras{
31
32// Referencing Python utility functions present in PyMethodBase
35static std::vector<size_t>(& GetDataFromTuple)(PyObject*) = PyMethodBase::GetDataFromTuple;
36
37namespace INTERNAL{
38
39// For adding Keras layer into RModel object
40void AddKerasLayer(RModel &rmodel, PyObject *fLayer);
41
42// Declaring Internal Functions for Keras layers which don't have activation as an additional attribute
43std::unique_ptr<ROperator> MakeKerasActivation(PyObject *fLayer); // For instantiating ROperator for Keras Activation Layer
44std::unique_ptr<ROperator> MakeKerasReLU(PyObject *fLayer); // For instantiating ROperator for Keras ReLU layer
45std::unique_ptr<ROperator> MakeKerasSelu(PyObject *fLayer); // For instantiating ROperator for Keras Selu layer
46std::unique_ptr<ROperator> MakeKerasSigmoid(PyObject *fLayer); // For instantiating ROperator for Keras Sigmoid layer
47std::unique_ptr<ROperator> MakeKerasPermute(PyObject *fLayer); // For instantiating ROperator for Keras Permute Layer
48
49// Declaring Internal function for Keras layers which have additional activation attribute
50std::unique_ptr<ROperator> MakeKerasDense(PyObject *fLayer); // For instantiating ROperator for Keras Dense Layer
51
52// For mapping Keras layer with the preparatory functions for ROperators
53using KerasMethodMap = std::unordered_map<std::string, std::unique_ptr<ROperator> (*)(PyObject *fLayer)>;
54using KerasMethodMapWithActivation = std::unordered_map<std::string, std::unique_ptr<ROperator> (*)(PyObject *fLayer)>;
55
57 {"Activation", &MakeKerasActivation},
58 {"Permute", &MakeKerasPermute},
59
60 // For activation layers
61 {"ReLU", &MakeKerasReLU},
62
63 // For layers with activation attributes
64 {"relu", &MakeKerasReLU},
65 {"selu", &MakeKerasSelu},
66 {"sigmoid", &MakeKerasSigmoid}
67};
68
70 {"Dense", &MakeKerasDense},
71 };
72
73
74//////////////////////////////////////////////////////////////////////////////////
75/// \brief Adds equivalent ROperator with respect to Keras model layer
76/// into the referenced RModel object
77///
78/// \param[in] rmodel RModel object
79/// \param[in] fLayer Python Keras layer as a Dictionary object
80/// \param[out] RModel object with the added ROperator
81///
82/// Function adds equivalent ROperator into the referenced RModel object.
83/// Keras models can have layers like Dense and Conv which have activation
84/// function as an attribute. Function first searches if layer object is among
85/// the ones which don't have activation attribute and then calls the respective
86/// preparation function to get the ROperator object, which is then added
87/// into the RModel object. If passed layer is among the ones which may have activation
88/// attribute, then it checks for the activation attribute, if present then first adds
89/// the primary operator into the RModel object, and then adds the operator for the
90/// activation function with appropriate changes in the names of input and output
91/// tensors for both of them.
92/// Example of such layers is the Dense Layer. For a dense layer with input tensor name
93/// dense2BiasAdd0 and output tensor name dense3Relu0 with relu as activation attribute
94/// will be transformed into a ROperator_Gemm with input tensor name dense2BiasAdd0
95/// & output tensor name dense3Dense (layerName+layerType), and a subsequent
96/// ROperator_Relu with input tensor name as dense3Dense and output tensor name
97/// as dense3Relu0.
98///
99/// For developing new preparatory functions for supporting Keras layers in future,
100/// all one needs is to extract the required properties and attributes from the fLayer
101/// dictionary which contains all the information about any Keras layer and after
102/// any required transformations, these are passed for instantiating the ROperator
103/// object.
104///
105/// The fLayer dictionary which holds all the information about a Keras layer has
106/// following structure:-
107///
108/// dict fLayer { 'layerType' : Type of the Keras layer
109/// 'layerAttributes' : Attributes of the keras layer as returned by layer.get_config()
110/// 'layerInput' : List of names of input tensors
111/// 'layerOutput' : List of names of output tensors
112/// 'layerDType' : Data-type of the Keras layer
113/// 'layerWeight' : List of weight tensor names of Keras layers
114/// }
115void AddKerasLayer(RModel& rmodel, PyObject* fLayer){
116 std::string fLayerType = PyStringAsString(PyDict_GetItemString(fLayer,"layerType"));
117
118 //For layers without additional activation attribute
119 auto findLayer = mapKerasLayer.find(fLayerType);
120 if(findLayer != mapKerasLayer.end()){
121 rmodel.AddOperator((findLayer->second)(fLayer));
122 return;
123 }
124
125 //For layers like Dense & Conv which has additional activation attribute
126 else if(mapKerasLayerWithActivation.find(fLayerType) != mapKerasLayerWithActivation.end()){
127 findLayer = mapKerasLayerWithActivation.find(fLayerType);
128 PyObject* fAttributes=PyDict_GetItemString(fLayer,"layerAttributes");
129
130 std::string fLayerName = PyStringAsString(PyDict_GetItemString(fAttributes,"name"));
131 std::string fLayerActivation = PyStringAsString(PyDict_GetItemString(fAttributes,"activation"));
132
133 if(fLayerActivation == "selu" || fLayerActivation == "sigmoid")
134 rmodel.AddNeededStdLib("cmath");
135
136 //Checking if additional attribute exixts
137 if(fLayerActivation != "linear"){
138 PyObject* fOutputs = PyDict_GetItemString(fLayer,"layerOutput");
139 PyObject* fInputs = PyDict_GetItemString(fLayer,"layerInput");
140 std::string fActivationLayerOutput = PyStringAsString(PyList_GetItem(fOutputs,0));
141
142 // Making changes in the names of the input and output tensor names
143 PyList_SetItem(fOutputs,0,PyUnicode_FromString((fLayerName+fLayerType).c_str()));
144 PyDict_SetItemString(fLayer,"layerOutput",fOutputs);
145 rmodel.AddOperator((findLayer->second)(fLayer));
146
147 std::string fActivationLayerInput = PyStringAsString(PyList_GetItem(fOutputs,0));
148 PyList_SetItem(fInputs,0,PyUnicode_FromString(fActivationLayerInput.c_str()));
149 PyList_SetItem(fOutputs,0,PyUnicode_FromString(fActivationLayerOutput.c_str()));
150 PyDict_SetItemString(fLayer,"layerInput",fInputs);
151 PyDict_SetItemString(fLayer,"layerOutput",fOutputs);
152
153 auto findActivationLayer = mapKerasLayer.find(fLayerActivation);
154 if(findActivationLayer == mapKerasLayer.end()){
155 throw std::runtime_error("TMVA::SOFIE - Parsing Keras Activation layer " + fLayerActivation + " is not yet supported");
156 }
157 rmodel.AddOperator((findActivationLayer->second)(fLayer));
158 }
159 else{
160 rmodel.AddOperator((findLayer->second)(fLayer));
161 }
162 return;
163 }
164
165 else{
166 throw std::runtime_error("TMVA::SOFIE - Parsing Keras layer " + fLayerType + " is not yet supported");
167 }
168
169}
170
171//////////////////////////////////////////////////////////////////////////////////
172/// \brief Prepares a ROperator object for Keras Dense Layer
173///
174/// \param[in] fLayer Python Keras layer as a Dictionary object
175/// \return Unique pointer to ROperator object
176///
177/// For Keras's Dense layer, the names of the input tensor, output tensor, and
178/// weight tensors are extracted, and then are passed to instantiate a
179/// ROperator_Gemm object using the required attributes.
180std::unique_ptr<ROperator> MakeKerasDense(PyObject* fLayer){
181 PyObject* fInputs = PyDict_GetItemString(fLayer,"layerInput");
182 PyObject* fOutputs = PyDict_GetItemString(fLayer,"layerOutput");
183 std::string fLayerDType = PyStringAsString(PyDict_GetItemString(fLayer,"layerDType"));
184
185 std::string fLayerInputName = PyStringAsString(PyList_GetItem(fInputs,0));
186 std::string fLayerOutputName = PyStringAsString(PyList_GetItem(fOutputs,0));
187
188 // Extracting names of weight tensors
189 // The names of Kernel weights and bias weights are found in the list
190 // of weight tensors from fLayer.
191 PyObject* fWeightNames = PyDict_GetItemString(fLayer,"layerWeight");
192 std::string fKernelName = PyStringAsString(PyList_GetItem(fWeightNames,0));
193 std::string fBiasName = PyStringAsString(PyList_GetItem(fWeightNames,1));
194
195 std::unique_ptr<ROperator> op;
196
197 float attr_alpha = 1.0;
198 float attr_beta = 1.0;
199 int_t attr_transA = 0;
200 int_t attr_transB = 0;
201
202 switch(ConvertStringToType(fLayerDType)){
204 op.reset(new ROperator_Gemm<float>(attr_alpha, attr_beta, attr_transA, attr_transB, fLayerInputName, fKernelName, fBiasName, fLayerOutputName));
205 break;
206
207 default:
208 throw std::runtime_error("TMVA::SOFIE - Unsupported - Operator Gemm does not yet support input type " + fLayerDType);
209 }
210 return op;
211}
212
213
214//////////////////////////////////////////////////////////////////////////////////
215/// \brief Prepares a ROperator object for Keras activation layer
216///
217/// \param[in] fLayer Python Keras layer as a Dictionary object
218/// \return Unique pointer to ROperator object
219///
220/// For Keras's keras.layers.Activation layer, the activation attribute is
221/// extracted and appropriate function for adding the function is called.
222std::unique_ptr<ROperator> MakeKerasActivation(PyObject* fLayer){
223 PyObject* fAttributes=PyDict_GetItemString(fLayer,"layerAttributes");
224 std::string fLayerActivation = PyStringAsString(PyDict_GetItemString(fAttributes,"activation"));
225
226 auto findLayer = mapKerasLayer.find(fLayerActivation);
227 if(findLayer == mapKerasLayer.end()){
228 throw std::runtime_error("TMVA::SOFIE - Parsing Keras Activation layer " + fLayerActivation + " is not yet supported");
229 }
230 return (findLayer->second)(fLayer);
231}
232
233
234//////////////////////////////////////////////////////////////////////////////////
235/// \brief Prepares a ROperator object for Keras ReLU activation
236///
237/// \param[in] fLayer Python Keras layer as a Dictionary object
238/// \return Unique pointer to ROperator object
239///
240/// For instantiating a ROperator_Relu object, the names of
241/// input & output tensors and the deta-type of the layer are extracted.
242std::unique_ptr<ROperator> MakeKerasReLU(PyObject* fLayer)
243{
244 PyObject* fInputs=PyDict_GetItemString(fLayer,"layerInput");
245 PyObject* fOutputs=PyDict_GetItemString(fLayer,"layerOutput");
246
247 std::string fLayerDType = PyStringAsString(PyDict_GetItemString(fLayer,"layerDType"));
248 std::string fLayerInputName = PyStringAsString(PyList_GetItem(fInputs,0));
249 std::string fLayerOutputName = PyStringAsString(PyList_GetItem(fOutputs,0));
250
251 std::unique_ptr<ROperator> op;
252 switch(ConvertStringToType(fLayerDType)){
254 op.reset(new ROperator_Relu<float>(fLayerInputName, fLayerOutputName));
255 break;
256 default:
257 throw std::runtime_error("TMVA::SOFIE - Unsupported - Operator Relu does not yet support input type " + fLayerDType);
258 }
259 return op;
260}
261
262
263//////////////////////////////////////////////////////////////////////////////////
264/// \brief Prepares a ROperator object for Keras Selu activation
265///
266/// \param[in] fLayer Python Keras layer as a Dictionary object
267/// \return Unique pointer to ROperator object
268///
269/// For instantiating a ROperator_Selu object, the names of
270/// input & output tensors and the deta-type of the layer are extracted.
271std::unique_ptr<ROperator> MakeKerasSelu(PyObject* fLayer){
272 PyObject* fInputs = PyDict_GetItemString(fLayer,"layerInput");
273 PyObject* fOutputs = PyDict_GetItemString(fLayer,"layerOutput");
274
275 std::string fLayerDType = PyStringAsString(PyDict_GetItemString(fLayer,"layerDType"));
276 std::string fLayerInputName = PyStringAsString(PyList_GetItem(fInputs,0));
277 std::string fLayerOutputName = PyStringAsString(PyList_GetItem(fOutputs,0));
278
279 std::unique_ptr<ROperator> op;
280 switch(ConvertStringToType(fLayerDType)){
282 op.reset(new ROperator_Selu<float>(fLayerInputName, fLayerOutputName));
283 break;
284 default:
285 throw std::runtime_error("TMVA::SOFIE - Unsupported - Operator Selu does not yet support input type " + fLayerDType);
286 }
287 return op;
288}
289
290
291//////////////////////////////////////////////////////////////////////////////////
292/// \brief Prepares a ROperator object for Keras Sigmoid activation
293///
294/// \param[in] fLayer Python Keras layer as a Dictionary object
295/// \return Unique pointer to ROperator object
296///
297/// For instantiating a ROperator_Sigmoid object, the names of
298/// input & output tensors and the deta-type of the layer are extracted.
299std::unique_ptr<ROperator> MakeKerasSigmoid(PyObject* fLayer){
300 PyObject* fInputs = PyDict_GetItemString(fLayer,"layerInput");
301 PyObject* fOutputs = PyDict_GetItemString(fLayer,"layerOutput");
302
303 std::string fLayerDType = PyStringAsString(PyDict_GetItemString(fLayer,"layerDType"));
304 std::string fLayerInputName = PyStringAsString(PyList_GetItem(fInputs,0));
305 std::string fLayerOutputName = PyStringAsString(PyList_GetItem(fOutputs,0));
306
307 std::unique_ptr<ROperator> op;
308 switch(ConvertStringToType(fLayerDType)){
310 op.reset(new ROperator_Sigmoid<float>(fLayerInputName, fLayerOutputName));
311 break;
312 default:
313 throw std::runtime_error("TMVA::SOFIE - Unsupported - Operator Sigmoid does not yet support input type " + fLayerDType);
314 }
315 return op;
316}
317
318
319//////////////////////////////////////////////////////////////////////////////////
320/// \brief Prepares a ROperator object for Keras Permute layer
321///
322/// \param[in] fLayer Python Keras layer as a Dictionary object
323/// \return Unique pointer to ROperator object
324///
325/// The Permute layer in Keras has an equivalent Tranpose operator in ONNX.
326/// For adding a Transpose operator, the permute dimensions are found, if they
327/// exist are passed in instantiating the ROperator, else default values are used.
328std::unique_ptr<ROperator> MakeKerasPermute(PyObject* fLayer)
329{
330 // Extracting required layer information
331 PyObject* fAttributes=PyDict_GetItemString(fLayer,"layerAttributes");
332 PyObject* fInputs=PyDict_GetItemString(fLayer,"layerInput");
333 PyObject* fOutputs=PyDict_GetItemString(fLayer,"layerOutput");
334
335 std::string fLayerDType = PyStringAsString(PyDict_GetItemString(fLayer,"layerDType"));
336 std::string fLayerInputName = PyStringAsString(PyList_GetItem(fInputs,0));
337 std::string fLayerOutputName = PyStringAsString(PyList_GetItem(fOutputs,0));
338
339 // Extracting the permute dimensions present in Attributes of the Keras layer
340 PyObject* fAttributePermute=PyDict_GetItemString(fAttributes,"dims");
341 std::vector<int_t>fPermuteDims;
342
343 // Building vector of permute dimensions from the Tuple object.
344 for(Py_ssize_t tupleIter=0;tupleIter<PyTuple_Size(fAttributePermute);++tupleIter){
345
346 fPermuteDims.push_back((int_t)PyLong_AsLong(PyTuple_GetItem(fAttributePermute,tupleIter)));
347 }
348 std::unique_ptr<ROperator> op;
349 switch(ConvertStringToType(fLayerDType)){
350 case ETensorType::FLOAT:{
351
352 // Adding the permute dimensions if present, else are avoided to use default values.
353 if (!fPermuteDims.empty()){
354 op.reset(new ROperator_Transpose<float>(fPermuteDims, fLayerInputName, fLayerOutputName));
355 }
356 else{
357 op.reset(new ROperator_Transpose<float> (fLayerInputName, fLayerOutputName));
358 }
359 break;
360 }
361 default:
362 throw std::runtime_error("TMVA::SOFIE - Unsupported - Operator Transpose does not yet support input type " + fLayerDType);
363 }
364 return op;
365 }
366
367}//INTERNAL
368
369
370//////////////////////////////////////////////////////////////////////////////////
371/// \param[in] filename file location of Keras .h5
372/// \return Parsed RModel object
373///
374/// The `Parse()` function defined in `TMVA::Experimental::SOFIE::PyKeras` will
375/// parse a trained Keras .h5 model into a RModel Object. After loading the model
376/// in a Python Session, the included layers are extracted with properties
377/// like Layer type, Attributes, Input tensor names, Output tensor names, data-type
378/// and names of the weight/initialized tensors.
379/// The extracted layers from the model are then passed into `AddKerasLayer()`
380/// which prepares the specific ROperator and adds them into the RModel object.
381/// The layers are also checked for adding any required routines for executing
382/// the generated Inference code.
383///
384/// For adding the Initialized tensors into the RModel object, the weights are
385/// extracted from the Keras model in the form of NumPy arrays, which are then
386/// passed into `AddInitializedTensor()` after appropriate casting.
387///
388/// Input tensor infos are required to be added which will contain their names,
389/// shapes and data-types. For keras models with single input tensors, the tensor
390/// shape is returned as a Tuple object, whereas for multi-input models,
391/// the tensor shape is returned as a List of Tuple object containing the shape
392/// of the individual input tensors. SOFIE's RModel also requires that the Keras
393/// models are initialized with Batch Size. The `GetDataFromTuple()` are called
394/// on the Tuple objects, which then returns the shape vector required to call
395/// the `AddInputTensorInfo()`.
396///
397/// For adding the Output Tensor infos, only the names of the model's output
398/// tensors are extracted and are then passed into `AddOutputTensorNameList()`.
399///
400/// Example Usage:
401/// ~~~ {.cpp}
402/// using TMVA::Experimental::SOFIE;
403/// RModel model = PyKeras::Parse("trained_model_dense.h5");
404/// ~~~
405RModel Parse(std::string filename){
406
407 char sep = '/';
408 #ifdef _WIN32
409 sep = '\\';
410 #endif
411
412 size_t isep = filename.rfind(sep, filename.length());
413 std::string filename_nodir = filename;
414 if (isep != std::string::npos){
415 filename_nodir = (filename.substr(isep+1, filename.length() - isep));
416 }
417
418 //Check on whether the Keras .h5 file exists
419 if(!std::ifstream(filename).good()){
420 throw std::runtime_error("Model file "+filename_nodir+" not found!");
421 }
422
423
424 std::time_t ttime = std::time(0);
425 std::tm* gmt_time = std::gmtime(&ttime);
426 std::string parsetime (std::asctime(gmt_time));
427
428 RModel rmodel(filename_nodir, parsetime);
429
430 //Intializing Python Interpreter and scope dictionaries
431 Py_Initialize();
432 PyObject* main = PyImport_AddModule("__main__");
433 PyObject* fGlobalNS = PyModule_GetDict(main);
434 PyObject* fLocalNS = PyDict_New();
435 if (!fGlobalNS) {
436 throw std::runtime_error("Can't init global namespace for Python");
437 }
438 if (!fLocalNS) {
439 throw std::runtime_error("Can't init local namespace for Python");
440 }
441
442 // Extracting model information
443 // For each layer: type,name,activation,dtype,input tensor's name,
444 // output tensor's name, kernel's name, bias's name
445 // None object is returned for if property doesn't belong to layer
446 PyRunString("import tensorflow.keras as keras",fGlobalNS,fLocalNS);
447 PyRunString("from tensorflow.keras.models import load_model",fGlobalNS,fLocalNS);
448 PyRunString("print('Keras Version: '+ keras.__version__)",fGlobalNS,fLocalNS);
449 PyRunString(TString::Format("model=load_model('%s')",filename.c_str()),fGlobalNS,fLocalNS);
450 PyRunString(TString::Format("model.load_weights('%s')",filename.c_str()),fGlobalNS,fLocalNS);
451 PyRunString("globals().update(locals())",fGlobalNS,fLocalNS);
452 PyRunString("modelData=[]",fGlobalNS,fLocalNS);
453 PyRunString("for idx in range(len(model.layers)):\n"
454 " layer=model.get_layer(index=idx)\n"
455 " globals().update(locals())\n"
456 " layerData={}\n"
457 " layerData['layerType']=layer.__class__.__name__\n"
458 " layerData['layerAttributes']=layer.get_config()\n"
459 " layerData['layerInput']=[x.name for x in layer.input] if isinstance(layer.input,list) else [layer.input.name]\n"
460 " layerData['layerOutput']=[x.name for x in layer.output] if isinstance(layer.output,list) else [layer.output.name]\n"
461 " layerData['layerDType']=layer.dtype\n"
462 " layerData['layerWeight']=[x.name for x in layer.weights]\n"
463 " modelData.append(layerData)",fGlobalNS,fLocalNS);
464
465
466 PyObject* fPModel = PyDict_GetItemString(fLocalNS,"modelData");
467 PyObject *fLayer;
468 Py_ssize_t fModelSize = PyList_Size(fPModel);
469 std::string fLayerType;
470
471 // Traversing through all the layers and passing the Layer object to `AddKerasLayer()`
472 // for adding the equivalent ROperators into the RModel object.
473 for(Py_ssize_t fModelIterator=0;fModelIterator<fModelSize;++fModelIterator){
474 fLayer = PyList_GetItem(fPModel,fModelIterator);
475 fLayerType = PyStringAsString(PyDict_GetItemString(fLayer,"layerType"));
476
477 // Ignoring the input layer for models built using Keras Functional API
478 if(fLayerType == "InputLayer")
479 continue;
480
481 // Adding any required routines depending on the Layer types for generating
482 // inference code.
483 else if(fLayerType == "Dense")
484 rmodel.AddBlasRoutines({"Gemm", "Gemv"});
485 INTERNAL::AddKerasLayer(rmodel,fLayer);
486
487 }
488
489 //Extracting model's weights
490 //For every initialized tensor, weightProp will have its name and dtype in string
491 //and value in numpy array
492 PyRunString("globals().update(locals())",fGlobalNS,fLocalNS);
493 PyRunString("weight=[]",fGlobalNS,fLocalNS);
494 PyRunString("for idx in range(len(model.get_weights())):\n"
495 " weightProp={}\n"
496 " weightProp['name']=model.weights[idx].name\n"
497 " weightProp['dtype']=(model.get_weights())[idx].dtype.name\n"
498 " weightProp['value']=(model.get_weights())[idx]\n"
499 " weight.append(weightProp)",fGlobalNS,fLocalNS);
500
501 PyObject *fWeightTensor, *fPWeight;
502 PyArrayObject *fWeightTensorValue;
503 std::string fWeightName;
504 ETensorType fWeightDType;
505 fPWeight = PyDict_GetItemString(fLocalNS,"weight");
506 std::vector<std::size_t> fWeightTensorShape;
507 std::size_t fWeightTensorSize;
508
509 // Traversing through all the Weight tensors
510 for (Py_ssize_t weightIter = 0; weightIter < PyList_Size(fPWeight); weightIter++){
511 fWeightTensor = PyList_GetItem(fPWeight, weightIter);
512 fWeightName = PyStringAsString(PyDict_GetItemString(fWeightTensor,"name"));
513 fWeightDType = ConvertStringToType(PyStringAsString(PyDict_GetItemString(fWeightTensor,"dtype")));
514
515 fWeightTensorValue = (PyArrayObject*)PyDict_GetItemString(fWeightTensor,"value");
516 fWeightTensorSize=1;
517 fWeightTensorShape.clear();
518
519 // Building the shape vector and finding the tensor size
520 for(int j=0; j<PyArray_NDIM(fWeightTensorValue); ++j){
521 fWeightTensorShape.push_back((std::size_t)(PyArray_DIM(fWeightTensorValue,j)));
522 fWeightTensorSize*=(std::size_t)(PyArray_DIM(fWeightTensorValue,j));
523 }
524
525 switch(fWeightDType){
526 case ETensorType::FLOAT : {
527 float* fWeightArray = (float*)PyArray_DATA(fWeightTensorValue);
528 std::shared_ptr<void> fData(malloc(fWeightTensorSize * sizeof(float)), free);
529 std::memcpy(fData.get(),fWeightArray, fWeightTensorSize * sizeof(float));
530 rmodel.AddInitializedTensor(fWeightName,ETensorType::FLOAT,fWeightTensorShape,fData);
531 break;
532 }
533 default:
534 throw std::runtime_error("Type error: TMVA SOFIE does not yet weight data layer type"+ConvertTypeToString(fWeightDType));
535 }
536 }
537
538
539 // Extracting input tensor info
540 // For every input tensor inputNames will have their names as string,
541 // inputShapes will have their shape as Python Tuple, and inputTypes
542 // will have their dtype as string
543 PyRunString("inputNames=model.input_names",fGlobalNS,fLocalNS);
544 PyRunString("inputShapes=model.input_shape",fGlobalNS,fLocalNS);
545 PyRunString("inputTypes=[]",fGlobalNS,fLocalNS);
546 PyRunString("for idx in range(len(model.inputs)):\n"
547 " inputTypes.append(model.inputs[idx].dtype.__str__()[9:-2])",fGlobalNS,fLocalNS);
548
549 PyObject* fPInputs = PyDict_GetItemString(fLocalNS,"inputNames");
550 PyObject* fPInputShapes = PyDict_GetItemString(fLocalNS,"inputShapes");
551 PyObject* fPInputTypes = PyDict_GetItemString(fLocalNS,"inputTypes");
552
553 std::string fInputName;
554 ETensorType fInputDType;
555
556 // For single input models, the model.input_shape will return a tuple
557 // describing the input tensor shape. For multiple inputs models,
558 // the model.input_shape will return a list of tuple, each describing
559 // the input tensor shape.
560 if(PyTuple_Check(fPInputShapes)){
561 fInputName = PyStringAsString(PyList_GetItem(fPInputs,0));
562 fInputDType = ConvertStringToType(PyStringAsString(PyList_GetItem(fPInputTypes,0)));
563
564 switch(fInputDType){
565
566 case ETensorType::FLOAT : {
567
568 // Getting the shape vector from the Tuple object
569 std::vector<size_t>fInputShape = GetDataFromTuple(fPInputShapes);
570 if (static_cast<int>(fInputShape[0]) <= 0){
571 fInputShape[0] = 1;
572 std::cout << "Model has not a defined batch size, assume is 1 - input shape : "
573 << TMVA::Experimental::SOFIE::ConvertShapeToString(fInputShape) << std::endl;
574 }
575 rmodel.AddInputTensorInfo(fInputName, ETensorType::FLOAT, fInputShape);
576 break;
577 }
578
579 default:
580 throw std::runtime_error("Type error: TMVA SOFIE does not yet suppport data type"+ConvertTypeToString(fInputDType));
581 }
582
583 }
584
585 else{
586
587 // Iterating through multiple input tensors
588 for(Py_ssize_t inputIter = 0; inputIter < PyList_Size(fPInputs);++inputIter){
589
590 fInputName = PyStringAsString(PyList_GetItem(fPInputs,inputIter));
591 fInputDType = ConvertStringToType(PyStringAsString(PyList_GetItem(fPInputTypes,inputIter)));
592
593 switch(fInputDType){
594 case ETensorType::FLOAT : {
595 PyObject* fInputShapeTuple=PyList_GetItem(fPInputShapes,inputIter);
596
597 std::vector<size_t>fInputShape = GetDataFromTuple(fInputShapeTuple);
598 if (static_cast<int>(fInputShape[0]) <= 0){
599 fInputShape[0] = 1;
600 std::cout << "Model has not a defined batch size, assume is 1 - input shape for tensor "
601 << fInputName << " : " << TMVA::Experimental::SOFIE::ConvertShapeToString(fInputShape) << std::endl;
602 }
603 rmodel.AddInputTensorInfo(fInputName, ETensorType::FLOAT, fInputShape);
604 break;
605 }
606
607 default:
608 throw std::runtime_error("Type error: TMVA SOFIE does not yet suppport data type"+ConvertTypeToString(fInputDType));
609
610 }
611 }
612 }
613
614 // For adding OutputTensorInfos, the names of the output
615 // tensors are extracted from the Keras model
616 PyRunString("outputNames=[]",fGlobalNS,fLocalNS);
617 PyRunString("for layerName in model.output_names:\n"
618 " outputNames.append(model.get_layer(layerName).output.name)",fGlobalNS,fLocalNS);
619 PyObject* fPOutputs = PyDict_GetItemString(fLocalNS,"outputNames");
620 std::vector<std::string> fOutputNames;
621 for(Py_ssize_t outputIter = 0; outputIter < PyList_Size(fPOutputs);++outputIter){
622 fOutputNames.push_back(PyStringAsString(PyList_GetItem(fPOutputs,outputIter)));
623 }
624 rmodel.AddOutputTensorNameList(fOutputNames);
625
626 return rmodel;
627}
628}//PyKeras
629}//SOFIE
630}//Experimental
631}//TMVA
typedef void(GLAPIENTRYP _GLUfuncptr)(void)
int main()
Definition Prototype.cxx:12
_object PyObject
#define free
Definition civetweb.c:1539
#define malloc
Definition civetweb.c:1536
void AddOutputTensorNameList(std::vector< std::string > outputtensornames)
Definition RModel.cxx:145
void AddInputTensorInfo(std::string input_name, ETensorType type, std::vector< Dim > shape)
Definition RModel.cxx:98
void AddInitializedTensor(std::string tensor_name, ETensorType type, std::vector< std::size_t > shape, std::shared_ptr< void > data)
Definition RModel.cxx:125
void AddBlasRoutines(std::vector< std::string > routines)
Definition RModel.hxx:72
void AddNeededStdLib(std::string libname)
Definition RModel.hxx:77
void AddOperator(std::unique_ptr< ROperator > op, int order_execution=-1)
Definition RModel.cxx:117
static std::vector< size_t > GetDataFromTuple(PyObject *tupleObject)
Utility function which retrieves and returns the values of the Tuple object as a vector of size_t.
static const char * PyStringAsString(PyObject *string)
Returns const char* from Python string in PyObject.
void PyRunString(TString code, TString errorMessage="Failed to run python code", int start=Py_single_input)
Execute Python code from string.
Basic string class.
Definition TString.h:136
static TString Format(const char *fmt,...)
Static method which formats a string using a printf style format descriptor and return a TString.
Definition TString.cxx:2336
std::unique_ptr< ROperator > MakeKerasPermute(PyObject *fLayer)
Prepares a ROperator object for Keras Permute layer.
std::unordered_map< std::string, std::unique_ptr< ROperator >(*)(PyObject *fLayer)> KerasMethodMap
void AddKerasLayer(RModel &rmodel, PyObject *fLayer)
Adds equivalent ROperator with respect to Keras model layer into the referenced RModel object.
std::unique_ptr< ROperator > MakeKerasDense(PyObject *fLayer)
Prepares a ROperator object for Keras Dense Layer.
std::unique_ptr< ROperator > MakeKerasReLU(PyObject *fLayer)
Prepares a ROperator object for Keras ReLU activation.
const KerasMethodMapWithActivation mapKerasLayerWithActivation
std::unordered_map< std::string, std::unique_ptr< ROperator >(*)(PyObject *fLayer)> KerasMethodMapWithActivation
std::unique_ptr< ROperator > MakeKerasSigmoid(PyObject *fLayer)
Prepares a ROperator object for Keras Sigmoid activation.
std::unique_ptr< ROperator > MakeKerasSelu(PyObject *fLayer)
Prepares a ROperator object for Keras Selu activation.
std::unique_ptr< ROperator > MakeKerasActivation(PyObject *fLayer)
Prepares a ROperator object for Keras activation layer.
static void(&) PyRunString(TString, PyObject *, PyObject *)
static const char *(&) PyStringAsString(PyObject *)
RModel Parse(std::string filename)
Parser function for translatng Keras .h5 model into a RModel object.
std::string ConvertShapeToString(std::vector< size_t > shape)
std::string ConvertTypeToString(ETensorType type)
ETensorType ConvertStringToType(std::string type)
create variable transformations