понедельник, 10 сентября 2018 г.

TensorFlow: сохранение и восстановление моделей

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

  • кодом для создания модели
  • тренировочными весами, или параметрами, для модели

Предоставление этих данных позволяет другим понять как модель работает и испробовать модель самим с новыми данными.

Опции

Существуют различные пути для сохранения TensorFlow моделей - в зависимости от API, который использовать. Это руководство использует tf.keras - высокоуровневое API для построения и тренировки моделей в TensorFlow.

Настройка

Получение примерного набора данных

Мы будем использовать MNIST набор данных для тренировки нашей модели и демонстрации сохраненных весов. Чтобы ускорить демонстрацию, используем только первые 1000 примеров:

from future import absolute_import, division, print_function

import os

import tensorflow as tf
from tensorflow import keras

tf.version

'1.9.0'

(train_images, train_labels), (test_images, test_labels) = tf.keras.datasets.mnist.load_data()

train_labels = train_labels[:1000]
test_labels = test_labels[:1000]

train_images = train_images[:1000].reshape(-1, 28 * 28) / 255.0
test_images = test_images[:1000].reshape(-1, 28 * 28) / 255.0

Downloading data from https://s3.amazonaws.com/img-datasets/mnist.npz
11493376/11490434 [==============================] - 2s 0us/step

Определение модели

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

# Возвращает короткую последовательную модель (sequential model)
def create_model():
  model = tf.keras.models.Sequential([
    keras.layers.Dense(512, activation=tf.nn.relu, input_shape=(784,)),
    keras.layers.Dropout(0.2),
    keras.layers.Dense(10, activation=tf.nn.softmax)
  ])
  
  model.compile(optimizer=tf.keras.optimizers.Adam(), 
                loss=tf.keras.losses.sparse_categorical_crossentropy,
                metrics=['accuracy'])
  
  return model


# Создание инстанса базовой модели
model = create_model()
model.summary()

_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense (Dense)                (None, 512)               401920    
_________________________________________________________________
dropout (Dropout)            (None, 512)               0         
_________________________________________________________________
dense_1 (Dense)              (None, 10)                5130      
=================================================================
Total params: 407,050
Trainable params: 407,050
Non-trainable params: 0
_________________________________________________________________

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

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

tf.keras.callbacks.ModelCheckpoint - это колбек (callback) (функция обратного вызова), который выполняет эту задачу. Колбек принимает пару аргументов, чтобы сконфигурировать создание контрольных точек.

Использование колбека контрольных точек

Тренируем модель и передаем ей ModelCheckpoint колбек:

checkpoint_path = "training_1/cp.ckpt"
checkpoint_dir = os.path.dirname(checkpoint_path)

# Создаем колбек контрольной точки
cp_callback = tf.keras.callbacks.ModelCheckpoint(checkpoint_path, 
                                                 save_weights_only=True,
                                                 verbose=1)

model = create_model()

model.fit(train_images, train_labels,  epochs = 10, 
          validation_data = (test_images,test_labels),
          callbacks = [cp_callback])  # передаем колбек

Train on 1000 samples, validate on 1000 samples
Epoch 1/10
1000/1000 [==============================] - 0s 342us/step - loss: 1.1603 - acc: 0.6670 - val_loss: 0.6827 - val_acc: 0.7880

Epoch 00001: saving model to training_1/cp.ckpt
Epoch 2/10
1000/1000 [==============================] - 0s 129us/step - loss: 0.4071 - acc: 0.8860 - val_loss: 0.5841 - val_acc: 0.8110

Epoch 00002: saving model to training_1/cp.ckpt
Epoch 3/10
1000/1000 [==============================] - 0s 118us/step - loss: 0.2796 - acc: 0.9350 - val_loss: 0.4610 - val_acc: 0.8520

Epoch 00003: saving model to training_1/cp.ckpt

Epoch 4/10
1000/1000 [==============================] - 0s 121us/step - loss: 0.2025 - acc: 0.9570 - val_loss: 0.4324 - val_acc: 0.8610

Epoch 00004: saving model to training_1/cp.ckpt
Epoch 5/10
1000/1000 [==============================] - 0s 117us/step - loss: 0.1489 - acc: 0.9690 - val_loss: 0.4290 - val_acc: 0.8620

Epoch 00005: saving model to training_1/cp.ckpt
Epoch 6/10
1000/1000 [==============================] - 0s 127us/step - loss: 0.1194 - acc: 0.9780 - val_loss: 0.4143 - val_acc: 0.8700

Epoch 00006: saving model to training_1/cp.ckpt
Epoch 7/10
1000/1000 [==============================] - 0s 118us/step - loss: 0.0845 - acc: 0.9860 - val_loss: 0.4208 - val_acc: 0.8670

Epoch 00007: saving model to training_1/cp.ckpt
Epoch 8/10
1000/1000 [==============================] - 0s 118us/step - loss: 0.0648 - acc: 0.9910 - val_loss: 0.4078 - val_acc: 0.8680

Epoch 00008: saving model to training_1/cp.ckpt
Epoch 9/10
1000/1000 [==============================] - 0s 121us/step - loss: 0.0531 - acc: 0.9970 - val_loss: 0.4184 - val_acc: 0.8670

Epoch 00009: saving model to training_1/cp.ckpt
Epoch 10/10
1000/1000 [==============================] - 0s 121us/step - loss: 0.0391 - acc: 0.9960 - val_loss: 0.4185 - val_acc: 0.8640

Epoch 00010: saving model to training_1/cp.ckpt

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

!ls {checkpoint_dir}

checkpoint  cp.ckpt.data-00000-of-00001  cp.ckpt.index

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

Теперь перестроим свежую, нетренированную модель, и оценим ее на тестовом наборе. Нетренированная модель будет выполняться на случайных уровнях (~10% аккуратности):

model = create_model()

loss, acc = model.evaluate(test_images, test_labels)
print("Untrained model, accuracy: {:5.2f}%".format(100*acc))

1000/1000 [==============================] - 0s 105us/step
Untrained model, accuracy:  7.20%

Затем загрузим веса из контрольной точки и переоценим:

model.load_weights(checkpoint_path)
loss,acc = model.evaluate(test_images, test_labels)
print("Restored model, accuracy: {:5.2f}%".format(100*acc))

1000/1000 [==============================] - 0s 24us/step
Restored model, accuracy: 86.40%

Свойства колбека контрольной точки

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

Натренируем новую модель и сохраним уникально названные контрольные точки каждые 5 эпох:

# включаем эпоху в название файла (используя str.format)
checkpoint_path = "training_2/cp-{epoch:04d}.ckpt"
checkpoint_dir = os.path.dirname(checkpoint_path)

cp_callback = tf.keras.callbacks.ModelCheckpoint(
    checkpoint_path, verbose=1, save_weights_only=True,
    # Сохраняем веса каждые 5 эпох.
    period=5)

model = create_model()
model.fit(train_images, train_labels,
          epochs = 50, callbacks = [cp_callback],
          validation_data = (test_images,test_labels),
          verbose=0)

Epoch 00005: saving model to training_2/cp-0005.ckpt

Epoch 00010: saving model to training_2/cp-0010.ckpt

Epoch 00015: saving model to training_2/cp-0015.ckpt

Epoch 00020: saving model to training_2/cp-0020.ckpt

Epoch 00025: saving model to training_2/cp-0025.ckpt

Epoch 00030: saving model to training_2/cp-0030.ckpt

Epoch 00035: saving model to training_2/cp-0035.ckpt

Epoch 00040: saving model to training_2/cp-0040.ckpt

Epoch 00045: saving model to training_2/cp-0045.ckpt

Epoch 00050: saving model to training_2/cp-0050.ckpt

Теперь посмотрим на результирующие контрольные точки (сортируя по дате изменения):

import pathlib

# Сортируем контрольные точки по времени изменения
checkpoints = pathlib.Path(checkpoint_dir).glob("*.index")
checkpoints = sorted(checkpoints, key=lambda cp:cp.stat().st_mtime)
checkpoints = [cp.with_suffix('') for cp in checkpoints]
latest = str(checkpoints[-1])
checkpoints

[PosixPath('training_2/cp-0030.ckpt'),
 PosixPath('training_2/cp-0035.ckpt'),
 PosixPath('training_2/cp-0040.ckpt'),
 PosixPath('training_2/cp-0045.ckpt'),
 PosixPath('training_2/cp-0050.ckpt')]

Следует отметить, что по умолчанию TensorFlow сохраняет только 5 наиболее недавних контрольных точек.

Для тестирования сбросим модель и загрузим последнюю контрольную точку:

model = create_model()
model.load_weights(latest)
loss, acc = model.evaluate(test_images, test_labels)
print("Restored model, accuracy: {:5.2f}%".format(100*acc))

1000/1000 [==============================] - 0s 82us/step
Restored model, accuracy: 87.80%

В какие файлы сохраняются контрольные точки

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

  • Один или более осколков, которые содержат веса модели.
  • Индексный файл, который отражает какие веса сохранены в каждом осколке.

Если модель была тренирована только на одной машине, будет присутствовать только один осколок с суффиксом: .data-00000-of-00001

Ручное сохранение весов

Выше было показано как загружать веса в модель.

Ручное сохранение весов настолько же просто, используйте Model.save_weights метод.

# Сохраняем веса
model.save_weights('./checkpoints/my_checkpoint')

# Восстанавливаем веса
model = create_model()
model.load_weights('./checkpoints/my_checkpoint')

loss,acc = model.evaluate(test_images, test_labels)
print("Restored model, accuracy: {:5.2f}%".format(100*acc))

1000/1000 [==============================] - 0s 59us/step
Restored model, accuracy: 87.80%

Сохранение модели целиком

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

Сохранение полностью функционирующих моделей в Keras очень удобно - можно загружать их в TensorFlow.js и затем тренировать и исполнять их в веб-браузере.

Keras предоставляет базовый формат сохранения, используя HDF5 стандарт. Для наших целей сохраненная модель может трактоваться как единый бинарный блоб (blob).

model = create_model()

model.fit(train_images, train_labels, epochs=5)

# Сохраняем модель целиком в HDF5 файле
model.save('my_model.h5')

Epoch 1/5
1000/1000 [==============================] - 0s 317us/step - loss: 1.1730 - acc: 0.6640
Epoch 2/5
1000/1000 [==============================] - 0s 109us/step - loss: 0.4257 - acc: 0.8790
Epoch 3/5
1000/1000 [==============================] - 0s 106us/step - loss: 0.2889 - acc: 0.9240
Epoch 4/5
1000/1000 [==============================] - 0s 104us/step - loss: 0.2171 - acc: 0.9390
Epoch 5/5
1000/1000 [==============================] - 0s 106us/step - loss: 0.1615 - acc: 0.9670

Теперь пересоздаем модель из этого файла:

# Пересоздаем ту же самую модель, включая веса и оптимизатор.
new_model = keras.models.load_model('my_model.h5')
new_model.summary()

_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_12 (Dense)             (None, 512)               401920    
_________________________________________________________________
dropout_6 (Dropout)          (None, 512)               0         
_________________________________________________________________
dense_13 (Dense)             (None, 10)                5130      
=================================================================
Total params: 407,050
Trainable params: 407,050
Non-trainable params: 0
_________________________________________________________________

Проверяем ее аккуратность:

loss, acc = new_model.evaluate(test_images, test_labels)
print("Restored model, accuracy: {:5.2f}%".format(100*acc))

1000/1000 [==============================] - 0s 78us/step
Restored model, accuracy: 86.60%

Эта техника сохраняет все:

  • Значения весов
  • Конфигурацию модели (архитектуру)
  • Конфигурацию оптимизатора

Keras сохраняет модели, проверяя архитектуру. На данный момент не доступно сохранение TensorFlow оптимизаторов (из tf.train). При их использовании необходимо перекомпилировать модель после загрузки - при этом освобождается состояние оптимизатора.