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

Зачем передавать обучение?

  • Меньше обучающих данных. Наличие большого количества обучающих данных не всегда практично, у вас может не быть всех необходимых данных, или время, необходимое для обучения больших наборов данных, может оказаться непомерно затратным/временным. В случае моделей CNN для распознавания изображений начальные внешние уровни модели глубокого обучения больше фокусируются на абстрактных функциях более высокого уровня, а верхние слои больше на деталях изображения. В этом случае имеет смысл повторно использовать внешние слои, обученные абстрактным функциям.
  • Лучшее обобщение и, следовательно, лучшие результаты. Предварительно обученные модели используются для трансферного обучения или обычно обучаются на большом объеме данных. Модели, обученные на предварительно обученных моделях в качестве основы, как правило, лучше обобщают невидимые данные, поскольку они были обучены идентифицировать более общие функции.
  • Улучшенная доступность для DL. Многие модели DL теперь легко доступны в Интернете и могут ускорить обучение и повысить производительность для различных вариантов использования DL.

Постановка задачи

Давайте рассмотрим конкретный вариант использования идентификации кошек и собак по изображению. Несмотря на то, что это довольно простая проблема классификации изображений, давайте посмотрим, как трансферное обучение может помочь нам повысить производительность модели при работе с небольшим набором данных. В этом случае мы сосредоточимся на большой консети, обученной на наборе данных ImageNet, с 1,4 миллионами помеченных изображений и 1000 различных классов. Мы будем использовать простую и широко используемую архитектуру сети VGG16.

Есть два способа использовать предварительно обученную сеть. Извлечение признаков и тонкая настройка. Давайте посмотрим их в действии один за другим.

Извлечение признаков

Мы можем использовать представления, полученные сетью более высокого уровня, чтобы извлечь из нее соответствующие функции. Затем эти функции можно запускать с новым классификатором и обучать с нуля. Convnets состоит из двух частей, объединяющих слой свертки с плотно связанным классификатором сверху. Первая часть называется базой свертки модели. Таким образом, в этом случае извлечение признаков будет состоять из повторного использования базового слоя свертки из ImageNet и его обучения с новым классификатором сверху.

Так почему бы не использовать плотно связанный слой от ImageNet, спросите вы? Представления, изученные базой свертки, имеют тенденцию быть более общими и, следовательно, пригодными для повторного использования. При этом представления, изученные плотными классификаторами, более специфичны для варианта использования и данных, на которых он обучен. По этой причине рекомендуется повторно использовать только базу свертки, поскольку она будет содержать более общую информацию, которая с большей вероятностью будет применима для различных задач классификации изображений. Таким образом, уровень общности представлений, захваченных, зависит от глубины слоя, чем выше слой, тем он более «общий». В этом случае мы используем всю базу свертки в качестве основы новой модели, поскольку она уже была обучена на наборах данных о кошках и собаках. Но в тех случаях, когда ваш целевой набор данных сильно отличается от исходной предварительно обученной модели, рекомендуется использовать только выбранные слои из базы свертки, а не весь.

Давайте посмотрим на это в действии.

Давайте создадим экземпляр модели VGG16.

from keras.applications import VGG16

conv_base = VGG16(weights='imagenet',
                  include_top=False,
                  input_shape=(150, 150, 3))

Распечатать сводку модели

conv_base.summary()

Окончательная карта признаков имеет форму (4,4,512). Это признак, поверх которого мы наклеим плотносвязный классификатор.

Давайте определим нашу новую модель и добавим convo_base в качестве слоя.

from keras import models
from keras import layers

model = models.Sequential()
model.add(conv_base)
model.add(layers.Flatten())
model.add(layers.Dense(256, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))

Давайте посмотрим сводку модели.

model.summary()

Прежде чем мы сможем скомпилировать и обучить нашу модель, нам нужно «заморозить» нашу conv_base, чтобы предотвратить обновление весов базовых слоев во время обучения. Это критический шаг, если мы этого не сделаем, то это эффективно уничтожит ранее изученные представления и помешает использованию предварительно обученной базовой модели.

Мы можем «заморозить» слой convo_base, установив для его обучаемого атрибута значение false, как показано ниже.

Сначала давайте проверим количество обучаемых атрибутов перед установкой флага.

print('Trainable weights '
      'before freezing the conv base:', len(model.trainable_weights))

conv_base.trainable = False

Теперь приступим к обучению нашей модели.

from keras.preprocessing.image import ImageDataGenerator

train_datagen = ImageDataGenerator(
      rescale=1./255,
      rotation_range=40,
      width_shift_range=0.2,
      height_shift_range=0.2,
      shear_range=0.2,
      zoom_range=0.2,
      horizontal_flip=True,
      fill_mode='nearest')

train_generator = train_datagen.flow_from_directory(
        # This is the target directory
        train_dir,
        # All images will be resized to 150x150
        target_size=(150, 150),
        batch_size=20,
        class_mode='binary')

validation_generator = test_datagen.flow_from_directory(
        validation_dir,
        target_size=(150, 150),
        batch_size=20,
        class_mode='binary')

model.compile(loss='binary_crossentropy',
              optimizer=optimizers.RMSprop(lr=2e-5),
              metrics=['acc'])

history = model.fit_generator(
      train_generator,
      steps_per_epoch=100,
      epochs=30,
      validation_data=validation_generator,
      validation_steps=50,
      verbose=2)

Сохраните модель

model.save('transfer_learning_model.h5')

Давайте построим результаты

acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(len(acc))
plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()
plt.figure()
plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()
plt.show()

А вот сюжеты:

Точность проверки составляет почти 95%

Тонкая настройка

Мы можем пойти дальше. Мы можем разморозить несколько верхних слоев как часть процесса извлечения признаков и обучить их вместе с нашим исходным плотным классификатором. Этот метод называется «точной настройкой», поскольку он использует больше представлений из модели, чтобы сделать их более соответствующими конкретному варианту использования. Точная настройка возможна только тогда, когда верхние слои базовых слоев convo уже обучены. Если нет, то ошибка, распространяющаяся по сети, будет усилена, чтобы быть полезной.

Вот шаги высокого уровня для тонкой настройки:

  1. Добавьте пользовательский слой поверх предварительно обученного базового слоя (в данном случае плотного классификатора).
  2. Заморозить базу
  3. Обучить сеть (база + классификатор)
  4. Разморозить несколько слоев в базе
  5. Тренируйтесь снова.

Мы видели первые 3 шага, давайте начнем с #4.

Вот краткое описание модели еще раз для быстрого ознакомления.

conv_base.summary()

Позволяет точно настроить последние 3 слоя convo

conv_base.trainable = True

set_trainable = False
for layer in conv_base.layers:
    if layer.name == 'block5_conv1':
        set_trainable = True
    if set_trainable:
        layer.trainable = True
    else:
        layer.trainable = False

Давайте настроим нашу сеть,

model.compile(loss='binary_crossentropy',
              optimizer=optimizers.RMSprop(lr=1e-5),
              metrics=['acc'])

history = model.fit_generator(
      train_generator,
      steps_per_epoch=100,
      epochs=100,
      validation_data=validation_generator,
      validation_steps=50)

Сохраните модель

model.save('transfer_learning_model2.h5')

Постройте результаты,

acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']

epochs = range(len(acc))

plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()

plt.figure()

plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()

plt.show()

Вот сюжеты.

Давайте оценим эту модель на тестовых данных

test_generator = test_datagen.flow_from_directory(
        test_dir,
        target_size=(150, 150),
        batch_size=20,
        class_mode='binary')

test_loss, test_acc = model.evaluate_generator(test_generator, steps=50)
print('test acc:', test_acc)

Точность улучшилась с 95 % до почти 97 % с использованием тонкой настройки. Это совсем неплохо.

Резюме

Как видите, мы взяли предварительно обученную модель для классификации изображений и повторно использовали ее основу для «извлечения признаков», имеющих отношение к решению задачи классификации собак и кошек. Мы добавили классификатор поверх нашей предварительно обученной модели и использовали очень мало сгенерированных данных для обучения нашей окончательной модели и впечатляющей точности предсказания модели. Тонкую настройку можно использовать в качестве дополнительного подхода к извлечению признаков, чтобы выжать из модели больше производительности.