Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
MethodPyKeras.cxx
Go to the documentation of this file.
1// @(#)root/tmva/pymva $Id$
2// Author: Stefan Wunsch, 2016
3
4#include <Python.h>
6
7#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
8#include <numpy/arrayobject.h>
9
10#include "TMVA/Types.h"
11#include "TMVA/Config.h"
13#include "TMVA/Results.h"
16#include "TMVA/Tools.h"
17#include "TMVA/Timer.h"
18#include "TObjString.h"
19#include "TSystem.h"
20#include "Math/Util.h"
21
22using namespace TMVA;
23
24namespace TMVA {
25namespace Internal {
26class PyGILRAII {
27 PyGILState_STATE m_GILState;
28
29public:
32};
33} // namespace Internal
34} // namespace TMVA
35
36REGISTER_METHOD(PyKeras)
37
38
40 : PyMethodBase(jobName, Types::kPyKeras, methodTitle, dsi, theOption) {
41 fNumEpochs = 10;
42 fNumThreads = 0;
43 fBatchSize = 100;
44 fVerbose = 1;
45 fContinueTraining = false;
46 fSaveBestOnly = true;
47 fTriesEarlyStopping = -1;
48 fLearningRateSchedule = ""; // empty string deactivates learning rate scheduler
49 fFilenameTrainedModel = ""; // empty string sets output model filename to default (in weights/)
50 fTensorBoard = ""; // empty string deactivates TensorBoard callback
51}
52
55 fNumEpochs = 10;
56 fNumThreads = 0;
57 fBatchSize = 100;
58 fVerbose = 1;
59 fContinueTraining = false;
60 fSaveBestOnly = true;
62 fLearningRateSchedule = ""; // empty string deactivates learning rate scheduler
63 fFilenameTrainedModel = ""; // empty string sets output model filename to default (in weights/)
64 fTensorBoard = ""; // empty string deactivates TensorBoard callback
65}
66
68 if (fPyVals != nullptr) Py_DECREF(fPyVals);
69 if (fPyOutput != nullptr) Py_DECREF(fPyOutput);
70}
71
78
79///////////////////////////////////////////////////////////////////////////////
80
82 DeclareOptionRef(fFilenameModel, "FilenameModel", "Filename of the initial Keras model");
83 DeclareOptionRef(fFilenameTrainedModel, "FilenameTrainedModel", "Filename of the trained output Keras model");
84 DeclareOptionRef(fBatchSize, "BatchSize", "Training batch size");
85 DeclareOptionRef(fNumEpochs, "NumEpochs", "Number of training epochs");
86 DeclareOptionRef(fNumThreads, "NumThreads", "Number of CPU threads (only for Tensorflow backend)");
87 DeclareOptionRef(fGpuOptions, "GpuOptions", "GPU options for tensorflow, such as allow_growth");
88 DeclareOptionRef(fUseTFKeras, "tf.keras", "Use tensorflow from Keras");
89 DeclareOptionRef(fUseTFKeras, "tfkeras", "Use tensorflow from Keras");
90 DeclareOptionRef(fVerbose, "Verbose", "Keras verbosity during training");
91 DeclareOptionRef(fContinueTraining, "ContinueTraining", "Load weights from previous training");
92 DeclareOptionRef(fSaveBestOnly, "SaveBestOnly", "Store only weights with smallest validation loss");
93 DeclareOptionRef(fTriesEarlyStopping, "TriesEarlyStopping", "Number of epochs with no improvement in validation loss after which training will be stopped. The default or a negative number deactivates this option.");
94 DeclareOptionRef(fLearningRateSchedule, "LearningRateSchedule", "Set new learning rate during training at specific epochs, e.g., \"50,0.01;70,0.005\"");
95 DeclareOptionRef(fTensorBoard, "TensorBoard",
96 "Write a log during training to visualize and monitor the training performance with TensorBoard");
97
98 DeclareOptionRef(fNumValidationString = "20%", "ValidationSize", "Part of the training data to use for validation. "
99 "Specify as 0.2 or 20% to use a fifth of the data set as validation set. "
100 "Specify as 100 to use exactly 100 events. (Default: 20%)");
101 DeclareOptionRef(fUserCodeName = "", "UserCode",
102 "Optional python code provided by the user to be executed before loading the Keras model");
103}
104
105////////////////////////////////////////////////////////////////////////////////
106/// Validation of the ValidationSize option. Allowed formats are 20%, 0.2 and
107/// 100 etc.
108/// - 20% and 0.2 selects 20% of the training set as validation data.
109/// - 100 selects 100 events as the validation data.
110///
111/// @return number of samples in validation set
112///
114{
116 UInt_t trainingSetSize = GetEventCollection(Types::kTraining).size();
117
118 // Parsing + Validation
119 // --------------------
120 if (fNumValidationString.EndsWith("%")) {
121 // Relative spec. format 20%
122 TString intValStr = TString(fNumValidationString.Strip(TString::kTrailing, '%'));
123
124 if (intValStr.IsFloat()) {
125 Double_t valSizeAsDouble = fNumValidationString.Atof() / 100.0;
126 nValidationSamples = GetEventCollection(Types::kTraining).size() * valSizeAsDouble;
127 } else {
128 Log() << kFATAL << "Cannot parse number \"" << fNumValidationString
129 << "\". Expected string like \"20%\" or \"20.0%\"." << Endl;
130 }
131 } else if (fNumValidationString.IsFloat()) {
132 Double_t valSizeAsDouble = fNumValidationString.Atof();
133
134 if (valSizeAsDouble < 1.0) {
135 // Relative spec. format 0.2
136 nValidationSamples = GetEventCollection(Types::kTraining).size() * valSizeAsDouble;
137 } else {
138 // Absolute spec format 100 or 100.0
140 }
141 } else {
142 Log() << kFATAL << "Cannot parse number \"" << fNumValidationString << "\". Expected string like \"0.2\" or \"100\"."
143 << Endl;
144 }
145
146 // Value validation
147 // ----------------
148 if (nValidationSamples < 0) {
149 Log() << kFATAL << "Validation size \"" << fNumValidationString << "\" is negative." << Endl;
150 }
151
152 if (nValidationSamples == 0) {
153 Log() << kFATAL << "Validation size \"" << fNumValidationString << "\" is zero." << Endl;
154 }
155
157 Log() << kFATAL << "Validation size \"" << fNumValidationString
158 << "\" is larger than or equal in size to training set (size=\"" << trainingSetSize << "\")." << Endl;
159 }
160
161 return nValidationSamples;
162}
163
164/// Function processing the options
165/// This is called only when creating the method before training not when
166/// reading from XML file. Called from MethodBase::ProcessSetup
167/// that is called from Factory::BookMethod
169
170 // Set default filename for trained model if option is not used
172 fFilenameTrainedModel = GetWeightFileDir() + "/TrainedModel_" + GetName();
173 if (fUseKeras3)
174 fFilenameTrainedModel += ".keras";
175 else
176 fFilenameTrainedModel += ".h5";
177 }
178
179 InitKeras();
180
181 // Setup model, either the initial model from `fFilenameModel` or
182 // the trained model from `fFilenameTrainedModel`
183 if (fContinueTraining) Log() << kINFO << "Continue training with trained model" << Endl;
185}
186
188 // initialize first Keras. This is done only here when class has
189 // all state variable set from options or read from XML file
190 // Import Keras
191
192 if (fUseTFKeras)
193 Log() << kINFO << "Setting up tf.keras" << Endl;
194 else
195 Log() << kINFO << "Setting up keras with " << gSystem->Getenv("KERAS_BACKEND") << " backend" << Endl;
196
197 bool useTFBackend = kFALSE;
199 bool kerasIsPresent = kFALSE;
200
201 if (!fUseTFKeras) {
202 auto ret = PyRun_String("import keras", Py_single_input, fGlobalNS, fLocalNS);
203 // need importing also in global namespace
204 if (ret != nullptr) ret = PyRun_String("import keras", Py_single_input, fGlobalNS, fGlobalNS);
205 if (ret != nullptr)
207 if (kerasIsPresent) {
208 // check compatibility with tensorflow
209 if (GetKerasBackend() == kTensorFlow ) {
211
212 PyRunString("keras_major_version = int(keras.__version__.split('.')[0])");
213 PyRunString("keras_minor_version = int(keras.__version__.split('.')[1])");
218 Log() << kINFO << "Using Keras version " << kerasMajorVersion << "." << kerasMinorVersion << Endl;
219 // only version 2.3 is latest multi-backend version.
220 // version 2.4 is just tf.keras and should not be used in standalone and will not work in this workflow
221 // see https://github.com/keras-team/keras/releases/tag/2.4.0
222 // for example variable keras.backend.tensorflow_backend will not exist anymore in keras 2.4
224
225 }
226 } else {
227 // Keras is not found. try tyo use tf.keras
228 Log() << kINFO << "Keras is not found. Trying using tf.keras" << Endl;
229 fUseTFKeras = 1;
230 }
231 }
232
233 // import Tensorflow (if requested or because is keras backend)
234 if (fUseTFKeras || useTFBackend) {
235 auto ret = PyRun_String("import tensorflow as tf", Py_single_input, fGlobalNS, fLocalNS);
236 if (ret != nullptr) ret = PyRun_String("import tensorflow as tf", Py_single_input, fGlobalNS, fGlobalNS);
237 if (ret == nullptr) {
238 Log() << kFATAL << "Importing TensorFlow failed" << Endl;
239 }
240 // check tensorflow version
241 PyRunString("tf_major_version = int(tf.__version__.split('.')[0])");
242 PyRunString("tf_minor_version = int(tf.__version__.split('.')[1])");
247 Log() << kINFO << "Using TensorFlow version " << tfMajorVersion << "." << tfMinorVersion << Endl;
248
249 if (tfMajorVersion < 2) {
250 if (fUseTFKeras == 1) {
251 Log() << kWARNING << "Using TensorFlow version 1.x which does not contain tf.keras - use then TensorFlow as Keras backend" << Endl;
253 // case when Keras was not found
254 if (!kerasIsPresent) {
255 Log() << kFATAL << "Keras is not present and not a suitable TensorFlow version is found " << Endl;
256 return;
257 }
258 }
259 }
260 else {
261 // using version larger than 2.0 - can use tf.keras
262 if (!kerasIsCompatible) {
263 Log() << kWARNING << "The Keras version is not compatible with TensorFlow 2. Use instead tf.keras" << Endl;
264 fUseTFKeras = 1;
265 }
266 if (tfMinorVersion >= 16) { // for version >=2.16 use Keras 3 API
267 fUseKeras3 = true;
268 Log() << kINFO << "Using the new Keras3 API available with tensorflow version " << tfMajorVersion << "." << tfMinorVersion << Endl;
270 Log() << kFATAL << "Cannot use .h5 files with new Keras 3 API. Use .keras" << Endl;
271 }
272 }
273
274 // if keras 2.3 and tensorflow 2 are found. Use tf.keras or keras ?
275 // at the moment default is tf.keras=false to keep compatibility
276 // but this might change in future releases
277 if (fUseTFKeras) {
278 Log() << kINFO << "Use Keras version from TensorFlow : tf.keras" << Endl;
279 fKerasString = "tf.keras";
280 PyRunString("K = tf.keras.backend");
281 PyRun_String("K = tf.keras.backend", Py_single_input, fGlobalNS, fGlobalNS);
282 }
283 else {
284 Log() << kINFO << "Use TensorFlow as Keras backend" << Endl;
285 fKerasString = "keras";
286 PyRunString("from keras.backend import tensorflow_backend as K");
287 PyRun_String("from keras.backend import tensorflow_backend as K", Py_single_input, fGlobalNS, fGlobalNS);
288 }
289
290 // extra options for tensorflow (for version < 2.16)
291 if (tfMajorVersion <=2 && tfMinorVersion < 16) {
292 // use different naming in tf2 for ConfigProto and Session
293 TString configProto = (tfMajorVersion >= 2) ? "tf.compat.v1.ConfigProto" : "tf.ConfigProto";
294 TString session = (tfMajorVersion >= 2) ? "tf.compat.v1.Session" : "tf.Session";
295
296 // in case specify number of threads
298 if (num_threads > 0) {
299 Log() << kINFO << "Setting the CPU number of threads = " << num_threads << Endl;
300
302 TString::Format("session_conf = %s(intra_op_parallelism_threads=%d,inter_op_parallelism_threads=%d)",
304 } else
305 PyRunString(TString::Format("session_conf = %s()", configProto.Data()));
306
307 // applying GPU options such as allow_growth=True to avoid allocating all memory on GPU
308 // that prevents running later TMVA-GPU
309 // Also new Nvidia RTX cards (e.g. RTX 2070) require this option
310 if (!fGpuOptions.IsNull()) {
312 for (int item = 0; item < optlist->GetEntries(); ++item) {
313 Log() << kINFO << "Applying GPU option: gpu_options." << optlist->At(item)->GetName() << Endl;
314 PyRunString(TString::Format("session_conf.gpu_options.%s", optlist->At(item)->GetName()));
315 }
316 }
317 PyRunString(TString::Format("sess = %s(config=session_conf)", session.Data()));
318
319 if (tfMajorVersion < 2) {
320 PyRunString("K.set_session(sess)");
321 } else {
322 PyRunString("tf.compat.v1.keras.backend.set_session(sess)");
323 }
324 } else {
325 // case using tensorflow >= 2.16
326 if (fNumThreads > 0) {
327 Log() << kINFO << "Setting the CPU number of threads = " << fNumThreads << Endl;
328 PyRunString(TString::Format("tf.config.threading.set_intra_op_parallelism_threads(%d)",fNumThreads));
329 PyRunString(TString::Format("tf.config.threading.set_inter_op_parallelism_threads(%d)",fNumThreads));
330 }
331 if (!fGpuOptions.IsNull()) {
333 for (int item = 0; item < optlist->GetEntries(); ++item) {
334 // this option will not work for Keras3
335 if (TString(optlist->At(item)->GetName())=="allow_growth=True" && !fUseKeras3) {
336 Log() << kINFO << "Applying GPU option: allow_growth=True " << Endl;
337 // allow memory growth on t he first GPY
338 PyRunString("physical_devices = tf.config.list_physical_devices('GPU')");
339 PyRunString("tf.config.experimental.set_memory_growth(physical_devices[0], True)");
340 }
341 }
342 }
343 }
344 }
345 // case not using a Tensorflow backend
346 else {
347 fKerasString = "keras";
348 if (fNumThreads > 0)
349 Log() << kWARNING << "Cannot set the given " << fNumThreads << " threads when not using tensorflow as backend"
350 << Endl;
351 if (!fGpuOptions.IsNull()) {
352 Log() << kWARNING << "Cannot set the given GPU option " << fGpuOptions
353 << " when not using tensorflow as backend" << Endl;
354 }
355 }
356
357}
358
360 /*
361 * Load Keras model from file
362 */
363
364 Log() << kINFO << " Loading Keras Model " << Endl;
365
366 PyRunString("load_model_custom_objects=None");
367
368
369
370 if (!fUserCodeName.IsNull()) {
371 Log() << kINFO << " Executing user initialization code from " << fUserCodeName << Endl;
372
373
374 // run some python code provided by user for model initialization if needed
375 TString cmd = "exec(open('" + fUserCodeName + "').read())";
376 TString errmsg = "Error executing the provided user code";
378
379 PyRunString("print('custom objects for loading model : ',load_model_custom_objects)");
380 }
381
382 // Load initial model or already trained model
384 if (loadTrainedModel) {
386 }
387 else {
389 }
390
391 PyRunString("model = " + fKerasString + ".models.load_model('" + filenameLoadModel +
392 "', custom_objects=load_model_custom_objects)", "Failed to load Keras model from file: " + filenameLoadModel);
393
394 Log() << kINFO << "Loaded model from file: " << filenameLoadModel << Endl;
395
396
397 /*
398 * Init variables and weights
399 */
400
401 // Get variables, classes and target numbers
405 else Log() << kFATAL << "Selected analysis type is not implemented" << Endl;
406
407 // Mark the model as setup
408 fModelIsSetup = true;
409 fModelIsSetupForEval = false;
410}
411
412///Setting up model for evaluation
413/// Add here some needed optimizations like disabling eager execution
415
416 InitKeras();
417
418 // disable eager execution (model will evaluate > 100 faster)
419 // need to be done before loading the model
420#ifndef R__MACOSX // problem disabling eager execution on Macos (conflict with multiprocessing)
421 if (fUseTFKeras && !fUseKeras3){
422 PyRunString("tf.compat.v1.disable_eager_execution()","Failed to disable eager execution");
423 Log() << kINFO << "Disabled TF eager execution when evaluating model " << Endl;
424 }
425#endif
426
427 SetupKerasModel(true);
428
429
431}
432
433/// Initialization function called from MethodBase::SetupMethod()
434/// Note that option string are not yet filled with their values.
435/// This is done before ProcessOption method or after reading from XML file
437
439
440 if (!PyIsInitialized()) {
441 Log() << kFATAL << "Python is not initialized" << Endl;
442 }
443 _import_array(); // required to use numpy arrays
444
445 // NOTE: sys.argv has to be cleared because otherwise TensorFlow breaks
446 PyRunString("import sys; sys.argv = ['']", "Set sys.argv failed");
447
448 // Set flag that model is not setup
449 fModelIsSetup = false;
450 fModelIsSetupForEval = false;
451}
452
454
455 if(!fModelIsSetup) Log() << kFATAL << "Model is not setup for training" << Endl;
456
457 /*
458 * Load training data to numpy array
459 */
460
464
465 Log() << kINFO << "Split TMVA training data in " << nTrainingEvents << " training events and "
466 << nValEvents << " validation events" << Endl;
467
468 float* trainDataX = new float[nTrainingEvents*fNVars];
469 float* trainDataY = new float[nTrainingEvents*fNOutputs];
470 float* trainDataWeights = new float[nTrainingEvents];
471 for (UInt_t i=0; i<nTrainingEvents; i++) {
472 const TMVA::Event* e = GetTrainingEvent(i);
473 // Fill variables
474 for (UInt_t j=0; j<fNVars; j++) {
475 trainDataX[j + i*fNVars] = e->GetValue(j);
476 }
477 // Fill targets
478 // NOTE: For classification, convert class number in one-hot vector,
479 // e.g., 1 -> [0, 1] or 0 -> [1, 0] for binary classification
481 for (UInt_t j=0; j<fNOutputs; j++) {
482 trainDataY[j + i*fNOutputs] = 0;
483 }
484 trainDataY[e->GetClass() + i*fNOutputs] = 1;
485 }
486 else if (GetAnalysisType() == Types::kRegression) {
487 for (UInt_t j=0; j<fNOutputs; j++) {
488 trainDataY[j + i*fNOutputs] = e->GetTarget(j);
489 }
490 }
491 else Log() << kFATAL << "Can not fill target vector because analysis type is not known" << Endl;
492 // Fill weights
493 // NOTE: If no weight branch is given, this defaults to ones for all events
494 trainDataWeights[i] = e->GetWeight();
495 }
496
506
507 /*
508 * Load validation data to numpy array
509 */
510
511 // NOTE: from TMVA, we get the validation data as a subset of all the training data
512 // we will not use test data for validation. They will be used for the real testing
513
514
515 float* valDataX = new float[nValEvents*fNVars];
516 float* valDataY = new float[nValEvents*fNOutputs];
517 float* valDataWeights = new float[nValEvents];
518 //validation events follows the trainig one in the TMVA training vector
519 for (UInt_t i=0; i< nValEvents ; i++) {
520 UInt_t ievt = nTrainingEvents + i; // TMVA event index
522 // Fill variables
523 for (UInt_t j=0; j<fNVars; j++) {
524 valDataX[j + i*fNVars] = e->GetValue(j);
525 }
526 // Fill targets
528 for (UInt_t j=0; j<fNOutputs; j++) {
529 valDataY[j + i*fNOutputs] = 0;
530 }
531 valDataY[e->GetClass() + i*fNOutputs] = 1;
532 }
533 else if (GetAnalysisType() == Types::kRegression) {
534 for (UInt_t j=0; j<fNOutputs; j++) {
535 valDataY[j + i*fNOutputs] = e->GetTarget(j);
536 }
537 }
538 else Log() << kFATAL << "Can not fill target vector because analysis type is not known" << Endl;
539 // Fill weights
540 valDataWeights[i] = e->GetWeight();
541 }
542
552
553 /*
554 * Train Keras model
555 */
556 Log() << kINFO << "Training Model Summary" << Endl;
557 PyRunString("model.summary()");
558
559 // Setup parameters
560
563 PyObject* pVerbose = PyLong_FromLong(fVerbose);
566 PyDict_SetItemString(fLocalNS, "verbose", pVerbose);
567
568 // Setup training callbacks
569 PyRunString("callbacks = []");
570
571 // Callback: Save only weights with smallest validation loss
572 if (fSaveBestOnly) {
573 PyRunString("callbacks.append(" + fKerasString +".callbacks.ModelCheckpoint('"+fFilenameTrainedModel+"', monitor='val_loss', verbose=verbose, save_best_only=True, mode='auto'))", "Failed to setup training callback: SaveBestOnly");
574 Log() << kINFO << "Option SaveBestOnly: Only model weights with smallest validation loss will be stored" << Endl;
575 }
576
577 // Callback: Stop training early if no improvement in validation loss is observed
578 if (fTriesEarlyStopping>=0) {
580 tries.Form("%i", fTriesEarlyStopping);
581 PyRunString("callbacks.append(" + fKerasString + ".callbacks.EarlyStopping(monitor='val_loss', patience="+tries+", verbose=verbose, mode='auto'))", "Failed to setup training callback: TriesEarlyStopping");
582 Log() << kINFO << "Option TriesEarlyStopping: Training will stop after " << tries << " number of epochs with no improvement of validation loss" << Endl;
583 }
584
585 // Callback: Learning rate scheduler
586 if (fLearningRateSchedule != "") {
587 // tokenize learning rate schedule (e.g. "10,0.01;20,0.001" given as epoch,lr)
588 std::vector<std::pair<std::string, std::string>> scheduleSteps;
590 for (auto obj : *lrSteps) {
591 TString step = obj->GetName();
592 auto x = step.Tokenize(",");
593 if (!x || x->GetEntries() != 2) {
594 Log() << kFATAL << "Invalid values given in LearningRateSchedule, it should be as \"10,0.1;20,0.01\""
595 << Endl;
596 }
597 scheduleSteps.push_back(std::make_pair<std::string, std::string>( std::string((*x)[0]->GetName() ) ,
598 std::string((*x)[1]->GetName() ) ) );
599 std::cout << " add learning rate schedule " << scheduleSteps.back().first << " : " << scheduleSteps.back().second << std::endl;
600 }
601 // Set scheduler function as piecewise function with given steps
602 TString epochsList = "epochs = [";
603 TString valuesList = "lrValues = [";
604 for (size_t i = 0; i < scheduleSteps.size(); i++) {
605 epochsList += TString(scheduleSteps[i].first.c_str());
606 valuesList += TString(scheduleSteps[i].second.c_str());
607 if (i < scheduleSteps.size()-1) {
608 epochsList += ", ";
609 valuesList += ", ";
610 }
611 }
612 epochsList += "]";
613 valuesList += "]";
614 TString scheduleFunction = "def schedule(epoch, lr):\n"
615 " i = 0\n"
616 " " + epochsList + "\n"
617 " " + valuesList + "\n"
618 " for e in epochs:\n"
619 " if (epoch < e) :\n"
620 " return lrValues[i]\n"
621 " i+=1\n"
622 " return lr\n";
624 "Failed to setup scheduler function with string: " + fLearningRateSchedule, Py_file_input);
625 // Setup callback
626 PyRunString("callbacks.append(" + fKerasString + ".callbacks.LearningRateScheduler(schedule, verbose=True))",
627 "Failed to setup training callback: LearningRateSchedule");
628 Log() << kINFO << "Option LearningRateSchedule: Set learning rate during training: " << fLearningRateSchedule << Endl;
629 }
630
631 // Callback: TensorBoard
632 if (fTensorBoard != "") {
633 TString logdir = TString("'") + fTensorBoard + TString("'");
635 "callbacks.append(" + fKerasString + ".callbacks.TensorBoard(log_dir=" + logdir +
636 ", histogram_freq=0, batch_size=batchSize, write_graph=True, write_grads=False, write_images=False))",
637 "Failed to setup training callback: TensorBoard");
638 Log() << kINFO << "Option TensorBoard: Log files for training monitoring are stored in: " << logdir << Endl;
639 }
640
641 // Train model
642 PyRunString("history = model.fit(trainX, trainY, sample_weight=trainWeights, batch_size=batchSize, epochs=numEpochs, verbose=verbose, validation_data=(valX, valY, valWeights), callbacks=callbacks)",
643 "Failed to train model");
644
645
646 std::vector<float> fHistory; // Hold training history (val_acc or loss etc)
647 fHistory.resize(fNumEpochs); // holds training loss or accuracy output
650 PyDict_SetItemString(fLocalNS, "HistoryOutput", (PyObject*)pHistory);
651
652 // Store training history data
653 Int_t iHis=0;
654 PyRunString("number_of_keys=len(history.history.keys())");
655 PyObject* PyNkeys=PyDict_GetItemString(fLocalNS, "number_of_keys");
657 for (iHis=0; iHis<nkeys; iHis++) {
658
659 PyRunString(TString::Format("copy_string=str(list(history.history.keys())[%d])",iHis));
661 if (!stra)
662 break;
664 PyObject* str = PyUnicode_AsEncodedString(repr, "utf-8", "~E~");
665 const char *name = PyBytes_AsString(str);
666
667 Log() << kINFO << "Getting training history for item:" << iHis << " name = " << name << Endl;
668 PyRunString(TString::Format("for i,p in enumerate(history.history[%s]):\n HistoryOutput[i]=p\n",name),
669 TString::Format("Failed to get %s from training history",name));
670 for (size_t i=0; i<fHistory.size(); i++)
671 fTrainHistory.AddValue(name,i+1,fHistory[i]);
672
673 }
674//#endif
675
676 /*
677 * Store trained model to file (only if option 'SaveBestOnly' is NOT activated,
678 * because we do not want to override the best model checkpoint)
679 */
680
681 if (!fSaveBestOnly) {
682 PyRunString("model.save('"+fFilenameTrainedModel+"', overwrite=True)",
683 "Failed to save trained model: "+fFilenameTrainedModel);
684 Log() << kINFO << "Trained model written to file: " << fFilenameTrainedModel << Endl;
685 }
686
687 /*
688 * Clean-up
689 */
690
691 delete[] trainDataX;
692 delete[] trainDataY;
693 delete[] trainDataWeights;
694 delete[] valDataX;
695 delete[] valDataY;
696 delete[] valDataWeights;
697}
698
702
703void MethodPyKeras::InitEvaluation(size_t nEvents = 1) {
704
705 size_t inputSize = fNVars*nEvents;
706 size_t outputSize = fNOutputs*nEvents;
707
708 // Init single evaluation
709 if (fNVars > 0 && ( fVals.size() != inputSize || fPyVals == nullptr)) {
710 fVals.resize(inputSize); // holds values used for classification and regression
711 npy_intp dimsVals[2] = {(npy_intp)nEvents, (npy_intp)fNVars};
712 if (fPyVals != nullptr) Py_DECREF(fPyVals); // delete previous object
714 if (!fPyVals)
715 Log() << kFATAL << "Failed to load data to Python array" << Endl;
717 }
718 // setup output variables (no need to do for multiple outputs- we call a different function)
719 if (fNOutputs > 0 && ( fOutput.size() != outputSize || fPyOutput == nullptr)) {
720 fOutput.resize(outputSize); // holds classification probabilities or regression output
721 // this is needed only for single-event evaluation
722 if (nEvents == 1) {
723 if (fPyOutput != nullptr) Py_DECREF(fPyOutput); // delete previous object
726 if (!fPyOutput)
727 Log() << kFATAL << "Failed to create output data Python array" << Endl;
729 }
730 }
731}
732
733
735 // Cannot determine error
737
738 // Check whether the model is setup
739 // NOTE: unfortunately this is needed because during evaluation ProcessOptions is not called again
741 // Setup the trained model
744 }
745
746 // Get signal probability (called mvaValue here)
747 const TMVA::Event* e = GetEvent();
748 for (UInt_t i=0; i<fNVars; i++) fVals[i] = e->GetValue(i);
749 int verbose = (int) Verbose();
750 std::string code = "for i,p in enumerate(model.predict(vals, verbose=" + ROOT::Math::Util::ToString(verbose)
751 + ")): output[i]=p\n";
752 PyRunString(code,"Failed to get predictions");
753
755}
756
757std::vector<Double_t> MethodPyKeras::GetMvaValues(Long64_t firstEvt, Long64_t lastEvt, Bool_t /*logProgress*/) {
758
759 // Check whether the model is setup
760 // NOTE: Unfortunately this is needed because during evaluation ProcessOptions is not called again
762 // Setup the trained model
764 }
765
766 // Load data to numpy array
767 Long64_t nEvents = Data()->GetNEvents();
768 if (firstEvt > lastEvt || lastEvt > nEvents) lastEvt = nEvents;
769 if (firstEvt < 0) firstEvt = 0;
770 nEvents = lastEvt-firstEvt;
771
772 InitEvaluation(nEvents);
773
774 assert (fVals.size() == fNVars*nEvents);
775 for (UInt_t i=0; i<nEvents; i++) {
776 Data()->SetCurrentEvent(i);
777 const TMVA::Event *e = GetEvent();
778 for (UInt_t j=0; j<fNVars; j++) {
779 fVals[j + i*fNVars] = e->GetValue(j);
780 }
781 }
782
783 std::vector<double> mvaValues(nEvents);
784
785 // Get prediction for all events
787 if (pModel==0) Log() << kFATAL << "Failed to get model Python object" << Endl;
788 PyArrayObject* pPredictions = (PyArrayObject*) PyObject_CallMethod(pModel, (char*)"predict", (char*)"O", fPyVals);
789 if (pPredictions==0) Log() << kFATAL << "Failed to get predictions" << Endl;
790
791 // Load predictions to double vector
792 // NOTE: The signal probability is given at the output
793 float* predictionsData = (float*) PyArray_DATA(pPredictions);
794
795 // need to loop on events since we take only the signal output of the two provided by Keras
796 for (UInt_t i=0; i<nEvents; i++) {
798 }
799
800 // we need to delete the PyArrayObjects
802
803 return mvaValues;
804}
805
806std::vector<Float_t>& MethodPyKeras::GetRegressionValues() {
807 // Check whether the model is setup
808 // NOTE: unfortunately this is needed because during evaluation ProcessOptions is not called again
810 // Setup the model and load weights
813 }
814
815 // Get regression values
816 const TMVA::Event* e = GetEvent();
817 for (UInt_t i=0; i<fNVars; i++) fVals[i] = e->GetValue(i);
818 int verbose = (int) Verbose();
819 std::string code = "for i,p in enumerate(model.predict(vals, verbose=" + ROOT::Math::Util::ToString(verbose)
820 + ")): output[i]=p\n";
821 PyRunString(code,"Failed to get predictions");
822
823 // Use inverse transformation of targets to get final regression values
824 Event eTrans(*e);
825 for (UInt_t i=0; i<fNOutputs; ++i) {
826 eTrans.SetTarget(i,fOutput[i]);
827 }
828
830 for (UInt_t i=0; i<fNOutputs; ++i) {
831 fOutput[i] = eTrans2->GetTarget(i);
832 }
833
834 return fOutput;
835}
836
838
839 // Check whether the model is setup
840 // NOTE: unfortunately this is needed because during evaluation ProcessOptions is not called again
842 // Setup the model and load weights
844 }
845
846 auto nEvents = Data()->GetNEvents();
847 InitEvaluation(nEvents);
848
849
850 assert (fVals.size() == fNVars*nEvents);
851 assert (fOutput.size() == fNOutputs*nEvents);
852 for (UInt_t i=0; i<nEvents; i++) {
853 Data()->SetCurrentEvent(i);
854 const TMVA::Event *e = GetEvent();
855 for (UInt_t j=0; j<fNVars; j++) {
856 fVals[j + i*fNVars] = e->GetValue(j);
857 }
858 }
859
860 // Get prediction for all events
862 if (pModel==0) Log() << kFATAL << "Failed to get model Python object" << Endl;
863 PyArrayObject* pPredictions = (PyArrayObject*) PyObject_CallMethod(pModel, (char*)"predict", (char*)"O", fPyVals);
864 if (pPredictions==0) Log() << kFATAL << "Failed to get predictions" << Endl;
865 // Load predictions to double vector
866 float* predictionsData = (float*) PyArray_DATA(pPredictions);
867
868 // need to loop on events since we use an inverse transformation to get final regression values
869 // this can be probably optimized
870 for (UInt_t ievt = 0; ievt < nEvents; ievt++) {
871 const TMVA::Event* e = GetEvent(ievt);
872 Event eTrans(*e);
873 for (UInt_t i = 0; i < fNOutputs; ++i) {
874 eTrans.SetTarget(i,predictionsData[ievt*fNOutputs + i]);
875 }
876 // apply the inverse transformation
878 for (UInt_t i = 0; i < fNOutputs; ++i) {
879 fOutput[ievt*fNOutputs + i] = eTrans2->GetTarget(i);
880 }
881 }
883 return fOutput;
884}
885
886
887
888std::vector<Float_t>& MethodPyKeras::GetMulticlassValues() {
889 // Check whether the model is setup
890 // NOTE: unfortunately this is needed because during evaluation ProcessOptions is not called again
892 // Setup the model and load weights
895 }
896 // Get class probabilites
897 const TMVA::Event* e = GetEvent();
898 for (UInt_t i=0; i<fNVars; i++) fVals[i] = e->GetValue(i);
899 int verbose = (int) Verbose();
900 std::string code = "for i,p in enumerate(model.predict(vals, verbose=" + ROOT::Math::Util::ToString(verbose)
901 + ")): output[i]=p\n";
902 PyRunString(code,"Failed to get predictions");
903
904 return fOutput;
905}
906
908 // Check whether the model is setup
909 // NOTE: unfortunately this is needed because during evaluation ProcessOptions is not called again
911 // Setup the model and load weights
913 }
914 auto nEvents = Data()->GetNEvents();
915 InitEvaluation(nEvents);
916
917 assert (fVals.size() == fNVars*nEvents);
918 assert (fOutput.size() == fNOutputs*nEvents);
919 for (UInt_t i=0; i<nEvents; i++) {
920 Data()->SetCurrentEvent(i);
921 const TMVA::Event *e = GetEvent();
922 for (UInt_t j=0; j<fNVars; j++) {
923 fVals[j + i*fNVars] = e->GetValue(j);
924 }
925 }
926
927 // Get prediction for all events
929 if (pModel==0) Log() << kFATAL << "Failed to get model Python object" << Endl;
930 PyArrayObject* pPredictions = (PyArrayObject*) PyObject_CallMethod(pModel, (char*)"predict", (char*)"O", fPyVals);
931 if (pPredictions==0) Log() << kFATAL << "Failed to get predictions" << Endl;
932 // Load predictions to double vector
933 float* predictionsData = (float*) PyArray_DATA(pPredictions);
934 std::copy(predictionsData, predictionsData+nEvents*fNOutputs, fOutput.begin());
935
937
938 return fOutput;
939}
940
943
945// typical length of text line:
946// "|--------------------------------------------------------------|"
947 Log() << Endl;
948 Log() << "Keras is a high-level API for the Theano and Tensorflow packages." << Endl;
949 Log() << "This method wraps the training and predictions steps of the Keras" << Endl;
950 Log() << "Python package for TMVA, so that dataloading, preprocessing and" << Endl;
951 Log() << "evaluation can be done within the TMVA system. To use this Keras" << Endl;
952 Log() << "interface, you have to generate a model with Keras first. Then," << Endl;
953 Log() << "this model can be loaded and trained in TMVA." << Endl;
954 Log() << Endl;
955}
956
958 // get the keras backend
959
960 // in case we use tf.keras backend is tensorflow
961 if (UseTFKeras()) return kTensorFlow;
962
963 // check first if using tensorflow backend
964 PyRunString("keras_backend_is_set = keras.backend.backend() == \"tensorflow\"");
965 PyObject * keras_backend = PyDict_GetItemString(fLocalNS,"keras_backend_is_set");
966 if (keras_backend != nullptr && keras_backend == Py_True)
967 return kTensorFlow;
968
969 PyRunString("keras_backend_is_set = keras.backend.backend() == \"theano\"");
970 keras_backend = PyDict_GetItemString(fLocalNS,"keras_backend_is_set");
971 if (keras_backend != nullptr && keras_backend == Py_True)
972 return kTheano;
973
974 PyRunString("keras_backend_is_set = keras.backend.backend() == \"cntk\"");
975 keras_backend = PyDict_GetItemString(fLocalNS,"keras_backend_is_set");
976 if (keras_backend != nullptr && keras_backend == Py_True)
977 return kCNTK;
978
979 return kUndefined;
980}
981
983 // get the keras backend name
985 if (type == kTensorFlow) return "TensorFlow";
986 if (type == kTheano) return "Theano";
987 if (type == kCNTK) return "CNTK";
988 return "Undefined";
989}
#define PyBytes_AsString
Definition CPyCppyy.h:64
#define REGISTER_METHOD(CLASS)
for example
_object PyObject
#define Py_single_input
#define e(i)
Definition RSha256.hxx:103
constexpr Bool_t kFALSE
Definition RtypesCore.h:108
long long Long64_t
Portable signed long integer 8 bytes.
Definition RtypesCore.h:83
constexpr Bool_t kTRUE
Definition RtypesCore.h:107
ROOT::Detail::TRangeCast< T, true > TRangeDynCast
TRangeDynCast is an adapter class that allows the typed iteration through a TCollection.
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t Int_t Int_t Window_t TString Int_t GCValues_t GetPrimarySelectionOwner GetDisplay GetScreen GetColormap GetNativeEvent const char const char dpyName wid window const char font_name cursor keysym reg const char only_if_exist regb h Point_t winding char text const char depth char const char Int_t count const char ColorStruct_t color const char Pixmap_t Pixmap_t PictureAttributes_t attr const char char ret_data h unsigned char height h Atom_t Int_t ULong_t ULong_t unsigned char prop_list Atom_t Atom_t Atom_t Time_t type
char name[80]
Definition TGX11.cxx:110
R__EXTERN TSystem * gSystem
Definition TSystem.h:572
OptionBase * DeclareOptionRef(T &ref, const TString &name, const TString &desc="")
MsgLogger & Log() const
Class that contains all the data information.
Definition DataSetInfo.h:62
UInt_t GetNClasses() const
UInt_t GetNTargets() const
Long64_t GetNEvents(Types::ETreeType type=Types::kMaxTreeType) const
Definition DataSet.h:206
Long64_t GetNTrainingEvents() const
Definition DataSet.h:68
void SetCurrentEvent(Long64_t ievt) const
Definition DataSet.h:88
const char * GetName() const override
Definition MethodBase.h:337
Bool_t Verbose() const
Definition MethodBase.h:506
Types::EAnalysisType GetAnalysisType() const
Definition MethodBase.h:440
const TString & GetWeightFileDir() const
Definition MethodBase.h:495
const Event * GetEvent() const
Definition MethodBase.h:754
DataSetInfo & DataInfo() const
Definition MethodBase.h:413
virtual void TestClassification()
initialization
UInt_t GetNVariables() const
Definition MethodBase.h:348
TransformationHandler & GetTransformationHandler(Bool_t takeReroutedIfAvailable=true)
Definition MethodBase.h:397
void NoErrorCalc(Double_t *const err, Double_t *const errUpper)
TrainingHistory fTrainHistory
Definition MethodBase.h:428
DataSet * Data() const
Definition MethodBase.h:412
const Event * GetTrainingEvent(Long64_t ievt) const
Definition MethodBase.h:774
std::vector< Float_t > GetAllRegressionValues() override
Get al regression values in one call.
void GetHelpMessage() const override
void ProcessOptions() override
Function processing the options This is called only when creating the method before training not when...
std::vector< float > fOutput
std::vector< Float_t > & GetRegressionValues() override
Bool_t UseTFKeras() const
EBackendType
enumeration defining the used Keras backend
void SetupKerasModel(Bool_t loadTrainedModel)
Double_t GetMvaValue(Double_t *errLower, Double_t *errUpper0) override
void DeclareOptions() override
UInt_t GetNumValidationSamples()
Validation of the ValidationSize option.
void TestClassification() override
initialization
void SetupKerasModelForEval()
Setting up model for evaluation Add here some needed optimizations like disabling eager execution.
std::vector< Float_t > GetAllMulticlassValues() override
Get all multi-class values.
std::vector< float > fVals
std::vector< Double_t > GetMvaValues(Long64_t firstEvt, Long64_t lastEvt, Bool_t logProgress) override
get all the MVA values for the events of the current Data type
MethodPyKeras(const TString &jobName, const TString &methodTitle, DataSetInfo &dsi, const TString &theOption="")
void Train() override
TString fLearningRateSchedule
std::vector< Float_t > & GetMulticlassValues() override
Bool_t HasAnalysisType(Types::EAnalysisType type, UInt_t numberClasses, UInt_t) override
void InitEvaluation(size_t nEvents)
void Init() override
Initialization function called from MethodBase::SetupMethod() Note that option string are not yet fil...
void ReadModelFromFile() override
EBackendType GetKerasBackend()
Get the Keras backend (can be: TensorFlow, Theano or CNTK)
Virtual base class for all TMVA method based on Python.
static int PyIsInitialized()
Check Python interpreter initialization status.
static PyObject * fGlobalNS
void PyRunString(TString code, TString errorMessage="Failed to run python code", int start=256)
Execute Python code from string.
void AddValue(TString Property, Int_t stage, Double_t value)
const Event * InverseTransform(const Event *, Bool_t suppressIfNoTargets=true) const
Singleton class for Global types used by TMVA.
Definition Types.h:71
@ kSignal
Never change this number - it is elsewhere assumed to be zero !
Definition Types.h:135
@ kMulticlass
Definition Types.h:129
@ kClassification
Definition Types.h:127
@ kRegression
Definition Types.h:128
@ kTraining
Definition Types.h:143
An array of TObjects.
Definition TObjArray.h:31
Basic string class.
Definition TString.h:138
@ kTrailing
Definition TString.h:284
TObjArray * Tokenize(const TString &delim) const
This function is used to isolate sequential tokens in a TString.
Definition TString.cxx:2270
Bool_t IsNull() const
Definition TString.h:422
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:2384
Bool_t Contains(const char *pat, ECaseCompare cmp=kExact) const
Definition TString.h:641
virtual const char * Getenv(const char *env)
Get environment variable.
Definition TSystem.cxx:1676
Double_t x[n]
Definition legend1.C:17
std::string ToString(const T &val)
Utility function for conversion to strings.
Definition Util.h:64
create variable transformations
MsgLogger & Endl(MsgLogger &ml)
Definition MsgLogger.h:148