Как выбрать стратегию снижения переоснащения? - PullRequest
6 голосов
/ 05 апреля 2019

Я применяю трансферное обучение в предварительно обученной сети с керасом.У меня есть патчи изображений с меткой двоичного класса и я хотел бы использовать CNN для прогнозирования метки класса в диапазоне [0;1] для невидимых патчей изображений.

  • сеть : ResNet50 предварительно обучен с imageNet, к которому я добавляю 3 слоя
  • данные :70305 обучающих образцов, 8000 проверочных образцов, 66823 тестовых образца, все со сбалансированным количеством меток обоих классов
  • изображения : 3 полосы (RGB) и 224x224 пикселей
  • настройка : 32 партии, размер конв.слой: 16

  • результат : после нескольких эпох у меня уже есть точность почти 1 и потеря близка к 0, в то время как на данных проверки точностиостается на уровне 0,5 и потери варьируются в зависимости от эпохи.В конце концов, CNN предсказывает только один класс для всех невидимых патчей.

  • проблема : похоже, моя сеть перегружена.

results screenshot enter image description here

Следующие стратегии могут уменьшить переоснащение:

  • увеличениеразмер пакета
  • уменьшить размер полностью подключенного слоя
  • добавить выпадающий слой
  • добавить увеличение данных
  • применить регуляризацию, изменив функцию потерь
  • разморозить больше предварительно обученных слоев
  • использовать другую сетевую архитектуру

Я пробовал размеры пакетов до 512 и изменил размер полностью подключенного слоя без особого успеха.Прежде чем просто случайным образом протестировать остальные, Я хотел бы спросить, как выяснить, что идет не так, почему, чтобы выяснить, какая из вышеперечисленных стратегий обладает наибольшим потенциалом .

Ниже моего кода:

def generate_data(imagePathTraining, imagesize, nBatches):
    datagen = ImageDataGenerator(rescale=1./255)
    generator = datagen.flow_from_directory\
        (directory=imagePathTraining,                           # path to the target directory
         target_size=(imagesize,imagesize),                     # dimensions to which all images found will be resize
         color_mode='rgb',                                      # whether the images will be converted to have 1, 3, or 4 channels
         classes=None,                                          # optional list of class subdirectories
         class_mode='categorical',                              # type of label arrays that are returned
         batch_size=nBatches,                                   # size of the batches of data
         shuffle=True)                                          # whether to shuffle the data
    return generator

def create_model(imagesize, nBands, nClasses):
    print("%s: Creating the model..." % datetime.now().strftime('%Y-%m-%d_%H-%M-%S'))
    # Create pre-trained base model
    basemodel = ResNet50(include_top=False,                     # exclude final pooling and fully connected layer in the original model
                         weights='imagenet',                    # pre-training on ImageNet
                         input_tensor=None,                     # optional tensor to use as image input for the model
                         input_shape=(imagesize,                # shape tuple
                                      imagesize,
                                      nBands),
                         pooling=None,                          # output of the model will be the 4D tensor output of the last convolutional layer
                         classes=nClasses)                      # number of classes to classify images into
    print("%s: Base model created with %i layers and %i parameters." %
          (datetime.now().strftime('%Y-%m-%d_%H-%M-%S'),
           len(basemodel.layers),
           basemodel.count_params()))

    # Create new untrained layers
    x = basemodel.output
    x = GlobalAveragePooling2D()(x)                             # global spatial average pooling layer
    x = Dense(16, activation='relu')(x)                         # fully-connected layer
    y = Dense(nClasses, activation='softmax')(x)                # logistic layer making sure that probabilities sum up to 1

    # Create model combining pre-trained base model and new untrained layers
    model = Model(inputs=basemodel.input,
                  outputs=y)
    print("%s: New model created with %i layers and %i parameters." %
          (datetime.now().strftime('%Y-%m-%d_%H-%M-%S'),
           len(model.layers),
           model.count_params()))

    # Freeze weights on pre-trained layers
    for layer in basemodel.layers:
        layer.trainable = False

    # Define learning optimizer
    optimizerSGD = optimizers.SGD(lr=0.01,                      # learning rate.
                                  momentum=0.0,                 # parameter that accelerates SGD in the relevant direction and dampens oscillations
                                  decay=0.0,                    # learning rate decay over each update
                                  nesterov=False)               # whether to apply Nesterov momentum

    # Compile model
    model.compile(optimizer=optimizerSGD,                       # stochastic gradient descent optimizer
                  loss='categorical_crossentropy',              # objective function
                  metrics=['accuracy'],                         # metrics to be evaluated by the model during training and testing
                  loss_weights=None,                            # scalar coefficients to weight the loss contributions of different model outputs
                  sample_weight_mode=None,                      # sample-wise weights
                  weighted_metrics=None,                        # metrics to be evaluated and weighted by sample_weight or class_weight during training and testing
                  target_tensors=None)                          # tensor model's target, which will be fed with the target data during training
    print("%s: Model compiled." % datetime.now().strftime('%Y-%m-%d_%H-%M-%S'))
    return model

def train_model(model, nBatches, nEpochs, imagePathTraining, imagesize, nSamples, valX,valY, resultPath):
    history = model.fit_generator(generator=generate_data(imagePathTraining, imagesize, nBatches),
                                  steps_per_epoch=nSamples//nBatches,     # total number of steps (batches of samples)
                                  epochs=nEpochs,               # number of epochs to train the model
                                  verbose=2,                    # verbosity mode. 0 = silent, 1 = progress bar, 2 = one line per epoch
                                  callbacks=None,               # keras.callbacks.Callback instances to apply during training
                                  validation_data=(valX,valY),  # generator or tuple on which to evaluate the loss and any model metrics at the end of each epoch
                                  class_weight=None,            # optional dictionary mapping class indices (integers) to a weight (float) value, used for weighting the loss function
                                  max_queue_size=10,            # maximum size for the generator queue
                                  workers=32,                   # maximum number of processes to spin up when using process-based threading
                                  use_multiprocessing=True,     # whether to use process-based threading
                                  shuffle=True,                 # whether to shuffle the order of the batches at the beginning of each epoch
                                  initial_epoch=0)              # epoch at which to start training
    print("%s: Model trained." % datetime.now().strftime('%Y-%m-%d_%H-%M-%S')) 
    return history

Ответы [ 2 ]

0 голосов
/ 24 апреля 2019

На основании приведенных выше предложений я изменил следующее:

  • Я изменил оптимизатор обучения (снизил скорость обучения до 0,001 и сделал адаптируемой к затуханию)
  • Я унифицировал генераторы данных (те же ImageDataGenerator для обучения и проверки)
  • Я использовал другую предварительно обученную базовую CNN (VGG19 вместо ResNet50)
  • Я увеличил количество узлов в обучаемом полностью связанном слое (с 16 до 1024), что увеличило точность окончательной проверки
  • Я увеличил коэффициент отсева (с 0,5 до 0,8), что минимизировало разрыв между тренировкой и точностью проверки и, таким образом, ограничило переоснащение
    def generate_data(path, imagesize, nBatches):
        datagen = ImageDataGenerator(preprocessing_function=preprocess_input)
        generator = datagen.flow_from_directory(directory=path,     # path to the target directory
             target_size=(imagesize,imagesize),                     # dimensions to which all images found will be resize
             color_mode='rgb',                                      # whether the images will be converted to have 1, 3, or 4 channels
             classes=None,                                          # optional list of class subdirectories
             class_mode='categorical',                              # type of label arrays that are returned
             batch_size=nBatches,                                   # size of the batches of data
             shuffle=True,                                          # whether to shuffle the data
             seed=42)                                               # random seed for shuffling and transformations
        return generator
    def create_model(imagesize, nBands, nClasses):
        # Create pre-trained base model
        basemodel = VGG19(include_top=False,                        # exclude final pooling and fully connected layer in the original model
                             weights='imagenet',                    # pre-training on ImageNet
                             input_tensor=None,                     # optional tensor to use as image input for the model
                             input_shape=(imagesize,                # shape tuple
                                          imagesize,
                                          nBands),
                             pooling=None,                          # output of the model will be the 4D tensor output of the last convolutional layer
                             classes=nClasses)                      # number of classes to classify images into

        # Freeze weights on pre-trained layers
        for layer in basemodel.layers:
            layer.trainable = False   

        # Create new untrained layers
        x = basemodel.output
        x = GlobalAveragePooling2D()(x)                             # global spatial average pooling layer
        x = Dense(1024, activation='relu')(x)                       # fully-connected layer
        x = Dropout(rate=0.8)(x)                                    # dropout layer
        y = Dense(nClasses, activation='softmax')(x)                # logistic layer making sure that probabilities sum up to 1

        # Create model combining pre-trained base model and new untrained layers
        model = Model(inputs=basemodel.input,
                      outputs=y)

        # Define learning optimizer
        optimizerSGD = optimizers.SGD(lr=0.001,                     # learning rate.
                                      momentum=0.9,                 # parameter that accelerates SGD in the relevant direction and dampens oscillations
                                      decay=learningRate/nEpochs,   # learning rate decay over each update
                                      nesterov=True)                # whether to apply Nesterov momentum
        # Compile model
        model.compile(optimizer=optimizerSGD,                       # stochastic gradient descent optimizer
                      loss='categorical_crossentropy',              # objective function
                      metrics=['accuracy'],                         # metrics to be evaluated by the model during training and testing
                      loss_weights=None,                            # scalar coefficients to weight the loss contributions of different model outputs
                      sample_weight_mode=None,                      # sample-wise weights
                      weighted_metrics=None,                        # metrics to be evaluated and weighted by sample_weight or class_weight during training and testing
                      target_tensors=None)                          # tensor model's target, which will be fed with the target data during training
        return model
    def train_model(model, nBatches, nEpochs, trainGenerator, valGenerator, resultPath):
        history = model.fit_generator(generator=trainGenerator,
                                      steps_per_epoch=trainGenerator.samples // nBatches,   # total number of steps (batches of samples)
                                      epochs=nEpochs,               # number of epochs to train the model
                                      verbose=2,                    # verbosity mode. 0 = silent, 1 = progress bar, 2 = one line per epoch
                                      callbacks=None,               # keras.callbacks.Callback instances to apply during training
                                      validation_data=valGenerator, # generator or tuple on which to evaluate the loss and any model metrics at the end of each epoch
                                      validation_steps=
                                      valGenerator.samples // nBatches,                     # number of steps (batches of samples) to yield from validation_data generator before stopping at the end of every epoch
                                      class_weight=None,            # optional dictionary mapping class indices (integers) to a weight (float) value, used for weighting the loss function
                                      max_queue_size=10,            # maximum size for the generator queue
                                      workers=1,                    # maximum number of processes to spin up when using process-based threading
                                      use_multiprocessing=False,    # whether to use process-based threading
                                      shuffle=True,                 # whether to shuffle the order of the batches at the beginning of each epoch
                                      initial_epoch=0)              # epoch at which to start training

        return history, model

evaluation plot

С этими модификациями я достиг следующих показателей для размера партии 32 после обучения в течение 100 эпох:

  • train_acc: 0,831
  • train_loss: 0,436
  • val_acc: 0,692
  • val_loss: 0,568

Я предполагаю, что эти настройки оптимальны, поскольку:

  • Кривые точности и потерь ведут себя одинаково для обучения и проверки
  • train_acc превышает val_acc только после 30 эпох
  • минимальное переоснащение (небольшая разница между train_acc и val_acc)
  • train_loss и val_loss непрерывно уменьшаются

Однако мне интересно:

  • если я буду готовиться к увеличению количества эпох, val_acc ценой более подходящего
  • почему f1-оценка , точность и вспоминание , полученное с помощью sklearn.metrics classification_report() на predict_generator() предсказаниях, составляет около 0,5, что указывает на отсутствие обучения для Классификация 2 класса.

Может быть, мне лучше открыть новый вопрос по этим вопросам.

0 голосов
/ 06 апреля 2019

Эти результаты кажутся почти слишком плохими, чтобы иметь дело с переоснащением. Вместо этого я подозреваю, что есть разница в данных, используемых для обучения и проверки.

Я отмечаю, что для тренировочных данных, которые вы используете ImageDataGenerator(rescale=1./255), однако для valX я не вижу такой обработки. Я бы порекомендовал использовать отдельный ImageDataGenerator с той же конфигурацией масштабирования и для данных проверки. Таким образом, различия настолько малы, насколько это возможно.

...