Перенос обучения и тонкая настройка

Посмотреть на TensorFlow.org Запускаем в Google Colab Посмотреть исходный код на GitHub Скачать блокнот

Настраивать

import numpy as np
import tensorflow as tf
from tensorflow import keras

Введение

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

Переносное обучение обычно выполняется для задач, в которых в наборе данных слишком мало данных для обучения полномасштабной модели с нуля.

Наиболее распространенное воплощение трансферного обучения в контексте глубокого обучения - это следующий рабочий процесс:

  1. Возьмите слои из ранее обученной модели.
  2. Заморозьте их, чтобы не уничтожить любую информацию, которую они содержат, во время будущих тренировочных раундов.
  3. Добавьте несколько новых обучаемых слоев поверх замороженных слоев. Они научатся превращать старые функции в прогнозы для нового набора данных.
  4. Обучите новые слои в вашем наборе данных.

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

Во- первых, мы будем идти над Keras trainable API подробно, который лежит в основе большинства передачи обучения и тонкой настройки рабочих процессов.

Затем мы продемонстрируем типичный рабочий процесс, взяв модель, предварительно обученную на наборе данных ImageNet, и повторно обучим ее на наборе данных классификации Kaggle «кошки против собак».

Это взято из глубокого обучения с Python и 2016 блоге «строит мощные модели классификации изображений с использованием очень мало данных» .

Замораживание слоев: понимание trainable атрибут

Слои и модели имеют три атрибута веса:

  • weights список всех весов переменных слоя.
  • trainable_weights список тех , которые предназначены для обновления ( с помощью градиентного спуска) , чтобы минимизировать потери во время тренировки.
  • non_trainable_weights список тех, которые не предназначены для обучения. Обычно они обновляются моделью во время прямого прохода.

Пример: Dense слой имеет 2 обучаемых весы (ядро и смещение)

layer = keras.layers.Dense(3)
layer.build((None, 4))  # Create the weights

print("weights:", len(layer.weights))
print("trainable_weights:", len(layer.trainable_weights))
print("non_trainable_weights:", len(layer.non_trainable_weights))
weights: 2
trainable_weights: 2
non_trainable_weights: 0

В общем, все веса - это веса, которые можно тренировать. Единственный встроенный слой , который имеет не-обучаемый вес является BatchNormalization слой. Он использует необучаемые веса, чтобы отслеживать среднее значение и дисперсию входных данных во время тренировки. Чтобы узнать , как использовать не обучаемые веса в ваших собственных слоях, увидеть руководство по написанию новых слоев с нуля .

Пример: BatchNormalization слой имеет 2 обучаемых веса и 2 без обучаемых весов

layer = keras.layers.BatchNormalization()
layer.build((None, 4))  # Create the weights

print("weights:", len(layer.weights))
print("trainable_weights:", len(layer.trainable_weights))
print("non_trainable_weights:", len(layer.non_trainable_weights))
weights: 4
trainable_weights: 2
non_trainable_weights: 2

Слои и модель также имеет логический атрибут trainable . Его значение можно изменить. Установка layer.trainable для False ходов все взвешивает слой с обучаемого , не включенным в обучаемом. Это называется «замораживание» слой: состояние замерзшего слоя не будет обновляться в процессе обучения (либо при обучении с fit() или при обучении с любой пользовательской петлей , которая опирается на trainable_weights применять градиентные обновления).

Пример: установка trainable к False

layer = keras.layers.Dense(3)
layer.build((None, 4))  # Create the weights
layer.trainable = False  # Freeze the layer

print("weights:", len(layer.weights))
print("trainable_weights:", len(layer.trainable_weights))
print("non_trainable_weights:", len(layer.non_trainable_weights))
weights: 2
trainable_weights: 0
non_trainable_weights: 2

Когда тренируемый вес становится недоступным для тренировки, его значение больше не обновляется во время тренировки.

# Make a model with 2 layers
layer1 = keras.layers.Dense(3, activation="relu")
layer2 = keras.layers.Dense(3, activation="sigmoid")
model = keras.Sequential([keras.Input(shape=(3,)), layer1, layer2])

# Freeze the first layer
layer1.trainable = False

# Keep a copy of the weights of layer1 for later reference
initial_layer1_weights_values = layer1.get_weights()

# Train the model
model.compile(optimizer="adam", loss="mse")
model.fit(np.random.random((2, 3)), np.random.random((2, 3)))

# Check that the weights of layer1 have not changed during training
final_layer1_weights_values = layer1.get_weights()
np.testing.assert_allclose(
    initial_layer1_weights_values[0], final_layer1_weights_values[0]
)
np.testing.assert_allclose(
    initial_layer1_weights_values[1], final_layer1_weights_values[1]
)
1/1 [==============================] - 1s 640ms/step - loss: 0.0945

Не следует путать layer.trainable атрибут с аргументом training в layer.__call__() (который контролирует , должен ли слой запустить его вперед проход в режиме вывода или в режиме обучения). Для получения дополнительной информации см Keras FAQ .

Рекурсивная установка trainable атрибута

Если вы установите trainable = False на модели или на любом слое , который имеет подуровни, все дети слои становятся не обучаемым , а также.

Пример:

inner_model = keras.Sequential(
    [
        keras.Input(shape=(3,)),
        keras.layers.Dense(3, activation="relu"),
        keras.layers.Dense(3, activation="relu"),
    ]
)

model = keras.Sequential(
    [keras.Input(shape=(3,)), inner_model, keras.layers.Dense(3, activation="sigmoid"),]
)

model.trainable = False  # Freeze the outer model

assert inner_model.trainable == False  # All layers in `model` are now frozen
assert inner_model.layers[0].trainable == False  # `trainable` is propagated recursively

Типичный рабочий процесс передачи-обучения

Это подводит нас к тому, как в Keras можно реализовать типичный рабочий процесс трансферного обучения:

  1. Создайте экземпляр базовой модели и загрузите в нее предварительно обученные веса.
  2. Закрепить все слои в базовой модели, установив trainable = False .
  3. Создайте новую модель поверх выходных данных одного (или нескольких) слоев базовой модели.
  4. Обучите новую модель на новом наборе данных.

Обратите внимание, что альтернативным, более легким рабочим процессом также может быть:

  1. Создайте экземпляр базовой модели и загрузите в нее предварительно обученные веса.
  2. Пропустите через него новый набор данных и запишите выходные данные одного (или нескольких) слоев базовой модели. Это называется выделением признаков.
  3. Используйте эти выходные данные в качестве входных данных для новой, меньшей модели.

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

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

Вот как выглядит первый рабочий процесс в Керасе:

Сначала создайте экземпляр базовой модели с предварительно обученными весами.

base_model = keras.applications.Xception(
    weights='imagenet',  # Load weights pre-trained on ImageNet.
    input_shape=(150, 150, 3),
    include_top=False)  # Do not include the ImageNet classifier at the top.

Затем заморозьте базовую модель.

base_model.trainable = False

Сверху создайте новую модель.

inputs = keras.Input(shape=(150, 150, 3))
# We make sure that the base_model is running in inference mode here,
# by passing `training=False`. This is important for fine-tuning, as you will
# learn in a few paragraphs.
x = base_model(inputs, training=False)
# Convert features of shape `base_model.output_shape[1:]` to vectors
x = keras.layers.GlobalAveragePooling2D()(x)
# A Dense classifier with a single unit (binary classification)
outputs = keras.layers.Dense(1)(x)
model = keras.Model(inputs, outputs)

Обучите модель на новых данных.

model.compile(optimizer=keras.optimizers.Adam(),
              loss=keras.losses.BinaryCrossentropy(from_logits=True),
              metrics=[keras.metrics.BinaryAccuracy()])
model.fit(new_dataset, epochs=20, callbacks=..., validation_data=...)

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

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

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

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

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

Вот как осуществить тонкую настройку всей базовой модели:

# Unfreeze the base model
base_model.trainable = True

# It's important to recompile your model after you make any changes
# to the `trainable` attribute of any inner layer, so that your changes
# are take into account
model.compile(optimizer=keras.optimizers.Adam(1e-5),  # Very low learning rate
              loss=keras.losses.BinaryCrossentropy(from_logits=True),
              metrics=[keras.metrics.BinaryAccuracy()])

# Train end-to-end. Be careful to stop before you overfit!
model.fit(new_dataset, epochs=10, callbacks=..., validation_data=...)

Важное замечание о compile() и trainable

Вызов compile() на модели предназначается , чтобы «заморозить» поведение этой модели. Это означает , что trainable значение атрибутов в то время как модель скомпилированной должно сохраняться на протяжении всего срока службы этой модели, пока compile не вызывается снова. Следовательно, при изменении любого trainable значения, убедитесь , что вызов compile() снова на вашу модель для ваших изменений , которые необходимо принять во внимание.

Важные замечания о BatchNormalization слоя

Многие модели изображения содержат BatchNormalization слои. Этот слой - особый случай во всех мыслимых смыслах. Вот несколько вещей, о которых следует помнить.

  • BatchNormalization содержит 2 без обучаемого веса , которые обновляются во время тренировки. Это переменные, отслеживающие среднее значение и дисперсию входных данных.
  • При установке bn_layer.trainable = False , то BatchNormalization слой будет работать в режиме вывода, и не будет обновлять свою среднюю и дисперсию статистики. Это не так для других слоев в целом, как вес обучаемости и умозаключения / режимы обучения два ортогональных понятия . Но два связаны в случае BatchNormalization слоя.
  • Когда вы разморозить модель , которая содержит BatchNormalization слоев, чтобы сделать тонкую настройку, вы должны держать BatchNormalization слои в режиме вывода при прохождении training=False при вызове базовой модели. В противном случае обновления, применяемые к необучаемым весам, внезапно разрушат то, чему модель научилась.

Вы увидите этот шаблон в действии в сквозном примере в конце этого руководства.

Передача обучения и точной настройки с помощью индивидуального цикла обучения

Если вместо fit() , вы используете свой собственный цикл обучения низкоуровневого, рабочий процесс остается по существу то же самое. Вы должны быть осторожны , чтобы только принять во внимание список model.trainable_weights при применении обновления градиента:

# Create base model
base_model = keras.applications.Xception(
    weights='imagenet',
    input_shape=(150, 150, 3),
    include_top=False)
# Freeze base model
base_model.trainable = False

# Create new model on top.
inputs = keras.Input(shape=(150, 150, 3))
x = base_model(inputs, training=False)
x = keras.layers.GlobalAveragePooling2D()(x)
outputs = keras.layers.Dense(1)(x)
model = keras.Model(inputs, outputs)

loss_fn = keras.losses.BinaryCrossentropy(from_logits=True)
optimizer = keras.optimizers.Adam()

# Iterate over the batches of a dataset.
for inputs, targets in new_dataset:
    # Open a GradientTape.
    with tf.GradientTape() as tape:
        # Forward pass.
        predictions = model(inputs)
        # Compute the loss value for this batch.
        loss_value = loss_fn(targets, predictions)

    # Get gradients of loss wrt the *trainable* weights.
    gradients = tape.gradient(loss_value, model.trainable_weights)
    # Update the weights of the model.
    optimizer.apply_gradients(zip(gradients, model.trainable_weights))

То же самое и для точной настройки.

Сквозной пример: точная настройка модели классификации изображений на наборе данных "кошки против собак"

Чтобы закрепить эти концепции, давайте рассмотрим конкретный пример обучения и точной настройки сквозной передачи. Мы загрузим модель Xception, предварительно обученную в ImageNet, и будем использовать ее в наборе данных классификации Kaggle «кошки против собак».

Получение данных

Во-первых, давайте возьмем набор данных кошек против собак с помощью TFDS. Если у вас есть свой собственный набор данных, вы , вероятно , хотите использовать утилиту tf.keras.preprocessing.image_dataset_from_directory для создания аналогичных меченого набора данных объектов из набора изображений на диске , поданных в папки класса конкретного.

Переносное обучение наиболее полезно при работе с очень маленькими наборами данных. Чтобы наш набор данных был небольшим, мы будем использовать 40% исходных обучающих данных (25 000 изображений) для обучения, 10% для проверки и 10% для тестирования.

import tensorflow_datasets as tfds

tfds.disable_progress_bar()

train_ds, validation_ds, test_ds = tfds.load(
    "cats_vs_dogs",
    # Reserve 10% for validation and 10% for test
    split=["train[:40%]", "train[40%:50%]", "train[50%:60%]"],
    as_supervised=True,  # Include labels
)

print("Number of training samples: %d" % tf.data.experimental.cardinality(train_ds))
print(
    "Number of validation samples: %d" % tf.data.experimental.cardinality(validation_ds)
)
print("Number of test samples: %d" % tf.data.experimental.cardinality(test_ds))
Number of training samples: 9305
Number of validation samples: 2326
Number of test samples: 2326

Это первые 9 изображений в наборе обучающих данных - как видите, все они разного размера.

import matplotlib.pyplot as plt

plt.figure(figsize=(10, 10))
for i, (image, label) in enumerate(train_ds.take(9)):
    ax = plt.subplot(3, 3, i + 1)
    plt.imshow(image)
    plt.title(int(label))
    plt.axis("off")

PNG

Мы также можем видеть, что метка 1 - это «собака», а метка 0 - «кошка».

Стандартизация данных

Наши необработанные изображения имеют разные размеры. Кроме того, каждый пиксель состоит из 3 целых значений от 0 до 255 (значения уровня RGB). Это не очень подходит для питания нейронной сети. Нам нужно сделать 2 вещи:

  • Стандартизируйте изображение до фиксированного размера. Выбираем 150х150.
  • Нормализация значение пикселей в диапазоне от -1 до 1. Мы будем делать это с помощью Normalization слоя как часть самой модели.

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

Здесь мы изменим размер изображения в конвейере данных (потому что глубокая нейронная сеть может обрабатывать только непрерывные пакеты данных), и мы сделаем масштабирование входного значения как часть модели, когда мы ее создадим.

Изменим размер изображения до 150x150:

size = (150, 150)

train_ds = train_ds.map(lambda x, y: (tf.image.resize(x, size), y))
validation_ds = validation_ds.map(lambda x, y: (tf.image.resize(x, size), y))
test_ds = test_ds.map(lambda x, y: (tf.image.resize(x, size), y))

Кроме того, давайте запакуем данные и воспользуемся кешированием и предварительной выборкой для оптимизации скорости загрузки.

batch_size = 32

train_ds = train_ds.cache().batch(batch_size).prefetch(buffer_size=10)
validation_ds = validation_ds.cache().batch(batch_size).prefetch(buffer_size=10)
test_ds = test_ds.cache().batch(batch_size).prefetch(buffer_size=10)

Использование случайного увеличения данных

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

from tensorflow import keras
from tensorflow.keras import layers

data_augmentation = keras.Sequential(
    [layers.RandomFlip("horizontal"), layers.RandomRotation(0.1),]
)

Визуализируем, как выглядит первое изображение первого пакета после различных случайных преобразований:

import numpy as np

for images, labels in train_ds.take(1):
    plt.figure(figsize=(10, 10))
    first_image = images[0]
    for i in range(9):
        ax = plt.subplot(3, 3, i + 1)
        augmented_image = data_augmentation(
            tf.expand_dims(first_image, 0), training=True
        )
        plt.imshow(augmented_image[0].numpy().astype("int32"))
        plt.title(int(labels[0]))
        plt.axis("off")
2021-09-01 18:45:34.772284: W tensorflow/core/kernels/data/cache_dataset_ops.cc:768] The calling iterator did not fully read the dataset being cached. In order to avoid unexpected truncation of the dataset, the partially cached contents of the dataset  will be discarded. This can happen if you have an input pipeline similar to `dataset.cache().take(k).repeat()`. You should use `dataset.take(k).cache().repeat()` instead.

PNG

Построить модель

Теперь давайте построим модель, которая следует схеме, которую мы объяснили ранее.

Обратите внимание, что:

  • Мы добавим Rescaling слоя входных значений шкалы (первоначально в [0, 255] диапазоне) в [-1, 1] диапазон.
  • Мы добавляем Dropout слой перед слоем классификации, для регуляризации.
  • Мы уверены , чтобы пройти training=False при вызове базовой модели, так что он работает в режиме вывода, так что статистика batchnorm не обновляется , даже после того, как мы разморозить базовую модель для тонкой настройки.
base_model = keras.applications.Xception(
    weights="imagenet",  # Load weights pre-trained on ImageNet.
    input_shape=(150, 150, 3),
    include_top=False,
)  # Do not include the ImageNet classifier at the top.

# Freeze the base_model
base_model.trainable = False

# Create new model on top
inputs = keras.Input(shape=(150, 150, 3))
x = data_augmentation(inputs)  # Apply random data augmentation

# Pre-trained Xception weights requires that input be scaled
# from (0, 255) to a range of (-1., +1.), the rescaling layer
# outputs: `(inputs * scale) + offset`
scale_layer = keras.layers.Rescaling(scale=1 / 127.5, offset=-1)
x = scale_layer(x)

# The base model contains batchnorm layers. We want to keep them in inference mode
# when we unfreeze the base model for fine-tuning, so we make sure that the
# base_model is running in inference mode here.
x = base_model(x, training=False)
x = keras.layers.GlobalAveragePooling2D()(x)
x = keras.layers.Dropout(0.2)(x)  # Regularize with dropout
outputs = keras.layers.Dense(1)(x)
model = keras.Model(inputs, outputs)

model.summary()
Downloading data from https://storage.googleapis.com/tensorflow/keras-applications/xception/xception_weights_tf_dim_ordering_tf_kernels_notop.h5
83689472/83683744 [==============================] - 2s 0us/step
83697664/83683744 [==============================] - 2s 0us/step
Model: "model"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_5 (InputLayer)         [(None, 150, 150, 3)]     0         
_________________________________________________________________
sequential_3 (Sequential)    (None, 150, 150, 3)       0         
_________________________________________________________________
rescaling (Rescaling)        (None, 150, 150, 3)       0         
_________________________________________________________________
xception (Functional)        (None, 5, 5, 2048)        20861480  
_________________________________________________________________
global_average_pooling2d (Gl (None, 2048)              0         
_________________________________________________________________
dropout (Dropout)            (None, 2048)              0         
_________________________________________________________________
dense_7 (Dense)              (None, 1)                 2049      
=================================================================
Total params: 20,863,529
Trainable params: 2,049
Non-trainable params: 20,861,480
_________________________________________________________________

Тренируйте верхний слой

model.compile(
    optimizer=keras.optimizers.Adam(),
    loss=keras.losses.BinaryCrossentropy(from_logits=True),
    metrics=[keras.metrics.BinaryAccuracy()],
)

epochs = 20
model.fit(train_ds, epochs=epochs, validation_data=validation_ds)
Epoch 1/20
151/291 [==============>...............] - ETA: 3s - loss: 0.1979 - binary_accuracy: 0.9096
Corrupt JPEG data: 65 extraneous bytes before marker 0xd9
268/291 [==========================>...] - ETA: 1s - loss: 0.1663 - binary_accuracy: 0.9269
Corrupt JPEG data: 239 extraneous bytes before marker 0xd9
282/291 [============================>.] - ETA: 0s - loss: 0.1628 - binary_accuracy: 0.9284
Corrupt JPEG data: 1153 extraneous bytes before marker 0xd9
Corrupt JPEG data: 228 extraneous bytes before marker 0xd9
291/291 [==============================] - ETA: 0s - loss: 0.1620 - binary_accuracy: 0.9286
Corrupt JPEG data: 2226 extraneous bytes before marker 0xd9
291/291 [==============================] - 29s 63ms/step - loss: 0.1620 - binary_accuracy: 0.9286 - val_loss: 0.0814 - val_binary_accuracy: 0.9686
Epoch 2/20
291/291 [==============================] - 8s 29ms/step - loss: 0.1178 - binary_accuracy: 0.9511 - val_loss: 0.0785 - val_binary_accuracy: 0.9695
Epoch 3/20
291/291 [==============================] - 9s 30ms/step - loss: 0.1121 - binary_accuracy: 0.9536 - val_loss: 0.0748 - val_binary_accuracy: 0.9712
Epoch 4/20
291/291 [==============================] - 9s 29ms/step - loss: 0.1082 - binary_accuracy: 0.9554 - val_loss: 0.0754 - val_binary_accuracy: 0.9703
Epoch 5/20
291/291 [==============================] - 8s 29ms/step - loss: 0.1034 - binary_accuracy: 0.9570 - val_loss: 0.0721 - val_binary_accuracy: 0.9725
Epoch 6/20
291/291 [==============================] - 8s 29ms/step - loss: 0.0975 - binary_accuracy: 0.9602 - val_loss: 0.0748 - val_binary_accuracy: 0.9699
Epoch 7/20
291/291 [==============================] - 9s 29ms/step - loss: 0.0989 - binary_accuracy: 0.9595 - val_loss: 0.0732 - val_binary_accuracy: 0.9716
Epoch 8/20
291/291 [==============================] - 8s 29ms/step - loss: 0.1027 - binary_accuracy: 0.9566 - val_loss: 0.0787 - val_binary_accuracy: 0.9678
Epoch 9/20
291/291 [==============================] - 8s 29ms/step - loss: 0.0959 - binary_accuracy: 0.9614 - val_loss: 0.0734 - val_binary_accuracy: 0.9729
Epoch 10/20
291/291 [==============================] - 8s 29ms/step - loss: 0.0995 - binary_accuracy: 0.9588 - val_loss: 0.0717 - val_binary_accuracy: 0.9721
Epoch 11/20
291/291 [==============================] - 8s 29ms/step - loss: 0.0957 - binary_accuracy: 0.9612 - val_loss: 0.0731 - val_binary_accuracy: 0.9725
Epoch 12/20
291/291 [==============================] - 8s 29ms/step - loss: 0.0936 - binary_accuracy: 0.9622 - val_loss: 0.0751 - val_binary_accuracy: 0.9716
Epoch 13/20
291/291 [==============================] - 8s 29ms/step - loss: 0.0965 - binary_accuracy: 0.9610 - val_loss: 0.0821 - val_binary_accuracy: 0.9695
Epoch 14/20
291/291 [==============================] - 8s 29ms/step - loss: 0.0939 - binary_accuracy: 0.9618 - val_loss: 0.0742 - val_binary_accuracy: 0.9712
Epoch 15/20
291/291 [==============================] - 8s 29ms/step - loss: 0.0974 - binary_accuracy: 0.9585 - val_loss: 0.0771 - val_binary_accuracy: 0.9712
Epoch 16/20
291/291 [==============================] - 8s 29ms/step - loss: 0.0947 - binary_accuracy: 0.9621 - val_loss: 0.0823 - val_binary_accuracy: 0.9699
Epoch 17/20
291/291 [==============================] - 8s 29ms/step - loss: 0.0947 - binary_accuracy: 0.9625 - val_loss: 0.0718 - val_binary_accuracy: 0.9708
Epoch 18/20
291/291 [==============================] - 8s 29ms/step - loss: 0.0928 - binary_accuracy: 0.9616 - val_loss: 0.0738 - val_binary_accuracy: 0.9716
Epoch 19/20
291/291 [==============================] - 8s 29ms/step - loss: 0.0922 - binary_accuracy: 0.9644 - val_loss: 0.0743 - val_binary_accuracy: 0.9716
Epoch 20/20
291/291 [==============================] - 8s 29ms/step - loss: 0.0885 - binary_accuracy: 0.9635 - val_loss: 0.0745 - val_binary_accuracy: 0.9695
<keras.callbacks.History at 0x7f849a3b2950>

Сделайте раунд точной настройки всей модели

Наконец, давайте разморозим базовую модель и обучим всю модель от начала до конца с низкой скоростью обучения.

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

# Unfreeze the base_model. Note that it keeps running in inference mode
# since we passed `training=False` when calling it. This means that
# the batchnorm layers will not update their batch statistics.
# This prevents the batchnorm layers from undoing all the training
# we've done so far.
base_model.trainable = True
model.summary()

model.compile(
    optimizer=keras.optimizers.Adam(1e-5),  # Low learning rate
    loss=keras.losses.BinaryCrossentropy(from_logits=True),
    metrics=[keras.metrics.BinaryAccuracy()],
)

epochs = 10
model.fit(train_ds, epochs=epochs, validation_data=validation_ds)
Model: "model"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_5 (InputLayer)         [(None, 150, 150, 3)]     0         
_________________________________________________________________
sequential_3 (Sequential)    (None, 150, 150, 3)       0         
_________________________________________________________________
rescaling (Rescaling)        (None, 150, 150, 3)       0         
_________________________________________________________________
xception (Functional)        (None, 5, 5, 2048)        20861480  
_________________________________________________________________
global_average_pooling2d (Gl (None, 2048)              0         
_________________________________________________________________
dropout (Dropout)            (None, 2048)              0         
_________________________________________________________________
dense_7 (Dense)              (None, 1)                 2049      
=================================================================
Total params: 20,863,529
Trainable params: 20,809,001
Non-trainable params: 54,528
_________________________________________________________________
Epoch 1/10
291/291 [==============================] - 43s 131ms/step - loss: 0.0802 - binary_accuracy: 0.9692 - val_loss: 0.0580 - val_binary_accuracy: 0.9764
Epoch 2/10
291/291 [==============================] - 37s 128ms/step - loss: 0.0542 - binary_accuracy: 0.9792 - val_loss: 0.0529 - val_binary_accuracy: 0.9764
Epoch 3/10
291/291 [==============================] - 37s 128ms/step - loss: 0.0400 - binary_accuracy: 0.9832 - val_loss: 0.0510 - val_binary_accuracy: 0.9798
Epoch 4/10
291/291 [==============================] - 37s 128ms/step - loss: 0.0313 - binary_accuracy: 0.9879 - val_loss: 0.0505 - val_binary_accuracy: 0.9819
Epoch 5/10
291/291 [==============================] - 37s 128ms/step - loss: 0.0272 - binary_accuracy: 0.9904 - val_loss: 0.0485 - val_binary_accuracy: 0.9807
Epoch 6/10
291/291 [==============================] - 37s 128ms/step - loss: 0.0284 - binary_accuracy: 0.9901 - val_loss: 0.0497 - val_binary_accuracy: 0.9824
Epoch 7/10
291/291 [==============================] - 37s 127ms/step - loss: 0.0198 - binary_accuracy: 0.9937 - val_loss: 0.0530 - val_binary_accuracy: 0.9802
Epoch 8/10
291/291 [==============================] - 37s 127ms/step - loss: 0.0173 - binary_accuracy: 0.9930 - val_loss: 0.0572 - val_binary_accuracy: 0.9819
Epoch 9/10
291/291 [==============================] - 37s 127ms/step - loss: 0.0113 - binary_accuracy: 0.9958 - val_loss: 0.0555 - val_binary_accuracy: 0.9837
Epoch 10/10
291/291 [==============================] - 37s 127ms/step - loss: 0.0091 - binary_accuracy: 0.9966 - val_loss: 0.0596 - val_binary_accuracy: 0.9832
<keras.callbacks.History at 0x7f83982d4cd0>

После 10 эпох тонкая настройка дает нам здесь хорошее улучшение.