Ejemplo n.º 1
0
def train_experiment():
    normal_train, normal_test = get_data(data_cnt=1200,
                                         split_ratio=0.5,
                                         file_name='roomselection_1800.csv')

    vae, summ, saver = make_model(encoded_size=3,
                                  input_shape=5,
                                  dense_layers_dims=[])

    history = vae_fit(vae,
                      summ,
                      saver,
                      normal_train,
                      normal_test,
                      10000,
                      200,
                      tag='5x3_bern_b')

    plts.plot_history(12, 4, history)
Ejemplo n.º 2
0
# saver.restore(sess, LOGDIR + '/headless_train.ckpt')

# output of TFP layer should be a distribution object
history = vae.fit(
    x=normal_train,
    y=normal_train,
    epochs=10000,
    batch_size=normal_train.shape[0],
    steps_per_epoch=200,  # 3*400 = 1200
    validation_data=(normal_test, normal_test),
    validation_steps=1,
    callbacks=[tensorboard_callback])
#, distribute=strategy)

plts.plot_history(12, 4, history)

save_path = saver.save(sess, LOGDIR + '/headless_train.ckpt')
vae.summary()

# # with tf.Session() as sess:
# #     sess.run(init)
#     '''
#     Sample
#     '''
#     # We'll just examine ten random digits.
#
#     # assert tf.executing_eagerly()
#     # x = np.random.uniform(size=(333, input_shape))
#
#     # z = vae.predict(x=x)
Ejemplo n.º 3
0
def main(_):

    print('Configuration = (PP: {}, R: {}, C: {}, B: {}, RT: {})'.format(
        FLAGS.preprocess, 
        FLAGS.regenerate, 
        FLAGS.clahe,
        FLAGS.blur,
        FLAGS.random_transform
    ))

    data_loader = DataLoader(TRAIN_FILE, LOG_FILE, IMG_DIR, 
                             angle_correction = FLAGS.angle_correction,
                             mirror_min_angle = FLAGS.mirror_min_angle,
                             normalize_factor = FLAGS.normalize_factor,
                             normalize_bins = FLAGS.normalize_bins)

    images, measurements = data_loader.load_dataset(regenerate = FLAGS.regenerate)

    print('Total samples: {}'.format(images.shape[0]))

    # Split in training and validation
    X_train, X_valid, Y_train, Y_valid = data_loader.split_train_test(images, measurements)

    print('Training samples: {}'.format(X_train.shape[0]))
    print('Validation samples: {}'.format(X_valid.shape[0]))

    plots.plot_distribution(Y_train[:,0], 'Training set distribution', save_path = os.path.join('images', 'train_distribution'))
    plots.plot_distribution(Y_valid[:,0], 'Validation set distribution', save_path = os.path.join('images', 'valid_distribution'))

    train_generator = data_loader.generator(X_train, Y_train, FLAGS.batch_size, 
                                            preprocess = FLAGS.preprocess, 
                                            random_transform = FLAGS.random_transform)
    valid_generator = data_loader.generator(X_valid, Y_valid, FLAGS.batch_size, 
                                            preprocess = FLAGS.preprocess, 
                                            random_transform = False)

    # The image processor gives us the input shape for the model (e.g. after cropping and resizing)
    model = build_model(ip.output_shape())

    print(model.summary())

    model.compile(optimizer = Adam(lr = FLAGS.learning_rate), loss = FLAGS.loss)

    date_time_str = time.strftime('%Y%m%d-%H%M%S')

    callbacks = [
        # To be used with tensorboard, creates the logs for the losses in the logs dir
        TensorBoard(log_dir = os.path.join(LOGS_DIR, date_time_str), 
                    histogram_freq = 0,
                    write_graph = False,
                    write_images = False),
        # Early stopping guard
        EarlyStopping(monitor='val_loss', 
                    patience = 3,
                    verbose = 0, 
                    mode = 'min')
    ]
    
    model_name = 'model_{}'.format(date_time_str)

    print('Training {} on {} samples (EP: {}, BS: {}, LR: {}, DO: {}, BN: {}, A: {}, L: {})...'.format(
        model_name, X_train.shape[0], FLAGS.epochs, FLAGS.batch_size, FLAGS.learning_rate, FLAGS.dropout, 
        '{}'.format(FLAGS.batch_norm if FLAGS.batch_norm > 0 else 'OFF'),
        FLAGS.activation, FLAGS.loss
    ))
    
    # Train the model
    history = model.fit_generator(train_generator, 
                                  nb_epoch = FLAGS.epochs,
                                  samples_per_epoch = X_train.shape[0],
                                  validation_data = valid_generator,
                                  nb_val_samples = X_valid.shape[0],
                                  callbacks = callbacks)

    model.save(os.path.join(MODELS_DIR, model_name + '.h5'))

    plots.plot_history(model_name, history)
Ejemplo n.º 4
0
def fine_tune(model_filename, model_name, depth_level, data_in, train_dir,
              valid_dir, test_dir, image_dir, model_dir, epochs, opt):
    """
    function that fine tunes a pre-trained cnn model using a small learning rate
    or trains a model from scracth
    :param model_filename: filename of hdf5 file (Keras model) to be loaded
    :param model_name: name of the original cnn model - necessary for preprocessing
        (currently only supports InceptionV3 and VGG19)
    :param depth_level: one of [shallow, intermediate, deep]
    :data_in: a tag to keep track of input data used
    :train_dir: training folder
    :valid_dir: validation folder
    :test_dir:  test folder
    :image_dir: image output location
    :model_dir: model output location
    :epochs: number of epochs to train
    :opt: string that maps to  keras optmizer to be used for training
    :return: 
        the entire trained model
        the test set accuracy
        the test set confusion matrix
    also saves a plot of the training loss/accuracy
    """
    ########
    # common trainig parameters:
    ########

    batch_size = 16
    loss = 'categorical_crossentropy'

    # save the number of classes:
    num_classes = len(os.listdir(train_dir))

    opts = {
        'SGD (1e-2)':
        optimizers.SGD(lr=0.01, momentum=0.0, clipvalue=5.),
        'SGD (1e-2) momentum 0.5':
        optimizers.SGD(lr=0.01, momentum=0.5, clipvalue=5.),
        'SGD (1e-2) momentum 0.9':
        optimizers.SGD(lr=0.01, momentum=0.9, clipvalue=5.),
        'SGD (1e-3)':
        optimizers.SGD(lr=0.001, momentum=0.0, clipvalue=5.),
        'SGD (1e-3) momentum 0.5':
        optimizers.SGD(lr=0.001, momentum=0.5, clipvalue=5.),
        'SGD (1e-3) momentum 0.9':
        optimizers.SGD(lr=0.001, momentum=0.9, clipvalue=5.),
        'RMSprop (1e-3) ':
        optimizers.RMSprop(lr=0.001, rho=0.9, epsilon=None, decay=0.0),
        'Adam (1e-2)':
        optimizers.Adam(lr=0.001,
                        beta_1=0.9,
                        beta_2=0.999,
                        epsilon=None,
                        decay=0.0,
                        amsgrad=False),
        'Adamax (2e-3)':
        optimizers.Adamax(lr=0.002,
                          beta_1=0.9,
                          beta_2=0.999,
                          epsilon=None,
                          decay=0.0)
    }
    opt = opts[opt]

    if model_filename:
        # user provided a filename of a model saved with weights
        tag = 'fine_tune'
        # load the model:
        model = load_model(os.path.join(model_dir, model_filename))
    else:
        print('starting new model with random weights')

        tag = 'randomly_initialized_weights'
        # we start a model from scratch
        base_model = load_part_model(model_name, depth_level, None)

        top_model = Sequential()
        top_model.add(
            GlobalAveragePooling2D(input_shape=base_model.output_shape[1:4]))
        top_model.add(Dense(512, activation='relu'))
        top_model.add(Dropout(rate=0.5))
        # the last layer depends on the number of classes
        top_model.add(Dense(num_classes, activation='softmax'))

        # set the entire model:
        # build the network
        model = Model(inputs=base_model.input,
                      outputs=top_model(base_model.output))

        # delete top model to release memory
        del top_model

    # make sure all layers are trainable
    for layer in model.layers:
        layer.trainable = True

    # set the generator
    datagen = ImageDataGenerator(
        preprocessing_function=model_preprocess(model_name))

    # do the same thing for both training and validation:
    train_generator = datagen.flow_from_directory(
        train_dir,
        target_size=model.input_shape[1:3],
        batch_size=batch_size,
        class_mode='categorical',
        shuffle=True)

    valid_generator = datagen.flow_from_directory(
        valid_dir,
        target_size=model.input_shape[1:3],
        batch_size=batch_size,
        class_mode='categorical',
        shuffle=False)

    if valid_generator.num_classes != train_generator.num_classes:
        print(
            'Warning! Different number of classes in training and validation')

    #################################
    # generators set
    #################################

    model.compile(optimizer=opt, loss=loss, metrics=['accuracy'])
    print(f'model needs {get_model_memory_usage(batch_size, model)} Gb')

    history = model.fit_generator(generator=train_generator,
                                  validation_data=valid_generator,
                                  shuffle=True,
                                  steps_per_epoch=len(train_generator),
                                  validation_steps=len(valid_generator),
                                  epochs=epochs)

    # plot and save the training history:
    plot_history(history, model_name, depth_level, data_in, tag, image_dir)

    # predict test values accuracy
    generator = datagen.flow_from_directory(test_dir,
                                            target_size=model.input_shape[1:3],
                                            batch_size=batch_size,
                                            class_mode='categorical',
                                            shuffle=False)

    pred = model.predict_generator(generator, verbose=0, steps=len(generator))

    # save results as dataframe
    df = pd.DataFrame(pred, columns=generator.class_indices.keys())
    df['file'] = generator.filenames
    df['true_label'] = df['file'].apply(os.path.dirname).apply(str.lower)
    df['pred_idx'] = np.argmax(df[generator.class_indices.keys()].to_numpy(),
                               axis=1)
    # save as the label (dictionary comprehension because generator.class_indices has the
    # key,values inverted to what we want
    df['pred_label'] = df['pred_idx'].map(
        {value: key
         for key, value in generator.class_indices.items()}).apply(str.lower)
    # save the maximum probability for easier reference:
    df['max_prob'] = np.amax(df[generator.class_indices.keys()].to_numpy(),
                             axis=1)

    y_true = df['true_label']
    y_pred = df['pred_label']

    # compute accuracy:
    acc = accuracy_score(y_true=y_true, y_pred=y_pred)

    # compute confusion matrix:
    cfm = confusion_matrix(y_true=y_true, y_pred=y_pred)

    cf_matrix(
        y_true,
        y_pred,
        image_dir,
        plot_name=f"conf_matrix_{data_in}_{model_name}_{depth_level}_{tag}.png",
        dpi=1000)

    # clear memory
    reset_keras()

    return acc, cfm, history
Ejemplo n.º 5
0
                # now, fine tune the model for the "second half" of the
                # epochs:
                model_filename = f"{model}_{depth_level}_{data_in}_frozen.hdf5"

                acc, _, h2 = fine_tune(model_filename, model, depth_level,
                                       data_in, train_dir, valid_dir, test_dir,
                                       image_dir, model_dir, epochs // 2, opt)

                for metr in h2.history:
                    print(metr)
                    for jj in h2.history[metr]:
                        hist.history[metr].append(jj)

                # plot the history:
                plot_history(hist, model, depth_level, data_in, 'fine_tune',
                             image_dir)

                print(f'{acc:.2f}')
                acc_dict[dict_counter] = {
                    "model": model,
                    "depth": depth_level,
                    "depth_sw":
                    depths_dict[depth_level] + data_mod_dict[data_in],
                    "dataset": data_in,
                    "mode": 'fine tune',
                    "accuracy": acc
                }
                dict_counter += 1
                reset_keras()
                ###############################################################
                # random initialization:
Ejemplo n.º 6
0
                "model": model,
                "dataset": data_in,
                "mode": 'feature extraction',
                "accuracy": acc
            }
            dict_counter += 1
            reset_keras()

            # plot the confusion matrix
            cf_matrix(y_true,
                      y_pred,
                      image_dir,
                      plot_name=f"conf_matrix_{data_in}_{model}_feat_extract",
                      title=f'Confusion Matrix for {model} - feature extract')
            # plot the history:
            plot_history(hist, model, data_in, 'feat_extract', image_dir,
                         f'{model} \nfeature extract')

            ###############################################################
            # fine tune:
            # to mantain the same overall epochs used in training,
            # first fine tune using half of the total epochs (save the history):
            _, _, hist = train_frozen(model, weights, data_in, train_dir,
                                      valid_dir, test_dir, image_dir,
                                      model_dir, epochs // 2, opt)

            # now, fine tune the model for the "second half" of the
            # epochs:
            model_filename = f"{model}_{data_in}_frozen.hdf5"

            acc, [y_true,
                  y_pred], h2 = fine_tune(model_filename, model, data_in,
Ejemplo n.º 7
0
# init datagenerator
train_generator = DataGen(data_path=patchespath, n_patches = n_patches_train, 
                shuffle=True, augment=True, indices=index_train , 
                batch_size=batch_size, patch_size=patch_size_padded, 
                n_classes=n_classes, channels=channels, max_size=max_size,pretrained_resnet50=pretrained_resnet50)
val_generator = DataGen(data_path = patchespath, n_patches = n_patches_val, 
                shuffle=True, augment=False, indices=index_val , 
                batch_size=batch_size, patch_size=patch_size_padded, 
                n_classes=n_classes, channels=channels, max_size=max_size,pretrained_resnet50=pretrained_resnet50)

# run
result = model.fit_generator(generator=train_generator, validation_data=val_generator, 
                epochs=epochs,callbacks=[checkpoint,tensorboard, stop]) 

# plot training history
plot_history(result)


#%% Test
"""
Test the keras model on independent test set
"""
from dataset import load_test_indices
from datagenerator import DataGen
from tensorflow.keras.models import load_model
from models.BilinearUpSampling import BilinearUpSampling2D

# init
batch_size = 125

# load model
def fine_tune(model_filename, model_name, data_in, train_dir, valid_dir,
              test_dir, image_dir, model_dir, epochs):
    """
    function that fine tunes a pre-trained cnn model using a small learning rate
    :param model_filename: filename of hdf5 file (Keras model) to be loaded
    :param model_name: name of the original cnn model - necessary for preprocessing
        (currently only supports InceptionV3 and VGG19)
    :data_in: a tag to keep track of input data used
    :train_dir: training folder
    :valid_dir: validation folder
    :test_dir:  test folder
    :image_dir: image output location
    :model_dir: model output location
    :epochs: number of epochs to train
    :return: 
        the entire trained model
        the test set accuracy
        the test set confusion matrix
    also saves a plot of the training loss/accuracy
    """
    ########
    # common trainig parameters:
    ########
    batch_size = 32
    lrate = 5 * 1e-5
    loss = 'categorical_crossentropy'
    opt = SGD(lr=lrate, momentum=0.0, clipvalue=5.)

    # load the model:
    model = load_model(os.path.join(model_dir, model_filename))

    # make sure all layers are trainable
    for layer in model.layers:
        layer.trainable = True

    # set the generator
    datagen = ImageDataGenerator(
        preprocessing_function=model_preprocess(model_name))

    # do the same thing for both training and validation:
    train_generator = datagen.flow_from_directory(
        train_dir,
        target_size=model.input_shape[1:3],
        batch_size=batch_size,
        class_mode='categorical',
        shuffle=True)

    valid_generator = datagen.flow_from_directory(
        valid_dir,
        target_size=model.input_shape[1:3],
        batch_size=batch_size,
        class_mode='categorical',
        shuffle=False)

    if valid_generator.num_classes != train_generator.num_classes:
        print(
            'Warning! Different number of classes in training and validation')

    #################################
    # generators set
    #################################

    model.compile(optimizer=opt, loss=loss, metrics=['accuracy'])
    print(f'model needs {get_model_memory_usage(batch_size, model)} Gb')

    history = model.fit_generator(generator=train_generator,
                                  validation_data=valid_generator,
                                  shuffle=True,
                                  epochs=epochs)

    # plot and save the training history:
    plot_history(history, model_name, depth_level, data_in, 'fine_tune',
                 image_dir)

    # predict test values accuracy
    generator = datagen.flow_from_directory(test_dir,
                                            target_size=model.input_shape[1:3],
                                            batch_size=batch_size,
                                            class_mode='categorical',
                                            shuffle=False)

    pred = model.predict_generator(generator, verbose=0, steps=len(generator))

    # save results as dataframe
    df = pd.DataFrame(pred, columns=generator.class_indices.keys())
    df['file'] = generator.filenames
    df['true_label'] = df['file'].apply(os.path.dirname).apply(str.lower)
    df['pred_idx'] = np.argmax(df[generator.class_indices.keys()].to_numpy(),
                               axis=1)
    # save as the label (dictionary comprehension because generator.class_indices has the
    # key,values inverted to what we want
    df['pred_label'] = df['pred_idx'].map(
        {value: key
         for key, value in generator.class_indices.items()}).apply(str.lower)
    # save the maximum probability for easier reference:
    df['max_prob'] = np.amax(df[generator.class_indices.keys()].to_numpy(),
                             axis=1)

    y_true = df['true_label']
    y_pred = df['pred_label']

    # compute accuracy:
    acc = accuracy_score(y_true=y_true, y_pred=y_pred)

    # compute confusion matrix:
    cfm = confusion_matrix(y_true=y_true, y_pred=y_pred)

    cf_matrix(
        y_true,
        y_pred,
        image_dir,
        plot_name=
        f"conf_matrix_{data_in}_{model_name}_{depth_level}_fine_tune.png",
        dpi=200)

    # clear memory
    reset_keras()

    return acc, cfm
def train_frozen(model_name, depth_level, weights, data_in, train_dir,
                 valid_dir, test_dir, image_dir, model_dir, epochs):
    """
    function that freezes a cnn model to extract features and train a small
    classification NN on top of the extracted features. 
    :param model_name: name of the cnn model to be loaded (currently only 
                       supports InceptionV3 and VGG19)
    :param depth_level: one of [shallow, intermediate, deep]
    :weights: one of ['imagenet', None]
    :data_in: a tag to keep track of input data used
    :train_dir: training folder
    :valid_dir: validation folder
    :test_dir:  test folder
    :image_dir: image output location
    :model_dir: model output location
    :epochs: number of epochs to train
    :return: 
        the test set accuracy
        the test set confusion matrix
    also saves a plot of the training loss/accuracy
    saves the model 
    saves a plot of the confusion matrix
    """
    ########
    # common trainig parameters:
    ########
    batch_size = 32
    lrate = 1e-3
    loss = 'categorical_crossentropy'
    opt = SGD(lr=lrate, momentum=0.0, clipvalue=5.)

    # load the base model:
    base_model = load_part_model(model_name, depth_level, weights)
    # freeze layers (layers will not be updated during the first training process)
    for layer in base_model.layers:
        layer.trainable = False

    # save the number of classes:
    num_classes = len(os.listdir(train_dir))

    # set the generator
    datagen = ImageDataGenerator(
        preprocessing_function=model_preprocess(model_name))

    # do the same thing for both training and validation:
    train_generator = datagen.flow_from_directory(
        train_dir,
        target_size=base_model.input_shape[1:3],
        batch_size=batch_size,
        class_mode='categorical',
        shuffle=True)

    valid_generator = datagen.flow_from_directory(
        valid_dir,
        target_size=base_model.input_shape[1:3],
        batch_size=batch_size,
        class_mode='categorical',
        shuffle=False)

    if valid_generator.num_classes != train_generator.num_classes:
        print(
            'Warning! Different number of classes in training and validation')

    #################################
    # generators set
    #################################

    # create the top model:
    top_model = Sequential()
    top_model.add(
        GlobalAveragePooling2D(input_shape=base_model.output_shape[1:4]))
    top_model.add(Dense(512, activation='relu'))
    top_model.add(Dropout(rate=0.5))
    # the last layer depends on the number of classes
    top_model.add(Dense(num_classes, activation='softmax'))

    # set the entire model:
    # build the network
    model = Model(inputs=base_model.input,
                  outputs=top_model(base_model.output))
    model.compile(optimizer=opt, loss=loss, metrics=['accuracy'])
    #print(model.summary())
    print(f'model needs {get_model_memory_usage(batch_size, model)} Gb')

    history = model.fit_generator(generator=train_generator,
                                  validation_data=valid_generator,
                                  shuffle=True,
                                  epochs=epochs)

    # save model:
    model.save(
        os.path.join(model_dir,
                     f"{model_name}_{depth_level}_{data_in}_frozen.hdf5"))

    # plot and save the training history:
    plot_history(history, model_name, depth_level, data_in, 'feat_extr',
                 image_dir)

    # predict test values accuracy
    generator = datagen.flow_from_directory(
        test_dir,
        target_size=base_model.input_shape[1:3],
        batch_size=batch_size,
        class_mode='categorical',
        shuffle=False)

    pred = model.predict_generator(generator, verbose=0, steps=len(generator))

    # save results as dataframe
    df = pd.DataFrame(pred, columns=generator.class_indices.keys())
    df['file'] = generator.filenames
    df['true_label'] = df['file'].apply(os.path.dirname).apply(str.lower)
    df['pred_idx'] = np.argmax(df[generator.class_indices.keys()].to_numpy(),
                               axis=1)
    # save as the label (dictionary comprehension because generator.class_indices has the
    # key,values inverted to what we want
    df['pred_label'] = df['pred_idx'].map(
        {value: key
         for key, value in generator.class_indices.items()}).apply(str.lower)
    # save the maximum probability for easier reference:
    df['max_prob'] = np.amax(df[generator.class_indices.keys()].to_numpy(),
                             axis=1)

    y_true = df['true_label']
    y_pred = df['pred_label']

    # compute accuracy:
    acc = accuracy_score(y_true=y_true, y_pred=y_pred)

    # compute confusion matrix:
    cfm = confusion_matrix(y_true=y_true, y_pred=y_pred)

    # plot confusion matrix:
    cf_matrix(
        y_true,
        y_pred,
        image_dir,
        plot_name=
        f"conf_matrix_{data_in}_{model_name}_{depth_level}_feat_extr.png",
        dpi=200)

    # clear memory
    reset_keras()

    return acc, cfm
def feature_extract_fit(model_name, depth_level, weights, data_in, train_dir,
                        valid_dir, test_dir, image_dir):
    """
    function that uses a cnn model to extract features and train a small
    classification NN on top of the extracted features. 
    :param model_name: name of the cnn model to be loaded (currently only 
                       supports InceptionV3 and VGG19)
    :param depth_level: one of [shallow, intermediate, deep]
    :weights: one of ['imagenet', None]
    :data_in: a tag to keep track of input data used
    :train_dir: training folder
    :valid_dir: validation folder
    :test_dir:  test folder
    :image_dir: image output location
    :return: 
        the entire trained model
        the test set accuracy
        the test set confusion matrix
    also saves a plot of the training loss/accuracy
    """
    ########
    # common trainig parameters:
    ########
    batch_size = 8
    epochs = 8
    lrate = 1e-4
    loss = 'categorical_crossentropy'
    opt = SGD(lr=lrate, momentum=0.0, clipvalue=5.)

    train_val_dict = {'train': train_dir, 'validation': valid_dir}

    # load the base model:
    base_model = load_part_model(model_name, depth_level, weights)

    # save the number of classes:
    num_classes = len(os.listdir(train_dir))

    #################################
    # Features can be too large to fit in memory.
    # Save the features as npy files (x, y in the same npy file)
    #################################
    # set the generator
    datagen = ImageDataGenerator(
        preprocessing_function=model_preprocess(model_name))

    # dictionary that will hold the folder names - to be populated inside loop
    feat_train_val_dict = {}
    # do the same thing for both training and validation:
    for dset in tqdm(train_val_dict):
        # extract training features, shuffling now as (x,y) will be combined in the
        # same file. Each file contains a single image feature (X) and its
        # label (y) will be part of the name
        generator = datagen.flow_from_directory(
            train_val_dict[dset],
            target_size=base_model.input_shape[1:3],
            batch_size=1,
            class_mode='sparse',
            shuffle=False)

        if num_classes != generator.num_classes:
            print(
                'Warning! Different number of classes in training and validation'
            )

        # create output folder
        folder_out = os.path.join(os.path.dirname(train_val_dict[dset]),
                                  f"feat_{dset}")
        rmtree(folder_out, ignore_errors=True)
        os.mkdir(folder_out)
        # save location into dictionary:
        feat_train_val_dict[dset] = folder_out

        # loop through all the samples, saving them in the appropriate folder
        sample = 0
        for X_set, y_set in generator:
            X_feat = base_model.predict(X_set)

            np.save(
                os.path.join(folder_out,
                             f'sample_{sample}_label_{np.int(y_set[0])}.npy'),
                X_feat)
            sample += 1

            if sample == len(generator):
                break

    # generator parameters
    params = {
        'dim': base_model.output_shape[1:3],
        'batch_size': batch_size,
        'n_classes': num_classes,
        'n_channels': base_model.output_shape[3]
    }

    # Generators
    train_generator = feats_generator(data_dir=feat_train_val_dict['train'],
                                      shuffle=True,
                                      **params)
    validation_generator = feats_generator(
        data_dir=feat_train_val_dict['validation'], shuffle=False, **params)

    #################################
    # Features saved, generators set
    #################################

    # create the top model:
    top_model = Sequential()
    top_model.add(
        GlobalAveragePooling2D(input_shape=base_model.output_shape[1:4]))
    #top_model.add(Conv2D(512))
    #top_model.add(GlobalAveragePooling2D())
    top_model.add(Dense(512, activation='relu'))
    top_model.add(Dropout(rate=0.5))
    # the last layer depends on the number of classes
    top_model.add(Dense(num_classes, activation='softmax'))

    # train model with parameters specified at the top of the function
    top_model.compile(optimizer=opt, loss=loss, metrics=['accuracy'])

    history = top_model.fit_generator(generator=train_generator,
                                      validation_data=validation_generator,
                                      epochs=epochs)

    # plot and save the training history:
    plot_history(history, model_name, depth_level, data_in, 'feat_extr',
                 image_dir)

    # set the entire model:
    # build the network
    model = Model(inputs=base_model.input,
                  outputs=top_model(base_model.output))

    model.compile(optimizer=opt, loss=loss, metrics=['accuracy'])

    # predict test values accuracy
    generator = datagen.flow_from_directory(
        test_dir,
        target_size=base_model.input_shape[1:3],
        batch_size=batch_size,
        class_mode='categorical',
        shuffle=False)

    pred = model.predict_generator(generator, verbose=0, steps=len(generator))

    # save results as dataframe
    df = pd.DataFrame(pred, columns=generator.class_indices.keys())
    df['file'] = generator.filenames
    df['true_label'] = df['file'].apply(os.path.dirname).apply(str.lower)
    df['pred_idx'] = np.argmax(df[generator.class_indices.keys()].to_numpy(),
                               axis=1)
    # save as the label (dictionary comprehension because generator.class_indices has the
    # key,values inverted to what we want
    df['pred_label'] = df['pred_idx'].map(
        {value: key
         for key, value in generator.class_indices.items()}).apply(str.lower)
    # save the maximum probability for easier reference:
    df['max_prob'] = np.amax(df[generator.class_indices.keys()].to_numpy(),
                             axis=1)

    y_true = df['true_label']
    y_pred = df['pred_label']

    # compute accuracy:
    acc = accuracy_score(y_true=y_true, y_pred=y_pred)

    # compute confusion matrix:
    cfm = confusion_matrix(y_true=y_true, y_pred=y_pred)

    # clear Keras/backend session (freeing memory and preventing slowdown)
    K.clear_session()

    return top_model, acc, cfm