Exemple #1
0
def get_offsprings(n, m, sig, lambd, sqrt_of_eig_vals, eig_vctrs, t,
                   benchmarkfunction, experiment_data, cma_np_rnd_generator):
    offspring_population = np.zeros((lambd, n))
    offspring_fitnesses = np.zeros(lambd)

    # decompose covariance matrix for sampling ("The CMA evolution strategy: a
    # tutorial, Hansen (2016), p. 28+29)
    # A = B*sqrt(M), with C = BMB^T (C=covariance matrix)
    A = np.matmul(eig_vctrs, sqrt_of_eig_vals)

    for k in range(lambd):
        z_k = cma_np_rnd_generator.normal(size=n)
        y_k = np.matmul(A, z_k)
        x_k = m + sig * y_k
        # x_k is equal to the following line but saves eigendecompositions:
        # m + sig * cma_np_rnd_generator.multivariate_normal(np.zeros(n), C)

        f_k = utils_dynopt.fitness(benchmarkfunction, x_k, t, experiment_data)

        offspring_population[k] = copy.copy(x_k)
        offspring_fitnesses[k] = f_k
        # if x_k is complex, f_k is unequal to fitness(offspring_population[k])
        # (28.8.19)
    return offspring_population, offspring_fitnesses
Exemple #2
0
    def __init__(self, benchmarkfunction, dim, n_generations, experiment_data,
                 predictor_name, pso_np_rnd_generator, pred_np_rnd_generator,
                 c1, c2, c3, insert_pred_as_ind, adaptive_c3, n_particles,
                 timesteps, n_neurons, epochs, batchsize, n_layers, apply_tl,
                 n_tllayers, tl_model_path, tl_learn_rate,
                 max_n_chperiod_reps):
        '''
        Initialize a DynamicPSO object.
        @param benchmarkfunction: (string)
        @param dim: (int) dimensionality of objective function, i.e. number of 
        features for each individual
        @param n_generations: (int) number of generations
        @param experiment_data: (dictionary)
        @param predictor_name: (string)
        @param pso_np_rnd_generator: numpy random generator for the PSO    
        @param pred_np_rnd_generator: numpy random generator for the predictor
        @param c1: (float) influence of particle's best solution
        @param c2: (float) influence of swarm's best solution
        @param c3: (float) influence of prediction
        @param insert_pred_as_ind: (bool) True if the predicted optimum should
        be inserted into the swarm
        @param adaptive_c3: (bool) True if c3 is set with adaptive control
        @param n_particles: (int) swarm size
        @param time_steps: (int) number of time steps the predictions use for the
        prediction
        @param n_neurons: (int) number of neurons within the first layer of the 
        RNN prediction model
        @param epochs: (int) number of epochs to train the RNN predicton model
        @param batch_size: (int) batch size for the RNN predictor

        '''
        # ---------------------------------------------------------------------
        # for the problem
        # ---------------------------------------------------------------------
        self.benchmarkfunction = benchmarkfunction
        self.dim = dim
        self.n_generations = n_generations
        self.experiment_data = experiment_data
        self.predictor_name = predictor_name

        # ---------------------------------------------------------------------
        # for the predictor
        # ---------------------------------------------------------------------
        self.n_time_steps = timesteps
        self.n_neurons = n_neurons
        self.n_epochs = epochs
        self.batch_size = batchsize
        self.n_layers = n_layers
        self.apply_tl = apply_tl
        self.tl_model_path = tl_model_path
        self.n_tllayers = n_tllayers

        # ---------------------------------------------------------------------
        # for PSO (fixed values)
        # ---------------------------------------------------------------------
        self.pso_np_rnd_generator = pso_np_rnd_generator
        self.pred_np_rnd_generator = pred_np_rnd_generator
        self.c1 = c1
        self.c2 = c2
        self.c3 = c3
        self.insert_pred_as_ind = insert_pred_as_ind
        self.adaptive_c3 = adaptive_c3
        self.n_particles = n_particles

        # ---------------------------------------------------------------------
        # values that are not passed as parameters to the constructor
        # ---------------------------------------------------------------------
        self.init_c1 = self.c1
        self.init_c2 = self.c2
        self.init_c3 = self.c3

        # TODO(exe) set following parameters as desired
        self.init_inertia = 0.7298
        self.inertia = self.init_inertia
        self.vmax = 1000.0  # must not be too small
        self.inertia_adapt_factor = 0.5  # like tau for Rechenberg
        self.inertia_min = 0.1
        self.inertia_max = 0.78540

        # ---------------------------------------------------------------------
        # for PSO (variable values)
        # ---------------------------------------------------------------------

        # initialize population and compute fitness.
        # np.random.rand has values in [0, 1). Therefore multiply with 100 for
        # larger values (one row for each particle)
        self.particles = self.pso_np_rnd_generator.rand(
            self.n_particles, self.dim) * 100  # TODO(exe)
        self.v_vals = self.pso_np_rnd_generator.rand(self.n_particles,
                                                     self.dim)
        # 1d numpy array
        self.fitness_vals = np.array([
            utils_dynopt.fitness(self.benchmarkfunction, p, 0,
                                 self.experiment_data) for p in self.particles
        ])
        # best history value (position) for each particle
        # (2d numpy array: 1 row for each particle)
        self.p_best_pos = copy.copy(self.particles)
        self.p_best_fit = copy.copy(self.fitness_vals)
        # best history value (position) of whole swarm (1d numpy array: 1 row)
        self.s_best_pos = copy.copy(self.particles[np.argmin(
            self.fitness_vals)])
        self.s_best_fit = np.min(self.fitness_vals)

        # ---------------------------------------------------------------------
        # for PSO (prediction and evaluation)
        # ---------------------------------------------------------------------

        # number of change period detected by the PSO
        self.detected_n_changes = 0
        # 1d numpy array containing for each generation the number of the
        # detected change period it belongs to
        self.detected_chgperiods_for_gens = []
        # storage for best fitness evaluation of each generation
        self.best_found_pos_per_gen = np.zeros((self.n_generations, self.dim))
        self.best_found_fit_per_gen = np.zeros(self.n_generations)
        # position & fitness of found optima (one for each change period)
        self.best_found_pos_per_chgperiod = []
        self.best_found_fit_per_chgperiod = []
        # position & fitness of predicted optima (one for each change period)
        self.pred_opt_pos_per_chgperiod = []
        self.pred_opt_fit_per_chgperiod = []
Exemple #3
0
    def optimize(self):
        '''
        @param mu: population size
        @param la: lambda (offspring population size)
        @param dim: dimensionality of objective function, i.e. number of features for each individual
        @param ro: number parents for recombination
        @param sel_strategie: selection strategy: 'plus' or 'comma'
        @param t_rechenberg: number of mutations after which sigma is adapted
        @param tau: # 0 < tau < 1, for Rechenberg
        @return: best fitness occured in any iteration
        '''
        # ---------------------------------------------------------------------
        # local variables for predictor
        # ---------------------------------------------------------------------
        train_data = []
        n_features = self.dim
        predictor = build_predictor(self.predictor_name, self.n_time_steps,
                                    n_features, self.batch_size,
                                    self.n_neurons)
        # denotes whether the predictor has been trained or not
        trained_first_time = False
        scaler = MyMinMaxScaler(feature_range=(-1, 1))

        # ---------------------------------------------------------------------
        # local variables for PSO
        # ---------------------------------------------------------------------
        # number of particles that are better than p_best after particle update
        n_succ_particles = 0

        # ---------------------------------------------------------------------
        for i in range(self.n_generations):
            print("generation: ", i)
            # adapt inertia weight
            self.adapt_inertia(n_succ_particles)
            # reset n_succ_particles after inertia adaptation
            n_succ_particles = 0

            # test for environment change
            env_changed = environment_changed(i, self.particles,
                                              self.fitness_vals,
                                              self.benchmarkfunction,
                                              self.experiment_data,
                                              self.pso_np_rnd_generator)

            # iteration number in which the last change was detected
            last_it_with_chg = 0
            if env_changed and i != 0:
                print("changed")
                # set c1, c2, c3 and inertia to their initial values
                self.reset_factors()

                # count change
                self.detected_n_changes += 1
                last_it_with_chg = i

                # store best found solution during change period as training data for predictor
                # TODO(dev) works only for plus-selection (not for
                # comma-selection)
                self.best_found_pos_per_chgperiod.append(
                    copy.copy(self.best_found_pos_per_gen[i - 1]))
                self.best_found_fit_per_chgperiod.append(
                    copy.copy(self.best_found_fit_per_gen[i - 1]))
                overall_n_train_data = len(self.best_found_pos_per_chgperiod)

                # prevent training with too few train data
                if overall_n_train_data <= self.n_time_steps or overall_n_train_data < 50 or self.predictor_name == "no":
                    my_pred_mode = "no"
                    # train_data is empty list
                    train_data = None
                    prediction = None
                else:
                    my_pred_mode = self.predictor_name

                    # scale data
                    scaler = scaler.fit(self.best_found_pos_per_chgperiod)
                    transf_best_found_pos_per_chgperiod = scaler.transform(
                        copy.copy(self.best_found_pos_per_chgperiod))

                    # choose training data
                    if not trained_first_time:
                        # first time, has not been trained before, therefore use all
                        # found optimum positions
                        trained_first_time = True
                        train_data = transf_best_found_pos_per_chgperiod
                    else:
                        # append the last new train data (one) and in addition
                        # n_time_steps already evaluated data in order to create a
                        # whole time series of n_time_steps together with the new
                        # data
                        train_data = []
                        for step_idx in range(self.n_time_steps + 1, 0, -1):
                            train_data.append(
                                transf_best_found_pos_per_chgperiod[-step_idx])
                        train_data = np.array(train_data)
                    # predict next optimum position
                    prediction = predict_next_optimum_position(
                        my_pred_mode, train_data, self.n_epochs,
                        self.batch_size, self.n_time_steps, n_features, scaler,
                        predictor, self.pred_np_rnd_generator)
                    self.pred_opt_pos_per_chgperiod.append(
                        copy.copy(prediction))
                    self.pred_opt_fit_per_chgperiod.append(
                        utils_dynopt.fitness(self.benchmarkfunction,
                                             prediction, i,
                                             self.experiment_data))

                # compute fitness again since fitness function changed
                self.fitness_vals = np.array([
                    utils_dynopt.fitness(self.benchmarkfunction, p, i,
                                         self.experiment_data)
                    for p in self.particles
                ])
                # reset p_best to current position
                self.p_best_pos = copy.copy(self.particles)
                self.p_best_fit = copy.copy(self.fitness_vals)
                # update s_best
                min_p_best_ind = np.argmin(self.p_best_fit)
                self.s_best_pos = copy.copy(self.p_best_pos[min_p_best_ind])
                self.s_best_fit = self.p_best_fit[min_p_best_ind]

                # adapt population (replace worst by random individuals)
                if self.insert_pred_as_ind:
                    pred_to_insert = copy.copy(prediction)
                else:
                    pred_to_insert = None
                self.particles, self.fitness_vals = replace_worst_individuals(
                    self.pso_np_rnd_generator, self.benchmarkfunction, i,
                    self.particles, self.fitness_vals, self.n_particles,
                    n_features, self.experiment_data, pred_to_insert)

                if prediction is None:
                    self.p_pred_diff_vals = np.zeros(
                        (self.n_particles, self.dim))
                else:
                    self.p_pred_diff_vals = prediction - self.particles
            else:
                self.p_pred_diff_vals = np.zeros((self.n_particles, self.dim))

            # store which change period the current generation belongs to
            self.detected_chgperiods_for_gens.append(self.detected_n_changes)
            # random values
            # TODO new values in every iteration?
            r1_vals = self.pso_np_rnd_generator.rand(self.n_particles,
                                                     self.dim)
            r2_vals = self.pso_np_rnd_generator.rand(self.n_particles,
                                                     self.dim)
            r3_vals = self.pso_np_rnd_generator.rand(self.n_particles,
                                                     self.dim)

            # update velocity
            self.s_best_diff_vals = self.s_best_pos - self.particles
            self.p_best_diff_vals = self.p_best_pos - self.particles

            # decrease importance of prediction with increasing #iterations
            try:
                c3 = self.c3 / (i - last_it_with_chg)
            except ZeroDivisionError:  # if i == last_it_with_chg
                c3 = self.c3
            c3 = self.c3

            tmp1 = self.inertia * self.v_vals
            tmp2 = self.c1 * r1_vals * self.p_best_diff_vals
            tmp3 = self.c2 * r2_vals * self.s_best_diff_vals
            tmp4 = c3 * r3_vals * self.p_pred_diff_vals
            self.v_vals = tmp1 + tmp2 + tmp3 + tmp4

            # velocity should be <= vmax
            too_large = np.abs(self.v_vals) > self.vmax
            self.v_vals[too_large] = (np.sign(self.v_vals) *
                                      self.vmax)[too_large]

            # update particles
            self.particles = self.particles + self.v_vals

            # compute fitness
            self.fitness_vals = np.array([
                utils_dynopt.fitness(self.benchmarkfunction, p, i,
                                     self.experiment_data)
                for p in self.particles
            ])

            # update p_best
            particle_better = self.fitness_vals < self.p_best_fit
            n_succ_particles = np.sum(particle_better)
            self.p_best_pos[particle_better] = copy.copy(
                self.particles[particle_better])
            self.p_best_fit[particle_better] = self.fitness_vals[
                particle_better]
            # update s_best
            self.s_best_pos = copy.copy(self.p_best_pos[np.argmin(
                self.p_best_fit)])
            self.s_best_fit = np.min(self.p_best_fit)

            # determine best particles/fitness (for evaluation/statistic)
            self.best_found_fit_per_gen[i] = copy.copy(self.s_best_fit)
            self.best_found_pos_per_gen[i] = copy.copy(self.s_best_pos)

            # adapt c3
            if self.adaptive_c3:
                self.adapt_c3()
        # store things last time
        self.best_found_pos_per_chgperiod.append(
            copy.copy(self.best_found_pos_per_gen[self.n_generations - 1]))
        self.best_found_fit_per_chgperiod.append(
            copy.copy(self.best_found_fit_per_gen[self.n_generations - 1]))

        # conversion
        self.best_found_pos_per_chgperiod = np.array(
            self.best_found_pos_per_chgperiod)
        self.pred_opt_pos_per_chgperiod = np.array(
            self.pred_opt_pos_per_chgperiod)
def prepare_data_train_and_predict(
        sess, gen_idx, n_features, predictors, experiment_data, n_epochs,
        batch_size, return_seq, shuffle_train_data, n_new_train_data,
        best_found_pos_per_chgperiod, train_interval, predict_diffs,
        n_time_steps, n_required_train_data, predictor_name,
        add_noisy_train_data, n_noisy_series, stddev_among_runs_per_chgp,
        test_mc_runs, benchmarkfunction, use_uncs, pred_unc_per_chgperiod,
        aleat_unc_per_chgperiod, pred_opt_pos_per_chgperiod,
        pred_opt_fit_per_chgperiod, train_error_per_chgperiod,
        train_error_for_epochs_per_chgperiod, glob_opt, trueprednoise,
        pred_np_rnd_generator):
    '''
    TODO use this function in dynpso
    '''
    n_past_chgps = len(best_found_pos_per_chgperiod)
    # number of train data that can be produced from the last chg. periods
    overall_n_train_data = calculate_n_train_samples(n_past_chgps,
                                                     predict_diffs,
                                                     n_time_steps)

    # prevent training with too few train data
    if (overall_n_train_data < n_required_train_data
            or predictor_name == "no"):
        my_pred_mode = "no"
        train_data = None
        prediction = None

    else:
        my_pred_mode = predictor_name

        # number of required change periods (to construct training data)
        n_required_chgps = calculate_n_required_chgps_from_n_train_samples(
            n_required_train_data, predict_diffs, n_time_steps)
        best_found_vals_per_chgperiod = best_found_pos_per_chgperiod[
            -n_required_chgps:]

        # transform absolute values to differences
        if predict_diffs:
            best_found_vals_per_chgperiod = np.subtract(
                best_found_vals_per_chgperiod[1:],
                best_found_vals_per_chgperiod[:-1])

        # scale data (the data are re-scaled directly after the
        # prediction in this iteration)
        scaler = fit_scaler(best_found_vals_per_chgperiod)
        train_data = scaler.transform(copy.copy(best_found_vals_per_chgperiod))

        # add noisy training data in order to make network more robust and
        # increase the number of training data
        if add_noisy_train_data:
            # 3d array [n_series, n_chgperiods, dims]
            noisy_series = get_noisy_time_series(
                np.array(best_found_pos_per_chgperiod), n_noisy_series,
                stddev_among_runs_per_chgp, pred_np_rnd_generator)
            if predict_diffs:
                noisy_series = np.array([
                    np.subtract(noisy_series[i, 1:], noisy_series[i, :-1])
                    for i in range(len(noisy_series))
                ])
            # scale data
            noisy_series = np.array([
                scaler.transform(copy.copy(noisy_series[i]))
                for i in range(len(noisy_series))
            ])
        else:
            noisy_series = None

        # train data
        train_data = np.array(train_data)
        # train the model only when train_interval new data are available
        do_training = n_new_train_data >= train_interval
        if do_training:
            n_new_train_data = 0
        # predict next optimum position or difference (and re-scale value)

        prdctns = []
        prdctns_fit = []
        pred_unc_per_predictor = []
        avg_al_unc_per_predictor = []
        train_error_per_predictor = []
        train_err_per_epoch_per_predictor = []
        prdct_nms = []
        for prdctr_name in predictors.keys():
            # make prediction with each single predictor
            (prdctn, train_error, train_err_per_epoch, pred_unc, avg_al_unc,
             ar_predictor) = predict_next_optimum_position(
                 prdctr_name, sess, train_data, noisy_series, n_epochs,
                 batch_size, n_time_steps, n_features, scaler,
                 predictors[prdctr_name], return_seq, shuffle_train_data,
                 do_training, best_found_pos_per_chgperiod, predict_diffs,
                 test_mc_runs, n_new_train_data, glob_opt, trueprednoise,
                 pred_np_rnd_generator)
            prdctns.append(prdctn)
            prdctns_fit.append(
                utils_dynopt.fitness(benchmarkfunction, prdctn, gen_idx,
                                     experiment_data))
            pred_unc_per_predictor.append(pred_unc)
            avg_al_unc_per_predictor.append(avg_al_unc)
            train_error_per_predictor.append(train_error)
            train_err_per_epoch_per_predictor.append(train_err_per_epoch)
            prdct_nms.append(prdctr_name)
            if prdctr_name == "autoregressive":
                predictors[prdctr_name] = ar_predictor

        # index of best prediction
        min_idx = np.argmin(prdctns_fit)
        prediction = prdctns[min_idx]
        prediction_fit = prdctns_fit[min_idx]
        pred_unc = pred_unc_per_predictor[min_idx]
        avg_al_unc = avg_al_unc_per_predictor[min_idx]
        train_error = train_error_per_predictor[min_idx]
        train_err_per_epoch = train_err_per_epoch_per_predictor[min_idx]

        # store results
        pred_opt_pos_per_chgperiod.append(copy.copy(prediction))
        pred_opt_fit_per_chgperiod.append(prediction_fit)
        if pred_unc is not None and use_uncs:
            pred_unc_per_chgperiod.append(copy.copy(pred_unc))
        if avg_al_unc is not None and use_uncs:
            aleat_unc_per_chgperiod.append(copy.copy(avg_al_unc))
        train_error_per_chgperiod.append(train_error)
        train_error_for_epochs_per_chgperiod.append(train_err_per_epoch)
    return my_pred_mode, predictors, n_new_train_data
Exemple #5
0
    def __init__(self, benchmarkfunction, dim, lenchgperiod, n_generations,
                 experiment_data, predictor_name, trueprednoise, lbound,
                 ubound, cma_np_rnd_generator, pred_np_rnd_generator,
                 timesteps, n_neurons, epochs, batchsize, n_layers,
                 train_interval, n_required_train_data, use_uncs,
                 train_mc_runs, test_mc_runs, train_dropout, test_dropout,
                 kernel_size, n_kernels, lr, cma_variant, pred_variant):
        '''
        Constructor
        '''
        # TODOs:
        # - alle "Speichervariablen" vom EA nutzen, damit Ausgabedateien identisch sind
        # ---------------------------------------------------------------------
        # for the problem
        # ---------------------------------------------------------------------
        self.benchmarkfunction = benchmarkfunction
        self.dim = dim
        self.lenchgperiod = lenchgperiod
        self.n_generations = n_generations  # TODO rename
        self.experiment_data = experiment_data
        self.predictor_name = predictor_name
        self.trueprednoise = trueprednoise

        self.lbound = lbound  # 100  # assumed that the input data follow this assumption
        self.ubound = ubound  # 200  # TODO(exe) , TODO insert into dynPSO
        # ---------------------------------------------------------------------
        # for the predictor
        # ---------------------------------------------------------------------
        self.n_time_steps = timesteps
        self.n_neurons = n_neurons
        self.n_epochs = epochs
        self.batch_size = batchsize
        self.n_layers = n_layers
        self.train_interval = train_interval

        # predictive uncertainty
        self.train_mc_runs = train_mc_runs
        self.test_mc_runs = test_mc_runs
        self.train_dropout = train_dropout
        self.test_dropout = test_dropout
        self.use_uncs = use_uncs  # True if uncertainties should be trained, predicted and used

        # TCN
        self.kernel_size = kernel_size
        self.n_kernels = n_kernels
        self.lr = lr

        # training/testing specifications
        # number train data with that the network at least is trained
        self.n_required_train_data = max(n_required_train_data,
                                         self.n_time_steps)
        self.predict_diffs = True  # predict position differences, TODO insert into PSO
        self.return_seq = False  # return values for all time steps not only the last one
        # True -> train data are shuffled before training and between epochs
        self.shuffle_train_data = True  # TODO move into script?

        # ---------------------------------------------------------------------
        # for EA/CMA-ES (fixed values)
        # ---------------------------------------------------------------------
        self.cma_np_rnd_generator = cma_np_rnd_generator
        self.pred_np_rnd_generator = pred_np_rnd_generator

        # -------------------------------------------------------------------------
        # fixed parameters
        # -------------------------------------------------------------------------
        self.lambd = 4 + floor(3 * log(self.dim))  # offsprings
        self.mu = floor(self.lambd / 2)  # parents
        # weights (vector of size ń)
        w_divisor = np.sum([(log(self.mu + 0.5) - log(j))
                            for j in range(1, self.mu + 1)])
        self.w = np.array([((log(self.mu + 0.5) - log(i)) / w_divisor)
                           for i in range(1, self.mu + 1)])
        # other
        self.mu_w = 1 / np.sum(np.square(self.w))
        self.c_sig = (self.mu_w + 2) / (self.dim + self.mu_w + 3)
        self.d_sig = 1 + self.c_sig + 2 * \
            max(0, sqrt((self.mu_w - 1) / (self.dim + 1)) - 1)
        self.c_c = 4 / (self.dim + 4)
        self.c_1 = (2 * min(1, self.lambd / 6)) / \
            ((self.dim + 1.3)**2 + self.mu_w)
        self.c_mu = (2 * (self.mu_w - 2 + 1 / self.mu_w)) / \
            ((self.dim + 2)**2 + self.mu_w)
        self.E = sqrt(self.dim) * \
            (1 - 1 / (4 * self.dim) + 1 / (21 * self.dim**2))

        self.c_o = self.c_c
        self.c_o1 = self.c_1

        # -------------------------------------------------------------------------
        # initialization
        # -------------------------------------------------------------------------
        self.m = self.cma_np_rnd_generator.randint(self.lbound, self.ubound,
                                                   self.dim)
        self.sig = cma_np_rnd_generator.rand()
        self.p_sig = np.zeros(self.dim)
        self.p_c = np.zeros(self.dim)
        self.C = np.identity(self.dim)
        self.p_sig_pred = np.zeros(self.dim)

        # ---------------------------------------------------------------------
        # options
        # ---------------------------------------------------------------------
        self.cma_variant = cma_variant
        self.pred_variant = pred_variant

        # ---------------------------------------------------------------------
        # values that are not passed as parameters to the constructor
        # ---------------------------------------------------------------------
        self.init_sigma = 1

        # ---------------------------------------------------------------------
        # for EA (variable values)
        # ---------------------------------------------------------------------
        # initialize population (mu candidates) and compute fitness.
        self.population = self.cma_np_rnd_generator.uniform(
            self.lbound, self.ubound, (self.mu, self.dim))
        # 2d numpy array (for each individual one row)
        self.population_fitness = np.array([
            utils_dynopt.fitness(self.benchmarkfunction, individual, 0,
                                 self.experiment_data)
            for individual in self.population
        ]).reshape(-1, 1)

        # ---------------------------------------------------------------------
        # for EA (prediction and evaluation)
        # ---------------------------------------------------------------------
        # number of change periods (new training data) since last training
        self.n_new_train_data = 0
        # number of changes detected by the EA
        self.detected_n_changes = 0
        # for each detected change the corresponding generation numbers
        self.detected_chgperiods_for_gens = []
        # best found individual for each generation (2d numpy array)
        self.best_found_pos_per_gen = np.zeros((self.n_generations, self.dim))
        # fitness of best found individual for each generation (1d numpy array)
        self.best_found_fit_per_gen = np.zeros(self.n_generations)
        # position of found optima (one for each change period)
        self.best_found_pos_per_chgperiod = []
        # fitness of found optima (one for each change period)
        self.best_found_fit_per_chgperiod = []
        # position, fitness & epistemic uncertainty of predicted optima (one for
        # each change period)
        self.pred_opt_pos_per_chgperiod = []
        self.pred_opt_fit_per_chgperiod = []
        self.pred_unc_per_chgperiod = []  # predictive variance
        self.aleat_unc_per_chgperiod = []  # average aleatoric uncertainty
        # training error per chgperiod (if prediction was done)
        self.train_error_per_chgperiod = []
        # training error per epoch for each chgperiod (if prediction was done)
        self.train_error_for_epochs_per_chgperiod = []

        # ---------------------------------------------------------------------
        # CMA-ES variables
        # ---------------------------------------------------------------------
        #self.angle_per_gen = []
        #self.sig_per_gen = []
        #self.p_sig_per_gen = []
        #self.h_sig_per_gen = []
        #self.p_c_per_gen = []
        #self.p_sig_pred_per_gen = []
        #self.m_per_gen = []

        # global optimum
        self.glob_opt_per_gen = []

        # sigma and mean
        self.sig_per_gen = []
        self.m_per_gen = []

        self.p_sig_pred_per_chgp = []

        # ---------------------------------------------------------------------
        # for EA (evaluation of variance) (repetitions of change periods)
        # not used for CMA-ES, only for similar output files like EA
        # ---------------------------------------------------------------------
        # add noisy data (noise equals standard deviation among change period
        # runs TODO could be replaced by "max_n_chgperiod_reps > 1"
        self.add_noisy_train_data = False  # False since unused
        self.n_noisy_series = 20
        # number repetitions of the single change periods (at least 1 -> 1 run)
        # TODO insert into PSO
        self.max_n_chgperiod_reps = 1  # arbitrary value since unused
        # population for last generation of change period (for each run)
        # used for determining the EAs variance for change periods
        # 4d list [runs, chgperiods, parents, dims]
        # TODO insert into PSO
        self.final_pop_per_run_per_chgperiod = [
            [] for _ in range(self.max_n_chgperiod_reps)
        ]
        # fitness of final population per run of each chgperiod
        # 3d list [runs, chgperiods, parents]
        # TODO insert into PSO
        self.final_pop_fitness_per_run_per_changeperiod = [
            [] for _ in range(self.max_n_chgperiod_reps)
        ]
        # best found solution for each run of all change periods
        # format [runs, chgperiods, dims]
        self.best_found_pos_per_run_per_chgp = [
            [] for _ in range(self.max_n_chgperiod_reps)
        ]
        # standard deviation and mean of the position of the best found solution
        # (computed over the change period runs)
        self.stddev_among_runs_per_chgp = [
            [] for _ in range(self.max_n_chgperiod_reps)
        ]
        self.mean_among_runs_per_chgp = [
            [] for _ in range(self.max_n_chgperiod_reps)
        ]

        # ---------------------------------------------------------------------

        assert (pred_variant in ["simplest", "a", "b", "c", "d", "g", "p", "pwm"] and cma_variant == "predcma_external") or \
            ((pred_variant in ["branke", "f"] or pred_variant.startswith("h")) and cma_variant == "predcma_internal") or \
            pred_variant == "None" and cma_variant in ["static", "resetcma"]
Exemple #6
0
    def repeat_selected_generations(self, generation_idcs, run_idcs,
                                    original_pop, original_pops_fit):
        '''
        @param generation_idcs: contains indices of generations that are repeated
        @param run_idcs: contains indices of runs that are repeated (begin with
        1 since one run already is run before)
        @param original_pop: population 
        '''

        # ---------------------------------------------------------------------
        # local variables for EA
        # ---------------------------------------------------------------------
        # number of successful mutations during t_rechenberg generations
        t_s = 0
        # overall number of mutations
        t_all = 0

        # ---------------------------------------------------------------------
        # store old values of class variables
        old_pop = copy.deepcopy(self.population)
        old_pop_fit = copy.deepcopy(self.population_fitness)
        # ---------------------------------------------------------------------
        #print("repeat chgperiod", flush=True)
        for r in run_idcs:
            #print("    repetition: ", r, flush=True)
            # set new values to class variables
            self.population = copy.deepcopy(original_pop)
            self.population_fitness = copy.deepcopy(original_pops_fit)

            for i in generation_idcs:
                # create la offsprings
                offspring_population = np.zeros((self.la, self.dim))
                offspring_pop_fitness = np.zeros((self.la, 1))
                for j in range(self.la):
                    # adapt sigma after t_rechenberg mutations
                    if t_all % self.t_rechenberg == 0 and t_all != 0:
                        adapt_sigma(self.sigma, self.t_rechenberg, self.tau,
                                    t_s)
                        # reset counters
                        t_all = 0
                        t_s = 0
                    # recombination
                    offspring = self.recombinate()
                    # mutation
                    mutated_offspring = self.mutate(offspring)
                    t_all += 1
                    # evaluate offspring
                    offspring_fitness = utils_dynopt.fitness(
                        self.benchmarkfunction, mutated_offspring, i,
                        self.experiment_data)

                    # add offspring to offspring population
                    offspring_population[j] = copy.copy(mutated_offspring)
                    offspring_pop_fitness[j][0] = offspring_fitness

                    # mutation successful?
                    if offspring_fitness < utils_dynopt.fitness(
                            self.benchmarkfunction, offspring, i,
                            self.experiment_data):
                        t_s += 1
                # select new population
                self.select(offspring_population, offspring_pop_fitness)
            # save final population of this run and its fitness values
            self.final_pop_per_run_per_chgperiod[r].append(
                copy.deepcopy(self.population))
            self.final_pop_fitness_per_run_per_changeperiod[r].append(
                copy.deepcopy(self.population_fitness))
            # determine best found position (with minimum fitness)
            min_fitness_index = np.argmin(self.population_fitness)
            self.best_found_pos_per_run_per_chgp[r].append(
                copy.deepcopy(self.population[min_fitness_index]))

        # ---------------------------------------------------------------------
        # compute standard deviation of best found position among runs
        # ---------------------------------------------------------------------
        # [chgperiods, dims]
        self.stddev_among_runs_per_chgp = np.std(
            self.best_found_pos_per_run_per_chgp, axis=0)
        self.mean_among_runs_per_chgp = np.average(
            self.best_found_pos_per_run_per_chgp, axis=0)

        # ---------------------------------------------------------------------
        # restore old values
        # ---------------------------------------------------------------------
        self.population = old_pop
        self.population_fitness = old_pop_fit
Exemple #7
0
    def optimize(self):
        # ---------------------------------------------------------------------
        # local variables for predictor
        # ---------------------------------------------------------------------
        predictors = build_all_predictors(
            self.predictor_name, self.n_time_steps, self.dim, self.batch_size,
            self.n_neurons, self.return_seq, self.apply_tl, self.n_layers,
            self.n_epochs, self.tl_rnn_type, self.n_tllayers,
            self.with_dense_first, self.tl_learn_rate, self.use_uncs,
            self.train_mc_runs, self.train_dropout, self.test_dropout,
            self.kernel_size, self.n_kernels, self.lr)
        sess = None  # necessary since is assigned anew after each training
        if self.predictor_name in [
                "rnn", "tfrnn", "tftlrnn", "tftlrnndense", "tcn",
                "hybrid-autoregressive-rnn"
        ]:
            import tensorflow as tf
            # if transfer learning then load weights
            if self.apply_tl:
                # instantiate saver to restore pre-trained weights/biases
                tl_variables, _, _, _, _, _ = get_variables_and_names(
                    self.n_tllayers)
                saver = tf.train.Saver(tl_variables)
            # start session
            config = tf.ConfigProto()
            config.gpu_options.allow_growth = True
            sess = tf.Session(config=config)
            # initialize empty model (otherwise exception)
            sess.run(tf.global_variables_initializer())
            sess.run(tf.local_variables_initializer())
            if self.apply_tl:
                # overwrite initial values with pre-trained weights/biases
                saver.restore(sess, self.tl_model_path)

        # ---------------------------------------------------------------------
        # local variables for EA
        # ---------------------------------------------------------------------
        # number of successful mutations during t_rechenberg generations
        t_s = 0
        # overall number of mutations
        t_all = 0

        # ---------------------------------------------------------------------
        # generations for that the repetitions are executed (is initialized
        # first time: all generations until a change is detected are appended)
        gens_for_rep = []
        # ---------------------------------------------------------------------
        start_pop_for_curr_chgperiod = self.population
        start_pops_fit_for_curr_chgperiod = self.population_fitness
        for i in range(self.n_generations):
            glob_opt = self.experiment_data['global_opt_pos_per_gen'][i]
            #print("generation , ", i, " glob opt: ", glob_opt)
            # store generations that have to be repeated (to compute the
            # variance of the ES)
            gens_for_rep.append(i)
            # test for environment change
            env_changed = environment_changed(i, self.population,
                                              self.population_fitness,
                                              self.benchmarkfunction,
                                              self.experiment_data,
                                              self.ea_np_rnd_generator)

            # test for environment change (but not in first generation)
            if env_changed and i != 0:
                # -------------------------------------------
                # for repetitions of chgperiods:
                # save population for first run of current chgperiod
                self.final_pop_per_run_per_chgperiod[0].append(
                    copy.deepcopy(self.population))
                self.final_pop_fitness_per_run_per_changeperiod[0].append(
                    copy.deepcopy(self.population_fitness))
                self.best_found_pos_per_run_per_chgp[0].append(
                    copy.deepcopy(self.best_found_pos_per_gen[i - 1]))

                # -------------------------------------------
                # only for experiments with repetitions of change periods
                if self.max_n_chgperiod_reps > 1:
                    run_indices = range(1, self.max_n_chgperiod_reps)
                    self.repeat_selected_generations(
                        gens_for_rep, run_indices,
                        start_pop_for_curr_chgperiod,
                        start_pops_fit_for_curr_chgperiod)
                    gens_for_rep = []

                # in the following the population of the first run is used (for prediction,...)
                # -------------------------------------------
                # reset sigma to initial value
                self.reset_parameters()
                # count change
                self.detected_n_changes += 1
                # count new train data
                self.n_new_train_data += 1
                print("(chg/gen)-(" + str(self.detected_n_changes) + "/" +
                      str(i) + ") ",
                      end="",
                      flush=True)
                # store best found solution during change period as training data for predictor
                # TODO(dev) works only for plus-selection (not for
                # comma-selection)
                self.best_found_pos_per_chgperiod.append(
                    copy.copy(self.best_found_pos_per_gen[i - 1]))
                self.best_found_fit_per_chgperiod.append(
                    copy.copy(self.best_found_fit_per_gen[i - 1]))

                # prepare data and predict optimum
                (my_pred_mode, updated_predictors,
                 self.n_new_train_data) = prepare_data_train_and_predict(
                     sess, i, self.dim, predictors, self.experiment_data,
                     self.n_epochs, self.batch_size, self.return_seq,
                     self.shuffle_train_data, self.n_new_train_data,
                     self.best_found_pos_per_chgperiod, self.train_interval,
                     self.predict_diffs, self.n_time_steps,
                     self.n_required_train_data, self.predictor_name,
                     self.add_noisy_train_data, self.n_noisy_series,
                     self.stddev_among_runs_per_chgp, self.test_mc_runs,
                     self.benchmarkfunction, self.use_uncs,
                     self.pred_unc_per_chgperiod, self.aleat_unc_per_chgperiod,
                     self.pred_opt_pos_per_chgperiod,
                     self.pred_opt_fit_per_chgperiod,
                     self.train_error_per_chgperiod,
                     self.train_error_for_epochs_per_chgperiod, glob_opt,
                     self.trueprednoise, self.pred_np_rnd_generator)
                predictors = updated_predictors

                # adapt population to environment change
                self.adapt_population(i, my_pred_mode)
                # store population
                start_pop_for_curr_chgperiod = copy.deepcopy(self.population)
                start_pops_fit_for_curr_chgperiod = copy.deepcopy(
                    self.population_fitness)

            self.detected_chgperiods_for_gens.append(self.detected_n_changes)

            # save start population of this generation
            self.population_of_last_gen = copy.copy(self.population)

            # create la offsprings
            offspring_population = np.zeros((self.la, self.dim))
            offspring_pop_fitness = np.zeros((self.la, 1))
            for j in range(self.la):
                # adapt sigma after t_rechenberg mutations
                if t_all % self.t_rechenberg == 0 and t_all != 0:
                    adapt_sigma(self.sigma, self.t_rechenberg, self.tau, t_s)
                    # reset counters
                    t_all = 0
                    t_s = 0
                # recombination
                offspring = self.recombinate()
                # mutation
                mutated_offspring = self.mutate(offspring)
                t_all += 1
                # evaluate offspring
                offspring_fitness = utils_dynopt.fitness(
                    self.benchmarkfunction, mutated_offspring, i,
                    self.experiment_data)

                # add offspring to offspring population
                offspring_population[j] = copy.copy(mutated_offspring)
                offspring_pop_fitness[j][0] = offspring_fitness

                # mutation successful?
                if offspring_fitness < utils_dynopt.fitness(
                        self.benchmarkfunction, offspring, i,
                        self.experiment_data):
                    t_s += 1
            # select new population
            self.select(offspring_population, offspring_pop_fitness)
            min_fitness_index = np.argmin(self.population_fitness)
            self.best_found_fit_per_gen[i] = copy.copy(
                self.population_fitness[min_fitness_index])
            self.best_found_pos_per_gen[i] = copy.copy(
                self.population[min_fitness_index])
            # print("best: ", self.population_fitness[min_fitness_index],
            #      "[", self.population[min_fitness_index], "]")
        if self.predictor_name in [
                "tfrnn", "tftlrnn", "tftlrnndense", "tcn", "rnn",
                "hybrid-autoregressive-rnn"
        ]:
            sess.close()
            tf.reset_default_graph()

        # save results for last change period
        self.best_found_pos_per_chgperiod.append(
            copy.copy(self.best_found_pos_per_gen[i - 1]))
        self.best_found_fit_per_chgperiod.append(
            copy.copy(self.best_found_fit_per_gen[i - 1]))
Exemple #8
0
    def adapt_population(self, curr_gen, my_pred_mode):
        '''
        Re-initialize (parts of) the population: insert immigrants that are
        generated by means of different re-initialization strategies.
        Explanations for re-initialization strategies (nRND, nVAR, nPRE, pKAL, 
        pUNC, pDEV, pRND) can be found in the ICANN 2019 paper.
        '''
        mean = 0.0
        n_immigrants = self.mu
        assert n_immigrants == self.mu
        random_immigrants = self.ea_np_rnd_generator.uniform(
            self.lbound, self.ubound, (n_immigrants, self.dim))
        if my_pred_mode == "no" or n_immigrants == 0:
            if self.reinitialization_mode == "no-RND" or self.predictor_name != "no":
                # completely random if no prediction is applied or the predictor
                # is only applied later when enough training data is available
                immigrants = random_immigrants
            elif self.reinitialization_mode == "no-VAR":
                # add to current individuals a noise with standard
                # deviation (x_t - x_t-1)
                sigma_per_ind, _ = self.get_delta()
                immigrants = np.array([
                    gaussian_mutation(x, mean, float(s),
                                      self.pred_np_rnd_generator)
                    for x, s in zip(self.population, sigma_per_ind)
                ])
            elif self.reinitialization_mode == "no-PRE":
                sigma_per_ind, parent_population = self.get_delta()
                predicted_pop = self.population + \
                    (self.population - parent_population)
                immigrants = np.array([
                    gaussian_mutation(x, mean, float(s),
                                      self.pred_np_rnd_generator)
                    for x, s in zip(predicted_pop, sigma_per_ind)
                ])
            else:
                warnings.warn("unknown reinitialization mode: " +
                              self.reinitialization_mode)

        elif my_pred_mode in [
                "rnn", "autoregressive", "tfrnn", "tftlrnn", "tftlrnndense",
                "tcn", "kalman", "truepred", "hybrid-autoregressive-rnn"
        ]:
            # last predicted optimum
            pred_optimum_position = self.pred_opt_pos_per_chgperiod[-1]
            # insert predicted optimum into immigrants
            immigrants = np.array([pred_optimum_position])
            n_remaining_immigrants = n_immigrants - len(immigrants)
            # initialize remaining immigrants
            # a) within the neighborhood of the predicted optimum and
            # b) completely randomly
            # ratio of a) and b): 2/3, 1/3
            if n_remaining_immigrants > 1:
                if self.reinitialization_mode == "pred-KAL":
                    self.sigma_factors = [1.0]
                # a)
                # immigrants randomly in the area around the optimum (in case
                # of TCN the area is bound to the predictive variance). There
                # are 4 different realizations of this type.
                two_third = math.ceil((n_remaining_immigrants / 3) * 2)
                n_immigrants_per_noise = two_third // len(self.sigma_factors)

                for z in self.sigma_factors:
                    noisy_optimum_positions = self.compute_noisy_opt_positions(
                        z, pred_optimum_position, n_immigrants_per_noise)
                    if len(noisy_optimum_positions) != 0:
                        immigrants = np.concatenate(
                            (immigrants, noisy_optimum_positions))
                # b) TODO only use respective re-initialization strategy
                # initialize remaining immigrants completely randomly
                n_remaining_immigrants = n_immigrants - len(immigrants)
                immigrants = np.concatenate(
                    (immigrants, random_immigrants[:n_remaining_immigrants]))
            else:
                # take one of the random immigrants
                immigrants = np.concatenate((immigrants, random_immigrants[0]))
        else:
            msg = "unknown prediction mode " + my_pred_mode
            warnings.warn(msg)

        assert len(
            immigrants) == n_immigrants, "false number of immigrants: " + str(
                len(immigrants))
        # build new population
        self.population = np.concatenate((self.population, immigrants))
        # compute fitness of new population
        self.population_fitness = np.array([
            utils_dynopt.fitness(self.benchmarkfunction, individual, curr_gen,
                                 self.experiment_data)
            for individual in self.population
        ]).reshape(-1, 1)
Exemple #9
0
    def __init__(self, benchmarkfunction, dim, n_generations, experiment_data,
                 predictor_name, trueprednoise, lbound, ubound,
                 ea_np_rnd_generator, pred_np_rnd_generator, mu, la, ro, mean,
                 sigma, trechenberg, tau, reinitialization_mode, sigma_factors,
                 timesteps, n_neurons, epochs, batchsize, n_layers, apply_tl,
                 n_tllayers, tl_model_path, tl_learn_rate, max_n_chperiod_reps,
                 add_noisy_train_data, train_interval, n_required_train_data,
                 use_uncs, train_mc_runs, test_mc_runs, train_dropout,
                 test_dropout, kernel_size, n_kernels, lr):
        '''
        Initialize a DynamicEA object.
        @param benchmarkfunction: (string)
        @param dim: (int) dimensionality of objective function, i.e. number of 
        features for each individual
        @param n_generations: (int) number of generations
        @param experiment_data: (dictionary)
        @param predictor_name: (string)
        @param lbound, ubound: (floats) lower/upper bound of solution space; 
        must fit the data in experiment_data!
        @param ea_np_rnd_generator: numpy random generator for the EA
        @param pred_np_rnd_generator: numpy random generator for the predictor
        @param mu: (int) population size
        @param la: (int) lambda, number of offspring individuals
        @param ro: (int) number parents for recombination
        @param mean: (float) mean for the Gaussian mutation of the EA
        @param sigma: (float) mutation strength for EA 
        @param trechenberg: number of mutations after which sigma is adapted
        @param tau: 0 < tau < 1, factor to adapt sigma (for Rechenberg 1/5 rule)
        @param timesteps: (int) number of time steps the predictions use for the
        prediction
        @param n_neurons: (int) number of neurons within the first layer of the 
        RNN prediction model
        @param epochs: (int) number of epochs to train the RNN predicton model
        @param batch_size: (int) batch size for the RNN predictor
        @param n_layers (int): overall number of RNN layers (incl. pre-trained
        ones), only for "tfrnn", "tftlrnn", or "tftlrnndense"
        @param apply_tl: (bool) True if transfer learning should be applied
        @param n_tllayers (int) number layers in pre-trained RNN 
        only for "tfrnn", "tftlrnn", or "tftlrnndense"
        @param tl_model_path: (string) path to pre-trained RNN
        @param tl_learn_rate (float): learning rate for pre-trained layers
        @param max_n_chperiod_reps: (int): how often each change period should
        be repeated (used for evaluation of variance of EA)
        @param add_noisy_train_data (bool): True if the variance over the 
        repeated change periods should be used as noise to disturbe the time
        series the prediction models learn from
        @param train_interval: (int) number of change periods that must have 
        passed before predictor is trained anew
        @param n_required_train_data: (int) number of training data that is
        used for training
        @param use_uncs: (True) if predictive uncertainty should be estimated; 
        only possible for predictors "kalman", "tcn" and "truepred" 
        @param train_mc_runs: (int) number of Monte Carlo runs during training
        (when predictive uncertainty is estimated)
        @param test_mc_runs: (int) number of Monte Carlo runs during prediciton
        @param train_dropout, test_dropout (float): dropout for training/test
        @param kernel_size (int): filter size for "tcn"
        @param n_kernels: (int) number filters for "tcn"
        @param lr: (float) learning rate for "tcn"
        '''
        # ---------------------------------------------------------------------
        # for the problem
        # ---------------------------------------------------------------------
        self.benchmarkfunction = benchmarkfunction
        self.dim = dim
        self.n_generations = n_generations
        self.experiment_data = experiment_data
        self.predictor_name = predictor_name
        self.trueprednoise = trueprednoise  # TODO unused so far

        self.lbound = lbound  # 100  # assumed that the input data follow this assumption
        self.ubound = ubound  # 200  # TODO(exe) , TODO insert into dynPSO
        # ---------------------------------------------------------------------
        # for the predictor
        # ---------------------------------------------------------------------
        self.n_time_steps = timesteps
        self.n_neurons = n_neurons
        self.n_epochs = epochs
        self.batch_size = batchsize
        self.n_layers = n_layers
        self.train_interval = train_interval

        # predictive uncertainty
        self.train_mc_runs = train_mc_runs
        self.test_mc_runs = test_mc_runs
        self.train_dropout = train_dropout
        self.test_dropout = test_dropout
        self.use_uncs = use_uncs  # True if uncertainties should be trained, predicted and used

        # TCN
        self.kernel_size = kernel_size
        self.n_kernels = n_kernels
        self.lr = lr

        # transfer learning
        self.apply_tl = apply_tl  # True if pre-trained model should be used
        self.tl_model_path = tl_model_path  # path to the trained tl model
        self.tl_rnn_type = "RNN"
        self.n_tllayers = n_tllayers
        self.with_dense_first = None
        self.tl_learn_rate = tl_learn_rate

        # training/testing specifications
        # number train data with that the network at least is trained
        self.n_required_train_data = max(n_required_train_data,
                                         self.n_time_steps)
        self.predict_diffs = True  # predict position differences, TODO insert into PSO
        self.return_seq = False  # return values for all time steps not only the last one
        # True -> train data are shuffled before training and between epochs
        self.shuffle_train_data = True  # TODO move into script?

        # ---------------------------------------------------------------------
        # for EA (fixed values)
        # ---------------------------------------------------------------------
        self.ea_np_rnd_generator = ea_np_rnd_generator
        self.pred_np_rnd_generator = pred_np_rnd_generator
        self.mu = mu
        self.la = la
        self.ro = ro
        self.mean = mean
        self.sigma = sigma
        self.t_rechenberg = trechenberg
        self.tau = tau
        self.reinitialization_mode = reinitialization_mode
        self.sigma_factors = sigma_factors

        # ---------------------------------------------------------------------
        # values that are not passed as parameters to the constructor
        # ---------------------------------------------------------------------
        self.init_sigma = self.sigma

        # ---------------------------------------------------------------------
        # for EA (variable values)
        # ---------------------------------------------------------------------
        # initialize population (mu candidates) and compute fitness.
        self.population = self.ea_np_rnd_generator.uniform(
            self.lbound, self.ubound, (self.mu, self.dim))
        # 2d numpy array (for each individual one row)
        self.population_fitness = np.array([
            utils_dynopt.fitness(self.benchmarkfunction, individual, 0,
                                 self.experiment_data)
            for individual in self.population
        ]).reshape(-1, 1)

        # ---------------------------------------------------------------------
        # for EA (prediction and evaluation)
        # ---------------------------------------------------------------------
        # number of change periods (new training data) since last training
        self.n_new_train_data = 0
        # number of changes detected by the EA
        self.detected_n_changes = 0
        # for each detected change the corresponding generation numbers
        self.detected_chgperiods_for_gens = []
        # best found individual for each generation (2d numpy array)
        self.best_found_pos_per_gen = np.zeros((self.n_generations, self.dim))
        # fitness of best found individual for each generation (1d numpy array)
        self.best_found_fit_per_gen = np.zeros(self.n_generations)
        # position of found optima (one for each change period)
        self.best_found_pos_per_chgperiod = []
        # fitness of found optima (one for each change period)
        self.best_found_fit_per_chgperiod = []
        # position, fitness & epistemic uncertainty of predicted optima (one for
        # each change period)
        self.pred_opt_pos_per_chgperiod = []
        self.pred_opt_fit_per_chgperiod = []
        self.pred_unc_per_chgperiod = []  # predictive variance
        self.aleat_unc_per_chgperiod = []  # average aleatoric uncertainty
        # training error per chgperiod (if prediction was done)
        self.train_error_per_chgperiod = []
        # training error per epoch for each chgperiod (if prediction was done)
        self.train_error_for_epochs_per_chgperiod = []
        # stores the population of the (beginning of the) last generation
        self.population_of_last_gen = None

        # ---------------------------------------------------------------------
        # for EA (evaluation of variance) (repetitions of change periods)
        # ---------------------------------------------------------------------
        # add noisy data (noise equals standard deviation among change period
        # runs TODO could be replaced by "max_n_chgperiod_reps > 1"
        self.add_noisy_train_data = add_noisy_train_data
        self.n_noisy_series = 20
        # number repetitions of the single change periods (at least 1 -> 1 run)
        # TODO insert into PSO
        self.max_n_chgperiod_reps = max_n_chperiod_reps
        # population for last generation of change period (for each run)
        # used for determining the EAs variance for change periods
        # 4d list [runs, chgperiods, parents, dims]
        # TODO insert into PSO
        self.final_pop_per_run_per_chgperiod = [
            [] for _ in range(self.max_n_chgperiod_reps)
        ]
        # fitness of final population per run of each chgperiod
        # 3d list [runs, chgperiods, parents]
        # TODO insert into PSO
        self.final_pop_fitness_per_run_per_changeperiod = [
            [] for _ in range(self.max_n_chgperiod_reps)
        ]
        # best found solution for each run of all change periods
        # format [runs, chgperiods, dims]
        self.best_found_pos_per_run_per_chgp = [
            [] for _ in range(self.max_n_chgperiod_reps)
        ]
        # standard deviation and mean of the position of the best found solution
        # (computed over the change period runs)
        self.stddev_among_runs_per_chgp = [
            [] for _ in range(self.max_n_chgperiod_reps)
        ]
        self.mean_among_runs_per_chgp = [
            [] for _ in range(self.max_n_chgperiod_reps)
        ]