In [ ]:
#@title Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

Eager execution

Note: Вся информация в этом разделе переведена с помощью русскоговорящего Tensorflow сообщества на общественных началах. Поскольку этот перевод не является официальным, мы не гарантируем что он на 100% аккуратен и соответствует официальной документации на английском языке. Если у вас есть предложение как исправить этот перевод, мы будем очень рады увидеть pull request в tensorflow/docs репозиторий GitHub. Если вы хотите помочь сделать документацию по Tensorflow лучше (сделать сам перевод или проверить перевод подготовленный кем-то другим), напишите нам на docs-ru@tensorflow.org list.

Eager execution в TensorFlow - это интерактивный режим, в котором все вычисления выполняются мгновенно: операции возвращают конкретные значения, вместо построения вычислительных графов. Это позволяет легко запускать любой код в TensorFlow, упрощает отладку моделей и устраняет необходимость в шаблонном boilerplate коде. Пройди все этапы в этом руководстве, просто запуская примеры кода в интерпретаторе Python ниже.

Eager execution является гибкой платформой машинного обучения для проведения исследований и экспериментов. Ее основные преимущества:

  • Интуитивный интерфейс — структурируй свой код и используй стандартные структуры данных Python. Быстро проверяй гипотезы с помощью небольших моделей на малых данных
  • Легкая отладка кода — Производи любые операции непосредственно на готовых моделях и проверяй изменения. Используй стандартные инструменты для отладки Python кода для незамедлительного отчета об ошибках
  • Естественный порядок выполнения — Используй порядок выполнения Python вместо графа вычислений, упрощая спецификацию динамических моделей

Eager execution поддерживает большинство операций TensoFlow и ускорение при помощи GPU. Смотри основные примеры, которые можно запускать в eager execution здесь: Примеры в Eager Execution.

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

Установка и использование

Чтобы запустить eager execution, добавь tf.enable_eager_execution() в начало программы или консольной сессии. Не добавляй эту операцию к другим модулям, которые вызывает программа.


In [ ]:
import tensorflow.compat.v1 as tf

Теперь ты можешь запускать любые операции TensorFlow и получать результаты мгновенно:


In [ ]:
tf.executing_eagerly()

In [ ]:
x = [[2.]]
m = tf.matmul(x, x)
print("привет, {}".format(m))

Включение режима eager execution изменяет то, как себя ведут операции TensorFlow: теперь они выполняются мгновенно. Объекты tf.Tensor относятся к конкретным значениям, вместо символических имен вычислительных графов. Так как теперь нет вычислительного графа, который нужно построить и затем запустить в сессии, можно легко инспектировать результаты при помощи функции print() или отладчика. Вычисления, вывод данных и проверка значений тензоров не нарушает порядок выполнения для расчета градиентов.

Eager execution легко работает с NumPy. Операции NumPy принимают аргументы tf.Tensor. Математические операции TensorFlow конвертируют объекты Python и массивы NumPy в объекты tf.Tensor. Метод tf.Tensor.numpy возвращает значение объекта как массив NumPy ndarray.


In [ ]:
a = tf.constant([[1, 2],
                 [3, 4]])
print(a)

In [ ]:
# Проверяем при помощи функции `print`
b = tf.add(a, 1)
print(b)

In [ ]:
# Поддерживается перегрузка операторов
print(a * b)

In [ ]:
# Используем значения NumPy
import numpy as np

c = np.multiply(a, b)
print(c)

In [ ]:
# Получаем значение NumPy из тензора
print(a.numpy())
# => [[1 2]
#     [3 4]]

Модуль tf.contrib.eager содержит символы, доступные как в eager, так и в стандартном режиме graph execution, и является весьма полезным при работе с графами:


In [ ]:
tfe = tf.contrib.eager

Динамический порядок выполнения

Большим преимуществом eager execution является то, что весь функционал языка хоста доступен в то время, как запущена модель. Например, можно легко написать решение задачи fizzbuzz:


In [ ]:
def fizzbuzz(max_num):
  counter = tf.constant(0)
  max_num = tf.convert_to_tensor(max_num)
  for num in range(1, max_num.numpy()+1):
    num = tf.constant(num)
    if int(num % 3) == 0 and int(num % 5) == 0:
      print('FizzBuzz')
    elif int(num % 3) == 0:
      print('Fizz')
    elif int(num % 5) == 0:
      print('Buzz')
    else:
      print(num.numpy())
    counter += 1

In [ ]:
fizzbuzz(15)

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

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

Многие модели машинного обучения состоят из сочетания слоев. Когда мы используем TensorFlow с eager execution, ты можешь либо создавать свои собственные слои, или использовать уже готовые, которые определены в tf.keras.layers.

В то время как ты можешь использовать любой объект Python для представления слоя, в TensorFlow есть удобный способ использования базовых классов слоев из tf.keras.layers.Layers. Используй их для создания своего собственного слоя:


In [ ]:
class MySimpleLayer(tf.keras.layers.Layer):
  def __init__(self, output_units):
    super(MySimpleLayer, self).__init__()
    self.output_units = output_units

  def build(self, input_shape):
    # Метод `build` вызывается первый раз, когда используется слой.
    # Создание переменных в build() позволяет задать размерность тензоров в зависимости
    # от размерности входных параметров, и таким образом устранить необходимость
    # пользователю уточнять формы полностью. Также возможно создавать переменные
    # во время __init__(), если ты уже знаешь их полные формы.
    self.kernel = self.add_variable(
      "kernel", [input_shape[-1], self.output_units])

  def call(self, input):
    # Перепишем call() вместо __call__, чтобы мы могли вести счет.
    return tf.matmul(input, self.kernel)

Используй слой tf.keras.layers.Dense вместо MySimpleLayer выше, так как он включает в себя надмножество (также может включать в себя смещение bias).

Когда составляешь слои модели, ты можешь использовать tf.keras.Sequential для создания моделей, которые являются линейный стеком слоев. Это легко использовать для стандартных моделей:


In [ ]:
model = tf.keras.Sequential([
  tf.keras.layers.Dense(10, input_shape=(784,)),  # укажем размерность входных данных
  tf.keras.layers.Dense(10)
])

Альтернативный способ - организовать модели в классы из tf.keras.Model. Это контейнер слоев, который также сам является слоем, что позволяет объектам tf.keras.Model содержать в себе другие объекты tf.keras.Model.


In [ ]:
class MNISTModel(tf.keras.Model):
  def __init__(self):
    super(MNISTModel, self).__init__()
    self.dense1 = tf.keras.layers.Dense(units=10)
    self.dense2 = tf.keras.layers.Dense(units=10)

  def call(self, input):
    """Запускаем модель."""
    result = self.dense1(input)
    result = self.dense2(result)
    result = self.dense2(result)  # повторно используем переменные из слоя dense2
    return result

model = MNISTModel()

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

Классы tf.keras.layers создают и содержат собственные переменные моделей, время действия которых привязаны к объектам слоев. Чтобы разделить переменные слоев, необходимо разделить их объекты.

Обучение в Eager

Вычисление градиентов

Автоматическое дифференцирование полезна для реализации алгоритмов машинного обучения, например таких ка метод обратного распространения ошибки backpropagation для обучения нейронных сетей. Во время eager execution используй tf.GradientTape для записи операций и для последующего вычисления градиента.

tf.GradientTape - это встроенная возможность обеспечения максимальной производительности модели, когда не записываются операции. Поскольку могут возникать разные операции во время каждого вызова, все операции прямого прохода записываются на "пленку" ("tape"). Для вычисления градиента, пленка воспроизводится в обратном порядке, а затем сбрасывается. Конкретная запись tf.GradientTape может произвести расчет только одного градиента; все последующие вызовы выдадут ошибку рабочей среды.


In [ ]:
w = tf.Variable([[1.0]])
with tf.GradientTape() as tape:
  loss = w * w

grad = tape.gradient(loss, w)
print(grad)  # => tf.Tensor([[ 2.]], shape=(1, 1), dtype=float32)

Обучение модели

В следующем примере мы создадим многослойную модель, которая будет классифицировать стандартный датасет MNIST, состоящий из изображений рукописных чисел. Данный пример продемонстрирует API оптимизатора и слоя для создания обучаемых графов в среде eager execution.


In [ ]:
# Загружаем и форматируем данные mnist
(mnist_images, mnist_labels), _ = tf.keras.datasets.mnist.load_data()

dataset = tf.data.Dataset.from_tensor_slices(
  (tf.cast(mnist_images[...,tf.newaxis]/255, tf.float32),
   tf.cast(mnist_labels,tf.int64)))
dataset = dataset.shuffle(1000).batch(32)

In [ ]:
# Создаем модель
mnist_model = tf.keras.Sequential([
  tf.keras.layers.Conv2D(16,[3,3], activation='relu', input_shape=(None,None,1)),
  tf.keras.layers.Conv2D(16,[3,3], activation='relu'),
  tf.keras.layers.GlobalAveragePooling2D(),
  tf.keras.layers.Dense(10)
])

Еще не приступив к обучению, попробуй вызвать модель и проинспектировать вывод данных в eager execution:


In [ ]:
for images,labels in dataset.take(1):
  print("Логиты: ", mnist_model(images[0:1]).numpy())

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


In [ ]:
optimizer = tf.train.AdamOptimizer()

loss_history = []

In [ ]:
for (batch, (images, labels)) in enumerate(dataset.take(400)):
  if batch % 80 == 0:
    print()
  print('.', end='')
  with tf.GradientTape() as tape:
    logits = mnist_model(images, training=True)
    loss_value = tf.losses.sparse_softmax_cross_entropy(labels, logits)

  loss_history.append(loss_value.numpy())
  grads = tape.gradient(loss_value, mnist_model.trainable_variables)
  optimizer.apply_gradients(zip(grads, mnist_model.trainable_variables),
                            global_step=tf.train.get_or_create_global_step())

In [ ]:
import matplotlib.pyplot as plt

plt.plot(loss_history)
plt.xlabel('Батч #')
plt.ylabel('Потери [энтропия]')

В этом примере используется модуль dataset.py из примера TensorFlow MNIST; загрузи этот файл в папку на своем устройстве. Затем запусти код для загрузки данных MNIST в рабочую папку и подготовь tf.data.Dataset к обучению:

Переменные и оптимизаторы

Объекты tf.Variable хранят переменные tf.Tensor значения, доступ к которым предоставляется во время обучения, чтобы проще производить автоматическую дифференцирование. Параметры модели могут быть сохранены в классах как переменные.

Лучше сохранять параметры модели, используя tf.Variable при помощи tf.GradientTape. Например таким образом, автоматическая дифференцирование примера выше может быть перезаписана:


In [ ]:
class Model(tf.keras.Model):
  def __init__(self):
    super(Model, self).__init__()
    self.W = tf.Variable(5., name='weight')
    self.B = tf.Variable(10., name='bias')
  def call(self, inputs):
    return inputs * self.W + self.B

# Маленький датасет из точек около 3 * x + 2
NUM_EXAMPLES = 2000
training_inputs = tf.random_normal([NUM_EXAMPLES])
noise = tf.random_normal([NUM_EXAMPLES])
training_outputs = training_inputs * 3 + 2 + noise

# Функция потерь для оптимизации
def loss(model, inputs, targets):
  error = model(inputs) - targets
  return tf.reduce_mean(tf.square(error))

def grad(model, inputs, targets):
  with tf.GradientTape() as tape:
    loss_value = loss(model, inputs, targets)
  return tape.gradient(loss_value, [model.W, model.B])

# Определим:
# 1. Модель
# 2. Производные функции потерь с учетом параметров модели
# 3. Стратегию обновления переменных, основываясь на производных
model = Model()
optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.01)

print("Изначальная потеря: {:.3f}".format(loss(model, training_inputs, training_outputs)))

# Тренировочный цикл:
for i in range(300):
  grads = grad(model, training_inputs, training_outputs)
  optimizer.apply_gradients(zip(grads, [model.W, model.B]),
                            global_step=tf.train.get_or_create_global_step())
  if i % 20 == 0:
    print("Потеря на шаге {:03d}: {:.3f}".format(i, loss(model, training_inputs, training_outputs)))

print("Окончательная потеря: {:.3f}".format(loss(model, training_inputs, training_outputs)))
print("W = {}, B = {}".format(model.W.numpy(), model.B.numpy()))

Использование объектов для состояния в eager execution

В graph execution состояния программы (такие, как переменные) хранятся в глобальных коллекциях и их срок действия определяется объектом tf.Session. Для сравнения, во время eager execution срок действия состояний объектов определяется сроком действия соответствующего объекта Python.

Переменные объекты

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


In [ ]:
if tf.test.is_gpu_available():
  with tf.device("gpu:0"):
    v = tf.Variable(tf.random_normal([1000, 1000]))
    v = None  # v больше не занимает место в памяти GPU

Объектное сохранение

tf.train.Checkpoint может сохранять и загружать tf.Variables как в, так и из контрольных точек:


In [ ]:
x = tf.Variable(10.)
checkpoint = tf.train.Checkpoint(x=x)

In [ ]:
x.assign(2.)   # Назначим новое значение переменной и сохраним
checkpoint_path = './ckpt/'
checkpoint.save('./ckpt/')

In [ ]:
x.assign(11.)  # Изменим переменную после сохранения

# Восстановим значение из контрольной точки
checkpoint.restore(tf.train.latest_checkpoint(checkpoint_path))

print(x)  # => 2.0

Для сохранения и загрузки моделей tf.train.Checkpoint сохраняет внутреннее состояние объектов без требования скрытых переменных. Чтобы сохранить текущее состояние модели, выбранный оптимизатор и глобальный шаг, просто передай их как аргумент к tf.train.Checkpoint:


In [ ]:
import os
import tempfile

model = tf.keras.Sequential([
  tf.keras.layers.Conv2D(16,[3,3], activation='relu'),
  tf.keras.layers.GlobalAveragePooling2D(),
  tf.keras.layers.Dense(10)
])
optimizer = tf.train.AdamOptimizer(learning_rate=0.001)
checkpoint_dir = tempfile.mkdtemp()
checkpoint_prefix = os.path.join(checkpoint_dir, "ckpt")
root = tf.train.Checkpoint(optimizer=optimizer,
                           model=model,
                           optimizer_step=tf.train.get_or_create_global_step())

root.save(checkpoint_prefix)
root.restore(tf.train.latest_checkpoint(checkpoint_dir))

Объектно-ориентированные показатели

tfe.metrics сохраняются как объекты. Обнови показатели модели, передав новые данные к вызываемому объекту, и получи результат при помощи метода tf.metrics.result как в этом примере:


In [ ]:
m = tfe.metrics.Mean("loss")
m(0)
m(5)
m.result()  # => 2.5
m([8, 9])
m.result()  # => 5.5

Статистика обучения в TensorBoard

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

tf.contrib.summary совместим как с режимами eager, так и graph execution. Операции для записи итогов обучения, такие как tf.contrib.summary.scalar, должны быть использованы в коде во время построения модели. Вот пример записи показателей модели через каждые 100 глобальных шагов:


In [ ]:
global_step = tf.train.get_or_create_global_step()

logdir = "./tb/"
writer = tf.contrib.summary.create_file_writer(logdir)
writer.set_as_default()

for _ in range(10):
  global_step.assign_add(1)
  # Обязательно укажи метод `record_summaries`
  with tf.contrib.summary.record_summaries_every_n_global_steps(100):
    # Основной код для построения модели идет ниже
    tf.contrib.summary.scalar('global_step', global_step)

In [ ]:
ls tb/

Углубленные темы автоматического дифференцирования

Динамические модели

tf.GradientTape также может быть использован для динамических моделей. Вот пример алгоритма поиска линии с возвратом, который выглядит как обычный код NumPy code, но с градиентами, и этот код - дифференцируемый, несмотря на сложный порядок выполнения:


In [ ]:
def line_search_step(fn, init_x, rate=1.0):
  with tf.GradientTape() as tape:
    # Переменные записываются автоматически, но следим за тензором вручную.
    tape.watch(init_x)
    value = fn(init_x)
  grad = tape.gradient(value, init_x)
  grad_norm = tf.reduce_sum(grad * grad)
  init_value = value
  while value > init_value - rate * grad_norm:
    x = init_x - rate * grad
    value = fn(x)
    rate /= 2.0
  return x, value

Дополнительные функции для вычисления градиентов

tf.GradientTape хоть и является эффективным интерфейсом для расчета градиентов, однако существует также другой API, который работает в стиле Autograd и используется для автоматического дифференцирования. Эти функции весьма полезны при написании математического кода только с тензорами и функциями градиентов, но без tf.variables:

  • tfe.gradients_function — Возвращает функцию, которая рассчитывает производные ее параметров функции ввода с учетом аргументов. Параметр функции ввода должен возвращать скалярное значение. Когда мы вызываем возвращенную функцию, она возвращает список объектов tf.Tensor: по одному элементу для каждого аргумента функции ввода. Поскольку нам могут быть интересны любые параметры функции, этот метод может быть весьма неудобным в использовании, если есть какие-либо зависимости от множества обучаемых параметров
  • tfe.value_and_gradients_function — Похожа на tfe.gradients_function, но когда мы вызываем возвращенную функцию, она возвращает значение функции ввода в дополнение к списку производных функции ввода с учетом ее аргументов

В следующем примере tf.gradients_function принимает функцию square как аргумент и возвращает функцию, которая рассчитывает частную производную square с учетом ее вводов. Для вычисления квадрата производной square числа 3, grad(3.0) возвращает 6:


In [ ]:
def square(x):
  return tf.multiply(x, x)

grad = tfe.gradients_function(square)

In [ ]:
square(3.).numpy()

In [ ]:
grad(3.)[0].numpy()

In [ ]:
# Квадрат производной второго порядка
gradgrad = tfe.gradients_function(lambda x: grad(x)[0])
gradgrad(3.)[0].numpy()

In [ ]:
# Производная третьего порядка - None
gradgradgrad = tfe.gradients_function(lambda x: gradgrad(x)[0])
gradgradgrad(3.)

In [ ]:
# С потоком выполнения
def abs(x):
  return x if x > 0. else -x

grad = tfe.gradients_function(abs)

In [ ]:
grad(3.)[0].numpy()

In [ ]:
grad(-3.)[0].numpy()

Собственные градиенты

Создание собственных градиентов - это простой способ переписать стандартные градиенты в режимах eager и graph execution. В блоке функции прямого прохода определи градиент с учетом вводов, выводов и промежуточных результатов. Например, вот легкий способ сжатия норм градиентов в обратном проходе backward pass:


In [ ]:
@tf.custom_gradient
def clip_gradient_by_norm(x, norm):
  y = tf.identity(x)
  def grad_fn(dresult):
    return [tf.clip_by_norm(dresult, norm), None]
  return y, grad_fn

Собственные градиенты часто используются для обеспечения численно стабильных градиентов для последовательности операций:


In [ ]:
def log1pexp(x):
  return tf.log(1 + tf.exp(x))
grad_log1pexp = tfe.gradients_function(log1pexp)

In [ ]:
# Вычисление градиента работает отлично если x = 0.
grad_log1pexp(0.)[0].numpy()

In [ ]:
# Однако, при x = 100 расчет не производится по причине числовой нестабильности.
grad_log1pexp(100.)[0].numpy()

Функцию log1pexp можно аналитически упростить при помощи собственного производного градиента. Реализация этого подхода, как в примере ниже, повторно использует значение tf.exp(x), которое было рассчитано во время прямого прохода, делает данный метод более эффективным за счет устранения избыточных вычислений:


In [ ]:
@tf.custom_gradient
def log1pexp(x):
  e = tf.exp(x)
  def grad(dy):
    return dy * (1 - 1 / (1 + e))
  return tf.log(1 + e), grad

grad_log1pexp = tfe.gradients_function(log1pexp)

In [ ]:
# Как и в предыдущий раз, вычисление градиента работает если x = 0.
grad_log1pexp(0.)[0].numpy()

In [ ]:
# А также теперь можно рассчитать градиент и при x = 100.
grad_log1pexp(100.)[0].numpy()

Улучшение производительности

Вычисления автоматически распределяются на графическом процессоре GPU во время работы в режиме eager execution. Если ты хочешь контролировать на каком устройстве производить вычисления, то можно просто указать его в блоке tf.device('/gpu:0') (или другом эквиваленте центрального процессора (далее в тексте и коде - CPU)):


In [ ]:
import time

def measure(x, steps):
  # TensorFlow инициализирует GPU во время первого использования,
  # это время исключается из учета производительности.
  tf.matmul(x, x)
  start = time.time()
  for i in range(steps):
    x = tf.matmul(x, x)
  # `tf.matmul` может возвращать значения до завершения процесса умножения
  # матриц (например, эта функция может возвращать значения сразу после
  # постановки операции в очередь в поток CUDA). Вызов x.numpy() ниже
  # удостоверяет, что все операции в очереди были завершены (также копирует
  # результаты в память устройства, с которого производились расчеты,
  # таким образом мы учитываем немного больше времени, чем просто для
  # операции умножения матриц `matmul`).
  _ = x.numpy()
  end = time.time()
  return end - start

shape = (1000, 1000)
steps = 200
print("Время, требующееся для умножения {} матрицы на себя {} раз:".format(shape, steps))

# Запускаем на CPU
with tf.device("/cpu:0"):
  print("СPU: {} секунд".format(measure(tf.random_normal(shape), steps)))

# Если доступен, запускаем на GPU
if tfe.num_gpus() > 0:
  with tf.device("/gpu:0"):
    print("GPU: {} секунд".format(measure(tf.random_normal(shape), steps)))
else:
  print("GPU: устройство не найдено")

Объект tf.Tensor может быть скопирован на другое устройство для выполнения своих операций:


In [ ]:
if tf.test.is_gpu_available():
  x = tf.random_normal([10, 10])

  x_gpu0 = x.gpu()
  x_cpu = x.cpu()

  _ = tf.matmul(x_cpu, x_cpu)    # Запускаем на CPU
  _ = tf.matmul(x_gpu0, x_gpu0)  # Запускаем на первом GPU:0

  if tfe.num_gpus() > 1:
    x_gpu1 = x.gpu(1)
    _ = tf.matmul(x_gpu1, x_gpu1)  # Запускаем на втором GPU:1

Тестирование

Для вычисления больших моделей, например таких, как ResNet50, обучение производится на GPU, а производительность работы в eager execution сопоставима с graph execution. Тем не менее этот пробел становится еще шире для моделей, где требуется меньше вычислений, и где требуется больше работы для оптимизации тех блоков кода, на которые приходится основная нагрузка при вычислении. Это особенно актуально для моделей с большим количеством малых операций.

Работаем с графами

В то время как режим eager execution делает разработку и отладку кода более интерактивной, graph execution в TensorFlow имеет преимущества для распределенного обучения, оптимизации производительности и развертывании моделей в продакшене. Однако, при написании кода в graph может сильно отличаться от стандартного кода Python, и как следствие, может быть более сложным для отладки.

Для построения и обучения моделей в графах, программа Python сначала создает граф, который будет представлять расчет, а затем вызывает Session.run для отправки графа в исполнение в рабочую среду на C++. Это включает в себя следующие шаги:

  • Автоматическое дифференцирование при помощи статичного autodiff
  • Простое развертывание на платформе независимого сервера
  • Оптимизации на графах (стандартные устранения подвыражений, свертка констант и так далее)
  • Компиляция и слияние ядра
  • Автоматическое распределение и репликация (размещение графов в распределенной системе)

Развертывание кода, написанного для eager execution - более сложная задача: либо генерировать граф из модели, либо запустить рабочую среду Python и написать код непосредственно на сервер.

Пишем совместимый код

Один и тот же код, написанный для работы в eager execution также построит граф в режиме graph execution. Это можно сделать просто запустив тот же код в новой сессии Python, где не активирован eager execution.

Большинство операций TensorFlow работают в eager execution, но есть несколько моментов, о которых обязательно нужно помнить:

  • Используй tf.data для обработки ввода вместо очередей. Это быстрее и проще
  • Используй объектно-ориентированные слои API - например такие, как tf.keras.layers и tf.keras.Model - так как они имеют специальное место для хранения переменных
  • Большинство кода моделей работают одинаково как в eager, так и graph execution, однако все-такие есть определенные исключения: например, динамические модели во время порядка выполнения Python изменяют вычисления на основе ввода
  • Как только eager execution активирован при помощи tf.enable_eager_execution, он не может быть выключен. Начни новую сессию Python для возвращения к graph execution

Лучше всего писать код сразу для eager и graph execution. Это даст тебе возможность для интерактивных экспериментов и возможность отладки кода в eager, а также обеспечит лучшую распределенную производительность в режиме graph execution.

Пиши код, отлавливай баги, повторяй любые циклы операций в режиме eager execution, затем импортируй граф модели для развертывания в продакшене. Используй tf.train.Checkpoint для сохранения и загрузки переменных моделей, это обеспечит легкое передвижение между режимами eager и graph. Смотри больше примеров здесь: tensorflow/contrib/eager/python/examples.

Использование режима eager в graph

Ты можешь выборочно активировать eager execution в режиме graph в TensorFlow при помощи tfe.py_func. Эту функцию следует использовать когда tf.enable_eager_execution() не был вызван.


In [ ]:
def my_py_func(x):
  x = tf.matmul(x, x)  # Ты можешь выполнять операции tf
  print(x)  # даже в режиме eager!
  return x

with tf.Session() as sess:
  x = tf.placeholder(dtype=tf.float32)
  # Вызовем функцию eager в режиме graph!
  pf = tfe.py_func(my_py_func, [x], tf.float32)

  sess.run(pf, feed_dict={x: [[2.0]]})  # [[4.0]]