Обучение и оценка с Keras

Смотрите на TensorFlow.org Запустите в Google Colab Изучайте код на GitHub Скачайте ноутбук

Это руководство охватывает обучение, оценку и прогнозирование (выводы) моделей в TensorFlow 2.0 в двух общих ситуациях:

  • При использовании встроенных API для обучения и валидации (таких как model.fit(), model.evaluate(), model.predict()). Этому посвящен раздел "Использование встроенных циклов обучения и оценки".
  • При написании пользовательских циклов с нуля с использованием eager execution и объекта GradientTape. Эти вопросы рассматриваются в разделе "Написание собственных циклов обучения и оценки с нуля".

В целом, независимо от того, используете ли вы встроенные циклы или пишете свои собственные, обучение и оценка моделей работает строго одинаково для всех видов моделей Keras: Sequential моделей, созданных с помощью Functional API, и написанных с нуля с использованием субклассирования моделей.

Это руководство не охватывает распределенное обучение.

Установка

from __future__ import absolute_import, division, print_function, unicode_literals

try:
  %tensorflow_version 2.x
except Exception:
  pass
import tensorflow as tf

tf.keras.backend.clear_session()  # Для простого сброса состояния ноутбука.

Часть I: Использование встроенных циклов обучения и оценки

При передаче данных во встроенные обучающие циклы модели вы должны либо использовать массивы Numpy (если ваши данные малы и умещаются в памяти), либо объекты tf.data Dataset. В следующих нескольких параграфах мы будем использовать набор данных MNIST в качестве массива Numpy, чтобы показать, как использовать оптимизаторы, функции потерь и метрики.

Обзор API: первый полный пример

Давайте рассмотрим следующую модель (здесь мы строим ее с помощью Functional API, но она может быть и Sequential или субклассированной моделью):

from tensorflow import keras
from tensorflow.keras import layers

inputs = keras.Input(shape=(784,), name='digits')
x = layers.Dense(64, activation='relu', name='dense_1')(inputs)
x = layers.Dense(64, activation='relu', name='dense_2')(x)
outputs = layers.Dense(10, activation='softmax', name='predictions')(x)

model = keras.Model(inputs=inputs, outputs=outputs)

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

# Загрузим учебный датасет для этого примера
(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()

# Предобработаем данные (это массивы Numpy)
x_train = x_train.reshape(60000, 784).astype('float32') / 255
x_test = x_test.reshape(10000, 784).astype('float32') / 255

y_train = y_train.astype('float32')
y_test = y_test.astype('float32')

# Зарезервируем 10,000 примеров для валидации
x_val = x_train[-10000:]
y_val = y_train[-10000:]
x_train = x_train[:-10000]
y_train = y_train[:-10000]

# Укажем конфигурацию обучения (оптимизатор, функция потерь, метрики)
model.compile(optimizer=keras.optimizers.RMSprop(),  # Оптимизатор
              # Минимизируемая функция потерь
              loss=keras.losses.SparseCategoricalCrossentropy(),
              # Список метрик для мониторинга
              metrics=[keras.metrics.SparseCategoricalAccuracy()])

# Обучим модель разбив данные на "пакеты"
# размером "batch_size", и последовательно итерируя
# весь датасет заданное количество "эпох"
print('# Обучаем модель на тестовых данных')
history = model.fit(x_train, y_train,
                    batch_size=64,
                    epochs=3,
                    # Мы передаем валидационные данные для
                    # мониторинга потерь и метрик на этих данных
                    # в конце каждой эпохи
                    validation_data=(x_val, y_val))

# Возвращаемый объект "history" содержит записи
# значений потерь и метрик во время обучения
print('\nhistory dict:', history.history)

# Оценим модель на тестовых данных, используя `evaluate`
print('\n# Оцениваем на тестовых данных')
results = model.evaluate(x_test, y_test, batch_size=128)
print('test loss, test acc:', results)

# Сгенерируем прогнозы (вероятности -- выходные данные последнего слоя)
# на новых данных с помощью `predict`
print('\n# Генерируем прогнозы для 3 образцов')
predictions = model.predict(x_test[:3])
print('размерность прогнозов:', predictions.shape)
# Обучаем модель на тестовых данных
Epoch 1/3
782/782 [==============================] - 2s 2ms/step - loss: 0.3288 - sparse_categorical_accuracy: 0.9058 - val_loss: 0.1859 - val_sparse_categorical_accuracy: 0.9483
Epoch 2/3
782/782 [==============================] - 2s 2ms/step - loss: 0.1550 - sparse_categorical_accuracy: 0.9546 - val_loss: 0.1321 - val_sparse_categorical_accuracy: 0.9615
Epoch 3/3
782/782 [==============================] - 2s 2ms/step - loss: 0.1142 - sparse_categorical_accuracy: 0.9662 - val_loss: 0.1162 - val_sparse_categorical_accuracy: 0.9668

history dict: {'loss': [0.32875269651412964, 0.15501517057418823, 0.11421658098697662], 'sparse_categorical_accuracy': [0.9057999849319458, 0.9545599818229675, 0.9662399888038635], 'val_loss': [0.1859005242586136, 0.1320723444223404, 0.11619525402784348], 'val_sparse_categorical_accuracy': [0.9483000040054321, 0.9614999890327454, 0.9667999744415283]}

# Оцениваем на тестовых данных
79/79 [==============================] - 0s 1ms/step - loss: 0.1193 - sparse_categorical_accuracy: 0.9639
test loss, test acc: [0.11930325627326965, 0.9639000296592712]

# Генерируем прогнозы для 3 образцов
размерность прогнозов: (3, 10)

Определение потерь, метрик и оптимизатора

Для обучения модели с помощью fit, вам нужно задать функцию потерь, оптимизатор, и опционально некоторые метрики для мониторинга.

Вам нужно передать их модели в качестве аргументов метода compile():

model.compile(optimizer=keras.optimizers.RMSprop(learning_rate=1e-3),
              loss=keras.losses.SparseCategoricalCrossentropy(),
              metrics=[keras.metrics.SparseCategoricalAccuracy()])

Аргумент metrics должен быть списком -- ваша модель может иметь любое количество метрик.

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

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

model.compile(optimizer=keras.optimizers.RMSprop(learning_rate=1e-3),
              loss='sparse_categorical_crossentropy',
              metrics=['sparse_categorical_accuracy'])

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

def get_uncompiled_model():
  inputs = keras.Input(shape=(784,), name='digits')
  x = layers.Dense(64, activation='relu', name='dense_1')(inputs)
  x = layers.Dense(64, activation='relu', name='dense_2')(x)
  outputs = layers.Dense(10, activation='softmax', name='predictions')(x)
  model = keras.Model(inputs=inputs, outputs=outputs)
  return model

def get_compiled_model():
  model = get_uncompiled_model()
  model.compile(optimizer=keras.optimizers.RMSprop(learning_rate=1e-3),
              loss='sparse_categorical_crossentropy',
              metrics=['sparse_categorical_accuracy'])
  return model

Вам доступно множество встроенных оптимизаторов, функций потерь и метрик

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

Оптимизаторы:

  • SGD() (с или без momentum)
  • RMSprop()
  • Adam()
  • и т.д.

Функции потерь:

  • MeanSquaredError()
  • KLDivergence()
  • CosineSimilarity()
  • и т.д.

Метрики:

  • AUC()
  • Precision()
  • Recall()
  • и т.д.

Кастомные функции потерь

Есть два способа обеспечить кастомные функции потерь с Keras. В примере создается функция принимающая на вход y_true и y_pred. Следующий пример показывает функцию потерь вычисляющую среднее расстояние между реальными данными и прогнозами:

def basic_loss_function(y_true, y_pred):
    return tf.math.reduce_mean(y_true - y_pred)

model.compile(optimizer=keras.optimizers.Adam(),
              loss=basic_loss_function)

model.fit(x_train, y_train, batch_size=64, epochs=3)
Epoch 1/3
782/782 [==============================] - 1s 1ms/step - loss: 4.3488
Epoch 2/3
782/782 [==============================] - 1s 1ms/step - loss: 4.3488
Epoch 3/3
782/782 [==============================] - 1s 1ms/step - loss: 4.3488

<tensorflow.python.keras.callbacks.History at 0x7f9f2810ab38>

Если вам нужна функция потерь у которой есть иные параметры кроме y_true и y_pred, вы можете субклассировать класс tf.keras.losses.Loss и реализовать следующие два метода:

  • __init__(self) —Принять параметры, передаваемые при вызове вашей функции потерь
  • call(self, y_true, y_pred) —Использовать цели (y_true) и предсказания модели (y_pred) для вычисления потерь модели

Параметры передаваемые в __init__() могут быть использованы во время call() при вычислении потерь.

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

class WeightedBinaryCrossEntropy(keras.losses.Loss):
    """
    Args:
      pos_weight: Скалярный вес для положительных меток функции потерь.
      weight: Скалярный вес для всей функции потерь.
      from_logits: Вычислять ли потери от логитов или вероятностей.
      reduction: Тип tf.keras.losses.Reduction для применения к функции потерь.
      name: Имя функции потерь.
    """
    def __init__(self, pos_weight, weight, from_logits=False,
                 reduction=keras.losses.Reduction.AUTO,
                 name='weighted_binary_crossentropy'):
        super(WeightedBinaryCrossEntropy, self).__init__(reduction=reduction,
                                                         name=name)
        self.pos_weight = pos_weight
        self.weight = weight
        self.from_logits = from_logits

    def call(self, y_true, y_pred):
        if not self.from_logits:
            # Вручную посчитаем взвешенную кросс-энтропию.
            # Формула следующая qz * -log(sigmoid(x)) + (1 - z) * -log(1 - sigmoid(x))
            # где z - метки, x - логиты, а q - веса.
            # Поскольку переданные значения от сигмоиды (предположим в этом случае)
            # sigmoid(x) будет заменено y_pred

            # qz * -log(sigmoid(x)) 1e-6 добавляется как эпсилон, чтобы не передать нуль в логарифм
            x_1 = y_true * self.pos_weight * -tf.math.log(y_pred + 1e-6)

            # (1 - z) * -log(1 - sigmoid(x)). Добавляем эпсилон, чтобы не пропустить нуль в логарифм
            x_2 = (1 - y_true) * -tf.math.log(1 - y_pred + 1e-6)

            return tf.add(x_1, x_2) * self.weight 

        # Используем встроенную функцию
        return tf.nn.weighted_cross_entropy_with_logits(y_true, y_pred, self.pos_weight) * self.weight


model.compile(optimizer=keras.optimizers.Adam(),
              loss=WeightedBinaryCrossEntropy(0.5, 2))

model.fit(x_train, y_train, batch_size=64, epochs=3)
Epoch 1/3
782/782 [==============================] - 1s 2ms/step - loss: 10.0201
Epoch 2/3
782/782 [==============================] - 1s 2ms/step - loss: 9.5187
Epoch 3/3
782/782 [==============================] - 1s 2ms/step - loss: 9.5175

<tensorflow.python.keras.callbacks.History at 0x7f9f201a7588>

Кастомные метрики

Если вам нужны метрики не являющиеся частью API, вы можете легко создать кастомные метрики субклассировав класс Metric. Вам нужно реализовать 4 метода:

  • __init__(self), в котором вы создадите переменные состояния для своей метрики.
  • update_state(self, y_true, y_pred, sample_weight=None), который использует ответы y_true и предсказания модели y_pred для обновления переменных состояния.
  • result(self), использующий переменные состояния для вычисления конечного результата.
  • reset_states(self), который переинициализирует состояние метрики.

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

Вот простой пример, показывающий, как реализовать метрику CategoricalTruePositives, которая считает сколько элементов были правильно классифицированы, как принадлежащие к данному классу:

class CategoricalTruePositives(keras.metrics.Metric):

    def __init__(self, name='categorical_true_positives', **kwargs):
      super(CategoricalTruePositives, self).__init__(name=name, **kwargs)
      self.true_positives = self.add_weight(name='tp', initializer='zeros')

    def update_state(self, y_true, y_pred, sample_weight=None):
      y_pred = tf.reshape(tf.argmax(y_pred, axis=1), shape=(-1, 1))
      values = tf.cast(y_true, 'int32') == tf.cast(y_pred, 'int32')
      values = tf.cast(values, 'float32')
      if sample_weight is not None:
        sample_weight = tf.cast(sample_weight, 'float32')
        values = tf.multiply(values, sample_weight)
      self.true_positives.assign_add(tf.reduce_sum(values))

    def result(self):
      return self.true_positives

    def reset_states(self):
      # Состояние метрики будет сброшено в начале каждой эпохи.
      self.true_positives.assign(0.)


model.compile(optimizer=keras.optimizers.RMSprop(learning_rate=1e-3),
              loss=keras.losses.SparseCategoricalCrossentropy(),
              metrics=[CategoricalTruePositives()])
model.fit(x_train, y_train,
          batch_size=64,
          epochs=3)

Epoch 1/3
782/782 [==============================] - 2s 2ms/step - loss: 0.1640 - categorical_true_positives: 47545.0000
Epoch 2/3
782/782 [==============================] - 1s 2ms/step - loss: 0.0787 - categorical_true_positives: 48808.0000
Epoch 3/3
782/782 [==============================] - 1s 2ms/step - loss: 0.0658 - categorical_true_positives: 49032.0000

<tensorflow.python.keras.callbacks.History at 0x7f9f201216a0>

Обработка функций потерь и метрик, не соответствующих стандартной сигнатуре

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

В таких случаях вы можете вызвать self.add_loss(loss_value) из метода call кастомного слоя. Вот простой пример, который добавляет регуляризацию активности (отметим что регуляризация активности встроена во все слои Keras -- этот слой используется только для приведения конкретного примера):

class ActivityRegularizationLayer(layers.Layer):

  def call(self, inputs):
    self.add_loss(tf.reduce_sum(inputs) * 0.1)
    return inputs  # Проходной слой.

inputs = keras.Input(shape=(784,), name='digits')
x = layers.Dense(64, activation='relu', name='dense_1')(inputs)

# Вставим регуляризацию активности в качестве слоя
x = ActivityRegularizationLayer()(x)

x = layers.Dense(64, activation='relu', name='dense_2')(x)
outputs = layers.Dense(10, activation='softmax', name='predictions')(x)

model = keras.Model(inputs=inputs, outputs=outputs)
model.compile(optimizer=keras.optimizers.RMSprop(learning_rate=1e-3),
              loss='sparse_categorical_crossentropy')

# Полученные потери будут намного больше чем раньше
# из-за компонента регуляризации.
model.fit(x_train, y_train,
          batch_size=64,
          epochs=1)
782/782 [==============================] - 1s 2ms/step - loss: 2.5250

<tensorflow.python.keras.callbacks.History at 0x7f9f20031518>

Вы можете сделать то же самое для логирования значений метрик:

class MetricLoggingLayer(layers.Layer):

  def call(self, inputs):
    # Аргумент `aggregation` определяет
    # как аггрегировать попакетные значения
    # в каждой эпохе:
    # в этом случае мы просто усредняем их.
    self.add_metric(keras.backend.std(inputs),
                    name='std_of_activation',
                    aggregation='mean')
    return inputs  # Проходной слой.


inputs = keras.Input(shape=(784,), name='digits')
x = layers.Dense(64, activation='relu', name='dense_1')(inputs)

# Вставка логирования std в качестве слоя.
x = MetricLoggingLayer()(x)

x = layers.Dense(64, activation='relu', name='dense_2')(x)
outputs = layers.Dense(10, activation='softmax', name='predictions')(x)

model = keras.Model(inputs=inputs, outputs=outputs)
model.compile(optimizer=keras.optimizers.RMSprop(learning_rate=1e-3),
              loss='sparse_categorical_crossentropy')
model.fit(x_train, y_train,
          batch_size=64,
          epochs=1)
782/782 [==============================] - 1s 2ms/step - loss: 0.3436 - std_of_activation: 0.9896

<tensorflow.python.keras.callbacks.History at 0x7f9ecde8e828>

В Functional API, вы можете также вызвать model.add_loss(loss_tensor), или model.add_metric(metric_tensor, name, aggregation).

Вот простой пример:

inputs = keras.Input(shape=(784,), name='digits')
x1 = layers.Dense(64, activation='relu', name='dense_1')(inputs)
x2 = layers.Dense(64, activation='relu', name='dense_2')(x1)
outputs = layers.Dense(10, activation='softmax', name='predictions')(x2)
model = keras.Model(inputs=inputs, outputs=outputs)

model.add_loss(tf.reduce_sum(x1) * 0.1)

model.add_metric(keras.backend.std(x1),
                 name='std_of_activation',
                 aggregation='mean')

model.compile(optimizer=keras.optimizers.RMSprop(1e-3),
              loss='sparse_categorical_crossentropy')
model.fit(x_train, y_train,
          batch_size=64,
          epochs=1)
782/782 [==============================] - 2s 2ms/step - loss: 2.5559 - std_of_activation: 0.0022

<tensorflow.python.keras.callbacks.History at 0x7f9ecdd41518>

Автоматическое выделение валидационного отложенного множества

В первом полном примере, как вы видели, мы использовали аргумент validation_data для передачи кортежа массивов Numpy (x_val, y_val) модели для оценки валидационных потерь и метрик в конце каждой эпохи.

Вот другая опция: аргумент validation_split позволяет вам автоматически зарезервировать часть ваших тренировочных данных для валидации. Значением аргумента является доля данных, которые должны быть зарезервированы для валидации, поэтому значение должно быть больше 0 и меньше 1. Например, validation_split=0.2 значит "используйте 20% данных для валидации", а validation_split=0.6 значит "используйте 60% данных для валидации".

Валидация вычисляется взятием последних x% записей массивов полученных вызовом fit, перед любым перемешиванием.

Вы можете использовать validation_split только когда обучаете данными Numpy.

model = get_compiled_model()
model.fit(x_train, y_train, batch_size=64, validation_split=0.2, epochs=1, steps_per_epoch=1)
1/1 [==============================] - 0s 330ms/step - loss: 2.3357 - sparse_categorical_accuracy: 0.1562 - val_loss: 2.2103 - val_sparse_categorical_accuracy: 0.2183

<tensorflow.python.keras.callbacks.History at 0x7f9f28058e48>

Обучение и оценка с tf.data Dataset

В последних нескольких параграфах вы видели, как обрабатывать потери, метрики и оптимизаторы, и посмотрели, как использовать аргументы validation_data и validation_split в fit, когда ваши данные передаются в виде массивов Numpy.

Давайте теперь рассмотрим случай, когда ваши данные поступают в форме tf.data Dataset.

tf.data API это набор утилит в TensorFlow 2.0 для загрузки и предобработки данных быстрым и масштабируемым способом.

Для полного руководства по созданию Dataset-ов, см. документацию tf.data.

Вы можете передать экземпляр Dataset напрямую в методы fit(), evaluate() и predict():

model = get_compiled_model()

# Сперва давайте создадим экземпляр тренировочного Dataset.
# Для нашего примера мы будем использовать те же данные MNIST что и ранее.
train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))
# Перемешаем и нарежем набор данных.
train_dataset = train_dataset.shuffle(buffer_size=1024).batch(64)

# Сейчас получим тестовый датасет.
test_dataset = tf.data.Dataset.from_tensor_slices((x_test, y_test))
test_dataset = test_dataset.batch(64)

# Поскольку датасет уже позаботился о разбивке на пакеты,
# мы не передаем аргумент `batch_size`.
model.fit(train_dataset, epochs=3)

# Вы можете также оценить модель или сделать прогнозы на датасете.
print('\n# Оценка')
model.evaluate(test_dataset)
Epoch 1/3
782/782 [==============================] - 2s 2ms/step - loss: 0.3326 - sparse_categorical_accuracy: 0.9043
Epoch 2/3
782/782 [==============================] - 2s 2ms/step - loss: 0.1616 - sparse_categorical_accuracy: 0.9524
Epoch 3/3
782/782 [==============================] - 2s 2ms/step - loss: 0.1176 - sparse_categorical_accuracy: 0.9646

# Оценка
157/157 [==============================] - 0s 2ms/step - loss: 0.1362 - sparse_categorical_accuracy: 0.9569

[0.13617590069770813, 0.9569000005722046]

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

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

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

model = get_compiled_model()

# Подготовка учебного датасета
train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))
train_dataset = train_dataset.shuffle(buffer_size=1024).batch(64)

# Использовать только 100 пакетов за эпоху (это 64 * 100 примеров)
model.fit(train_dataset.take(100), epochs=3)
Epoch 1/3
100/100 [==============================] - 0s 2ms/step - loss: 0.7903 - sparse_categorical_accuracy: 0.7970
Epoch 2/3
100/100 [==============================] - 0s 2ms/step - loss: 0.3287 - sparse_categorical_accuracy: 0.9098
Epoch 3/3
100/100 [==============================] - 0s 2ms/step - loss: 0.2583 - sparse_categorical_accuracy: 0.9267

<tensorflow.python.keras.callbacks.History at 0x7f9ecdaee9b0>

Использование валидационного датасета

Вы можете передать экземпляр Dataset как аргумент validation_data в fit:

model = get_compiled_model()

# Подготовим учебный датасет
train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))
train_dataset = train_dataset.shuffle(buffer_size=1024).batch(64)

# Подготовим валидационный датасет
val_dataset = tf.data.Dataset.from_tensor_slices((x_val, y_val))
val_dataset = val_dataset.batch(64)

model.fit(train_dataset, epochs=3, validation_data=val_dataset)
Epoch 1/3
782/782 [==============================] - 2s 2ms/step - loss: 0.3293 - sparse_categorical_accuracy: 0.9069 - val_loss: 0.1795 - val_sparse_categorical_accuracy: 0.9484
Epoch 2/3
782/782 [==============================] - 2s 2ms/step - loss: 0.1542 - sparse_categorical_accuracy: 0.9536 - val_loss: 0.1325 - val_sparse_categorical_accuracy: 0.9627
Epoch 3/3
782/782 [==============================] - 2s 2ms/step - loss: 0.1141 - sparse_categorical_accuracy: 0.9657 - val_loss: 0.1327 - val_sparse_categorical_accuracy: 0.9601

<tensorflow.python.keras.callbacks.History at 0x7f9ecd9f3b38>

В конце каждой эпохи модель будет проходить по валидационному Dataset и вычислять потери и валидационные метрики.

Если вы хотите запустить проверку только на определенном количестве пакетов из этого Dataset, вы можете передать аргументvalidation_steps, который указывает, сколько шагов валидации должна выполнить модель до прерывания валидации и перехода к следующей эпохе:

model = get_compiled_model()

# Подготовка тренировочных данных
train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))
train_dataset = train_dataset.shuffle(buffer_size=1024).batch(64)

# Подготовка валидационных данных
val_dataset = tf.data.Dataset.from_tensor_slices((x_val, y_val))
val_dataset = val_dataset.batch(64)

model.fit(train_dataset, epochs=3,
          # Запускаем валидацию только на первых 10 пакетах датасета
          # используя аргумент `validation_steps`
          validation_data=val_dataset, validation_steps=10)
Epoch 1/3
782/782 [==============================] - 2s 2ms/step - loss: 0.3415 - sparse_categorical_accuracy: 0.9029 - val_loss: 0.3153 - val_sparse_categorical_accuracy: 0.9219
Epoch 2/3
782/782 [==============================] - 2s 2ms/step - loss: 0.1619 - sparse_categorical_accuracy: 0.9520 - val_loss: 0.2681 - val_sparse_categorical_accuracy: 0.9281
Epoch 3/3
782/782 [==============================] - 2s 2ms/step - loss: 0.1208 - sparse_categorical_accuracy: 0.9636 - val_loss: 0.1913 - val_sparse_categorical_accuracy: 0.9484

<tensorflow.python.keras.callbacks.History at 0x7f9ecd9ed860>

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

Аргумент validation_split (генерирующий отложенную выборку из тренировочных данных) не поддерживается при обучении на объектах Dataset, поскольку для этого требуется возможность индексирования элементов, что невозможно в общем в Dataset API.

Другие поддерживаемые форматы входных данных

Кроме массивов Numpy и TensorFlow Dataset-ов, возможно обучить модель Keras с использованием датафрейма Pandas , или с генераторами Python которые выдают значения пакетами.

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

Использование весов для примеров и классов

Кроме входных данных и меток модели можно передавать веса примеров и веса классов при использовании fit:

  • При обучении на данных Numpy: с помощью аргументов sample_weight и class_weight.
  • При обучении на Dataset-ах: если Dataset вернет кортеж (input_batch, target_batch, sample_weight_batch) .

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

Словарь "class weights" является более специфичным экземпляром той же концепции: он сопоставляет индексы классов с весам которые должны быть использованы для примеров принадлежащих этому классу. Например, если класс "0" представлен втрое меньше чем класс "1" в ваших данных, вы можете использовать class_weight={0: 1., 1: 0.5}.

Вот пример Numpy веса классов или веса элементов чтобы придать большее значение корректной классификации класса #5 (соответствующий цифре "5" в датасете MNIST).

import numpy as np

class_weight = {0: 1., 1: 1., 2: 1., 3: 1., 4: 1.,
                # Установим вес "2" для класса "5",
                # сделав этот класс в 2x раз важнее
                5: 2.,
                6: 1., 7: 1., 8: 1., 9: 1.}
print('Fit with class weight')
model.fit(x_train, y_train,
          class_weight=class_weight,
          batch_size=64,
          epochs=4)

# Вот тот же пример использующий `sample_weight`:
sample_weight = np.ones(shape=(len(y_train),))
sample_weight[y_train == 5] = 2.
print('\nОбучение с весом класса')

model = get_compiled_model()
model.fit(x_train, y_train,
          sample_weight=sample_weight,
          batch_size=64,
          epochs=4)
Fit with class weight
Epoch 1/4
782/782 [==============================] - 2s 2ms/step - loss: 0.1079 - sparse_categorical_accuracy: 0.9691
Epoch 2/4
782/782 [==============================] - 2s 2ms/step - loss: 0.0892 - sparse_categorical_accuracy: 0.9744
Epoch 3/4
782/782 [==============================] - 2s 2ms/step - loss: 0.0778 - sparse_categorical_accuracy: 0.9782
Epoch 4/4
782/782 [==============================] - 2s 2ms/step - loss: 0.0661 - sparse_categorical_accuracy: 0.9817

Обучение с весом класса
Epoch 1/4
782/782 [==============================] - 2s 2ms/step - loss: 0.3829 - sparse_categorical_accuracy: 0.8988
Epoch 2/4
782/782 [==============================] - 2s 2ms/step - loss: 0.1811 - sparse_categorical_accuracy: 0.9496
Epoch 3/4
782/782 [==============================] - 1s 2ms/step - loss: 0.1311 - sparse_categorical_accuracy: 0.9633
Epoch 4/4
782/782 [==============================] - 2s 2ms/step - loss: 0.1026 - sparse_categorical_accuracy: 0.9707

<tensorflow.python.keras.callbacks.History at 0x7f9ea45bd198>

Вот соответствующий Dataset пример:

sample_weight = np.ones(shape=(len(y_train),))
sample_weight[y_train == 5] = 2.

# Создадим  Dataset включающий веса элементов
# (3-тий элемент в возвращаемом кортеже).
train_dataset = tf.data.Dataset.from_tensor_slices(
    (x_train, y_train, sample_weight))

# Перемешаем и нарежем датасет.
train_dataset = train_dataset.shuffle(buffer_size=1024).batch(64)

model = get_compiled_model()
model.fit(train_dataset, epochs=3)
Epoch 1/3
782/782 [==============================] - 2s 2ms/step - loss: 0.3799 - sparse_categorical_accuracy: 0.8998
Epoch 2/3
782/782 [==============================] - 2s 2ms/step - loss: 0.1761 - sparse_categorical_accuracy: 0.9519
Epoch 3/3
782/782 [==============================] - 2s 2ms/step - loss: 0.1276 - sparse_categorical_accuracy: 0.9643

<tensorflow.python.keras.callbacks.History at 0x7f9ea44c7c88>

Передача данных в модели с несколькими входами и выходами

В предыдущих примерах, мы рассматривали модель с единственным входом (тензор размера (764,)) и одним выходом (тензор прогнозов размера (10,)). Но как насчет моделей, у которых есть несколько входов или выходов?

Рассмотрим следующую модель, в которой на входными данными являются изображения размера (32, 32, 3) (это (высота, ширина, каналы)) и временные ряды размера (None, 10) (это (временные шаги, признаки)). У нашей модели будет два выхода вычисленных из комбинации этих входов: a "score" (размерности (1,)) и вероятностное распределение по пяти классам (размерности (5,)).

from tensorflow import keras
from tensorflow.keras import layers

image_input = keras.Input(shape=(32, 32, 3), name='img_input')
timeseries_input = keras.Input(shape=(None, 10), name='ts_input')

x1 = layers.Conv2D(3, 3)(image_input)
x1 = layers.GlobalMaxPooling2D()(x1)

x2 = layers.Conv1D(3, 3)(timeseries_input)
x2 = layers.GlobalMaxPooling1D()(x2)

x = layers.concatenate([x1, x2])

score_output = layers.Dense(1, name='score_output')(x)
class_output = layers.Dense(5, activation='softmax', name='class_output')(x)

model = keras.Model(inputs=[image_input, timeseries_input],
                    outputs=[score_output, class_output])

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

keras.utils.plot_model(model, 'multi_input_and_output_model.png', show_shapes=True)

png

Во время компиляции мы можем указать разные функции потерь для разных выходов, передав функции потерь в виде списка:

model.compile(
    optimizer=keras.optimizers.RMSprop(1e-3),
    loss=[keras.losses.MeanSquaredError(),
          keras.losses.CategoricalCrossentropy()])

Если мы передаем только одну функцию потерь модели, она будет применена к каждому выходу, что здесь не подходит.

Аналогично для метрик:

model.compile(
    optimizer=keras.optimizers.RMSprop(1e-3),
    loss=[keras.losses.MeanSquaredError(),
          keras.losses.CategoricalCrossentropy()],
    metrics=[[keras.metrics.MeanAbsolutePercentageError(),
              keras.metrics.MeanAbsoluteError()],
             [keras.metrics.CategoricalAccuracy()]])

Так как мы дали имена нашим выходным слоям, мы могли бы также указать функции потерь и метрики для каждого выхода в dict:

model.compile(
    optimizer=keras.optimizers.RMSprop(1e-3),
    loss={'score_output': keras.losses.MeanSquaredError(),
          'class_output': keras.losses.CategoricalCrossentropy()},
    metrics={'score_output': [keras.metrics.MeanAbsolutePercentageError(),
                              keras.metrics.MeanAbsoluteError()],
             'class_output': [keras.metrics.CategoricalAccuracy()]})

Мы рекомендуем использовать имена и словари если у вас более 2 выходов.

Имеется возможность присвоить разные веса разным функциям потерь (например, в нашем примере мы можем захотеть отдать предпочтение потере "score", увеличив в 2 раза важность потери класса), используя аргумент loss_weights:

model.compile(
    optimizer=keras.optimizers.RMSprop(1e-3),
    loss={'score_output': keras.losses.MeanSquaredError(),
          'class_output': keras.losses.CategoricalCrossentropy()},
    metrics={'score_output': [keras.metrics.MeanAbsolutePercentageError(),
                              keras.metrics.MeanAbsoluteError()],
             'class_output': [keras.metrics.CategoricalAccuracy()]},
    loss_weights={'score_output': 2., 'class_output': 1.})

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

# Функции потерь списком
model.compile(
    optimizer=keras.optimizers.RMSprop(1e-3),
    loss=[None, keras.losses.CategoricalCrossentropy()])

# Функции потерь словарем
model.compile(
    optimizer=keras.optimizers.RMSprop(1e-3),
    loss={'class_output': keras.losses.CategoricalCrossentropy()})

Передача данных в модель с несколькими входами и выходами в fit работает аналогично тому, как мы определяем функцию потерь в compile: вы можете передать списки массивов Numpy (совпадающие 1:1 с выходами на которых есть функции потерь) или словари сопоставляющие имена выходов массивам Numpy тренировочных данных.

model.compile(
    optimizer=keras.optimizers.RMSprop(1e-3),
    loss=[keras.losses.MeanSquaredError(),
          keras.losses.CategoricalCrossentropy()])

# Сгенерируем случайные Numpy данные
img_data = np.random.random_sample(size=(100, 32, 32, 3))
ts_data = np.random.random_sample(size=(100, 20, 10))
score_targets = np.random.random_sample(size=(100, 1))
class_targets = np.random.random_sample(size=(100, 5))

# Обучаемся на списках
model.fit([img_data, ts_data], [score_targets, class_targets],
          batch_size=32,
          epochs=3)

# Альтернативно, обучаемся на словарях
model.fit({'img_input': img_data, 'ts_input': ts_data},
          {'score_output': score_targets, 'class_output': class_targets},
          batch_size=32,
          epochs=3)
Epoch 1/3
4/4 [==============================] - 0s 6ms/step - loss: 4.5125 - score_output_loss: 0.1761 - class_output_loss: 4.3364
Epoch 2/3
4/4 [==============================] - 0s 3ms/step - loss: 4.3694 - score_output_loss: 0.1135 - class_output_loss: 4.2559
Epoch 3/3
4/4 [==============================] - 0s 3ms/step - loss: 4.3073 - score_output_loss: 0.1141 - class_output_loss: 4.1932
Epoch 1/3
4/4 [==============================] - 0s 4ms/step - loss: 4.2815 - score_output_loss: 0.1170 - class_output_loss: 4.1645
Epoch 2/3
4/4 [==============================] - 0s 3ms/step - loss: 4.2467 - score_output_loss: 0.1155 - class_output_loss: 4.1312
Epoch 3/3
4/4 [==============================] - 0s 3ms/step - loss: 4.2062 - score_output_loss: 0.1111 - class_output_loss: 4.0951

<tensorflow.python.keras.callbacks.History at 0x7f9f81741278>

Ниже пример для Dataset: также как мы сделали для массивов Numpy, Dataset должен возвращать кортеж словарей.

train_dataset = tf.data.Dataset.from_tensor_slices(
    ({'img_input': img_data, 'ts_input': ts_data},
     {'score_output': score_targets, 'class_output': class_targets}))
train_dataset = train_dataset.shuffle(buffer_size=1024).batch(64)

model.fit(train_dataset, epochs=3)
Epoch 1/3
2/2 [==============================] - 0s 8ms/step - loss: 4.1950 - score_output_loss: 0.1110 - class_output_loss: 4.0840
Epoch 2/3
2/2 [==============================] - 0s 3ms/step - loss: 4.1871 - score_output_loss: 0.1112 - class_output_loss: 4.0759
Epoch 3/3
2/2 [==============================] - 0s 3ms/step - loss: 4.1801 - score_output_loss: 0.1111 - class_output_loss: 4.0689

<tensorflow.python.keras.callbacks.History at 0x7f9f81707630>

Использование колбеков

Колбеки в Keras это объекты которые вызываются в разных местах во время обучения (в начале эпохи, в конце пакета, в конце эпохи, и т.д.) и которые могут быть использованы для реализации такого поведения, как:

Выполнение валидации в различных точках во время обучения (кроме встроенной валидации в конце каждой эпохи)

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

Колбеки могут переданы списком для вашего вызова fit:

model = get_compiled_model()

callbacks = [
    keras.callbacks.EarlyStopping(
        # Прекратить обучение если `val_loss` больше не улучшается
        monitor='val_loss',
        # "больше не улучшается" определим как "не лучше чем 1e-2 и меньше"
        min_delta=1e-2,
        # "больше не улучшается" далее определим как "как минимум в течение 2 эпох"
        patience=2,
        verbose=1)
]
model.fit(x_train, y_train,
          epochs=20,
          batch_size=64,
          callbacks=callbacks,
          validation_split=0.2)
Epoch 1/20
625/625 [==============================] - 1s 2ms/step - loss: 0.3721 - sparse_categorical_accuracy: 0.8957 - val_loss: 0.2344 - val_sparse_categorical_accuracy: 0.9278
Epoch 2/20
625/625 [==============================] - 1s 2ms/step - loss: 0.1764 - sparse_categorical_accuracy: 0.9477 - val_loss: 0.1916 - val_sparse_categorical_accuracy: 0.9423
Epoch 3/20
625/625 [==============================] - 1s 2ms/step - loss: 0.1309 - sparse_categorical_accuracy: 0.9614 - val_loss: 0.1630 - val_sparse_categorical_accuracy: 0.9513
Epoch 4/20
625/625 [==============================] - 1s 2ms/step - loss: 0.1023 - sparse_categorical_accuracy: 0.9693 - val_loss: 0.1487 - val_sparse_categorical_accuracy: 0.9571
Epoch 5/20
625/625 [==============================] - 1s 2ms/step - loss: 0.0849 - sparse_categorical_accuracy: 0.9747 - val_loss: 0.1466 - val_sparse_categorical_accuracy: 0.9573
Epoch 6/20
625/625 [==============================] - 1s 2ms/step - loss: 0.0725 - sparse_categorical_accuracy: 0.9779 - val_loss: 0.1412 - val_sparse_categorical_accuracy: 0.9602
Epoch 00006: early stopping

<tensorflow.python.keras.callbacks.History at 0x7f9ecdf6c4a8>

Доступно большое количество встроенных колбеков

  • ModelCheckpoint: Периодических сохраняет модель.
  • EarlyStopping: Останавливает обучение, в том случае когда валидационная метрика прекращает улучшаться.
  • TensorBoard: периодически пишет логи модели которые могут быть визуализированы в TensorBoard (больше деталей в разделе "Визуализация").
  • CSVLogger: стримит значения потерь и метрик в файл CSV.
  • и т.д.

Написание собственного колбека

Вы можете создать собственный колбек расширив базовый класс keras.callbacks.Callback. Колбек имеет доступ к ассоциированной модели посредством свойства класса self.model.

Вот простой пример сохранения списка значений попакетных потерь во время обучения:

class LossHistory(keras.callbacks.Callback):

    def on_train_begin(self, logs):
        self.losses = []

    def on_batch_end(self, batch, logs):
        self.losses.append(logs.get('loss'))

Сохранение контрольных точек моделей

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

Проще всего сделать это с помощью колбека ModelCheckpoint:

model = get_compiled_model()

callbacks = [
    keras.callbacks.ModelCheckpoint(
        filepath='mymodel_{epoch}.h5',
        # Путь по которому нужно сохранить модель
        # Два параметра ниже значат что мы перезапишем
        # текущий чекпоинт в том и только в том случае, когда
        # улучится значение `val_loss`.
        save_best_only=True,
        monitor='val_loss',
        verbose=1)
]
model.fit(x_train, y_train,
          epochs=3,
          batch_size=64,
          callbacks=callbacks,
          validation_split=0.2)
Epoch 1/3
598/625 [===========================>..] - ETA: 0s - loss: 0.3669 - sparse_categorical_accuracy: 0.8979
Epoch 00001: val_loss improved from inf to 0.21266, saving model to mymodel_1.h5
625/625 [==============================] - 1s 2ms/step - loss: 0.3598 - sparse_categorical_accuracy: 0.8999 - val_loss: 0.2127 - val_sparse_categorical_accuracy: 0.9369
Epoch 2/3
614/625 [============================>.] - ETA: 0s - loss: 0.1675 - sparse_categorical_accuracy: 0.9502
Epoch 00002: val_loss improved from 0.21266 to 0.17571, saving model to mymodel_2.h5
625/625 [==============================] - 1s 2ms/step - loss: 0.1670 - sparse_categorical_accuracy: 0.9503 - val_loss: 0.1757 - val_sparse_categorical_accuracy: 0.9460
Epoch 3/3
597/625 [===========================>..] - ETA: 0s - loss: 0.1254 - sparse_categorical_accuracy: 0.9623
Epoch 00003: val_loss improved from 0.17571 to 0.16032, saving model to mymodel_3.h5
625/625 [==============================] - 1s 2ms/step - loss: 0.1251 - sparse_categorical_accuracy: 0.9625 - val_loss: 0.1603 - val_sparse_categorical_accuracy: 0.9526

<tensorflow.python.keras.callbacks.History at 0x7f9ecde84748>

Вы также можете написать собственный колбек для сохранения и восстановления моделей.

Полное руководство по сериализации и сохранению, см. Руководство по сохранению и сериализации моделей.

Использование расписаний скорости обучения

Обычным паттерном при тренировке моделей глубокого обучения является постепенное сокращение скорости обучения по мере тренировки модели. Это общеизвестно как "снижение скорости обучения".

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

Передача расписания оптимизатору

Вы можете легко использовать график статического снижения скорости обучения передав объект расписания в качестве аргумента learning_rate вашему оптимизатору:

initial_learning_rate = 0.1
lr_schedule = keras.optimizers.schedules.ExponentialDecay(
    initial_learning_rate,
    decay_steps=100000,
    decay_rate=0.96,
    staircase=True)

optimizer = keras.optimizers.RMSprop(learning_rate=lr_schedule)

Доступно несколько встроенных схем снижения скорости обучения: ExponentialDecay, PiecewiseConstantDecay, PolynomialDecay и InverseTimeDecay.

Использование колбеков для реализации графика динамического изменения скорости обучения

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

Однако колбеки имеют доступ ко всем метрикам, включая метрики валидации! Поэтому, вы можете достичь этого паттерна, используя колбек, который изменяет текущую скорость обучения на оптимизаторе. Фактически, есть и встроенный колбек ReduceLROnPlateau.

Визуализация потерь и метрик во время обучения

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

  • Живые графики функции потерь и метрик для обучения и оценки
  • (опционально) Визуализации гистограмм активаций ваших слоев
  • (опционально) 3D-визуализации пространств вложения, изученных вашими слоями Embedding

Если вы установили TensorFlow с помощью pip, вы можете запустить TensorBoard из командной строки:

tensorboard --logdir=/full_path_to_your_logs

Использование колбека TensorBoard

Самый легкий способ использовать TensorBoard с моделью Keras и методом fit - это колбек TensorBoard.

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

tensorboard_cbk = keras.callbacks.TensorBoard(log_dir='/full_path_to_your_logs')
model.fit(dataset, epochs=10, callbacks=[tensorboard_cbk])

Колбек TensorBoard имеет много полезных опций, в том числе, писать ли лог вложений, гистограмм и как часто писать логи:

keras.callbacks.TensorBoard(
  log_dir='/full_path_to_your_logs',
  histogram_freq=0,  # Как часто писать лог визуализаций гистограмм
  embeddings_freq=0,  # Как часто писать лог визуализаций вложений
  update_freq='epoch')  # Как часто писать логи (по умолчанию: однажды за эпоху)

Часть II: Написание собственных циклов обучения и оценки с нуля

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

Использование GradientTape: первый полный пример

Вызов модели внутри области видимости GradientTape позволяет получить градиенты обучаемых весов слоя относительно значения потерь. Используя экземпляр оптимизатора, вы можете использовать эти градиенты для обновления переменных (которые можно получить с помощью model.trainable_weights).

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

# Получим модель.
inputs = keras.Input(shape=(784,), name='digits')
x = layers.Dense(64, activation='relu', name='dense_1')(inputs)
x = layers.Dense(64, activation='relu', name='dense_2')(x)
outputs = layers.Dense(10, name='predictions')(x)
model = keras.Model(inputs=inputs, outputs=outputs)

# Создадим экземпляр оптимизатора.
optimizer = keras.optimizers.SGD(learning_rate=1e-3)
# Создадимм экземпляр функции потерь.
loss_fn = keras.losses.SparseCategoricalCrossentropy(from_logits=True)

# Подготовим тренировочный датасет.
batch_size = 64
train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))
train_dataset = train_dataset.shuffle(buffer_size=1024).batch(batch_size)

# Итерируем по эпохам.
epochs = 3
for epoch in range(epochs):
  print('Начинаем эпоху %d' % (epoch,))

  # Итерируем по пакетам в датасете.
  for step, (x_batch_train, y_batch_train) in enumerate(train_dataset):

    # Откроем GradientTape чтобы записать операции
    # выполняемые во время прямого прохода, включающего автодифференцирование.
    with tf.GradientTape() as tape:

      # Запустим прямой проход слоя.
      # Операции применяемые слоем к своим
      # входным данным будут записаны
      # на GradientTape.
      logits = model(x_batch_train, training=True)  # Логиты для минибатчей

      # Вычислим значение потерь для этого минибатча.
      loss_value = loss_fn(y_batch_train, logits)

    # Используем gradient tape для автоматического извлечения градиентов
    #  обучаемых переменных относительно потерь.
    grads = tape.gradient(loss_value, model.trainable_weights)

    # Выполним один шаг градиентного спуска обновив
    # значение переменных минимизирующих потери.
    optimizer.apply_gradients(zip(grads, model.trainable_weights))

    # Пишем лог каждые 200 пакетов.
    if step % 200 == 0:
        print('Потери на обучении (для одного пакета) на шаге %s: %s' % (step, float(loss_value)))
        print('Уже увидено: %s примеров' % ((step + 1) * 64))
Начинаем эпоху 0
Потери на обучении (для одного пакета) на шаге 0: 2.3449981212615967
Уже увидено: 64 примеров
Потери на обучении (для одного пакета) на шаге 200: 2.205333709716797
Уже увидено: 12864 примеров
Потери на обучении (для одного пакета) на шаге 400: 2.164766311645508
Уже увидено: 25664 примеров
Потери на обучении (для одного пакета) на шаге 600: 2.0825345516204834
Уже увидено: 38464 примеров
Начинаем эпоху 1
Потери на обучении (для одного пакета) на шаге 0: 1.9740973711013794
Уже увидено: 64 примеров
Потери на обучении (для одного пакета) на шаге 200: 1.9146617650985718
Уже увидено: 12864 примеров
Потери на обучении (для одного пакета) на шаге 400: 1.8843693733215332
Уже увидено: 25664 примеров
Потери на обучении (для одного пакета) на шаге 600: 1.61124587059021
Уже увидено: 38464 примеров
Начинаем эпоху 2
Потери на обучении (для одного пакета) на шаге 0: 1.6007016897201538
Уже увидено: 64 примеров
Потери на обучении (для одного пакета) на шаге 200: 1.4690532684326172
Уже увидено: 12864 примеров
Потери на обучении (для одного пакета) на шаге 400: 1.4627854824066162
Уже увидено: 25664 примеров
Потери на обучении (для одного пакета) на шаге 600: 1.3417736291885376
Уже увидено: 38464 примеров

Низкоуровневая обработка метрик

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

  • Создайте экземпляр метрики в начале цикла
  • Вызовите metric.update_state() после каждого пакета
  • Вызовите metric.result() когда вам нужно показать текущее значение метрики
  • Вызовите metric.reset_states() когда вам нужно очистить состояние метрики (обычно в конце каждой эпохи)

Давайте используем это знание, чтобы посчитать SparseCategoricalAccuracy на валидационных данных в конце каждой эпохи:

# Получим модель
inputs = keras.Input(shape=(784,), name='digits')
x = layers.Dense(64, activation='relu', name='dense_1')(inputs)
x = layers.Dense(64, activation='relu', name='dense_2')(x)
outputs = layers.Dense(10, activation='softmax', name='predictions')(x)
model = keras.Model(inputs=inputs, outputs=outputs)

# Создадим экземпляр оптимизатора для обучения модели.
optimizer = keras.optimizers.SGD(learning_rate=1e-3)
# Создадим экземпляр функции потерь.
loss_fn = keras.losses.SparseCategoricalCrossentropy()

# Подготовим метрику.
train_acc_metric = keras.metrics.SparseCategoricalAccuracy()
val_acc_metric = keras.metrics.SparseCategoricalAccuracy()

# Подготовим тренировочный датасет.
batch_size = 64
train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))
train_dataset = train_dataset.shuffle(buffer_size=1024).batch(batch_size)

# Подготовим валидационный датасет.
val_dataset = tf.data.Dataset.from_tensor_slices((x_val, y_val))
val_dataset = val_dataset.batch(64)


# Итерируем по эпохам.
epochs = 3
for epoch in range(epochs):
  print('Начало эпохи %d' % (epoch,))

  # Итерируем по пакетам в датасете.
  for step, (x_batch_train, y_batch_train) in enumerate(train_dataset):
    with tf.GradientTape() as tape:
      logits = model(x_batch_train)
      loss_value = loss_fn(y_batch_train, logits)
    grads = tape.gradient(loss_value, model.trainable_weights)
    optimizer.apply_gradients(zip(grads, model.trainable_weights))

    # Обновляем метрику на обучении.
    train_acc_metric(y_batch_train, logits)

    # Пишем лог каждые 200 пакетов.
    if step % 200 == 0:
        print('Потери на обучении (за один пакет) на шаге %s: %s' % (step, float(loss_value)))
        print('Уже просмотрено: %s примеров' % ((step + 1) * 64))

  # Покажем метрики в конце каждой эпохи.
  train_acc = train_acc_metric.result()
  print('Accuracy на обучении за эпоху: %s' % (float(train_acc),))
  # Сбросим тренировочные метрики в конце каждой эпохи
  train_acc_metric.reset_states()

  # Запустим валидационный цикл в конце эпохи.
  for x_batch_val, y_batch_val in val_dataset:
    val_logits = model(x_batch_val)
    # Обновим валидационные метрики
    val_acc_metric(y_batch_val, val_logits)
  val_acc = val_acc_metric.result()
  val_acc_metric.reset_states()
  print('Accuracy на валидации: %s' % (float(val_acc),))
Начало эпохи 0
Потери на обучении (за один пакет) на шаге 0: 2.2573368549346924
Уже просмотрено: 64 примеров
Потери на обучении (за один пакет) на шаге 200: 2.163559675216675
Уже просмотрено: 12864 примеров
Потери на обучении (за один пакет) на шаге 400: 2.0606279373168945
Уже просмотрено: 25664 примеров
Потери на обучении (за один пакет) на шаге 600: 1.949332356452942
Уже просмотрено: 38464 примеров
Accuracy на обучении за эпоху: 0.3661400079727173
Accuracy на валидации: 0.6039999723434448
Начало эпохи 1
Потери на обучении (за один пакет) на шаге 0: 1.896554946899414
Уже просмотрено: 64 примеров
Потери на обучении (за один пакет) на шаге 200: 1.7287356853485107
Уже просмотрено: 12864 примеров
Потери на обучении (за один пакет) на шаге 400: 1.587087869644165
Уже просмотрено: 25664 примеров
Потери на обучении (за один пакет) на шаге 600: 1.4524238109588623
Уже просмотрено: 38464 примеров
Accuracy на обучении за эпоху: 0.6578199863433838
Accuracy на валидации: 0.7289999723434448
Начало эпохи 2
Потери на обучении (за один пакет) на шаге 0: 1.319152593612671
Уже просмотрено: 64 примеров
Потери на обучении (за один пакет) на шаге 200: 1.2145123481750488
Уже просмотрено: 12864 примеров
Потери на обучении (за один пакет) на шаге 400: 1.2595399618148804
Уже просмотрено: 25664 примеров
Потери на обучении (за один пакет) на шаге 600: 1.076259732246399
Уже просмотрено: 38464 примеров
Accuracy на обучении за эпоху: 0.7414000034332275
Accuracy на валидации: 0.7914999723434448

Низкоуровневая обработка дополнительных потерь

В предыдущем разделе вы видели, что для слоя можно добавить потери регуляризации, вызвав self.add_loss(value) в методе call.

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

Вспомните пример из предыдущего раздела, где есть слой, который создает потери регуляризации:

class ActivityRegularizationLayer(layers.Layer):

  def call(self, inputs):
    self.add_loss(1e-2 * tf.reduce_sum(inputs))
    return inputs

inputs = keras.Input(shape=(784,), name='digits')
x = layers.Dense(64, activation='relu', name='dense_1')(inputs)
# Вставим регуляризацию активности в качестве слоя
x = ActivityRegularizationLayer()(x)
x = layers.Dense(64, activation='relu', name='dense_2')(x)
outputs = layers.Dense(10, activation='softmax', name='predictions')(x)

model = keras.Model(inputs=inputs, outputs=outputs)

Когда вы вызываете модель как тут:

logits = model(x_train)

потери которые она создает во время прямого прохода добавляются в атрибут model.losses:

logits = model(x_train[:64])
print(model.losses)
[<tf.Tensor: shape=(), dtype=float32, numpy=7.8510036>]

Отслеживаемые потери сначала очищаются в начале модели __call__, поэтому вы увидите только потери, созданные во время текущего одного прямого прохода. Например, при повторном вызове модели и последующем запросе к losses отображаются только последние потери, создано во время последнего вызова:

logits = model(x_train[:64])
logits = model(x_train[64: 128])
logits = model(x_train[128: 192])
print(model.losses)
[<tf.Tensor: shape=(), dtype=float32, numpy=7.747477>]

Чтобы учесть эти потери во время обучения, все, что вам нужно сделать, это модифицировать цикл обучения, добавив к полному значению потерь sum(model.losses):

optimizer = keras.optimizers.SGD(learning_rate=1e-3)

epochs = 3
for epoch in range(epochs):
  print('Начало эпохи %d' % (epoch,))

  for step, (x_batch_train, y_batch_train) in enumerate(train_dataset):
    with tf.GradientTape() as tape:
      logits = model(x_batch_train)
      loss_value = loss_fn(y_batch_train, logits)

      # Добавляем дополнительные потери, созданные во время прямого прохода:
      loss_value += sum(model.losses)

    grads = tape.gradient(loss_value, model.trainable_weights)
    optimizer.apply_gradients(zip(grads, model.trainable_weights))

    # Пишем лог каждые 200 пакетов.
    if step % 200 == 0:
        print('Ошибка на обучении (за один пакет) на шаге %s: %s' % (step, float(loss_value)))
        print('Просмотрено: %s примеров' % ((step + 1) * 64))
Начало эпохи 0
Ошибка на обучении (за один пакет) на шаге 0: 10.01724624633789
Просмотрено: 64 примеров
Ошибка на обучении (за один пакет) на шаге 200: 2.4885847568511963
Просмотрено: 12864 примеров
Ошибка на обучении (за один пакет) на шаге 400: 2.4041237831115723
Просмотрено: 25664 примеров
Ошибка на обучении (за один пакет) на шаге 600: 2.3538320064544678
Просмотрено: 38464 примеров
Начало эпохи 1
Ошибка на обучении (за один пакет) на шаге 0: 2.3293869495391846
Просмотрено: 64 примеров
Ошибка на обучении (за один пакет) на шаге 200: 2.3294177055358887
Просмотрено: 12864 примеров
Ошибка на обучении (за один пакет) на шаге 400: 2.325629472732544
Просмотрено: 25664 примеров
Ошибка на обучении (за один пакет) на шаге 600: 2.328559398651123
Просмотрено: 38464 примеров
Начало эпохи 2
Ошибка на обучении (за один пакет) на шаге 0: 2.3214528560638428
Просмотрено: 64 примеров
Ошибка на обучении (за один пакет) на шаге 200: 2.3222503662109375
Просмотрено: 12864 примеров
Ошибка на обучении (за один пакет) на шаге 400: 2.314688205718994
Просмотрено: 25664 примеров
Ошибка на обучении (за один пакет) на шаге 600: 2.3108527660369873
Просмотрено: 38464 примеров

Это была последняя часть пазла! Вы достигли конца руководства.

Сейчас вы знаете все, что нужно об использовании встроенных циклов обучения и написании своих собственных с нуля.