пятница, 14 сентября 2018 г.

TensorFlow: Keras, сложные модели, сохранение, распределение

Создание сложных моделей

Функциональное (Functional) API

tf.keras.Sequential модель - это просто стек слоев, которые не могут представлять произвольные модели. Для построения моделей с комплексной топологией необходимо использовать Keras функциональное (functional) API, таких моделей как:

  • Модели с многими входами
  • Модели с многими выходами
  • Модели с общими слоями (тот же самый слой вызывается несколько раз)
  • Модели с непоследовательными потоками данных (например, остаточные соединения)

Построение модели с функциональным API:

  1. Вызывается экземпляр слоя и возвращается тензор.
  2. Вводные тензоры и выводные тензоры использованы для определения tf.keras.Model экземпляра.
  3. Эта модель тренируется так же как Sequential модель.

Следующий пример использует функциональное API для построения простой, полностью связанной сети:

# Выполняет тензор заполнитель
inputs = keras.Input(shape=(32,))  

# экземпляр слоя вызывается на тензоре и возвращает тензор
x = keras.layers.Dense(64, activation='relu')(inputs)
x = keras.layers.Dense(64, activation='relu')(x)
predictions = keras.layers.Dense(10, activation='softmax')(x)

# Воплощение модели с заданными вводами и выводами
model = keras.Model(inputs=inputs, outputs=predictions)

# Шаг компиляции определяет тренировочную конфигурацию
model.compile(optimizer=tf.train.RMSPropOptimizer(0.001),
              loss='categorical_crossentropy',
              metrics=['accuracy'])

# Тренируем в течение 5 эпох
model.fit(data, labels, batch_size=32, epochs=5)

Создание подклассов модели

Построим полностью кастомизированную модель посредством создания подкласса tf.keras.Model и определения собственного прохода вперед. Создаем слои в init методе и устанавливаем их как атрибуты экземпляра класса. Определим следующий проход в методе вызова.

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

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

Следующий пример показывает подкласс tf.keras.Model, используя пользовательский проход вперед:

class MyModel(keras.Model):

  def init(self, num_classes=10):
    super(MyModel, self).init(name='my_model')
    self.num_classes = num_classes
    # Определяем слои здесь
    self.dense_1 = keras.layers.Dense(32, activation='relu')
    self.dense_2 = keras.layers.Dense(num_classes, activation='sigmoid')

  def call(self, inputs):
    # Определяем будущий проход здесь,
    # используя слои определенные ранее(в __init__).
    x = self.dense_1(inputs)
    return self.dense_2(x)

  def compute_output_shape(self, input_shape):
    # Необходимо переопределить эту функцию
    # чтобы использовать модель с подклассами
    # как часть модели функционального стиля.
    # В ином случае этот метод опционален.
    shape = tf.TensorShape(input_shape).as_list()
    shape[-1] = self.num_classes
    return tf.TensorShape(shape)


# Воплощаем модель с подклассами.
model = MyModel(num_classes=10)

# Шаг компиляции определяет тренировочную конфигурацию.
model.compile(optimizer=tf.train.RMSPropOptimizer(0.001),
              loss='categorical_crossentropy',
              metrics=['accuracy'])

# Тренируем в течение 5 эпох.
model.fit(data, labels, batch_size=32, epochs=5)

Кастомные слои

Создаем кастомный слой созданием подкласса tf.keras.layers.Layer и имплементируя следующие методы:

  • build: Создает веса слоя. Добавляет веса с add_weight методом.
  • call: Задает будущий проход.
  • compute_output_shape: Определяет как вычислить выводную форму слоя при данной входной форме.

Опционально слой может быть сериализован имплеметацией get_config метода и from_config классового метода.

Вот пример кастомного слоя, который имплементирует matmul входа с матрицей ядра:

class MyLayer(keras.layers.Layer):

  def init(self, output_dim, **kwargs):
    self.output_dim = output_dim
    super(MyLayer, self).init(**kwargs)

  def build(self, input_shape):
    shape = tf.TensorShape((input_shape[1], self.output_dim))
    # Создаем переменную тренируемого веса для этого слоя.
    self.kernel = self.add_weight(name='kernel',
                                  shape=shape,
                                  initializer='uniform',
                                  trainable=True)
    super(MyLayer, self).build(input_shape)

  def call(self, inputs):
    return tf.matmul(inputs, self.kernel)

  def compute_output_shape(self, input_shape):
    shape = tf.TensorShape(input_shape).as_list()
    shape[-1] = self.output_dim
    return tf.TensorShape(shape)

  def get_config(self):
    base_config = super(MyLayer, self).get_config()
    base_config['output_dim'] = self.output_dim

  @classmethod
  def from_config(cls, config):
    return cls(**config)


# Создание слоя с использованием кастомного слоя
model = keras.Sequential([MyLayer(10),
                          keras.layers.Activation('softmax')])

# Шаг компиляции определяет тренировочную конфигурацию
model.compile(optimizer=tf.train.RMSPropOptimizer(0.001),
              loss='categorical_crossentropy',
              metrics=['accuracy'])

# Тренируем в течение 5 эпох.
model.fit(data, targets, batch_size=32, epochs=5)

Колбеки (callbacks)

Колбек (callback) - это объект переданный модели для кастомизирования и расширения ее модели в ходе тренировки. Можно писать собственный кастомный колбек или использовать встроенный tf.keras.callbacks, который включает:

  • tf.keras.callbacks.ModelCheckpoint: Сохраняет контрольные точки модели с регулярными интервалами.
  • tf.keras.callbacks.LearningRateScheduler: Динамически изменяет скорость обучения.
  • tf.keras.callbacks.EarlyStopping: Прерывает тренировку, когда валидационная производительность перестает улучшаться.
  • tf.keras.callbacks.TensorBoard: Отображает поведение модели, используя TensorBoard.

Чтобы использовать tf.keras.callbacks.Callback передаем ее методу fit модели:

callbacks = [
  # Прерываем тренировку 
  # если val_loss перестает улучшаться в течение 2 эпох
  keras.callbacks.EarlyStopping(patience=2, monitor='val_loss'),
  # Записываем логи TensorBoard в ./logs директорию
  keras.callbacks.TensorBoard(log_dir='./logs')
]
model.fit(data, labels, batch_size=32, epochs=5, callbacks=callbacks,
          validation_data=(val_data, val_targets))

Сохранение и восстановление

Только веса

Сохраняем и загружаем веса модели, используя tf.keras.Model.save_weights:

# Сохраняем веса в TensorFlow Checkpoint файл
model.save_weights('./my_model')

# Восстанавливаем состояние модели,
# это требует модель с точно такой же архитектурой.
model.load_weights('my_model')

По умолчанию это сохраняет веса модели в файл формата контрольной точки TensorFlow. Веса могут быть также сохранены в Keras HDF5 формате (по умолчанию для мульти-бекенд имплементации Keras):

# Сохраняем веса в HDF5 файле
model.save_weights('my_model.h5', save_format='h5')

# Восстанавливаем состояние модели
model.load_weights('my_model.h5')

Сохранение и восстановление только конфигурации

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

# Сериализация модели в JSON формате
json_string = model.to_json()

# Пересоздание модели (со свежей инициализацией)
fresh_model = keras.models.from_json(json_string)

# Сериализация модели в YAML формате
yaml_string = model.to_yaml()

# Пересоздание модели
fresh_model = keras.models.from_yaml(yaml_string)

Предостережение: модели с подклассами не сериализуемы, потому что их архитектура определена в Python коде в теле call метода.

Сохранение и восстановление целой модели

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

# Создаем обычную модель
model = keras.Sequential([
  keras.layers.Dense(10, activation='softmax', input_shape=(32,)),
  keras.layers.Dense(10, activation='softmax')
])
model.compile(optimizer='rmsprop',
              loss='categorical_crossentropy',
              metrics=['accuracy'])
model.fit(data, targets, batch_size=32, epochs=5)


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

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

Нетерпеливое исполнение

Нетерпеливое исполнение (eager execution) - это императивное программное окружение, которое применяет операции немедленно. Это не требуется для Keras, но поддерживается tf.keras и полезно для проверки программ и отладки.

Все tf.keras API для построения моделей совместимы с нетерпеливым исполнением. И, хотя Sequential и фукнциональное API могут быть использованы, нетерпеливое исполнение имеет особые преимущества при создании подклассов моделей и построении кастомных слоев.

Распределение

Estimators (оценщики)

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

tf.keras.Model может быть тренирована с tf.estimator API преобразованием модели в tf.estimator.Estimator объект с tf.keras.estimator.model_to_estimator.

model = keras.Sequential([layers.Dense(10,activation='softmax'),
                          layers.Dense(10,activation='softmax')])

model.compile(optimizer=tf.train.RMSPropOptimizer(0.001),
              loss='categorical_crossentropy',
              metrics=['accuracy'])

estimator = keras.estimator.model_to_estimator(model)

Исполнение на многих GPU

tf.keras модели могут выполняться на нескольких GPU, используя tf.contrib.distribute.DistributionStrategy. Это API предоставляет распределенную тренировку на нескольких GPU почти без изменений в существующем коде.

На данный момент tf.contrib.distribute.MirroredStrategy единственная поддерживаемая стратегия распределения. MirroredStrategy выполняет внутри-графовую репликацию с синхронной тренировкой, используя all-reduce на единственной машине. Чтобы использовать DistributionStrategy с Keras, преобразуем tf.keras.Model в tf.estimator.Estimator с tf.keras.estimator.model_to_estimator, затем тренируем как estimator.

Следующий пример распределяет tf.keras.Model по нескольким GPU на единственной машине.

Во-первых, определим простую модель:

model = keras.Sequential()
model.add(keras.layers.Dense(16, activation='relu', input_shape=(10,)))
model.add(keras.layers.Dense(1, activation='sigmoid'))

optimizer = tf.train.GradientDescentOptimizer(0.2)

model.compile(loss='binary_crossentropy', optimizer=optimizer)
model.summary()

Определим входной пайплайн. input_fn возвращает tf.data.Dataset объект, используемый чтобы распределить данные по многим устройствам - с каждым устройством, обрабатывающим часть входного пакета.

def input_fn():
  x = np.random.random((1024, 10))
  y = np.random.randint(2, size=(1024, 1))
  x = tf.cast(x, tf.float32)
  dataset = tf.data.Dataset.from_tensor_slices((x, y))
  dataset = dataset.repeat(10)
  dataset = dataset.batch(32)
  return dataset

Затем создаем tf.estimator.RunConfig и устанавливаем train_distribute аргумент к tf.contrib.distribute.MirroredStrategy экземпляру. При создании MirroredStrategy можно определить лист устройств или установить num_gpus аргумент. По умолчанию используются все доступные GPU:

strategy = tf.contrib.distribute.MirroredStrategy()
config = tf.estimator.RunConfig(train_distribute=strategy)

Преобразуем Keras модель в tf.estimator.Estimator экземпляр:

keras_estimator = keras.estimator.model_to_estimator(
  keras_model=model,
  config=config,
  model_dir='/tmp/model_dir')

Наконец, тренируем Estimator экземпляр предоставляя input_fn и steps аргументы:

keras_estimator.train(input_fn=input_fn, steps=10)