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 "TSystem.h"
19#include "Math/Util.h"
20
21using namespace TMVA;
22
23namespace TMVA {
24namespace Internal {
25class PyGILRAII {
26 PyGILState_STATE m_GILState;
27
28public:
29 PyGILRAII() : m_GILState(PyGILState_Ensure()) {}
30 ~PyGILRAII() { PyGILState_Release(m_GILState); }
31};
32} // namespace Internal
33} // namespace TMVA
34
35REGISTER_METHOD(PyKeras)
36
38
39MethodPyKeras::MethodPyKeras(const TString &jobName, const TString &methodTitle, DataSetInfo &dsi, const TString &theOption)
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;
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
53MethodPyKeras::MethodPyKeras(DataSetInfo &theData, const TString &theWeightFile)
54 : PyMethodBase(Types::kPyKeras, theData, theWeightFile) {
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}
69
71 if (type == Types::kRegression) return kTRUE;
72 if (type == Types::kClassification && numberClasses == 2) return kTRUE;
73 if (type == Types::kMulticlass && numberClasses >= 2) return kTRUE;
74 return kFALSE;
75}
76
77///////////////////////////////////////////////////////////////////////////////
78
80 DeclareOptionRef(fFilenameModel, "FilenameModel", "Filename of the initial Keras model");
81 DeclareOptionRef(fFilenameTrainedModel, "FilenameTrainedModel", "Filename of the trained output Keras model");
82 DeclareOptionRef(fBatchSize, "BatchSize", "Training batch size");
83 DeclareOptionRef(fNumEpochs, "NumEpochs", "Number of training epochs");
84 DeclareOptionRef(fNumThreads, "NumThreads", "Number of CPU threads (only for Tensorflow backend)");
85 DeclareOptionRef(fGpuOptions, "GpuOptions", "GPU options for tensorflow, such as allow_growth");
86 DeclareOptionRef(fUseTFKeras, "tf.keras", "Use tensorflow from Keras");
87 DeclareOptionRef(fUseTFKeras, "tfkeras", "Use tensorflow from Keras");
88 DeclareOptionRef(fVerbose, "Verbose", "Keras verbosity during training");
89 DeclareOptionRef(fContinueTraining, "ContinueTraining", "Load weights from previous training");
90 DeclareOptionRef(fSaveBestOnly, "SaveBestOnly", "Store only weights with smallest validation loss");
91 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.");
92 DeclareOptionRef(fLearningRateSchedule, "LearningRateSchedule", "Set new learning rate during training at specific epochs, e.g., \"50,0.01;70,0.005\"");
93 DeclareOptionRef(fTensorBoard, "TensorBoard",
94 "Write a log during training to visualize and monitor the training performance with TensorBoard");
95
96 DeclareOptionRef(fNumValidationString = "20%", "ValidationSize", "Part of the training data to use for validation. "
97 "Specify as 0.2 or 20% to use a fifth of the data set as validation set. "
98 "Specify as 100 to use exactly 100 events. (Default: 20%)");
99 DeclareOptionRef(fUserCodeName = "", "UserCode",
100 "Optional python code provided by the user to be executed before loading the Keras model");
101}
102
103////////////////////////////////////////////////////////////////////////////////
104/// Validation of the ValidationSize option. Allowed formats are 20%, 0.2 and
105/// 100 etc.
106/// - 20% and 0.2 selects 20% of the training set as validation data.
107/// - 100 selects 100 events as the validation data.
108///
109/// @return number of samples in validation set
110///
112{
113 Int_t nValidationSamples = 0;
114 UInt_t trainingSetSize = GetEventCollection(Types::kTraining).size();
115
116 // Parsing + Validation
117 // --------------------
118 if (fNumValidationString.EndsWith("%")) {
119 // Relative spec. format 20%
120 TString intValStr = TString(fNumValidationString.Strip(TString::kTrailing, '%'));
121
122 if (intValStr.IsFloat()) {
123 Double_t valSizeAsDouble = fNumValidationString.Atof() / 100.0;
124 nValidationSamples = GetEventCollection(Types::kTraining).size() * valSizeAsDouble;
125 } else {
126 Log() << kFATAL << "Cannot parse number \"" << fNumValidationString
127 << "\". Expected string like \"20%\" or \"20.0%\"." << Endl;
128 }
129 } else if (fNumValidationString.IsFloat()) {
130 Double_t valSizeAsDouble = fNumValidationString.Atof();
131
132 if (valSizeAsDouble < 1.0) {
133 // Relative spec. format 0.2
134 nValidationSamples = GetEventCollection(Types::kTraining).size() * valSizeAsDouble;
135 } else {
136 // Absolute spec format 100 or 100.0
137 nValidationSamples = valSizeAsDouble;
138 }
139 } else {
140 Log() << kFATAL << "Cannot parse number \"" << fNumValidationString << "\". Expected string like \"0.2\" or \"100\"."
141 << Endl;
142 }
143
144 // Value validation
145 // ----------------
146 if (nValidationSamples < 0) {
147 Log() << kFATAL << "Validation size \"" << fNumValidationString << "\" is negative." << Endl;
148 }
149
150 if (nValidationSamples == 0) {
151 Log() << kFATAL << "Validation size \"" << fNumValidationString << "\" is zero." << Endl;
152 }
153
154 if (nValidationSamples >= (Int_t)trainingSetSize) {
155 Log() << kFATAL << "Validation size \"" << fNumValidationString
156 << "\" is larger than or equal in size to training set (size=\"" << trainingSetSize << "\")." << Endl;
157 }
158
159 return nValidationSamples;
160}
161
162/// Function processing the options
163/// This is called only when creating the method before training not when
164/// reading from XML file. Called from MethodBase::ProcessSetup
165/// that is called from Factory::BookMethod
167
168 // Set default filename for trained model if option is not used
170 fFilenameTrainedModel = GetWeightFileDir() + "/TrainedModel_" + GetName() + ".h5";
171 }
172
173 InitKeras();
174
175 // Setup model, either the initial model from `fFilenameModel` or
176 // the trained model from `fFilenameTrainedModel`
177 if (fContinueTraining) Log() << kINFO << "Continue training with trained model" << Endl;
179}
180
182 // initialize first Keras. This is done only here when class has
183 // all state variable set from options or read from XML file
184 // Import Keras
185
186 if (fUseTFKeras)
187 Log() << kINFO << "Setting up tf.keras" << Endl;
188 else
189 Log() << kINFO << "Setting up keras with " << gSystem->Getenv("KERAS_BACKEND") << " backend" << Endl;
190
191 bool useTFBackend = kFALSE;
192 bool kerasIsCompatible = kTRUE;
193 bool kerasIsPresent = kFALSE;
194
195 if (!fUseTFKeras) {
196 auto ret = PyRun_String("import keras", Py_single_input, fGlobalNS, fLocalNS);
197 // need importing also in global namespace
198 if (ret != nullptr) ret = PyRun_String("import keras", Py_single_input, fGlobalNS, fGlobalNS);
199 if (ret != nullptr)
200 kerasIsPresent = kTRUE;
201 if (kerasIsPresent) {
202 // check compatibility with tensorflow
203 if (GetKerasBackend() == kTensorFlow ) {
204 useTFBackend = kTRUE;
205
206 PyRunString("keras_major_version = int(keras.__version__.split('.')[0])");
207 PyRunString("keras_minor_version = int(keras.__version__.split('.')[1])");
208 PyObject *pyKerasMajorVersion = PyDict_GetItemString(fLocalNS, "keras_major_version");
209 PyObject *pyKerasMinorVersion = PyDict_GetItemString(fLocalNS, "keras_minor_version");
210 int kerasMajorVersion = PyLong_AsLong(pyKerasMajorVersion);
211 int kerasMinorVersion = PyLong_AsLong(pyKerasMinorVersion);
212 Log() << kINFO << "Using Keras version " << kerasMajorVersion << "." << kerasMinorVersion << Endl;
213 // only version 2.3 is latest multi-backend version.
214 // version 2.4 is just tf.keras and should not be used in standalone and will not work in this workflow
215 // see https://github.com/keras-team/keras/releases/tag/2.4.0
216 // for example variable keras.backend.tensorflow_backend will not exist anymore in keras 2.4
217 kerasIsCompatible = (kerasMajorVersion >= 2 && kerasMinorVersion == 3);
218
219 }
220 } else {
221 // Keras is not found. try tyo use tf.keras
222 Log() << kINFO << "Keras is not found. Trying using tf.keras" << Endl;
223 fUseTFKeras = 1;
224 }
225 }
226
227 // import Tensoprflow (if requested or because is keras backend)
228 if (fUseTFKeras || useTFBackend) {
229 auto ret = PyRun_String("import tensorflow as tf", Py_single_input, fGlobalNS, fLocalNS);
230 if (ret != nullptr) ret = PyRun_String("import tensorflow as tf", Py_single_input, fGlobalNS, fGlobalNS);
231 if (ret == nullptr) {
232 Log() << kFATAL << "Importing TensorFlow failed" << Endl;
233 }
234 // check tensorflow version
235 PyRunString("tf_major_version = int(tf.__version__.split('.')[0])");
236 PyObject *pyTfVersion = PyDict_GetItemString(fLocalNS, "tf_major_version");
237 int tfVersion = PyLong_AsLong(pyTfVersion);
238 Log() << kINFO << "Using TensorFlow version " << tfVersion << Endl;
239
240 if (tfVersion < 2) {
241 if (fUseTFKeras == 1) {
242 Log() << kWARNING << "Using TensorFlow version 1.x which does not contain tf.keras - use then TensorFlow as Keras backend" << Endl;
244 // case when Keras was not found
245 if (!kerasIsPresent) {
246 Log() << kFATAL << "Keras is not present and not a suitable TensorFlow version is found " << Endl;
247 return;
248 }
249 }
250 }
251 else {
252 // using version larger than 2.0 - can use tf.keras
253 if (!kerasIsCompatible) {
254 Log() << kWARNING << "The Keras version is not compatible with TensorFlow 2. Use instead tf.keras" << Endl;
255 fUseTFKeras = 1;
256 }
257 }
258
259 // if keras 2.3 and tensorflow 2 are found. Use tf.keras or keras ?
260 // at the moment default is tf.keras=false to keep compatibility
261 // but this might change in future releases
262 if (fUseTFKeras) {
263 Log() << kINFO << "Use Keras version from TensorFlow : tf.keras" << Endl;
264 fKerasString = "tf.keras";
265 PyRunString("K = tf.keras.backend");
266 PyRun_String("K = tf.keras.backend", Py_single_input, fGlobalNS, fGlobalNS);
267 }
268 else {
269 Log() << kINFO << "Use TensorFlow as Keras backend" << Endl;
270 fKerasString = "keras";
271 PyRunString("from keras.backend import tensorflow_backend as K");
272 PyRun_String("from keras.backend import tensorflow_backend as K", Py_single_input, fGlobalNS, fGlobalNS);
273 }
274
275 // extra options for tensorflow
276 // use different naming in tf2 for ConfigProto and Session
277 TString configProto = (tfVersion >= 2) ? "tf.compat.v1.ConfigProto" : "tf.ConfigProto";
278 TString session = (tfVersion >= 2) ? "tf.compat.v1.Session" : "tf.Session";
279
280 // in case specify number of threads
281 int num_threads = fNumThreads;
282 if (num_threads > 0) {
283 Log() << kINFO << "Setting the CPU number of threads = " << num_threads << Endl;
284
286 TString::Format("session_conf = %s(intra_op_parallelism_threads=%d,inter_op_parallelism_threads=%d)",
287 configProto.Data(), num_threads, num_threads));
288 } else
289 PyRunString(TString::Format("session_conf = %s()", configProto.Data()));
290
291 // applying GPU options such as allow_growth=True to avoid allocating all memory on GPU
292 // that prevents running later TMVA-GPU
293 // Also new Nvidia RTX cards (e.g. RTX 2070) require this option
294 if (!fGpuOptions.IsNull()) {
295 TObjArray *optlist = fGpuOptions.Tokenize(",");
296 for (int item = 0; item < optlist->GetEntries(); ++item) {
297 Log() << kINFO << "Applying GPU option: gpu_options." << optlist->At(item)->GetName() << Endl;
298 PyRunString(TString::Format("session_conf.gpu_options.%s", optlist->At(item)->GetName()));
299 }
300 }
301 PyRunString(TString::Format("sess = %s(config=session_conf)", session.Data()));
302
303 if (tfVersion < 2) {
304 PyRunString("K.set_session(sess)");
305 } else {
306 PyRunString("tf.compat.v1.keras.backend.set_session(sess)");
307 }
308 }
309 // case not using a Tensorflow backend
310 else {
311 fKerasString = "keras";
312 if (fNumThreads > 0)
313 Log() << kWARNING << "Cannot set the given " << fNumThreads << " threads when not using tensorflow as backend"
314 << Endl;
315 if (!fGpuOptions.IsNull()) {
316 Log() << kWARNING << "Cannot set the given GPU option " << fGpuOptions
317 << " when not using tensorflow as backend" << Endl;
318 }
319 }
320
321}
322
323void MethodPyKeras::SetupKerasModel(bool loadTrainedModel) {
324 /*
325 * Load Keras model from file
326 */
327
328 Log() << kINFO << " Loading Keras Model " << Endl;
329
330 PyRunString("load_model_custom_objects=None");
331
332
333
334 if (!fUserCodeName.IsNull()) {
335 Log() << kINFO << " Executing user initialization code from " << fUserCodeName << Endl;
336
337
338 // run some python code provided by user for model initialization if needed
339 TString cmd = "exec(open('" + fUserCodeName + "').read())";
340 TString errmsg = "Error executing the provided user code";
341 PyRunString(cmd, errmsg);
342
343 PyRunString("print('custom objects for loading model : ',load_model_custom_objects)");
344 }
345
346 // Load initial model or already trained model
347 TString filenameLoadModel;
348 if (loadTrainedModel) {
349 filenameLoadModel = fFilenameTrainedModel;
350 }
351 else {
352 filenameLoadModel = fFilenameModel;
353 }
354
355 PyRunString("model = " + fKerasString + ".models.load_model('" + filenameLoadModel +
356 "', custom_objects=load_model_custom_objects)", "Failed to load Keras model from file: " + filenameLoadModel);
357
358 Log() << kINFO << "Loaded model from file: " << filenameLoadModel << Endl;
359
360
361 /*
362 * Init variables and weights
363 */
364
365 // Get variables, classes and target numbers
369 else Log() << kFATAL << "Selected analysis type is not implemented" << Endl;
370
371 // Mark the model as setup
372 fModelIsSetup = true;
373 fModelIsSetupForEval = false;
374}
375
376///Setting up model for evaluation
377/// Add here some needed optimizations like disabling eager execution
379
380 InitKeras();
381
382 // disable eager execution (model will evaluate > 100 faster)
383 // need to be done before loading the model
384#ifndef R__MACOSX // problem siabling eager execution on Macos (conflict with multiprocessing)
385 if (fUseTFKeras){
386 PyRunString("tf.compat.v1.disable_eager_execution()","Failed to disable eager execution");
387 Log() << kINFO << "Disabled TF eager execution when evaluating model " << Endl;
388 }
389#endif
390
391 SetupKerasModel(true);
392
393 // Init evaluation (needed for getMvaValue)
394 if (fNVars > 0) {
395 fVals.resize(fNVars); // holds values used for classification and regression
396 npy_intp dimsVals[2] = {(npy_intp)1, (npy_intp)fNVars};
397 PyArrayObject* pVals = (PyArrayObject*)PyArray_SimpleNewFromData(2, dimsVals, NPY_FLOAT, (void*)fVals.data());
398 PyDict_SetItemString(fLocalNS, "vals", (PyObject*)pVals);
399 }
400 // setup output variables
401 if (fNOutputs > 0) {
402 fOutput.resize(fNOutputs); // holds classification probabilities or regression output
403 npy_intp dimsOutput[2] = {(npy_intp)1, (npy_intp)fNOutputs};
404 PyArrayObject* pOutput = (PyArrayObject*)PyArray_SimpleNewFromData(2, dimsOutput, NPY_FLOAT, (void*)fOutput.data());
405 PyDict_SetItemString(fLocalNS, "output", (PyObject*)pOutput);
406 }
407
409}
410
411/// Initialization function called from MethodBase::SetupMethod()
412/// Note that option string are not yet filled with their values.
413/// This is done before ProcessOption method or after reading from XML file
415
417
418 if (!PyIsInitialized()) {
419 Log() << kFATAL << "Python is not initialized" << Endl;
420 }
421 _import_array(); // required to use numpy arrays
422
423 // NOTE: sys.argv has to be cleared because otherwise TensorFlow breaks
424 PyRunString("import sys; sys.argv = ['']", "Set sys.argv failed");
425
426 // Set flag that model is not setup
427 fModelIsSetup = false;
428 fModelIsSetupForEval = false;
429}
430
432
433 if(!fModelIsSetup) Log() << kFATAL << "Model is not setup for training" << Endl;
434
435 /*
436 * Load training data to numpy array
437 */
438
439 UInt_t nAllEvents = Data()->GetNTrainingEvents();
440 UInt_t nValEvents = GetNumValidationSamples();
441 UInt_t nTrainingEvents = nAllEvents - nValEvents;
442
443 Log() << kINFO << "Split TMVA training data in " << nTrainingEvents << " training events and "
444 << nValEvents << " validation events" << Endl;
445
446 float* trainDataX = new float[nTrainingEvents*fNVars];
447 float* trainDataY = new float[nTrainingEvents*fNOutputs];
448 float* trainDataWeights = new float[nTrainingEvents];
449 for (UInt_t i=0; i<nTrainingEvents; i++) {
450 const TMVA::Event* e = GetTrainingEvent(i);
451 // Fill variables
452 for (UInt_t j=0; j<fNVars; j++) {
453 trainDataX[j + i*fNVars] = e->GetValue(j);
454 }
455 // Fill targets
456 // NOTE: For classification, convert class number in one-hot vector,
457 // e.g., 1 -> [0, 1] or 0 -> [1, 0] for binary classification
459 for (UInt_t j=0; j<fNOutputs; j++) {
460 trainDataY[j + i*fNOutputs] = 0;
461 }
462 trainDataY[e->GetClass() + i*fNOutputs] = 1;
463 }
464 else if (GetAnalysisType() == Types::kRegression) {
465 for (UInt_t j=0; j<fNOutputs; j++) {
466 trainDataY[j + i*fNOutputs] = e->GetTarget(j);
467 }
468 }
469 else Log() << kFATAL << "Can not fill target vector because analysis type is not known" << Endl;
470 // Fill weights
471 // NOTE: If no weight branch is given, this defaults to ones for all events
472 trainDataWeights[i] = e->GetWeight();
473 }
474
475 npy_intp dimsTrainX[2] = {(npy_intp)nTrainingEvents, (npy_intp)fNVars};
476 npy_intp dimsTrainY[2] = {(npy_intp)nTrainingEvents, (npy_intp)fNOutputs};
477 npy_intp dimsTrainWeights[1] = {(npy_intp)nTrainingEvents};
478 PyArrayObject* pTrainDataX = (PyArrayObject*)PyArray_SimpleNewFromData(2, dimsTrainX, NPY_FLOAT, (void*)trainDataX);
479 PyArrayObject* pTrainDataY = (PyArrayObject*)PyArray_SimpleNewFromData(2, dimsTrainY, NPY_FLOAT, (void*)trainDataY);
480 PyArrayObject* pTrainDataWeights = (PyArrayObject*)PyArray_SimpleNewFromData(1, dimsTrainWeights, NPY_FLOAT, (void*)trainDataWeights);
481 PyDict_SetItemString(fLocalNS, "trainX", (PyObject*)pTrainDataX);
482 PyDict_SetItemString(fLocalNS, "trainY", (PyObject*)pTrainDataY);
483 PyDict_SetItemString(fLocalNS, "trainWeights", (PyObject*)pTrainDataWeights);
484
485 /*
486 * Load validation data to numpy array
487 */
488
489 // NOTE: from TMVA, we get the validation data as a subset of all the training data
490 // we will not use test data for validation. They will be used for the real testing
491
492
493 float* valDataX = new float[nValEvents*fNVars];
494 float* valDataY = new float[nValEvents*fNOutputs];
495 float* valDataWeights = new float[nValEvents];
496 //validation events follows the trainig one in the TMVA training vector
497 for (UInt_t i=0; i< nValEvents ; i++) {
498 UInt_t ievt = nTrainingEvents + i; // TMVA event index
499 const TMVA::Event* e = GetTrainingEvent(ievt);
500 // Fill variables
501 for (UInt_t j=0; j<fNVars; j++) {
502 valDataX[j + i*fNVars] = e->GetValue(j);
503 }
504 // Fill targets
506 for (UInt_t j=0; j<fNOutputs; j++) {
507 valDataY[j + i*fNOutputs] = 0;
508 }
509 valDataY[e->GetClass() + i*fNOutputs] = 1;
510 }
511 else if (GetAnalysisType() == Types::kRegression) {
512 for (UInt_t j=0; j<fNOutputs; j++) {
513 valDataY[j + i*fNOutputs] = e->GetTarget(j);
514 }
515 }
516 else Log() << kFATAL << "Can not fill target vector because analysis type is not known" << Endl;
517 // Fill weights
518 valDataWeights[i] = e->GetWeight();
519 }
520
521 npy_intp dimsValX[2] = {(npy_intp)nValEvents, (npy_intp)fNVars};
522 npy_intp dimsValY[2] = {(npy_intp)nValEvents, (npy_intp)fNOutputs};
523 npy_intp dimsValWeights[1] = {(npy_intp)nValEvents};
524 PyArrayObject* pValDataX = (PyArrayObject*)PyArray_SimpleNewFromData(2, dimsValX, NPY_FLOAT, (void*)valDataX);
525 PyArrayObject* pValDataY = (PyArrayObject*)PyArray_SimpleNewFromData(2, dimsValY, NPY_FLOAT, (void*)valDataY);
526 PyArrayObject* pValDataWeights = (PyArrayObject*)PyArray_SimpleNewFromData(1, dimsValWeights, NPY_FLOAT, (void*)valDataWeights);
527 PyDict_SetItemString(fLocalNS, "valX", (PyObject*)pValDataX);
528 PyDict_SetItemString(fLocalNS, "valY", (PyObject*)pValDataY);
529 PyDict_SetItemString(fLocalNS, "valWeights", (PyObject*)pValDataWeights);
530
531 /*
532 * Train Keras model
533 */
534 Log() << kINFO << "Training Model Summary" << Endl;
535 PyRunString("model.summary()");
536
537 // Setup parameters
538
539 PyObject* pBatchSize = PyLong_FromLong(fBatchSize);
540 PyObject* pNumEpochs = PyLong_FromLong(fNumEpochs);
541 PyObject* pVerbose = PyLong_FromLong(fVerbose);
542 PyDict_SetItemString(fLocalNS, "batchSize", pBatchSize);
543 PyDict_SetItemString(fLocalNS, "numEpochs", pNumEpochs);
544 PyDict_SetItemString(fLocalNS, "verbose", pVerbose);
545
546 // Setup training callbacks
547 PyRunString("callbacks = []");
548
549 // Callback: Save only weights with smallest validation loss
550 if (fSaveBestOnly) {
551 PyRunString("callbacks.append(" + fKerasString +".callbacks.ModelCheckpoint('"+fFilenameTrainedModel+"', monitor='val_loss', verbose=verbose, save_best_only=True, mode='auto'))", "Failed to setup training callback: SaveBestOnly");
552 Log() << kINFO << "Option SaveBestOnly: Only model weights with smallest validation loss will be stored" << Endl;
553 }
554
555 // Callback: Stop training early if no improvement in validation loss is observed
556 if (fTriesEarlyStopping>=0) {
557 TString tries;
558 tries.Form("%i", fTriesEarlyStopping);
559 PyRunString("callbacks.append(" + fKerasString + ".callbacks.EarlyStopping(monitor='val_loss', patience="+tries+", verbose=verbose, mode='auto'))", "Failed to setup training callback: TriesEarlyStopping");
560 Log() << kINFO << "Option TriesEarlyStopping: Training will stop after " << tries << " number of epochs with no improvement of validation loss" << Endl;
561 }
562
563 // Callback: Learning rate scheduler
564 if (fLearningRateSchedule!="") {
565 // Setup a python dictionary with the desired learning rate steps
566 PyRunString("strScheduleSteps = '"+fLearningRateSchedule+"'\n"
567 "schedulerSteps = {}\n"
568 "for c in strScheduleSteps.split(';'):\n"
569 " x = c.split(',')\n"
570 " schedulerSteps[int(x[0])] = float(x[1])\n",
571 "Failed to setup steps for scheduler function from string: "+fLearningRateSchedule,
572 Py_file_input);
573 // Set scheduler function as piecewise function with given steps
574 PyRunString("def schedule(epoch, model=model, schedulerSteps=schedulerSteps):\n"
575 " if epoch in schedulerSteps: return float(schedulerSteps[epoch])\n"
576 " else: return float(model.optimizer.lr.get_value())\n",
577 "Failed to setup scheduler function with string: "+fLearningRateSchedule,
578 Py_file_input);
579 // Setup callback
580 PyRunString("callbacks.append(" + fKerasString + ".callbacks.LearningRateScheduler(schedule))",
581 "Failed to setup training callback: LearningRateSchedule");
582 Log() << kINFO << "Option LearningRateSchedule: Set learning rate during training: " << fLearningRateSchedule << Endl;
583 }
584
585 // Callback: TensorBoard
586 if (fTensorBoard != "") {
587 TString logdir = TString("'") + fTensorBoard + TString("'");
589 "callbacks.append(" + fKerasString + ".callbacks.TensorBoard(log_dir=" + logdir +
590 ", histogram_freq=0, batch_size=batchSize, write_graph=True, write_grads=False, write_images=False))",
591 "Failed to setup training callback: TensorBoard");
592 Log() << kINFO << "Option TensorBoard: Log files for training monitoring are stored in: " << logdir << Endl;
593 }
594
595 // Train model
596 PyRunString("history = model.fit(trainX, trainY, sample_weight=trainWeights, batch_size=batchSize, epochs=numEpochs, verbose=verbose, validation_data=(valX, valY, valWeights), callbacks=callbacks)",
597 "Failed to train model");
598
599
600 std::vector<float> fHistory; // Hold training history (val_acc or loss etc)
601 fHistory.resize(fNumEpochs); // holds training loss or accuracy output
602 npy_intp dimsHistory[1] = { (npy_intp)fNumEpochs};
603 PyArrayObject* pHistory = (PyArrayObject*)PyArray_SimpleNewFromData(1, dimsHistory, NPY_FLOAT, (void*)&fHistory[0]);
604 PyDict_SetItemString(fLocalNS, "HistoryOutput", (PyObject*)pHistory);
605
606 // Store training history data
607 Int_t iHis=0;
608 PyRunString("number_of_keys=len(history.history.keys())");
609 PyObject* PyNkeys=PyDict_GetItemString(fLocalNS, "number_of_keys");
610 int nkeys=PyLong_AsLong(PyNkeys);
611 for (iHis=0; iHis<nkeys; iHis++) {
612
613 PyRunString(TString::Format("copy_string=str(list(history.history.keys())[%d])",iHis));
614 PyObject* stra=PyDict_GetItemString(fLocalNS, "copy_string");
615 if(!stra) break;
616#if PY_MAJOR_VERSION < 3 // for Python2
617 const char *stra_name = PyBytes_AsString(stra);
618 // need to add string delimiter for Python2
619 TString sname = TString::Format("'%s'",stra_name);
620 const char * name = sname.Data();
621#else // for Python3
622 PyObject* repr = PyObject_Repr(stra);
623 PyObject* str = PyUnicode_AsEncodedString(repr, "utf-8", "~E~");
624 const char *name = PyBytes_AsString(str);
625#endif
626
627 Log() << kINFO << "Getting training history for item:" << iHis << " name = " << name << Endl;
628 PyRunString(TString::Format("for i,p in enumerate(history.history[%s]):\n HistoryOutput[i]=p\n",name),
629 TString::Format("Failed to get %s from training history",name));
630 for (size_t i=0; i<fHistory.size(); i++)
631 fTrainHistory.AddValue(name,i+1,fHistory[i]);
632
633 }
634//#endif
635
636 /*
637 * Store trained model to file (only if option 'SaveBestOnly' is NOT activated,
638 * because we do not want to override the best model checkpoint)
639 */
640
641 if (!fSaveBestOnly) {
642 PyRunString("model.save('"+fFilenameTrainedModel+"', overwrite=True)",
643 "Failed to save trained model: "+fFilenameTrainedModel);
644 Log() << kINFO << "Trained model written to file: " << fFilenameTrainedModel << Endl;
645 }
646
647 /*
648 * Clean-up
649 */
650
651 delete[] trainDataX;
652 delete[] trainDataY;
653 delete[] trainDataWeights;
654 delete[] valDataX;
655 delete[] valDataY;
656 delete[] valDataWeights;
657}
658
661}
662
664 // Cannot determine error
665 NoErrorCalc(errLower, errUpper);
666
667 // Check whether the model is setup
668 // NOTE: unfortunately this is needed because during evaluation ProcessOptions is not called again
670 // Setup the trained model
672 }
673
674 // Get signal probability (called mvaValue here)
675 const TMVA::Event* e = GetEvent();
676 for (UInt_t i=0; i<fNVars; i++) fVals[i] = e->GetValue(i);
677 int verbose = (int) Verbose();
678 std::string code = "for i,p in enumerate(model.predict(vals, verbose=" + ROOT::Math::Util::ToString(verbose)
679 + ")): output[i]=p\n";
680 PyRunString(code,"Failed to get predictions");
681
683}
684
685std::vector<Double_t> MethodPyKeras::GetMvaValues(Long64_t firstEvt, Long64_t lastEvt, Bool_t logProgress) {
686 // Check whether the model is setup
687 // NOTE: Unfortunately this is needed because during evaluation ProcessOptions is not called again
689 // Setup the trained model
691 }
692
693 // Load data to numpy array
694 Long64_t nEvents = Data()->GetNEvents();
695 if (firstEvt > lastEvt || lastEvt > nEvents) lastEvt = nEvents;
696 if (firstEvt < 0) firstEvt = 0;
697 nEvents = lastEvt-firstEvt;
698
699 // use timer
700 Timer timer( nEvents, GetName(), kTRUE );
701
702 if (logProgress)
703 Log() << kHEADER << Form("[%s] : ",DataInfo().GetName())
704 << "Evaluation of " << GetMethodName() << " on "
705 << (Data()->GetCurrentType() == Types::kTraining ? "training" : "testing")
706 << " sample (" << nEvents << " events)" << Endl;
707
708 float* data = new float[nEvents*fNVars];
709 for (UInt_t i=0; i<nEvents; i++) {
710 Data()->SetCurrentEvent(i);
711 const TMVA::Event *e = GetEvent();
712 for (UInt_t j=0; j<fNVars; j++) {
713 data[j + i*fNVars] = e->GetValue(j);
714 }
715 }
716
717 std::vector<double> mvaValues(nEvents);
718 npy_intp dimsData[2] = {(npy_intp)nEvents, (npy_intp)fNVars};
719 PyArrayObject* pDataMvaValues = (PyArrayObject*)PyArray_SimpleNewFromData(2, dimsData, NPY_FLOAT, (void*)data);
720 if (pDataMvaValues==0) Log() << "Failed to load data to Python array" << Endl;
721
722 // Get prediction for all events
723 PyObject* pModel = PyDict_GetItemString(fLocalNS, "model");
724 if (pModel==0) Log() << kFATAL << "Failed to get model Python object" << Endl;
725 PyArrayObject* pPredictions = (PyArrayObject*) PyObject_CallMethod(pModel, (char*)"predict", (char*)"O", pDataMvaValues);
726 if (pPredictions==0) Log() << kFATAL << "Failed to get predictions" << Endl;
727 delete[] data;
728 // Load predictions to double vector
729 // NOTE: The signal probability is given at the output
730 float* predictionsData = (float*) PyArray_DATA(pPredictions);
731
732 for (UInt_t i=0; i<nEvents; i++) {
733 mvaValues[i] = (double) predictionsData[i*fNOutputs + TMVA::Types::kSignal];
734 }
735
736 if (logProgress) {
737 Log() << kINFO
738 << "Elapsed time for evaluation of " << nEvents << " events: "
739 << timer.GetElapsedTime() << " " << Endl;
740 }
741
742
743 return mvaValues;
744}
745
746std::vector<Float_t>& MethodPyKeras::GetRegressionValues() {
747 // Check whether the model is setup
748 // NOTE: unfortunately this is needed because during evaluation ProcessOptions is not called again
750 // Setup the model and load weights
751 //std::cout << "setup model for evaluation" << std::endl;
752 //PyRunString("tf.compat.v1.disable_eager_execution()","Failed to disable eager execution");
754 }
755
756 // Get regression values
757 const TMVA::Event* e = GetEvent();
758 for (UInt_t i=0; i<fNVars; i++) fVals[i] = e->GetValue(i);
759 int verbose = (int) Verbose();
760 std::string code = "for i,p in enumerate(model.predict(vals, verbose=" + ROOT::Math::Util::ToString(verbose)
761 + ")): output[i]=p\n";
762 PyRunString(code,"Failed to get predictions");
763
764 // Use inverse transformation of targets to get final regression values
765 Event * eTrans = new Event(*e);
766 for (UInt_t i=0; i<fNOutputs; ++i) {
767 eTrans->SetTarget(i,fOutput[i]);
768 }
769
770 const Event* eTrans2 = GetTransformationHandler().InverseTransform(eTrans);
771 for (UInt_t i=0; i<fNOutputs; ++i) {
772 fOutput[i] = eTrans2->GetTarget(i);
773 }
774
775 return fOutput;
776}
777
778std::vector<Float_t>& MethodPyKeras::GetMulticlassValues() {
779 // Check whether the model is setup
780 // NOTE: unfortunately this is needed because during evaluation ProcessOptions is not called again
782 // Setup the model and load weights
784 }
785
786 // Get class probabilites
787 const TMVA::Event* e = GetEvent();
788 for (UInt_t i=0; i<fNVars; i++) fVals[i] = e->GetValue(i);
789 int verbose = (int) Verbose();
790 std::string code = "for i,p in enumerate(model.predict(vals, verbose=" + ROOT::Math::Util::ToString(verbose)
791 + ")): output[i]=p\n";
792 PyRunString(code,"Failed to get predictions");
793
794 return fOutput;
795}
796
798}
799
801// typical length of text line:
802// "|--------------------------------------------------------------|"
803 Log() << Endl;
804 Log() << "Keras is a high-level API for the Theano and Tensorflow packages." << Endl;
805 Log() << "This method wraps the training and predictions steps of the Keras" << Endl;
806 Log() << "Python package for TMVA, so that dataloading, preprocessing and" << Endl;
807 Log() << "evaluation can be done within the TMVA system. To use this Keras" << Endl;
808 Log() << "interface, you have to generate a model with Keras first. Then," << Endl;
809 Log() << "this model can be loaded and trained in TMVA." << Endl;
810 Log() << Endl;
811}
812
814 // get the keras backend
815
816 // in case we use tf.keras backend is tensorflow
817 if (UseTFKeras()) return kTensorFlow;
818
819 // check first if using tensorflow backend
820 PyRunString("keras_backend_is_set = keras.backend.backend() == \"tensorflow\"");
821 PyObject * keras_backend = PyDict_GetItemString(fLocalNS,"keras_backend_is_set");
822 if (keras_backend != nullptr && keras_backend == Py_True)
823 return kTensorFlow;
824
825 PyRunString("keras_backend_is_set = keras.backend.backend() == \"theano\"");
826 keras_backend = PyDict_GetItemString(fLocalNS,"keras_backend_is_set");
827 if (keras_backend != nullptr && keras_backend == Py_True)
828 return kTheano;
829
830 PyRunString("keras_backend_is_set = keras.backend.backend() == \"cntk\"");
831 keras_backend = PyDict_GetItemString(fLocalNS,"keras_backend_is_set");
832 if (keras_backend != nullptr && keras_backend == Py_True)
833 return kCNTK;
834
835 return kUndefined;
836}
837
839 // get the keras backend name
841 if (type == kTensorFlow) return "TensorFlow";
842 if (type == kTheano) return "Theano";
843 if (type == kCNTK) return "CNTK";
844 return "Undefined";
845}
#define PyBytes_AsString
Definition CPyCppyy.h:86
#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:101
long long Long64_t
Definition RtypesCore.h:80
constexpr Bool_t kTRUE
Definition RtypesCore.h:100
#define ClassImp(name)
Definition Rtypes.h:377
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void data
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
char * Form(const char *fmt,...)
Formats a string in a circular formatting buffer.
Definition TString.cxx:2467
R__EXTERN TSystem * gSystem
Definition TSystem.h:560
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
Types::ETreeType GetCurrentType() const
Definition DataSet.h:194
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
void SetTarget(UInt_t itgt, Float_t value)
set the target value (dimension itgt) to value
Definition Event.cxx:367
Float_t GetTarget(UInt_t itgt) const
Definition Event.h:102
Bool_t Verbose() const
Definition MethodBase.h:503
const char * GetName() const
Definition MethodBase.h:334
Types::EAnalysisType GetAnalysisType() const
Definition MethodBase.h:437
const TString & GetWeightFileDir() const
Definition MethodBase.h:492
const TString & GetMethodName() const
Definition MethodBase.h:331
const Event * GetEvent() const
Definition MethodBase.h:751
DataSetInfo & DataInfo() const
Definition MethodBase.h:410
virtual void TestClassification()
initialization
UInt_t GetNVariables() const
Definition MethodBase.h:345
TransformationHandler & GetTransformationHandler(Bool_t takeReroutedIfAvailable=true)
Definition MethodBase.h:394
void NoErrorCalc(Double_t *const err, Double_t *const errUpper)
TrainingHistory fTrainHistory
Definition MethodBase.h:425
DataSet * Data() const
Definition MethodBase.h:409
const Event * GetTrainingEvent(Long64_t ievt) const
Definition MethodBase.h:771
void GetHelpMessage() const
void Init()
Initialization function called from MethodBase::SetupMethod() Note that option string are not yet fil...
std::vector< float > fOutput
virtual void TestClassification()
initialization
void ProcessOptions()
Function processing the options This is called only when creating the method before training not when...
Bool_t UseTFKeras() const
EBackendType
enumeration defining the used Keras backend
void SetupKerasModel(Bool_t loadTrainedModel)
std::vector< Float_t > & GetMulticlassValues()
UInt_t GetNumValidationSamples()
Validation of the ValidationSize option.
Double_t GetMvaValue(Double_t *errLower, Double_t *errUpper)
void SetupKerasModelForEval()
Setting up model for evaluation Add here some needed optimizations like disabling eager execution.
std::vector< Float_t > & GetRegressionValues()
std::vector< float > fVals
Bool_t HasAnalysisType(Types::EAnalysisType type, UInt_t numberClasses, UInt_t)
MethodPyKeras(const TString &jobName, const TString &methodTitle, DataSetInfo &dsi, const TString &theOption="")
TString fLearningRateSchedule
EBackendType GetKerasBackend()
Get the Keras backend (can be: TensorFlow, Theano or CNTK)
std::vector< Double_t > GetMvaValues(Long64_t firstEvt, Long64_t lastEvt, Bool_t logProgress)
get all the MVA values for the events of the current Data type
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.
Timing information for training and evaluation of MVA methods.
Definition Timer.h:58
TString GetElapsedTime(Bool_t Scientific=kTRUE)
returns pretty string with elapsed time
Definition Timer.cxx:146
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
Int_t GetEntries() const override
Return the number of objects in array (i.e.
TObject * At(Int_t idx) const override
Definition TObjArray.h:164
virtual const char * GetName() const
Returns name of object.
Definition TObject.cxx:439
Basic string class.
Definition TString.h:139
Bool_t IsFloat() const
Returns kTRUE if string contains a floating point or integer number.
Definition TString.cxx:1836
const char * Data() const
Definition TString.h:380
@ kTrailing
Definition TString.h:278
TObjArray * Tokenize(const TString &delim) const
This function is used to isolate sequential tokens in a TString.
Definition TString.cxx:2242
Bool_t IsNull() const
Definition TString.h:418
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:2356
void Form(const char *fmt,...)
Formats a string using a printf style format descriptor.
Definition TString.cxx:2334
virtual const char * Getenv(const char *env)
Get environment variable.
Definition TSystem.cxx:1666
std::string ToString(const T &val)
Utility function for conversion to strings.
Definition Util.h:50
create variable transformations
MsgLogger & Endl(MsgLogger &ml)
Definition MsgLogger.h:148