"""Objective function for all training based hyperparameter optimizations."""importnumpyasnpfromoptunaimportTrial,TrialPrunedfrommala.network.hyperparameter_optunaimportHyperparameterOptunafrommala.network.hyperparameter_oatimportHyperparameterOATfrommala.network.networkimportNetworkfrommala.network.trainerimportTrainerfrommala.common.parametersimportParametersfrommalaimportprintout
[docs]classObjectiveBase:""" Represents the objective function of a training process. This is usually the result of a training of a network. Parameters ---------- params : mala.common.parametes.Parameters Parameters used to create this objective. data_handler : mala.datahandling.data_handler.DataHandler datahandler to be used during the hyperparameter optimization. """def__init__(self,params,data_handler):self.params:Parameters=paramsself._data_handler=data_handler# We need to find out if we have to reparametrize the lists with the# layers and the activations.contains_single_layer=any(map(lambdap:"ff_neurons_layer"inp.name,self.params.hyperparameters.hlist,))contains_multiple_layer_neurons=any(map(lambdap:"ff_multiple_layers_neurons"inp.name,self.params.hyperparameters.hlist,))contains_multiple_layers_count=any(map(lambdap:"ff_multiple_layers_count"inp.name,self.params.hyperparameters.hlist,))ifcontains_multiple_layer_neurons!=contains_multiple_layers_count:print("You selected multiple layers to be optimized, but either ""the range of neurons or number of layers is missing. ""This input will be ignored.")self._optimize_layer_list=contains_single_layeror(contains_multiple_layer_neuronsandcontains_multiple_layers_count)self._optimize_activation_list=list(map(lambdap:"layer_activation"inp.name,self.params.hyperparameters.hlist,)).count(True)self._trial_type=self.params.hyperparameters.hyper_opt_methoddef__call__(self,trial):""" Get objective value for a trial (=set of hyperparameters). Parameters ---------- trial : optuna.trial.Trial A trial is a set of hyperparameters; can be an optuna based trial or simply a OAT compatible list. """# Parse the parameters included in the trial.self.parse_trial(trial)if(self._trial_type=="optuna"andself.params.hyperparameters.pruner=="naswot"):iftrial.should_prune():raiseTrialPruned()# Train a network for as often as the user desires.final_validation_loss=[]foriinrange(0,self.params.hyperparameters.number_training_per_trial):test_network=Network(self.params)test_trainer=Trainer(self.params,test_network,self._data_handler)test_trainer.train_network()final_validation_loss.append(test_trainer.final_validation_loss)if(self._trial_type=="optuna"andself.params.hyperparameters.pruner=="multi_training"):# This is a little bit hacky, since report is actually# meant for values DURING training, but we instead# use it for one of the losses during multiple trainings.# It should not pose a problem though.trial.report(test_trainer.final_validation_loss,i)iftrial.should_prune():raiseTrialPruned()ifself.params.hyperparameters.number_training_per_trial>1:printout("Losses from multiple runs are: ",min_verbosity=2)printout(final_validation_loss,min_verbosity=2)ifself.params.hyperparameters.trial_ensemble_evaluation=="mean":returnnp.mean(final_validation_loss)elif(self.params.hyperparameters.trial_ensemble_evaluation=="mean_std"):mean=np.mean(final_validation_loss)# Cannot calculate the standar deviation of a bunch of infinities.ifnp.isinf(mean):returnmeanelse:returnnp.mean(final_validation_loss)+np.std(final_validation_loss)else:raiseException("No way to estimate the trial metric from ensemble"" training provided.")
[docs]defparse_trial(self,trial):""" Parse a trial into a network architecture. Parameters ---------- trial A trial is a set of hyperparameters; can be an optuna based trial or simply a OAT compatible list. """ifself._trial_type=="optuna":self.parse_trial_optuna(trial)elifself._trial_type=="oat":self.parse_trial_oat(trial)else:raiseException("Cannot parse trial, unknown hyperparameter"" optimization method.")
[docs]defparse_trial_optuna(self,trial:Trial):""" Parse an optuna style trial into the params attribute. Parameters ---------- trial : optuna.trial.Trial. A set of hyperparameters encoded by optuna. """ifself._optimize_layer_list:self.params.network.layer_sizes=[self._data_handler.input_dimension]ifself._optimize_activation_list>0:self.params.network.layer_activations=[]# Some layers may have been turned off by optuna.turned_off_layers=[]# This is one because of the input layer.layer_counter=1par:HyperparameterOptunaforparinself.params.hyperparameters.hlist:ifpar.name=="learning_rate":self.params.running.learning_rate=par.get_parameter(trial)# If the user wants to optimize multiple layers simultaneously,# we have to parse to parameters at the same time.elifpar.name=="ff_multiple_layers_neurons":neurons_per_layer=par.get_parameter(trial)number_layers=0max_number_layers=0other_par:HyperparameterOptunaforother_parinself.params.hyperparameters.hlist:ifother_par.name=="ff_multiple_layers_count":number_layers=other_par.get_parameter(trial)max_number_layers=max(other_par.choices)ifnumber_layers>0:foriinrange(0,number_layers):ifneurons_per_layer>0:self.params.network.layer_sizes.append(neurons_per_layer)else:turned_off_layers.append(layer_counter)layer_counter+=1ifnumber_layers!=max_number_layers:foriinrange(number_layers,max_number_layers):turned_off_layers.append(layer_counter)layer_counter+=1else:foriinrange(0,max_number_layers):turned_off_layers.append(layer_counter)layer_counter+=1elifpar.name=="ff_multiple_layers_count":# This is parsed directly abve.passelif"ff_neurons_layer"inpar.name:ifself.params.network.nn_type=="feed-forward":# Check for zero neuron layers; These indicate layers# that can be left out.layer_size=par.get_parameter(trial)iflayer_size>0:self.params.network.layer_sizes.append(par.get_parameter(trial))else:turned_off_layers.append(layer_counter)layer_counter+=1elif"optimizer"==par.name:self.params.running.optimizer=par.get_parameter(trial)elif"mini_batch_size"==par.name:self.params.running.mini_batch_size=par.get_parameter(trial)elif"early_stopping_epochs"==par.name:self.params.running.early_stopping_epochs=par.get_parameter(trial)elif"learning_rate_patience"==par.name:self.params.running.learning_rate_patience=par.get_parameter(trial)elif"learning_rate_decay"==par.name:self.params.running.learning_rate_decay=par.get_parameter(trial)elif"layer_activation"inpar.name:passelse:raiseException("Optimization of hyperparameter ",par.name,"not supported at the moment.",)# We have to process the activations separately, because they depend on# the results of the layer lists.layer_counter=0forparinself.params.hyperparameters.hlist:if"layer_activation"inpar.name:iflayer_counternotinturned_off_layers:self.params.network.layer_activations.append(par.get_parameter(trial))layer_counter+=1ifself._optimize_layer_list:self.params.network.layer_sizes.append(self._data_handler.output_dimension)
[docs]defparse_trial_oat(self,trial):""" Parse an OA based trial into the params attribute. Parameters ---------- trial : numpy.ndarray Row in an orthogonal array which respresents current trial. """ifself._optimize_layer_list:self.params.network.layer_sizes=[self._data_handler.input_dimension]ifself._optimize_activation_list:self.params.network.layer_activations=[]# Some layers may have been turned off by optuna.turned_off_layers=[]# This is one because of the input layer.layer_counter=1par:HyperparameterOATforfactor_idx,parinenumerate(self.params.hyperparameters.hlist):if"learning_rate"==par.name:self.params.running.learning_rate=par.get_parameter(trial,factor_idx)# If the user wants to optimize multiple layers simultaneously,# we have to parse to parameters at the same time.elifpar.name=="ff_multiple_layers_neurons":neurons_per_layer=par.get_parameter(trial,factor_idx)number_layers=0max_number_layers=0other_par:HyperparameterOATforother_idx,other_parinenumerate(self.params.hyperparameters.hlist):ifother_par.name=="ff_multiple_layers_count":number_layers=other_par.get_parameter(trial,other_idx)max_number_layers=max(other_par.choices)ifnumber_layers>0:foriinrange(0,number_layers):ifneurons_per_layer>0:self.params.network.layer_sizes.append(neurons_per_layer)else:turned_off_layers.append(layer_counter)layer_counter+=1ifnumber_layers!=max_number_layers:foriinrange(number_layers,max_number_layers):turned_off_layers.append(layer_counter)layer_counter+=1else:foriinrange(0,max_number_layers):turned_off_layers.append(layer_counter)layer_counter+=1elifpar.name=="ff_multiple_layers_count":# This is parsed directly abve.passelif"ff_neurons_layer"inpar.name:ifself.params.network.nn_type=="feed-forward":# Check for zero neuron layers; These indicate layers# that can be left out.layer_size=par.get_parameter(trial,factor_idx)iflayer_size>0:self.params.network.layer_sizes.append(par.get_parameter(trial,factor_idx))else:turned_off_layers.append(layer_counter)layer_counter+=1elif"optimizer"==par.name:self.params.running.optimizer=par.get_parameter(trial,factor_idx)elif"mini_batch_size"==par.name:self.params.running.mini_batch_size=par.get_parameter(trial,factor_idx)elif"early_stopping_epochs"==par.name:self.params.running.early_stopping_epochs=par.get_parameter(trial,factor_idx)elif"learning_rate_patience"==par.name:self.params.running.learning_rate_patience=par.get_parameter(trial,factor_idx)elif"learning_rate_decay"==par.name:self.params.running.learning_rate_decay=par.get_parameter(trial,factor_idx)elif"layer_activation"inpar.name:passelse:raiseException("Optimization of hyperparameter ",par.name,"not supported at the moment.",)# We have to process the activations separately, because they depend on# the results of the layer lists.layer_counter=0par:HyperparameterOATforfactor_idx,parinenumerate(self.params.hyperparameters.hlist):if"layer_activation"inpar.name:iflayer_counternotinturned_off_layers:self.params.network.layer_activations.append(par.get_parameter(trial,factor_idx))layer_counter+=1ifself._optimize_layer_list:self.params.network.layer_sizes.append(self._data_handler.output_dimension)