Ejemplo n.º 1
0
class NeuralNetwork:
    """
    Features
    - Run a single config_num
    - Run multiple config_nums
    - Will automatically write and save results of model - see save_dir and write_dir for more information
    Notes:
    - Supports Tensorboard
    - By default, will load all addons
    """
    def __init__(self,
                 channel,
                 gen,
                 binary=True,
                 write_filename='NN_output',
                 show_graph=False):
        print(f'Loaded in {channel}, binary={binary}, gen={gen}')
        self.addons_config_reco = {
            'neutrino': {
                'load_alpha': False,
                'termination': 1000,
                'imputer_mode': 'remove',
                'save_alpha': True,
            },
            'met': {},
            'ip': {},
            'sv': {}
        }
        self.addons_config_gen = {
            'neutrino': {
                'load_alpha': False,
                'termination': 1000,
                'imputer_mode': 'remove',
                'save_alpha': True,
            },
            'met': {},
            'ip': {},
            'sv': {}
        }
        self.show_graph = show_graph
        self.channel = channel
        self.binary = binary
        self.write_filename = write_filename
        self.gen = gen
        self.save_dir = 'NN_output'
        self.write_dir = 'NN_output'
        self.model = None

    def run(self,
            config_num,
            read=True,
            from_hdf=True,
            epochs=50,
            batch_size=1024,
            patience=20,
            strict=False):
        if not self.gen:
            df = self.initialize(self.addons_config_reco,
                                 read=read,
                                 from_hdf=from_hdf,
                                 strict=strict)
        else:
            df = self.initialize(self.addons_config_gen,
                                 read=read,
                                 from_hdf=from_hdf)
        X_train, X_test, y_train, y_test = self.configure(df, config_num)
        print(
            f'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Training config {config_num}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'
        )
        # self.model = self.custom_model()
        # print('training with custom model')
        if self.model is None:
            print(f'Training with DEFAULT - kristof_model')
        model = self.train(X_train,
                           X_test,
                           y_train,
                           y_test,
                           epochs=epochs,
                           batch_size=batch_size,
                           patience=patience)
        if self.binary:
            auc = self.evaluateBinary(model, X_test, y_test, self.history)
        else:
            w_a = df.w_a
            w_b = df.w_b
            auc = self.evaluate(model, X_test, y_test, self.history, w_a, w_b)
        self.write(auc, self.history, self.addons_config_reco)

    def runMultiple(self,
                    configs,
                    read=True,
                    from_hdf=True,
                    epochs=50,
                    batch_size=1024,
                    patience=10):
        if not self.gen:
            df = self.initialize(self.addons_config_reco,
                                 read=read,
                                 from_hdf=from_hdf)
        else:
            df = self.initialize(self.addons_config_gen,
                                 read=read,
                                 from_hdf=from_hdf)
        for config_num in configs:
            print(
                f'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Training config {config_num}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'
            )
            X_train, X_test, y_train, y_test = self.configure(df, config_num)
            model = self.train(X_train,
                               X_test,
                               y_train,
                               y_test,
                               epochs=epochs,
                               batch_size=batch_size,
                               patience=patience)
            if self.binary:
                auc = self.evaluateBinary(model, X_test, y_test, self.history)
            else:
                w_a = df.w_a
                w_b = df.w_b
                auc = self.evaluate(model, X_test, y_test, self.history, w_a,
                                    w_b)
            self.write(self.gen, auc, self.history, self.addons_config_reco)

    def runTuning(self,
                  config_num,
                  tuning_mode='random_sk',
                  read=True,
                  from_hdf=True):
        if not self.gen:
            df = self.initialize(self.addons_config_reco,
                                 read=read,
                                 from_hdf=from_hdf)
        else:
            df = self.initialize(self.addons_config_gen,
                                 read=read,
                                 from_hdf=from_hdf)
        X_train, X_test, y_train, y_test = self.configure(df, config_num)
        print(
            f'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Tuning on config {config_num}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'
        )
        tuner = Tuner(mode=tuning_mode)
        if tuning_mode in {'random_sk', 'grid_search_cv'}:
            model_grid, grid_result, param_grid = tuner.tune(
                X_train, y_train, X_test, y_test)
            self.layers = grid_result.best_params_['layers']
            self.batch_norm = grid_result.best_params_['batch_norm']
            self.dropout = grid_result.best_params_['dropout']
            self.epochs = grid_result.best_params_['epochs']
            self.batchsize = grid_result.best_params_['batch_size']
            self.learning_rate = grid_result.best_params_['learning_rate']
            self.activation = grid_result.best_params_['activation']
            self.initializer_std = grid_result.best_params_['initializer_std']
            self.nodes = grid_result.best_params_[
                'nodes']  # !!! Kristof's edit on 25 Feb trying to fix the random_sk with the extra hyperparameters
            self.model_str = 'grid_model'
            grid_best_score = grid_result.best_score_
            self.model = tuner.gridModel(layers=self.layers,
                                         batch_norm=self.batch_norm,
                                         dropout=self.dropout)
            print("Best: %f using %s" %
                  (grid_result.best_score_, grid_result.best_params_))
            means = grid_result.cv_results_['mean_test_score']
            stds = grid_result.cv_results_['std_test_score']
            params = grid_result.cv_results_['params']
            for mean, stdev, param in zip(means, stds, params):
                print("%f (%f) with: %r" % (mean, stdev, param))
        elif tuning_mode in {'hyperband', 'bayesian', 'random_kt'}:
            self.model, best_hps, param_grid = tuner.tune(
                X_train, y_train, X_test, y_test)
            # hard coded epochs, batchsize - KerasTuner doesn't search over this parameter space
            self.epochs = 50
            self.batchsize = 10000
            self.layers = best_hps.get('num_layers')
            self.batch_norm = best_hps.get('batch_norm')
            self.dropout = best_hps.get('dropout')
            self.learning_rate = best_hps.get('learning_rate')
            self.activation = best_hps.get('activation')
            self.initializer_std = best_hps.get('initializer_std')
            self.model_str = 'hyper_model'
            grid_best_score = None
        elif tuning_mode in {'hyperopt'}:
            self.model, best_params, param_grid = tuner.tune(
                X_train, y_train, X_test, y_test)
            self.nodes = int(best_params['nodes'])
            self.layers = int(best_params['num_layers'])
            self.batch_norm = best_params['batch_norm']
            self.dropout = best_params['dropout']
            self.epochs = int(best_params['epochs'])
            self.batchsize = int(best_params['batch_size'])
            self.learning_rate = best_params['learning_rate']
            self.activation = best_params['activation']
            self.initializer_std = best_params['initializer_std']
            self.model_str = 'hyperopt_model'
            grid_best_score = None
        else:
            raise ValueError('Tuning mode not understood')
        model = self.train(X_train,
                           X_test,
                           y_train,
                           y_test,
                           epochs=self.epochs,
                           batch_size=self.batchsize,
                           verbose=0)
        if self.binary:
            auc = self.evaluateBinary(model, X_test, y_test, self.history)
        else:
            w_a = df.w_a
            w_b = df.w_b
            auc = self.evaluate(model, X_test, y_test, self.history, w_a, w_b)
        if not self.gen:
            file = f'{self.write_dir}/tuning_reco_{self.channel}.txt'
        else:
            file = f'{self.write_dir}/tuning_gen_{self.channel}.txt'
        self.model.save(f'{self.write_dir}/tuning_model_{self.channel}.h5')
        with open(file, 'a+') as f:
            print(f'Writing HPs to {file}')
            time_str = datetime.datetime.now().strftime('%Y/%m/%d|%H:%M:%S')
            # message = f'{time_str},{auc},{self.config_num},{self.layers},{self.batch_norm},{self.dropout},{self.epochs},{self.batchsize},{tuning_mode},{grid_best_score},{param_grid}\n'
            message = f'{time_str},{auc},{self.config_num},{self.nodes},{self.layers},{self.batch_norm},{self.dropout},{self.epochs},{self.batchsize},{tuning_mode},{grid_best_score},{self.learning_rate},{self.activation},{self.initializer_std},{param_grid}\n'
            print(f"Message: {message}")
            f.write(message)
        model_save_str = f'./saved_models/{self.channel}/model_{config_num}'
        model.save(model_save_str)

    def runWithSmearing(self,
                        config_num,
                        features: list,
                        from_hdf=True,
                        sample=False):
        """ 
        Need to update smearing_hp.txt to get hyperparameter for tuning 
        Trying for config 1.6 smearing first
        """
        if not self.gen:
            print('CHANGING GEN TO TRUE - REQUIRED FOR SMEARING')
            self.gen = True
        print('SETTING SAVE_ALPHA TO FALSE')
        self.addons_config_gen['neutrino']['save_alpha'] = False
        self.addons_config_gen['neutrino']['load_alpha'] = False
        df = self.initializeWithSmear(features,
                                      self.addons_config_gen,
                                      from_hdf=from_hdf,
                                      sample=sample)
        # get config 3.9 model if not rho_rho, if rho_rho, get 3.2
        with open(f'./NN_output/smearing_hp_{config_num}.txt', 'r') as fh:
            num_list = [line for line in fh]
        if self.channel == 'rho_rho':
            nn_arch = num_list[0].split(',')
        elif self.channel == 'rho_a1':
            nn_arch = num_list[1].split(',')
        else:
            nn_arch = num_list[2].split(',')
        optimal_auc = float(nn_arch[1])
        nodes = int(nn_arch[3])
        num_layers = int(nn_arch[4])
        batch_norm = bool(nn_arch[5])
        dropout = float(nn_arch[6])
        epochs = int(nn_arch[7])
        batch_size = int(nn_arch[8])
        learning_rate = float(nn_arch[11])
        activation = str(nn_arch[12])
        initializer_std = float(nn_arch[13])
        params = {
            'nodes': nodes,
            'num_layers': num_layers,
            'batch_norm': batch_norm,
            'dropout': dropout,
            'epochs': epochs,
            'batch_size': batch_size,
            'learning_rate': learning_rate,
            'activation': activation,
            'initializer_std': initializer_std,
        }
        print(f'Training with {params}')
        tuner = Tuner()
        self.model, _ = tuner.hyperOptModelNN(params)
        X_train, X_test, y_train, y_test = self.configure(df, config_num)
        model = self.train(X_train,
                           X_test,
                           y_train,
                           y_test,
                           epochs=epochs,
                           batch_size=batch_size)
        # model = self.train(X_train, X_test, y_train, y_test, epochs=50, batch_size=10000)
        if self.binary:
            auc = self.evaluateBinary(model,
                                      X_test,
                                      y_test,
                                      self.history,
                                      plot=False)
        else:
            w_a = df.w_a
            w_b = df.w_b
            auc = self.evaluate(model,
                                X_test,
                                y_test,
                                self.history,
                                w_a,
                                w_b,
                                plot=False)
        return auc, optimal_auc

    def runSingleSmearAnalysis(self, features_list, from_hdf=True):
        """
        for kristof
        stick with 1.6
        - feature 'pi_1' flag
        """
        config_num = 3.6
        f = open(
            self.save_dir + '/' + self.channel + '_' + str(config_num) +
            '_smearing_aucs.txt', 'a')
        for feature in tqdm(features_list):
            # auc, optimal_auc = self.runWithSmearing(1.6, [feature], from_hdf=from_hdf)
            if isinstance(feature, list):
                auc, optimal_auc = self.runWithSmearing(
                    config_num, feature, from_hdf=from_hdf,
                    sample=True)  # sample=True should it be turned on?
                degradation_auc = optimal_auc - auc
                f.write('-'.join(feature) + ',' + str(degradation_auc) + ',' +
                        str(optimal_auc) + '\n')
                print('-'.join(feature) + ',' + str(degradation_auc) + ',' +
                      str(auc) + ',' + str(optimal_auc) + '\n')

            else:
                auc, optimal_auc = self.runWithSmearing(config_num, [feature],
                                                        from_hdf=from_hdf)
                degradation_auc = optimal_auc - auc
                f.write(feature + ',' + str(degradation_auc) + ',' +
                        str(optimal_auc) + '\n')
                print(feature + ',' + str(degradation_auc) + ',' + str(auc) +
                      ',' + str(optimal_auc) + '\n')
        f.close()
        # plotting bar chart

    def runSmearAnalysis(self, features_list, from_hdf=True):
        config_num = 1.6
        f = open(
            self.save_dir + '/' + self.channel + '_' + str(config_num) +
            '_smearing_aucs.txt', 'a')
        auc, optimal_auc = self.runWithSmearing(config_num,
                                                features_list,
                                                from_hdf=from_hdf)
        degradation_auc = optimal_auc - auc
        print('-'.join(features_list) + ',' + str(degradation_auc) + ',' +
              str(optimal_auc) + '\n')
        f.write('-'.join(features_list) + ',' + str(degradation_auc) + ',' +
                str(optimal_auc) + '\n')
        f.close()

    def initialize(self,
                   addons_config={},
                   read=True,
                   from_hdf=True,
                   strict=False):
        """
        Initialize NN by loading/ creating the input data for NN via DataLoader
        Params:
        addons(dict) - addon map each value being an addon configuration
        read - will read df inputs instead of creating them
        from_hdf - will read events from HDF5 instead of .root file
        Returns: df of NN inputs (to be configured) 
        """
        if not addons_config:
            addons = []
        else:
            addons = addons_config.keys()
        if not self.gen:
            if self.channel == 'rho_rho':
                variables = config.variables_rho_rho
            elif self.channel == 'rho_a1':
                variables = config.variables_rho_a1
            elif self.channel == 'a1_a1':
                variables = config.variables_a1_a1
            else:
                raise ValueError('Incorrect channel inputted')
        else:
            if self.channel == 'rho_rho':
                variables = config.variables_gen_rho_rho
            elif self.channel == 'rho_a1':
                variables = config.variables_gen_rho_a1
            elif self.channel == 'a1_a1':
                variables = config.variables_gen_a1_a1
            else:
                raise ValueError('Incorrect channel inputted')
        self.DL = DataLoader(variables, self.channel, self.gen)
        CC = ConfigChecker(self.channel, self.binary, self.gen)
        CC.checkInitialize(self.DL, addons_config, read, from_hdf)
        if read:
            print("WARNING: skipping over creating new configs")
            if not self.gen:
                df = self.DL.loadRecoData(self.binary, addons)
            else:
                df = self.DL.loadGenData(self.binary, addons)
        else:
            if not self.gen:
                df = self.DL.createRecoData(self.binary,
                                            from_hdf,
                                            addons,
                                            addons_config,
                                            strict=strict)
            else:
                df = self.DL.createGenData(self.binary, from_hdf, addons,
                                           addons_config)
        return df

    def initializeWithSmear(self,
                            features,
                            addons_config={},
                            from_hdf=True,
                            sample=True):
        """ NO READ FUNCTION - ALWAYS CREATE SMEARING DIST """
        if not addons_config:
            addons = []
        else:
            addons = addons_config.keys()
        if not self.gen:
            if self.channel == 'rho_rho':
                variables = config.variables_rho_rho
                variables_smear = config.variables_smearing_rho_rho
            elif self.channel == 'rho_a1':
                variables = config.variables_rho_a1
                variables_smear = config.variables_smearing_rho_a1
            elif self.channel == 'a1_a1':
                variables = config.variables_a1_a1
                variables_smear = config.variables_smearing_a1_a1
            else:
                raise ValueError('Incorrect channel inputted')
        else:
            if self.channel == 'rho_rho':
                variables = config.variables_gen_rho_rho
                variables_smear = config.variables_smearing_rho_rho
            elif self.channel == 'rho_a1':
                variables = config.variables_gen_rho_a1
                variables_smear = config.variables_smearing_rho_a1
            elif self.channel == 'a1_a1':
                variables = config.variables_gen_a1_a1
                variables_smear = config.variables_smearing_a1_a1
            else:
                raise ValueError('Incorrect channel inputted')
        self.DL = DataLoader(variables, self.channel, self.gen)
        CC = ConfigChecker(self.channel, self.binary, self.gen)
        CC.checkInitialize(self.DL, addons_config, read, from_hdf)
        print(f'Loading .root info with using HDF5 as {from_hdf}')
        df_orig = self.DL.readGenData(from_hdf=from_hdf)
        print('Cleaning data')
        df_clean, df_ps_clean, df_sm_clean = self.DL.cleanGenData(df_orig)
        # print(df_clean.pi_E_1)
        SM = Smearer(variables_smear, self.channel, features)
        df_smeared = SM.createSmearedData(df_clean,
                                          from_hdf=from_hdf,
                                          sample=sample)
        # remove Nans
        df_smeared = df_smeared.dropna()
        df_ps_smeared, df_sm_smeared = SM.selectPSSMFromData(df_smeared)
        # addons = []
        # addons_config = {}
        df_smeared_transformed = self.DL.createTrainTestData(df_smeared,
                                                             df_ps_smeared,
                                                             df_sm_smeared,
                                                             binary,
                                                             True,
                                                             addons,
                                                             addons_config,
                                                             save=False)
        # df_orig_transformed = self.DL.createTrainTestData(df_clean, df_ps_clean, df_sm_clean, binary, True, addons, addons_config, save=False)
        # deal with imaginary numbers from boosting
        df_smeared_transformed = df_smeared_transformed.dropna()
        # df_smeared_transformed = df_smeared_transformed.apply(np.real)
        # m_features = [x for x in df_smeared_transformed.columns if x.startswith('m')]
        # for m_feature in m_features:
        #     df_smeared_transformed = df_smeared_transformed[df_smeared_transformed[m_feature]!=0]
        # debugging part
        # df.to_hdf('smearing/df_smeared.h5', 'df')
        # df_smeared.to_hdf('./smearing/df_smeared.h5', 'df')
        # df_clean.to_hdf('./smearing/df_orig.h5', 'df')
        # df_smeared_transformed.to_hdf('smearing/df_smeared_transformed.h5', 'df')
        # df_orig_transformed.to_hdf('smearing/df_orig_transformed.h5', 'df')
        # exit()
        return df_smeared_transformed

    def configure(self, df, config_num):
        """
        Configures NN inputs - selects config_num and creates train/test split
        """
        self.config_num = config_num
        CL = ConfigLoader(df, self.channel, self.gen)
        X_train, X_test, y_train, y_test = CL.configTrainTestData(
            self.config_num, self.binary)
        return X_train, X_test, y_train, y_test

    def train(self,
              X_train,
              X_test,
              y_train,
              y_test,
              epochs=50,
              batch_size=1024,
              patience=20,
              save=False,
              verbose=1):
        self.epochs = epochs
        self.batch_size = batch_size
        if self.model is None:
            print('Using Kristof model')
            self.model = self.kristof_model(X_train.shape[1])
        self.history = tf.keras.callbacks.History()
        early_stop = tf.keras.callbacks.EarlyStopping(monitor='val_auc',
                                                      patience=patience)
        if not self.gen:
            log_dir = f"logs/fit/{self.channel}_reco/" + datetime.datetime.now(
            ).strftime(
                "%Y%m%d-%H%M%S"
            ) + f'_{self.config_num}_{self.layers}_{self.epochs}_{self.batch_size}_{self.model_str}'
        else:
            log_dir = f"logs/fit/{self.channel}_gen/" + datetime.datetime.now(
            ).strftime("%Y%m%d-%H%M%S") + f'_{self.epochs}'
        if self.binary:
            log_dir += '_b'
        tensorboard_callback = tf.keras.callbacks.TensorBoard(log_dir=log_dir,
                                                              histogram_freq=1)
        self.model.fit(
            X_train,
            y_train,
            batch_size=batch_size,
            epochs=epochs,
            callbacks=[self.history, tensorboard_callback, early_stop],
            validation_data=(X_test, y_test),
            verbose=verbose)
        if save:
            self.model.save(f'./saved_models/{self.save_dir}/NN')
        return self.model

    def evaluate(self, model, X_test, y_test, history, w_a, w_b, plot=True):
        if plot:
            config_str = self.createConfigStr()
            E = Evaluator(model, self.binary, self.save_dir, config_str)
        else:
            E = Evaluator(model, self.binary, self.save_dir, None)
        auc = E.evaluate(X_test,
                         y_test,
                         history,
                         plot,
                         show=self.show_graph,
                         w_a=w_a,
                         w_b=w_b)
        return auc

    def write(self, auc, history, addons_config):
        if not addons_config:
            addons = []
        else:
            addons = addons_config.keys()
        addons_loaded = "None"
        if addons:
            addons_loaded = '_' + '_'.join(addons)
        if not self.gen:
            file = f'{self.write_dir}/{self.write_filename}_reco_{self.channel}.txt'
        else:
            file = f'{self.write_dir}/{self.write_filename}_gen_{self.channel}.txt'
        with open(file, 'a+') as f:
            print(f'Writing to {file}')
            time_str = datetime.datetime.now().strftime('%Y/%m/%d|%H:%M:%S')
            actual_epochs = len(history.history["loss"])
            f.write(
                f'{time_str},{auc},{self.config_num},{self.layers},{self.epochs},{actual_epochs},{self.batch_size},{self.binary},{self.model_str},{addons_loaded}\n'
            )
        print('Finish writing')
        f.close()

    def evaluateBinary(self, model, X_test, y_test, history, plot=True):
        if plot:
            config_str = self.createConfigStr()
            E = Evaluator(model, self.binary, self.save_dir, config_str)
        else:
            E = Evaluator(model, self.binary, self.save_dir, None)
        auc = E.evaluate(X_test, y_test, history, plot, show=self.show_graph)
        return auc

    def createConfigStr(self):
        if self.binary:
            config_str = f'config{self.config_num}_{self.layers}_{self.epochs}_{self.batch_size}_{self.model_str}_binary'
        else:
            config_str = f'config{self.config_num}_{self.layers}_{self.epochs}_{self.batch_size}_{self.model_str}'
        return config_str

    def seq_model(self, units=(300, 300), batch_norm=False, dropout=None):
        self.model = tf.keras.models.Sequential()
        self.layers = len(units)
        for unit in units:
            self.model.add(
                tf.keras.layers.Dense(unit, kernel_initializer='normal'))
            if batch_norm:
                self.model.add(tf.keras.layers.BatchNormalization())
            self.model.add(tf.keras.layers.Activation('relu'))
            if dropout is not None:
                self.model.add(tf.keras.layers.Dropout(dropout))
        self.model.add(tf.keras.layers.Dense(1, activation="sigmoid"))
        metrics = ['AUC', 'accuracy']
        self.model.compile(loss='binary_crossentropy',
                           optimizer='adam',
                           metrics=metrics)
        self.model_str = "seq_model"
        return self.model

    def kristof_model(self, dimensions):
        # model by kristof
        model = tf.keras.models.Sequential()
        self.layers = 2
        self.model_str = "kristof_model"
        metrics = ['AUC', 'accuracy']
        model.add(
            tf.keras.layers.Dense(300,
                                  input_dim=dimensions,
                                  kernel_initializer='normal',
                                  activation='relu'))
        model.add(
            tf.keras.layers.Dense(300,
                                  kernel_initializer='normal',
                                  activation='relu')
        )  # !!! originally there were just 2 hidden layers
        #model.add(tf.keras.layers.Dense(300, kernel_initializer='normal', activation='relu'))
        #model.add(tf.keras.layers.Dense(300, kernel_initializer='normal', activation='relu'))
        #model.add(tf.keras.layers.Dense(300, kernel_initializer='normal', activation='relu'))
        model.add(tf.keras.layers.Dense(1, activation="sigmoid"))
        opt = tf.keras.optimizers.Adam(learning_rate=0.001)
        model.compile(loss='binary_crossentropy',
                      optimizer=opt,
                      metrics=metrics)
        return model

    def custom_model(self):
        from tensorflow.keras.backend import sigmoid

        def swish(x, beta=1):
            return (x * sigmoid(beta * x))

        model = tf.keras.models.Sequential()
        from tensorflow.keras import utils
        from tensorflow.keras.layers import Activation
        utils.get_custom_objects().update({'swish': Activation(swish)})
        self.layers = 2
        self.model_str = "custom_model"
        metrics = ['AUC', 'accuracy']
        model.add(
            tf.keras.layers.Dense(300,
                                  kernel_initializer='normal',
                                  activation='swish'))
        model.add(
            tf.keras.layers.Dense(300,
                                  kernel_initializer='normal',
                                  activation='swish'))
        model.add(tf.keras.layers.Dense(1, activation="sigmoid"))
        opt = tf.keras.optimizers.Adam(learning_rate=0.001)
        model.compile(loss='binary_crossentropy',
                      optimizer=opt,
                      metrics=metrics)
        return model
Ejemplo n.º 2
0
class NeuralNetwork:
    """
    Features
    - Run a single config_num
    - Run multiple config_nums
    - Will automatically write and save results of model - see save_dir and write_dir for more information
    Notes:
    - Supports Tensorboard
    """

    def __init__(self,  channel, binary, write_filename, show_graph=False):
        self.show_graph = show_graph
        self.channel = channel
        self.binary = binary
        self.write_filename = write_filename
        # variables are bare bones only for rho_rho
        # TODO: make variables for all channels neatly in data loader
        self.variables_rho_rho = [
            "wt_cp_sm", "wt_cp_ps", "wt_cp_mm", "rand",
            "aco_angle_1",
            "mva_dm_1", "mva_dm_2",
            "tau_decay_mode_1", "tau_decay_mode_2",
            "pi_E_1", "pi_px_1", "pi_py_1", "pi_pz_1",
            "pi_E_2", "pi_px_2", "pi_py_2", "pi_pz_2",
            "pi0_E_1", "pi0_px_1", "pi0_py_1", "pi0_pz_1",
            "pi0_E_2", "pi0_px_2", "pi0_py_2", "pi0_pz_2",
            "y_1_1", "y_1_2",
            'met', 'metx', 'mety',
            'metcov00', 'metcov01', 'metcov10', 'metcov11',
            "gen_nu_p_1", "gen_nu_phi_1", "gen_nu_eta_1", #leading neutrino, gen level
            "gen_nu_p_2", "gen_nu_phi_2", "gen_nu_eta_2" #subleading neutrino, gen level
        ]
        self.variables_rho_a1 = [
            "wt_cp_sm", "wt_cp_ps", "wt_cp_mm", "rand",
            "aco_angle_1",
            "mva_dm_1", "mva_dm_2",
            "tau_decay_mode_1", "tau_decay_mode_2",
            "pi_E_1", "pi_px_1", "pi_py_1", "pi_pz_1",
            "pi_E_2", "pi_px_2", "pi_py_2", "pi_pz_2",
            "pi0_E_1", "pi0_px_1", "pi0_py_1", "pi0_pz_1",
            "pi2_px_2", "pi2_py_2", "pi2_pz_2", "pi2_E_2",
            "pi3_px_2", "pi3_py_2", "pi3_pz_2", "pi3_E_2",
            "ip_x_1", "ip_y_1", "ip_z_1",
            "sv_x_2", "sv_y_2", "sv_z_2",
            "y_1_1", "y_1_2",
        ]
        self.variables_a1_a1 = [
            "wt_cp_sm", "wt_cp_ps", "wt_cp_mm", "rand",
            "aco_angle_1",
            "mva_dm_1", "mva_dm_2",
            "tau_decay_mode_1", "tau_decay_mode_2",
            "pi_E_1", "pi_px_1", "pi_py_1", "pi_pz_1",
            "pi_E_2", "pi_px_2", "pi_py_2", "pi_pz_2",
            "pi2_E_1", "pi2_px_1", "pi2_py_1", "pi2_pz_1",
            "pi3_E_1", "pi3_px_1", "pi3_py_1", "pi3_pz_1",
            "pi2_px_2", "pi2_py_2", "pi2_pz_2", "pi2_E_2",
            "pi3_px_2", "pi3_py_2", "pi3_pz_2", "pi3_E_2",
            "ip_x_1", "ip_y_1", "ip_z_1",
            "sv_x_2", "sv_y_2", "sv_z_2",
            "y_1_1", "y_1_2",
        ]
        self.save_dir = 'NN_output'
        self.write_dir = 'NN_output'
        self.model = None

    def run(self, config_num, read=True, from_pickle=True, epochs=50, batch_size=1024, patience=10, addons_config={}):
        df = self.initialize(addons_config, read=read, from_pickle=from_pickle)
        X_train, X_test, y_train, y_test = self.configure(df, config_num)
        print(f'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Training config {config_num}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
        model = self.train(X_train, X_test, y_train, y_test, epochs=epochs, batch_size=batch_size, patience=patience)
        if self.binary:
            auc = self.evaluateBinary(model, X_test, y_test, self.history)
        else:
            w_a = df.w_a
            w_b = df.w_b
            auc = self.evaluate(model, X_test, y_test, self.history, w_a, w_b)
        self.write(auc, self.history)

    def runMultiple(self, configs, read=True, from_pickle=True, epochs=50, batch_size=1024, patience=10, addons_config={}):
        df = self.initialize(addons_config, read=read, from_pickle=from_pickle)
        for config_num in configs:
            print(f'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Training config {config_num}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
            X_train, X_test, y_train, y_test = self.configure(df, config_num)
            model = self.train(X_train, X_test, y_train, y_test, epochs=epochs, batch_size=batch_size, patience=patience)
            if self.binary:
                auc = self.evaluateBinary(model, X_test, y_test, self.history)
            else:
                w_a = df.w_a
                w_b = df.w_b
                auc = self.evaluate(model, X_test, y_test, self.history, w_a, w_b)
            self.write(auc, self.history)

    def runHPTuning(self, config_num, read=True, from_pickle=True, epochs=50, tuner_epochs=50, batch_size=10000, tuner_batch_size=10000, patience=10, tuner_mode=0, addons_config={}):
        df = self.initialize(addons_config, read=read, from_pickle=from_pickle)
        X_train, X_test, y_train, y_test = self.configure(df, config_num)
        print(f'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Tuning on config {config_num}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
        best_hps, tuner = self.tuneHP(self.hyperModel, X_train, X_test, y_train, y_test, tuner_epochs=tuner_epochs, tuner_batch_size=tuner_batch_size, tuner_mode=tuner_mode)
        print(tuner.results_summary())
        model = tuner.hypermodel.build(best_hps)
        self.model = model
        # model.fit(X_train, y_train, epochs=epochs, validation_data = (X_test, y_test), verbose=0)
        # verbose is set 0 to prevent logs from spam
        model = self.train(X_train, X_test, y_train, y_test, epochs=epochs, batch_size=batch_size, patience=patience, verbose=0)
        if self.binary:
            auc = self.evaluateBinary(model, X_test, y_test, self.history)
        else:
            w_a = df.w_a
            w_b = df.w_b
            auc = self.evaluate(model, X_test, y_test, self.history, w_a, w_b)
        file = f'{self.write_dir}/best_hp_{self.channel}.txt'
        with open(file, 'a+') as f:
            # print(f'Writing HPs to {file}')
            time_str = datetime.datetime.now().strftime('%Y/%m/%d|%H:%M:%S')
            best_num_layers = best_hps.get('num_layers')
            # best_batch_norm = best_hps.get('batch_norm')
            # best_dropout = best_hps.get('dropout')
            actual_epochs = len(self.history.history["loss"])
            # message = f'{time_str},{auc},{self.config_num},{best_num_layers},{best_batch_norm},{best_dropout},{tuner_mode}\n'
            message = f'{time_str},{auc},{self.config_num},{best_num_layers},{tuner_mode}\n'
            print(f"Message: {message}")
        #     f.write(message)
        # model.save('./hp_model_1/')

    def runGridSearch(self, config_num, read=True, from_pickle=True, addons_config={}, search_mode=0):
        """
        Runs grid search on NN with given config_num
        search_mode = 0: GridSearch
        search_mode = 1: RandomSearch
        """
        df = self.initialize(addons_config, read=read, from_pickle=from_pickle)
        X_train, X_test, y_train, y_test = self.configure(df, config_num)
        print(f'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Grid searching on config {config_num}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
        model = KerasClassifier(self.gridModel, verbose=0)
        if search_mode == 0:
            layers = [2,3,4,5,6]
            batch_norms = [True, False]
            dropouts = [None, 0.2]
            epochs = [100, 200, 500]
            batch_sizes = [8192, 16384, 65536, 131072]
            param_grid = dict(
                layers=layers,
                batch_norm=batch_norms,
                dropout=dropouts,
                epochs=epochs,
                batch_size=batch_sizes
            )
            grid = GridSearchCV(estimator=model, param_grid=param_grid, cv=2, verbose=2, scoring='roc_auc')
        elif search_mode == 1:
            layers = np.arange(1,11)
            batch_norms = [True, False]
            dropouts = [None, 0.1, 0.2, 0.3, 0.4, 0.5]
            epochs = [50, 100, 200, 500]
            batch_sizes = [2**i for i in range(8, 19)]
            # layers = [2,3]
            # batch_norms = [True, False]
            # dropouts = [None, 0.2]
            # epochs = [200, 500]
            # batch_sizes = [8192, 16384, 65536, 131072]
            param_grid = dict(
                layers=layers,
                batch_norm=batch_norms,
                dropout=dropouts,
                epochs=epochs,
                batch_size=batch_sizes
            )
            # can increase the distributions of params
            grid = RandomizedSearchCV(estimator=model, param_distributions=param_grid, cv=2, verbose=2, scoring='roc_auc', random_state=seed_value, n_iter=20)
        else:
            raise ValueError('Search mode not defined correctly')
        grid_result = grid.fit(X_train, y_train)
        print(grid_result)
        best_num_layers = grid_result.best_params_['layers']
        best_batch_norm = grid_result.best_params_['batch_norm']
        best_dropout = grid_result.best_params_['dropout']
        best_epochs = grid_result.best_params_['epochs']
        best_batchsize = grid_result.best_params_['batch_size']
        self.model = self.gridModel(layers=best_num_layers, batch_norm=best_batch_norm, dropout=best_dropout)
        model = self.train(X_train, X_test, y_train, y_test, epochs=best_epochs, batch_size=best_batchsize, verbose=0)
        if self.binary:
            auc = self.evaluateBinary(model, X_test, y_test, self.history)
        else:
            w_a = df.w_a
            w_b = df.w_b
            auc = self.evaluate(model, X_test, y_test, self.history, w_a, w_b)
        print("Best: %f using %s" % (grid_result.best_score_, grid_result.best_params_))
        means = grid_result.cv_results_['mean_test_score']
        stds = grid_result.cv_results_['std_test_score']
        params = grid_result.cv_results_['params']
        for mean, stdev, param in zip(means, stds, params):
            print("%f (%f) with: %r" % (mean, stdev, param))
        file = f'{self.write_dir}/grid_search_{self.channel}.txt'
        with open(file, 'a+') as f:
            print(f'Writing HPs to {file}')
            time_str = datetime.datetime.now().strftime('%Y/%m/%d|%H:%M:%S')
            message = f'{time_str},{auc},{self.config_num},{best_num_layers},{best_batch_norm},{best_dropout},{best_epochs},{best_batchsize},{search_mode},{grid_result.best_score_},{param_grid}\n'
            print(f"Message: {message}")
            f.write(message)
        model.save(f'./saved_models/grid_search_model_{config_num}_{self.channel}_{search_mode}/')

    def tuneHP(self, hyperModel, X_train, X_test, y_train, y_test, tuner_epochs=50, tuner_batch_size=10000, tuner_mode=0):
        if tuner_mode == 0:
            tuner = kt.Hyperband(hyperModel,
                                 objective=kt.Objective("auc", direction="max"),  # ['loss', 'auc', 'accuracy', 'val_loss', 'val_auc', 'val_accuracy']
                                 max_epochs=200,
                                 hyperband_iterations=3,
                                 factor=3,
                                 seed=seed_value,
                                 directory='tuning',
                                 project_name='model_hyperband_1',
                                 overwrite=True)
        elif tuner_mode == 1:
            tuner = kt.BayesianOptimization(hyperModel,
                                            objective='val_loss',
                                            max_trials=100,
                                            seed=seed_value,
                                            directory='tuning',
                                            project_name='model_bayesian_1',
                                            overwrite=True)
        elif tuner_mode == 2:
            tuner = kt.RandomSearch(hyperModel,
                                    objective='val_loss',
                                    max_trials=1000,
                                    seed=seed_value,
                                    directory='tuning',
                                    project_name='model_random_1',
                                    overwrite=True)
        else:
            raise ValueError('Invalid tuner mode')
        tuner.search(X_train, y_train, epochs=tuner_epochs, batch_size=tuner_batch_size, validation_data=(X_test, y_test), verbose=0)
        # tuner.search(X_train, y_train, epochs=tuner_epochs, validation_data=(X_test, y_test), verbose=1)
        best_hps = tuner.get_best_hyperparameters(num_trials=1)[0]
        print(tuner.search_space_summary())
        return best_hps, tuner

    def initialize(self, addons_config={}, read=True, from_pickle=True):
        """
        Initialize NN by loading/ creating the input data for NN via DataLoader
        Params:
        addons(dict) - addon map each value being an addon configuration
        read - will read df inputs instead of creating them
        from_pickle - will read events from pickle instead of .root file
        Returns: df of NN inputs (to be configured) 
        """
        if not addons_config:
            addons = []
        else:
            addons = addons_config.keys()
        if self.channel == 'rho_rho':
            self.DL = DataLoader(self.variables_rho_rho, self.channel)
        elif self.channel == 'rho_a1':
            self.DL = DataLoader(self.variables_rho_a1, self.channel)
        elif self.channel == 'a1_a1':
            self.DL = DataLoader(self.variables_a1_a1, self.channel)
        else:
            raise ValueError('Incorrect channel inputted')
        if read:
            df = self.DL.loadRecoData(self.binary, addons)
        else:
            df = self.DL.createRecoData(self.binary, from_pickle, addons, addons_config)
        return df

    def configure(self, df, config_num):
        """
        Configures NN inputs - selects config_num and creates train/test split
        """
        self.config_num = config_num
        CL = ConfigLoader(df, self.channel)
        X_train, X_test, y_train, y_test = CL.configTrainTestData(self.config_num, self.binary)
        return X_train, X_test, y_train, y_test


    def train(self, X_train, X_test, y_train, y_test, epochs=50, batch_size=1024, patience=10, save=False, verbose=1):
        self.epochs = epochs
        self.batch_size = batch_size
        if self.model is None:
            self.model = self.kristof_model(X_train.shape[1])
        self.history = tf.keras.callbacks.History()
        early_stop = tf.keras.callbacks.EarlyStopping(monitor='val_loss', patience=patience)
        log_dir = "logs/fit/" + datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
        tensorboard_callback = tf.keras.callbacks.TensorBoard(log_dir=log_dir, histogram_freq=1)
        self.model.fit(X_train, y_train,
                       batch_size=batch_size,
                       epochs=epochs,
                       callbacks=[self.history, tensorboard_callback], #, early_stop],
                       validation_data=(X_test, y_test),
                       verbose=verbose)
        if save:
            self.model.save(f'./saved_models/{self.save_dir}/NN')
        return self.model

    def evaluate(self, model, X_test, y_test, history, w_a, w_b):
        config_str = self.createConfigStr()
        E = Evaluator(model, self.binary, self.save_dir, config_str)
        auc = E.evaluate(X_test, y_test, history, show=self.show_graph, w_a=w_a, w_b=w_b)
        return auc

    def write(self, auc, history):
        file = f'{self.write_dir}/{self.write_filename}.txt'
        with open(file, 'a+') as f:
            print(f'Writing to {file}')
            time_str = datetime.datetime.now().strftime('%Y/%m/%d|%H:%M:%S')
            actual_epochs = len(history.history["loss"])
            f.write(f'{time_str},{auc},{self.config_num},{self.layers},{self.epochs},{actual_epochs},{self.batch_size},{self.binary},{self.model_str}\n')
        print('Finish writing')
        f.close()

    def evaluateBinary(self, model, X_test, y_test, history):
        config_str = self.createConfigStr()
        E = Evaluator(model, self.binary, self.save_dir, config_str)
        auc = E.evaluate(X_test, y_test, history, show=self.show_graph)
        return auc

    def createConfigStr(self):
        if self.binary:
            config_str = f'config{self.config_num}_{self.layers}_{self.epochs}_{self.batch_size}_{self.model_str}_binary'
        else:
            config_str = f'config{self.config_num}_{self.layers}_{self.epochs}_{self.batch_size}_{self.model_str}'
        return config_str

    def seq_model(self, units=(300, 300), batch_norm=False, dropout=None):
        self.model = tf.keras.models.Sequential()
        self.layers = len(units)
        for unit in units:
            self.model.add(tf.keras.layers.Dense(unit, kernel_initializer='normal'))
            if batch_norm:
                self.model.add(tf.keras.layers.BatchNormalization())
            self.model.add(tf.keras.layers.Activation('relu'))
            if dropout is not None:
                self.model.add(tf.keras.layers.Dropout(dropout))
        self.model.add(tf.keras.layers.Dense(1, activation="sigmoid"))
        metrics = ['AUC', 'accuracy']
        self.model.compile(loss='binary_crossentropy', optimizer='adam', metrics=metrics)
        self.model_str = "seq_model"
        return self.model

    def hyperModel(self, hp):
        self.model = tf.keras.models.Sequential()
        num_layers = hp.Int('num_layers', 2, 3)
        self.layers = num_layers
        for i in range(num_layers):
            self.model.add(tf.keras.layers.Dense(units=300, kernel_initializer='normal'))
            # if hp.Boolean('batch_norm', default=False):
                # self.model.add(tf.keras.layers.BatchNormalization())
            # self.model.add(tf.keras.layers.Dropout(rate=hp.Float('dropout', min_value=0.0, max_value=0.2, default=0.0, step=0.2)))
        self.model.add(tf.keras.layers.Dense(1, activation="sigmoid"))
        metrics = ['AUC', 'accuracy']
        self.model.compile(loss='binary_crossentropy', optimizer='adam', metrics=metrics)
        self.model_str = "hyper_model"
        return self.model

    def gridModel(self, layers=2, batch_norm=False, dropout=None):
        self.model = tf.keras.models.Sequential()
        self.layers = layers
        for i in range(layers):
            self.model.add(tf.keras.layers.Dense(300, kernel_initializer='normal'))
            if batch_norm:
                self.model.add(tf.keras.layers.BatchNormalization())
            self.model.add(tf.keras.layers.Activation('relu'))
            if dropout is not None:
                self.model.add(tf.keras.layers.Dropout(dropout))
        self.model.add(tf.keras.layers.Dense(1, activation="sigmoid"))
        metrics = ['AUC', 'accuracy']
        self.model.compile(loss='binary_crossentropy', optimizer='adam', metrics=metrics)
        self.model_str = "grid_model"
        return self.model

    def kristof_model(self, dimensions):
        # model by kristof
        model = tf.keras.models.Sequential()
        self.layers = 2
        self.model_str = "kristof_model"
        metrics = ['AUC', 'accuracy']
        model.add(tf.keras.layers.Dense(300, input_dim=dimensions, kernel_initializer='normal', activation='relu'))
        model.add(tf.keras.layers.Dense(300, kernel_initializer='normal', activation='relu'))
        model.add(tf.keras.layers.Dense(1, activation="sigmoid"))
        opt = tf.keras.optimizers.Adam(learning_rate=0.001)
        model.compile(loss='binary_crossentropy', optimizer=opt, metrics=metrics)
        return model