7#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
8#include <numpy/arrayobject.h>
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.");
96 "Write a log during training to visualize and monitor the training performance with TensorBoard");
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%)");
102 "Optional python code provided by the user to be executed before loading the Keras model");
115 Int_t nValidationSamples = 0;
129 <<
"\". Expected string like \"20%\" or \"20.0%\"." <<
Endl;
134 if (valSizeAsDouble < 1.0) {
139 nValidationSamples = valSizeAsDouble;
142 Log() << kFATAL <<
"Cannot parse number \"" <<
fNumValidationString <<
"\". Expected string like \"0.2\" or \"100\"."
148 if (nValidationSamples < 0) {
152 if (nValidationSamples == 0) {
156 if (nValidationSamples >= (
Int_t)trainingSetSize) {
158 <<
"\" is larger than or equal in size to training set (size=\"" << trainingSetSize <<
"\")." <<
Endl;
161 return nValidationSamples;
193 Log() << kINFO <<
"Setting up tf.keras" <<
Endl;
195 Log() << kINFO <<
"Setting up keras with " <<
gSystem->Getenv(
"KERAS_BACKEND") <<
" backend" <<
Endl;
197 bool useTFBackend =
kFALSE;
198 bool kerasIsCompatible =
kTRUE;
199 bool kerasIsPresent =
kFALSE;
206 kerasIsPresent =
kTRUE;
207 if (kerasIsPresent) {
210 useTFBackend =
kTRUE;
212 PyRunString(
"keras_major_version = int(keras.__version__.split('.')[0])");
213 PyRunString(
"keras_minor_version = int(keras.__version__.split('.')[1])");
214 PyObject *pyKerasMajorVersion = PyDict_GetItemString(
fLocalNS,
"keras_major_version");
215 PyObject *pyKerasMinorVersion = PyDict_GetItemString(
fLocalNS,
"keras_minor_version");
216 int kerasMajorVersion = PyLong_AsLong(pyKerasMajorVersion);
217 int kerasMinorVersion = PyLong_AsLong(pyKerasMinorVersion);
218 Log() << kINFO <<
"Using Keras version " << kerasMajorVersion <<
"." << kerasMinorVersion <<
Endl;
223 kerasIsCompatible = (kerasMajorVersion >= 2 && kerasMinorVersion == 3);
228 Log() << kINFO <<
"Keras is not found. Trying using tf.keras" <<
Endl;
237 if (
ret ==
nullptr) {
238 Log() << kFATAL <<
"Importing TensorFlow failed" <<
Endl;
241 PyRunString(
"tf_major_version = int(tf.__version__.split('.')[0])");
242 PyRunString(
"tf_minor_version = int(tf.__version__.split('.')[1])");
243 PyObject *pyTfMajorVersion = PyDict_GetItemString(
fLocalNS,
"tf_major_version");
244 PyObject *pyTfMinorVersion = PyDict_GetItemString(
fLocalNS,
"tf_minor_version");
245 int tfMajorVersion = PyLong_AsLong(pyTfMajorVersion);
246 int tfMinorVersion = PyLong_AsLong(pyTfMinorVersion);
247 Log() << kINFO <<
"Using TensorFlow version " << tfMajorVersion <<
"." << tfMinorVersion <<
Endl;
249 if (tfMajorVersion < 2) {
251 Log() << kWARNING <<
"Using TensorFlow version 1.x which does not contain tf.keras - use then TensorFlow as Keras backend" <<
Endl;
254 if (!kerasIsPresent) {
255 Log() << kFATAL <<
"Keras is not present and not a suitable TensorFlow version is found " <<
Endl;
262 if (!kerasIsCompatible) {
263 Log() << kWARNING <<
"The Keras version is not compatible with TensorFlow 2. Use instead tf.keras" <<
Endl;
266 if (tfMinorVersion >= 16) {
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;
278 Log() << kINFO <<
"Use Keras version from TensorFlow : tf.keras" <<
Endl;
284 Log() << kINFO <<
"Use TensorFlow as Keras backend" <<
Endl;
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);
291 if (tfMajorVersion <=2 && tfMinorVersion < 16) {
293 TString configProto = (tfMajorVersion >= 2) ?
"tf.compat.v1.ConfigProto" :
"tf.ConfigProto";
294 TString session = (tfMajorVersion >= 2) ?
"tf.compat.v1.Session" :
"tf.Session";
298 if (num_threads > 0) {
299 Log() << kINFO <<
"Setting the CPU number of threads = " << num_threads <<
Endl;
302 TString::Format(
"session_conf = %s(intra_op_parallelism_threads=%d,inter_op_parallelism_threads=%d)",
303 configProto.
Data(), num_threads, num_threads));
312 for (
int item = 0; item < optlist->
GetEntries(); ++item) {
313 Log() << kINFO <<
"Applying GPU option: gpu_options." << optlist->
At(item)->
GetName() <<
Endl;
319 if (tfMajorVersion < 2) {
322 PyRunString(
"tf.compat.v1.keras.backend.set_session(sess)");
333 for (
int item = 0; item < optlist->
GetEntries(); ++item) {
336 Log() << kINFO <<
"Applying GPU option: allow_growth=True " <<
Endl;
338 PyRunString(
"physical_devices = tf.config.list_physical_devices('GPU')");
339 PyRunString(
"tf.config.experimental.set_memory_growth(physical_devices[0], True)");
349 Log() << kWARNING <<
"Cannot set the given " <<
fNumThreads <<
" threads when not using tensorflow as backend"
352 Log() << kWARNING <<
"Cannot set the given GPU option " <<
fGpuOptions
353 <<
" when not using tensorflow as backend" <<
Endl;
364 Log() << kINFO <<
" Loading Keras Model " <<
Endl;
376 TString errmsg =
"Error executing the provided user code";
379 PyRunString(
"print('custom objects for loading model : ',load_model_custom_objects)");
384 if (loadTrainedModel) {
392 "', custom_objects=load_model_custom_objects)",
"Failed to load Keras model from file: " + filenameLoadModel);
394 Log() << kINFO <<
"Loaded model from file: " << filenameLoadModel <<
Endl;
405 else Log() << kFATAL <<
"Selected analysis type is not implemented" <<
Endl;
422 PyRunString(
"tf.compat.v1.disable_eager_execution()",
"Failed to disable eager execution");
423 Log() << kINFO <<
"Disabled TF eager execution when evaluating model " <<
Endl;
441 Log() << kFATAL <<
"Python is not initialized" <<
Endl;
446 PyRunString(
"import sys; sys.argv = ['']",
"Set sys.argv failed");
463 UInt_t nTrainingEvents = nAllEvents - nValEvents;
465 Log() << kINFO <<
"Split TMVA training data in " << nTrainingEvents <<
" training events and "
466 << nValEvents <<
" validation events" <<
Endl;
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++) {
474 for (UInt_t j=0; j<
fNVars; j++) {
475 trainDataX[j + i*
fNVars] =
e->GetValue(j);
488 trainDataY[j + i*
fNOutputs] =
e->GetTarget(j);
491 else Log() << kFATAL <<
"Can not fill target vector because analysis type is not known" <<
Endl;
494 trainDataWeights[i] =
e->GetWeight();
497 npy_intp dimsTrainX[2] = {(npy_intp)nTrainingEvents, (npy_intp)
fNVars};
498 npy_intp dimsTrainY[2] = {(npy_intp)nTrainingEvents, (npy_intp)
fNOutputs};
499 npy_intp dimsTrainWeights[1] = {(npy_intp)nTrainingEvents};
500 PyArrayObject* pTrainDataX = (PyArrayObject*)PyArray_SimpleNewFromData(2, dimsTrainX, NPY_FLOAT, (
void*)trainDataX);
501 PyArrayObject* pTrainDataY = (PyArrayObject*)PyArray_SimpleNewFromData(2, dimsTrainY, NPY_FLOAT, (
void*)trainDataY);
502 PyArrayObject* pTrainDataWeights = (PyArrayObject*)PyArray_SimpleNewFromData(1, dimsTrainWeights, NPY_FLOAT, (
void*)trainDataWeights);
505 PyDict_SetItemString(
fLocalNS,
"trainWeights", (
PyObject*)pTrainDataWeights);
515 float* valDataX =
new float[nValEvents*
fNVars];
516 float* valDataY =
new float[nValEvents*
fNOutputs];
517 float* valDataWeights =
new float[nValEvents];
519 for (UInt_t i=0; i< nValEvents ; i++) {
520 UInt_t ievt = nTrainingEvents + i;
523 for (UInt_t j=0; j<
fNVars; j++) {
524 valDataX[j + i*
fNVars] =
e->GetValue(j);
538 else Log() << kFATAL <<
"Can not fill target vector because analysis type is not known" <<
Endl;
540 valDataWeights[i] =
e->GetWeight();
543 npy_intp dimsValX[2] = {(npy_intp)nValEvents, (npy_intp)
fNVars};
544 npy_intp dimsValY[2] = {(npy_intp)nValEvents, (npy_intp)
fNOutputs};
545 npy_intp dimsValWeights[1] = {(npy_intp)nValEvents};
546 PyArrayObject* pValDataX = (PyArrayObject*)PyArray_SimpleNewFromData(2, dimsValX, NPY_FLOAT, (
void*)valDataX);
547 PyArrayObject* pValDataY = (PyArrayObject*)PyArray_SimpleNewFromData(2, dimsValY, NPY_FLOAT, (
void*)valDataY);
548 PyArrayObject* pValDataWeights = (PyArrayObject*)PyArray_SimpleNewFromData(1, dimsValWeights, NPY_FLOAT, (
void*)valDataWeights);
556 Log() << kINFO <<
"Training Model Summary" <<
Endl;
564 PyDict_SetItemString(
fLocalNS,
"batchSize", pBatchSize);
565 PyDict_SetItemString(
fLocalNS,
"numEpochs", pNumEpochs);
566 PyDict_SetItemString(
fLocalNS,
"verbose", pVerbose);
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;
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;
588 std::vector<std::pair<std::string, std::string>> scheduleSteps;
590 for (
auto obj : *lrSteps) {
593 if (!
x ||
x->GetEntries() != 2) {
594 Log() << kFATAL <<
"Invalid values given in LearningRateSchedule, it should be as \"10,0.1;20,0.01\""
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;
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) {
614 TString scheduleFunction =
"def schedule(epoch, lr):\n"
616 " " + epochsList +
"\n"
617 " " + valuesList +
"\n"
618 " for e in epochs:\n"
619 " if (epoch < e) :\n"
620 " return lrValues[i]\n"
627 "Failed to setup training callback: LearningRateSchedule");
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;
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");
646 std::vector<float> fHistory;
648 npy_intp dimsHistory[1] = { (npy_intp)
fNumEpochs};
649 PyArrayObject* pHistory = (PyArrayObject*)PyArray_SimpleNewFromData(1, dimsHistory, NPY_FLOAT, (
void*)&fHistory[0]);
654 PyRunString(
"number_of_keys=len(history.history.keys())");
656 int nkeys=PyLong_AsLong(PyNkeys);
657 for (iHis=0; iHis<nkeys; iHis++) {
663 PyObject* repr = PyObject_Repr(stra);
664 PyObject* str = PyUnicode_AsEncodedString(repr,
"utf-8",
"~E~");
667 Log() << kINFO <<
"Getting training history for item:" << iHis <<
" name = " <<
name <<
Endl;
670 for (
size_t i=0; i<fHistory.size(); i++)
693 delete[] trainDataWeights;
696 delete[] valDataWeights;
705 size_t inputSize =
fNVars*nEvents;
710 fVals.resize(inputSize);
711 npy_intp dimsVals[2] = {(npy_intp)nEvents, (npy_intp)
fNVars};
713 fPyVals = PyArray_SimpleNewFromData(2, dimsVals, NPY_FLOAT, (
void*)
fVals.data());
715 Log() << kFATAL <<
"Failed to load data to Python array" <<
Endl;
724 npy_intp dimsOutput[2] = {(npy_intp)1, (npy_intp)
fNOutputs};
725 fPyOutput = PyArray_SimpleNewFromData(2, dimsOutput, NPY_FLOAT, (
void*)
fOutput.data());
727 Log() << kFATAL <<
"Failed to create output data Python array" <<
Endl;
748 for (UInt_t i=0; i<
fNVars; i++)
fVals[i] =
e->GetValue(i);
751 +
")): output[i]=p\n";
768 if (firstEvt > lastEvt || lastEvt > nEvents) lastEvt = nEvents;
769 if (firstEvt < 0) firstEvt = 0;
770 nEvents = lastEvt-firstEvt;
775 for (UInt_t i=0; i<nEvents; i++) {
778 for (UInt_t j=0; j<
fNVars; j++) {
783 std::vector<double> mvaValues(nEvents);
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;
793 float* predictionsData = (
float*) PyArray_DATA(pPredictions);
796 for (UInt_t i=0; i<nEvents; i++) {
801 Py_DECREF(pPredictions);
817 for (UInt_t i=0; i<
fNVars; i++)
fVals[i] =
e->GetValue(i);
820 +
")): output[i]=p\n";
852 for (UInt_t i=0; i<nEvents; i++) {
855 for (UInt_t j=0; j<
fNVars; j++) {
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;
866 float* predictionsData = (
float*) PyArray_DATA(pPredictions);
870 for (UInt_t ievt = 0; ievt < nEvents; ievt++) {
882 Py_DECREF(pPredictions);
898 for (UInt_t i=0; i<
fNVars; i++)
fVals[i] =
e->GetValue(i);
901 +
")): output[i]=p\n";
919 for (UInt_t i=0; i<nEvents; i++) {
922 for (UInt_t j=0; j<
fNVars; j++) {
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;
933 float* predictionsData = (
float*) PyArray_DATA(pPredictions);
934 std::copy(predictionsData, predictionsData+nEvents*
fNOutputs,
fOutput.begin());
936 Py_DECREF(pPredictions);
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;
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)
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)
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)
986 if (type ==
kTheano)
return "Theano";
987 if (type ==
kCNTK)
return "CNTK";
#define REGISTER_METHOD(CLASS)
for example
int Int_t
Signed integer 4 bytes (int).
bool Bool_t
Boolean (0=false, 1=true) (bool).
double Double_t
Double 8 bytes.
OptionBase * DeclareOptionRef(T &ref, const TString &name, const TString &desc="")
Class that contains all the data information.
UInt_t GetNClasses() const
UInt_t GetNTargets() const
Long64_t GetNEvents(Types::ETreeType type=Types::kMaxTreeType) const
Long64_t GetNTrainingEvents() const
void SetCurrentEvent(Long64_t ievt) const
void SetTarget(UInt_t itgt, Float_t value)
set the target value (dimension itgt) to value
Float_t GetTarget(UInt_t itgt) const
PyGILState_STATE m_GILState
const char * GetName() const override
Types::EAnalysisType GetAnalysisType() const
const std::vector< TMVA::Event * > & GetEventCollection(Types::ETreeType type)
returns the event collection (i.e.
const TString & GetWeightFileDir() const
const Event * GetEvent() const
DataSetInfo & DataInfo() const
virtual void TestClassification()
initialization
UInt_t GetNVariables() const
TransformationHandler & GetTransformationHandler(Bool_t takeReroutedIfAvailable=true)
void NoErrorCalc(Double_t *const err, Double_t *const errUpper)
TrainingHistory fTrainHistory
const Event * GetTrainingEvent(Long64_t ievt) const
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
Int_t fTriesEarlyStopping
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.
bool fModelIsSetupForEval
TString fNumValidationString
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
TString GetKerasBackendName()
MethodPyKeras(const TString &jobName, const TString &methodTitle, DataSetInfo &dsi, const TString &theOption="")
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).
TString fFilenameTrainedModel
static int PyIsInitialized()
Check Python interpreter initialization status.
static PyObject * fGlobalNS
PyMethodBase(const TString &jobName, Types::EMVA methodType, const TString &methodTitle, DataSetInfo &dsi, const TString &theOption="")
void PyRunString(TString code, TString errorMessage="Failed to run python code", int start=256)
Execute Python code from string.
Singleton class for Global types used by TMVA.
@ kSignal
Never change this number - it is elsewhere assumed to be zero !
Int_t GetEntries() const override
Return the number of objects in array (i.e.
TObject * At(Int_t idx) const override
virtual const char * GetName() const
Returns name of object.
Bool_t IsFloat() const
Returns kTRUE if string contains a floating point or integer number.
const char * Data() const
TObjArray * Tokenize(const TString &delim) const
This function is used to isolate sequential tokens in a TString.
static TString Format(const char *fmt,...)
Static method which formats a string using a printf style format descriptor and return a TString.
void Form(const char *fmt,...)
Formats a string using a printf style format descriptor.
std::string ToString(const T &val)
Utility function for conversion to strings.
create variable transformations
MsgLogger & Endl(MsgLogger &ml)