четверг, 20 декабря 2018 г.

TensorFlow: AutoGraph, примеры использования

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

Настройка

Импортируем TensorFlow, AutoGraph и вспомогательные модули:

from future import division, print_function, absolute_import

import tensorflow as tf
layers = tf.keras.layers
from tensorflow.contrib import autograph


import numpy as np
import matplotlib.pyplot as plt

Стремительное исполнение (eager execution) включено для демонстрационных целей, но AutoGraph работает в окружениях как стремительного, так и в графового исполнения:

tf.enable_eager_execution()

Примечание: Преобразованный код AutoGraph предназначен для запуска во время выполнения графа. Когда стремительное исполнение включено, используйте явные графы (как показано в этом примере) или tf.contrib.eager.defun.

Автоматическое преобразование Python потока управления

AutoGraph преобразует большую часть языка Python в эквивалентный код построения графов TensorFlow.

Примечание: В реальных приложениях пакетирование важно для повышения производительности. Лучший код для преобразования в AutoGraph - это код, в котором поток управления определяется на уровне batch. Если вы принимаете решения на индивидуальном уровне example, вы должны индексировать и группировать примеры, чтобы поддерживать производительность при применении логики потока управления.

AutoGraph преобразует функцию, например:

def square_if_positive(x):
  if x > 0:
    x = x * x
  else:
    x = 0.0
  return x

В функцию, которая использует построение графа:

print(autograph.to_code(square_if_positive))

from future import print_function
import tensorflow as tf

def tf__square_if_positive(x):
  try:
    with ag__.function_scope('square_if_positive'):

      def if_true():
        with ag__.function_scope('if_true'):
          x_1, = x,
          x_1 = x_1 * x_1
          return x_1,

      def if_false():
        with ag__.function_scope('if_false'):
          x_2, = x,
          x_2 = 0.0
          return x_2,
      x = ag__.utils.run_cond(tf.greater(x, 0), if_true, if_false)
      return x
  except:
    ag__.rewrite_graph_construction_error(ag_source_map__)



tf__square_if_positive.autograph_info__ = {}

Код написанный для стремительного исполнения может выполняться в tf.Graph с теми же результатами, но с преимуществами графового исполнения:

print('Eager results: %2.2f, %2.2f' % 
(square_if_positive(tf.constant(9.0)), 
 square_if_positive(tf.constant(-9.0))))

Eager results: 81.00, 0.00

Генерируем графовую версию и выполняем ее:

# -*- coding: utf-8 -*-

tf_square_if_positive = autograph.to_graph(square_if_positive)

with tf.Graph().as_default():  
  # Работает как обычная операция: принимает тензоры, 
  # возвращает тензоры.
  # Вы можете проверять граф, 
  # используя tf.get_default_graph().as_graph_def()
  g_out1 = tf_square_if_positive(tf.constant( 9.0))
  g_out2 = tf_square_if_positive(tf.constant(-9.0))
  with tf.Session() as sess:
    print('Graph results: %2.2f, %2.2f\n' % 
          (sess.run(g_out1), sess.run(g_out2)))

Graph results: 81.00, 0.00

AutoGraph поддерживает общие Python выражения, такие как while, for, if, break, и return, с поддержкой вложенности. Сравните эту функцию со сложной графовой версией, показанной в следующих блоках кода:

# Продолжаем в цикле
def sum_even(items):
  s = 0
  for c in items:
    if c % 2 > 0:
      continue
    s += c
  return s

print('Eager result: %d' % sum_even(tf.constant([10,12,15,20])))

tf_sum_even = autograph.to_graph(sum_even)

with tf.Graph().as_default(), tf.Session() as sess:
    print('Graph result: %d\n\n' % 
          sess.run(tf_sum_even(tf.constant([10,12,15,20]))))

Eager result: 42
Graph result: 42

print(autograph.to_code(sum_even))

from future import print_function
import tensorflow as tf

def tf__sum_even(items):
  try:
    with ag__.function_scope('sum_even'):
      s = 0

      def extra_test(s_2):
        with ag__.function_scope('extra_test'):
          return True

      def loop_body(loop_vars, s_2):
        with ag__.function_scope('loop_body'):
          c = loop_vars
          continue_ = tf.constant(False)

          def if_true():
            with ag__.function_scope('if_true'):
              continue__1, = continue_,
              continue__1 = tf.constant(True)
              return continue__1,

          def if_false():
            with ag__.function_scope('if_false'):
              return continue_,
          continue_ = ag__.utils.run_cond(tf.greater(c % 2, 0), 
                                          if_true, if_false)

          def if_true_1():
            with ag__.function_scope('if_true_1'):
              s_1, = s_2,
              s_1 += c
              return s_1,

          def if_false_1():
            with ag__.function_scope('if_false_1'):
              return s_2,
          s_2 = ag__.utils.run_cond(tf.logical_not(continue_), 
                                    if_true_1, if_false_1)
          return s_2,
      s = ag__.for_stmt(items, extra_test, loop_body, (s,))
      return s
  except:
    ag__.rewrite_graph_construction_error(ag_source_map__)



tf__sum_even.autograph_info__ = {}


Декоратор

Если вам не нужен простой доступ к изначальной Python функции, используйте convert декоратор:

@autograph.convert()
def fizzbuzz(i, n):
  while i < n:
    msg = ''
    if i % 3 == 0:
      msg += 'Fizz'
    if i % 5 == 0:
      msg += 'Buzz'
    if msg == '':
      msg = tf.as_string(i)
    print(msg)
    i += 1
  return i

with tf.Graph().as_default():
  final_i = fizzbuzz(tf.constant(10), tf.constant(16))
  # Работает как обычная операция: принимает тензоры, 
  # возвращает тензоры.
  # Вы можете проверять граф, используя 
  # tf.get_default_graph().as_graph_def()
  with tf.Session() as sess:
    sess.run(final_i)

Buzz
11
Fizz
13
14
FizzBuzz


Примеры

Продемонстрируем некоторые полезные свойства языка Python

Assert

AutoGraph автоматически преобразовывает Python assert выражение в эквивалентный tf.Assert код:

@autograph.convert()
def inverse(x):
  assert x != 0.0, 'Do not pass zero!'
  return 1.0 / x

with tf.Graph().as_default(), tf.Session() as sess:
  try:
    print(sess.run(inverse(tf.constant(0.0))))
  except tf.errors.InvalidArgumentError as e:
    print('Got error message:\n    %s' % e.message)

Got error message:
    assertion failed: [Do not pass zero!]
     [[node inverse/Assert/Assert (defined at /tmp/tmpzsz37apn.py:6)  
     = Assert[T=[DT_STRING], summarize=3, 
     _device="/job:localhost/replica:0/task:0/device:CPU:0"]
     (inverse/NotEqual, inverse/Assert/Assert/data_0)]]

Print

Используйте Python print функцию в графе:

@autograph.convert()
def count(n):
  i=0
  while i < n:
    print(i)
    i += 1
  return n
    
with tf.Graph().as_default(), tf.Session() as sess:
    sess.run(count(tf.constant(5)))

Lists (списки)

Добавляйте в списки (lists) в циклах (тензор list операции автоматически созданы):

@autograph.convert()
def arange(n):
  z = []
  # Мы запрашиваем dtype элемента списка
  autograph.set_element_type(z, tf.int32)
  
  for i in tf.range(n):
    z.append(i)

  # когда мы закончили со списком
  # складываем его в стек (как np.stack)
  return autograph.stack(z) 


with tf.Graph().as_default(), tf.Session() as sess:
    sess.run(arange(tf.constant(10)))

Вложенный поток контроля

@autograph.convert()
def nearest_odd_square(x):
  if x > 0:
    x = x * x
    if x % 2 == 0:
      x = x + 1
  return x

with tf.Graph().as_default():  
  with tf.Session() as sess:
    print(sess.run(nearest_odd_square(tf.constant(4))))
    print(sess.run(nearest_odd_square(tf.constant(5))))
    print(sess.run(nearest_odd_square(tf.constant(6))))

17
25
37

While цикл

@autograph.convert()
def square_until_stop(x, y):
  while x < y:
    x = x * x
  return x
    
with tf.Graph().as_default():  
  with tf.Session() as sess:
    print(sess.run(square_until_stop(tf.constant(4), tf.constant(100))))

256

For цикл

@autograph.convert()
def squares(nums):

  result = []
  autograph.set_element_type(result, tf.int64)

  for num in nums: 
    result.append(num * num)
    
  return autograph.stack(result)
    
with tf.Graph().as_default():  
  with tf.Session() as sess:
    print(sess.run(squares(tf.constant(np.arange(10)))))

[ 0  1  4  9 16 25 36 49 64 81]

Break

@autograph.convert()
def argwhere_cumsum(x, threshold):
  current_sum = 0.0
  idx = 0
  for i in tf.range(len(x)):
    idx = i
    if current_sum >= threshold:
      break
    current_sum += x[i]
  return idx

N = 10
with tf.Graph().as_default():  
  with tf.Session() as sess:
    idx = argwhere_cumsum(tf.ones(N), tf.constant(float(N/2)))
    print(sess.run(idx))

5


Взаимодействие с tf.Keras

Теперь когда вы увидели основы, давайте построим некоторые компоненты модели с помощью autograph.

Довольно просто интегрировать autograph с tf.keras.

Функции без состояния

Для функций без состояния, таких как collatz показанной ниже, простейший путь включить их в keras модель - обернуть их как слой, используя tf.keras.layers.Lambda.

import numpy as np

@autograph.convert()
def collatz(x):
  x = tf.reshape(x,())
  assert x > 0
  n = tf.convert_to_tensor((0,)) 
  while not tf.equal(x, 1):
    n += 1
    if tf.equal(x%2, 0):
      x = x // 2
    else:
      x = 3 * x + 1
      
  return n

with tf.Graph().as_default():
  model = tf.keras.Sequential([
    tf.keras.layers.Lambda(collatz, input_shape=(1,), output_shape=())
  ])
  
result = model.predict(np.array([6171]))
result

array([261], dtype=int32)

Пользовательские слои и модели

Простейший путь использовать AutoGraph с Keras слоями и моделями - это @autograph.convert() call метод.

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

# K используется для проверки в каком мы режиме 
# - в тренировочном или тестовом.
K = tf.keras.backend

class StochasticNetworkDepth(tf.keras.Sequential):
  def init(self, pfirst=1.0, plast=0.5, *args,**kwargs):
    self.pfirst = pfirst
    self.plast = plast
    super().init(*args,**kwargs)
        
  def build(self,input_shape):
    super().build(input_shape.as_list())
    self.depth = len(self.layers)
    self.plims = np.linspace(self.pfirst, self.plast, 
                                          self.depth + 1)[:-1]
    
  @autograph.convert()
  def call(self, inputs):
    training = tf.cast(K.learning_phase(), dtype=bool)  
    if not training: 
      count = self.depth
      return super(StochasticNetworkDepth, self).call(inputs), count
    
    p = tf.random_uniform((self.depth,))
    
    keeps = (p <= self.plims)
    x = inputs
    
    count = tf.reduce_sum(tf.cast(keeps, tf.int32))
    for i in range(self.depth):
      if keeps[i]:
        x = self.layers[i](x)
      
    # возвращаем вывод последнего слоя и количество выполненных слоев
    return x, count

Попробуем модель на данных с формой как в MNIST наборе:

train_batch = np.random.randn(64, 28, 28, 1).astype(np.float32)

Построим простой стек conv слоев, в созданной нами модели:

with tf.Graph().as_default() as g:
  model = StochasticNetworkDepth(
        pfirst=1.0, plast=0.5)

  for n in range(20):
    model.add(
          layers.Conv2D(filters=16, activation=tf.nn.relu,
                        kernel_size=(3, 3), padding='same'))

  model.build(tf.TensorShape((None, None, None, 1)))
  
  init = tf.global_variables_initializer()

Теперь протестируем модель, чтобы убедиться, что она ведет себя так как ожидается в train и test режимах:

# Используем явную сессию здесь, таким образом мы можем установить
# train/test switch, и проверить количество слоев, 
# возвращаенное call методом
with tf.Session(graph=g) as sess:
  init.run()
 
  for phase, name in enumerate(['test','train']):
    K.set_learning_phase(phase)
    result, count = model(tf.convert_to_tensor(train_batch, 
                                               dtype=tf.float32))

    result1, count1 = sess.run((result, count))
    result2, count2 = sess.run((result, count))

    delta = (result1 - result2)
    print(name, "sum abs delta: ", abs(delta).mean())
    print("    layers 1st call: ", count1)
    print("    layers 2nd call: ", count2)
    print()

test sum abs delta:  0.0
    layers 1st call:  20
    layers 2nd call:  20

train sum abs delta:  0.0010305465
    layers 1st call:  14
    layers 2nd call:  17


Более сложный пример: внутриграфовый тренировочный цикл

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

Ввиду того что написание потока контроля в AutoGraph является простым, то выполнение тренировочного цикла в TensorFlow графе должно быть также простым.

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

Загрузка данных

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

Downloading data from 
https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz
11493376/11490434 [==============================] - 0s 0us/step

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

def mlp_model(input_shape):
  model = tf.keras.Sequential((
      tf.keras.layers.Dense(100, activation='relu', 
                            input_shape=input_shape),
      tf.keras.layers.Dense(100, activation='relu'),
      tf.keras.layers.Dense(10, activation='softmax')))
  model.build()
  return model


def predict(m, x, y):
  y_p = m(tf.reshape(x, (-1, 28 * 28)))
  losses = tf.keras.losses.categorical_crossentropy(y, y_p)
  l = tf.reduce_mean(losses)
  accuracies = tf.keras.metrics.categorical_accuracy(y, y_p)
  accuracy = tf.reduce_mean(accuracies)
  return l, accuracy


def fit(m, x, y, opt):
  l, accuracy = predict(m, x, y)
  # Autograph автоматически добавляет необходимые 
  # tf.control_dependencies здесь.
  # (Без них ничего не зависит от opt.minimize, 
  # поэтому ничего не выполняется.)
  # Это выполняется схожим образом с кодом с нетерпеливого исполнения.
  opt.minimize(l)
  return l, accuracy


def setup_mnist_data(is_training, batch_size):
  if is_training:
    ds = tf.data.Dataset.from_tensor_slices((train_images, 
                                             train_labels))
    ds = ds.shuffle(batch_size * 10)
  else:
    ds = tf.data.Dataset.from_tensor_slices((test_images, test_labels))

  ds = ds.repeat()
  ds = ds.batch(batch_size)
  return ds


def get_next_batch(ds):
  itr = ds.make_one_shot_iterator()
  image, label = itr.get_next()
  x = tf.to_float(image) / 255.0
  y = tf.one_hot(tf.squeeze(label), 10)
  return x, y 

Определение тренировочного цикла

# Используйте recursive = True 
# для того чтобы рекурсивно преобразовать функции вызванные этой.
@autograph.convert(recursive=True)
def train(train_ds, test_ds, hp):
  m = mlp_model((28 * 28,))
  opt = tf.train.AdamOptimizer(hp.learning_rate)
  
  # Мы сохраним наши потери в список. Для того чтобы AutoGraph
  # преобразовывал эти списки в их графовые эквиваленты,
  # нам необходимо определить тип элементов листов.
  train_losses = []
  autograph.set_element_type(train_losses, tf.float32)
  test_losses = []
  autograph.set_element_type(test_losses, tf.float32)
  train_accuracies = []
  autograph.set_element_type(train_accuracies, tf.float32)
  test_accuracies = []
  autograph.set_element_type(test_accuracies, tf.float32)
  
  # Этот тренировочный цикл целиком будет выполняться внутри графа.
  i = tf.constant(0)
  while i < hp.max_steps:
    train_x, train_y = get_next_batch(train_ds)
    test_x, test_y = get_next_batch(test_ds)
    step_train_loss, step_train_accuracy = fit(m, train_x, train_y, opt)
    step_test_loss, step_test_accuracy = predict(m, test_x, test_y)
    if i % (hp.max_steps // 10) == 0:
      print('Step', i, 'train loss:', step_train_loss, 'test loss:',
            step_test_loss, 'train accuracy:', step_train_accuracy,
            'test accuracy:', step_test_accuracy)
    train_losses.append(step_train_loss)
    test_losses.append(step_test_loss)
    train_accuracies.append(step_train_accuracy)
    test_accuracies.append(step_test_accuracy)
    i += 1
  
  # Мы записали значения наших потерь и аккуратностей
  # в список в графе с помощью AutoGraph.
  # Для того чтобы вернуть значения как Tensor, 
  # нам необходимо сложить их друг на друга перед их возвращением.
  return (autograph.stack(train_losses), 
          autograph.stack(test_losses),  
          autograph.stack(train_accuracies), 
          autograph.stack(test_accuracies))

Теперь построим граф и выполним тренировочный цикл:

with tf.Graph().as_default() as g:
  hp = tf.contrib.training.HParams(
      learning_rate=0.005,
      max_steps=500,
  )
  train_ds = setup_mnist_data(True, 50)
  test_ds = setup_mnist_data(False, 1000)
  (train_losses, test_losses, train_accuracies,
   test_accuracies) = train(train_ds, test_ds, hp)

  init = tf.global_variables_initializer()
  
with tf.Session(graph=g) as sess:
  sess.run(init)
  (train_losses, test_losses, train_accuracies,
   test_accuracies) = sess.run([train_losses, 
                                test_losses, 
                                train_accuracies,
                                test_accuracies])
  
plt.title('MNIST train/test losses')
plt.plot(train_losses, label='train loss')
plt.plot(test_losses, label='test loss')
plt.legend()
plt.xlabel('Training step')
plt.ylabel('Loss')
plt.show()
plt.title('MNIST train/test accuracies')
plt.plot(train_accuracies, label='train accuracy')
plt.plot(test_accuracies, label='test accuracy')
plt.legend(loc='lower right')
plt.xlabel('Training step')
plt.ylabel('Accuracy')
plt.show()

Step 0 train loss: 2.39437 test loss: 2.3646827 
train accuracy: 0.02 test accuracy: 0.036
Step 50 train loss: 0.32149887 test loss: 0.5104491 
train accuracy: 0.9 test accuracy: 0.839
Step 100 train loss: 0.33964032 test loss: 0.32525328 
train accuracy: 0.88 test accuracy: 0.898
Step 150 train loss: 0.27726996 test loss: 0.37781104 
train accuracy: 0.9 test accuracy: 0.867
Step 200 train loss: 0.18788506 test loss: 0.274848 
train accuracy: 0.94 test accuracy: 0.921
Step 250 train loss: 0.35899135 test loss: 0.26668844 
train accuracy: 0.86 test accuracy: 0.912
Step 300 train loss: 0.11612513 test loss: 0.24186224 
train accuracy: 0.98 test accuracy: 0.916
Step 350 train loss: 0.18267077 test loss: 0.25310495 
train accuracy: 0.96 test accuracy: 0.915
Step 400 train loss: 0.17388473 test loss: 0.18192373 
train accuracy: 0.96 test accuracy: 0.939
Step 450 train loss: 0.12562008 test loss: 0.19091153 
train accuracy: 0.96 test accuracy: 0.94


Читайте также другие статьи в блоге:

TensorFlow: стремительное исполнение (eager execution), базовое использование

TensorFlow: стремительное исполнение (eager execution), продвинутое использование

TensorFlow: Keras, базовое использование