Предварительное условие: Учебник 0 (настройка Google Colab, времени выполнения TPU и облачного хранилища)

Трансферное обучение - обязательный навык для практиков глубокого обучения. Классическим набором данных для демонстрации эффективности трансферного обучения является соревнование Kaggle Собаки против кошек, в котором каждая запись данных представляет собой изображение кошки или собаки:

Решение Кераса. В официальном блоге Кераса есть старый учебник о собаках против кошек. Однако способ, которым они это сделали, довольно сложен. На высоком уровне их руководство состоит из двух основных этапов.

  1. Обучите простую двухуровневую сеть поверх замороженной сети VGG16, предварительно обученной на ImageNet. Это приводит к точности проверки около 90%.
  2. Разморозьте последние 5 сверточных слоев предварительно обученного VGG16 и повторно обучите их вместе, когда появятся 2 новых слоя из шага 1. Это дает точность 94%.

У них также есть довольно запутанная реализация шага 1. Обычно, когда мы выполняем переносное обучение, мы меняем заголовок предварительно обученного уровня. Здесь «голова» ConvNet - это последний блок полностью связанных слоев. В исходной модели VGG16 головка выводит прогноз с 1001 классом. Для нашей задачи у нас есть только два класса: собаки и кошки, поэтому мы меняем голову на бинарный классификатор.

В Руководстве по Keras шаг 1 гораздо более жестокий: они отрезают голову VGG16, не давая ему новую голову. Затем они позволили обезглавленному VGG16 выполнять прогнозы на основе набора данных Собаки против кошек. Поскольку у модели нет головы, она не может выводить какие-либо ярлыки; вместо этого из его шеи поступает какой-то материал, который должен питать головной блок в качестве входных данных. В Руководстве по Keras они собирают эту кровь шеи (которую они называют узкими местами), рассматривают ее как новый набор данных и обучают неглубокую модель с 2 слоями.

Наше решение. В этом руководстве мы следуем гораздо более простому подходу - напрямую обучаем всю модель с помощью нового бинарного классификатора головы (то есть он предсказывает вероятности «собака» и «кошка». ”), И мы фиксируем базовую модель на протяжении всего обучения. Как мы скоро увидим, это приводит к 99% точности за одну эпоху обучения.

Кроме того, мы не будем использовать VGG16, как в блоге Keras, который старый (2014 г.) и небольшой (16 слоев). Мы находимся в 2019 году, и у нас есть гораздо более мощное оружие: InceptionResNetV2. Взгляните на этот BFN:

Подготовка предварительно обученной модели. Сначала мы настроили Fenwicks:

import numpy as np
import tensorflow as tf
import os
if tf.io.gfile.exists('./fenwicks'):
  tf.io.gfile.rmtree('./fenwicks')
!git clone -q https://github.com/fenwickslab/fenwicks.git 
import fenwicks as fw
fw.colab_utils.setup_gcs()

И варианты гиперпараметров:

ROOT_DIR = 'gs://gs_colab'
PROJECT = 'tutorial4'
MODEL = "InceptionResNetV2" #@param ["InceptionResNetV2", "ResNet50", "ResNet50V2", "InceptionV3", "MobileNetV2", "Xception"]

BATCH_SIZE = 128 #@param ["128", "256", "512"] {type:"raw"}
EPOCHS = 1 #@param {type:"slider", min:0, max:50, step:1}
LEARNING_RATE = 0.001 #@param ["0.001", "0.01"] {type:"raw"}
WARMUP = 0.05 #@param {type:"slider", min:0, max:0.5, step:0.05}

В этой серии руководств мы всегда используем переменные data_dir, work_dir и model_dir для хранения файлов данных, временных файлов, созданных во время обучения, и предварительно обученных весов моделей соответственно.

fw.colab_tpu.setup_gcs()
data_dir, work_dir = fw.io.get_project_dirs(ROOT_DIR, PROJECT)

Теперь давайте создадим базовую модель: InceptionResNetV2, предварительно обученный в ImageNet, предоставленный Керасом.

base_model = fw.keras_models.get_model(MODEL, BUCKET)

Внутри Fenwicks сначала загружает веса модели на локальный диск машины Colab, а затем сохраняет веса в формате файла Tensorflow checkpoint. После этого он загружает контрольную точку в model_dir на GCS. Возвращаемое значение base_model содержит:

  • Функция model_func, которая создает структуру InceptionResNet.
  • Путь к файлу весов модели, weight_dir.
  • Имена переменных веса модели, weight_vars.
  • Размер изображения по умолчанию базовой модели. Для InceptionResNetV2 размер изображения по умолчанию составляет 299x299. Для многих других моделей, таких как ResNet50, размер изображения по умолчанию составляет 224x224.
  • Функция нормализатора изображения. Для InceptionResNetV2 нормализатор просто (i) делит каждый пиксель на 255, (ii) вычитает на 0,5 и (iii) умножает на 2. Это значение по умолчанию для большинства моделей Google. В других моделях, таких как ResNeXt, используется стандартный масштабатор, который вычитает среднее значение, а затем делит на стандартное отклонение.

Подготовка набора данных. Сначала мы загружаем файлы данных на локальный диск с сайта fast.ai:

data_dir_local = fw.datasets.untar_data(fw.datasets.URLs.DVC,
  os.path.join('.', PROJECT_NAME))
train_dir_local = os.path.join(data_dir_local, 'dogscats/train')
valid_dir_local = os.path.join(data_dir_local, 'dogscats/valid')

Файлы данных fast.ai в основном представляют собой обучающий набор (25k изображений) от Kaggle, далее разделенный на обучающий набор из 23k изображений и набор для проверки 2k. Затем мы преобразуем их в TFRecord и загружаем в GCS с помощью единственного лайнера Фенвикса:

train_fn = os.path.join(data_dir, 'train.tfrec')
valid_fn = os.path.join(data_dir, 'valid.tfrec')
paths_train, y_train, labels_train =
  fw.data.data_dir_tfrecord(train_dir_local, train_fn)
paths_valid, y_valid, labels_valid =
  fw.data.data_dir_tfrecord(valid_dir_local, valid_fn)
n_train, n_valid = len(y_train), len(y_valid)
n_classes = len(labels_train)

Создание нашей новой ConvNet. Мы строим нашу модель трансферного обучения, добавляя слои один за другим к Sequential модели Фенвика. Первый уровень - это базовая модель, для которой мы фиксируем веса, устанавливая для его свойства trainable значение False, как в Keras. Затем мы добавляем слой DenseBN (полностью связанный слой, за которым следует пакетная нормализация) и классификатор:

def build_nn(c=256):
  base = base_model.model_func()
  base.trainable = False
  model = fw.Sequential()
  model.add(base)
  model.add(tf.keras.layers.Flatten())
  model.add(fw.layers.DenseBN(c))
  model.add(fw.layers.Classifier(n_classes))
  return model

Мы будем обучать нашу модель с Адамом и графиком скорости обучения косинусом:

steps_per_epoch = n_train // BATCH_SIZE
total_steps = steps_per_epoch * EPOCHS
warmup_steps = int(total_steps * WARMUP)
cosine_decay = tf.train.cosine_decay_restarts
lr_func = fw.train.one_cycle_lr(LEARNING_RATE, total_steps, warmup_steps, cosine_decay)
fw.plt.plot_lr_func(lr_func, total_steps)
opt_func = fw.train.adam_optimizer(lr_func)
model_func = fw.train.get_clf_model_func(build_nn, opt_func)
fw.plt.plot_lr_func(lr_func, total_steps=total_steps)

Входной конвейер. Наш входной конвейер читает из подготовленных ранее TFRecord. Мы используем преобразования данных из Начального руководства Google:

tfms = fw.transform.get_inception_transforms(h, w,
    training=training, normalizer=base_model.normalizer)

Преобразования Inception включают случайную обрезку, горизонтальный поворот и быстрое искажение цвета. Мы визуализируем их в Уроке 7 и сравним с другими преобразованиями в Учебнике 8. На основе этих преобразований мы создаем входной синтаксический анализатор, а затем входные функции, одну для обучающих данных и одну для данных проверки.

def get_input_func(fn, training):
  h = w = base_model.img_size
  tfms = fw.transform.get_inception_transforms(h, w,
    training=training, normalizer=base_model.normalizer)
  parser = fw.data.get_tfexample_image_parser(tfms)
  return lambda params: fw.data.tfrecord_ds(
    fn, parser, params['batch_size'], training=training)

train_input_func = get_input_func(train_fn, True)
valid_input_func = get_input_func(valid_fn, False)

Обучение и оценка модели. Наконец, мы готовы построить TPUEstimator и обучить модель:

est = fw.train.get_tpu_estimator(steps_per_epoch,  model_func,
  work_dir, base_model.weight_dir, base_model.weight_vars,
  trn_bs=BATCH_SIZE, val_bs = n_valid)

est.train(train_input_func, steps=total_steps)

Само обучение занимает мало времени, так как мы тренируем модель только за 1 эпоху. Однако компиляция гигантской сети InceptionResNetV2 в машинный код TPU и загрузка предварительно обученных весов из GCS в TPU занимает очень и очень много времени. В общей сложности весь процесс занимает около 4 минут.

Проверим точность валидации:

result = est.evaluate(valid_input_func, steps=1)
print(f'Test results: accuracy={result["accuracy"] * 100: .2f}%,
  loss={result["loss"]: .2f}.')

Наконец, давайте немедленно очистим файлы, чтобы сэкономить место на GCS, поскольку Google начинает взимать с нас плату, когда мы используем более 5 ГБ. При желании мы также можем очистить data_dir и model_dir.

fw.io.create_clean_dir(work_dir)

Вот полный блокнот.



Все уроки: