Показаны сообщения с ярлыком TensorFlow. Показать все сообщения
Показаны сообщения с ярлыком TensorFlow. Показать все сообщения

пятница, 21 декабря 2018 г.

TensorFlow: embeddings (встраивания)

В этом посте представлена концепция встраиваний (embeddings), приведен простой пример как обучить встраивание (embedding) в TensorFlow, и объясняется, как просматривать встраивания с помощью TensorBoard Embedding Projector.

Встривание (embedding) - это отображение (mapping) дискретных объектов, таких как слова, на векторы действительных чисел. Например, 300-мерное встраивание для английских слов может включать в себя:

blue:(0.01359, 0.00075997, 0.24608, ..., -0.2524, 1.0048, 0.06259)
blues:(0.01396, 0.11887, -0.48963, ..., 0.033483, -0.10007, 0.1158)
orange:(-0.24776, -0.12359, 0.20986, ..., 0.079717, 0.23865, -0.014213)
oranges:(-0.35609, 0.21854, 0.080944, ..., -0.35413, 0.38511, -0.070976)

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

Встраивания важны для ввода данных в машинном обучении. Классификаторы и нейронные сети в целом работают на векторах действительных чисел. Они тренируются лучше всего на плотных векторах, где все значения способствуют определению объекта. Тем не менее, многие важные вводные данные в машинном обучении, такие как слова текста, не имеют естественного векторного представления. Функции встраивания являются стандартным и эффективный способ превратить такие дискретные вводные объекты в полезные непрерывные векторы.

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

blue:  (red, 47.6°), (yellow, 51.9°), (purple, 52.4°)
blues:  (jazz, 53.3°), (folk, 59.1°), (bluegrass, 60.6°)
orange:  (yellow, 53.5°), (colored, 58.0°), (bright, 59.9°)
oranges:  (apples, 45.3°), (lemons, 48.3°), (mangoes, 50.4°)

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

Встраивания (enbeddings) в TensorFlow

Чтобы создать встраивание слов в TensorFlow, мы сначала разбиваем текст на слова, а затем назначаем целое число каждому слову в словаре. Давайте предположим, что это уже сделано, и что word_ids является вектором этих целых чисел. Например, предложение «I have a cat.». Можно разделить на [«I», «have», «a», «cat», «.»], и тогда соответствующий тензор word_ids будет иметь форму [5]и состоять из 5 целых чисел. Чтобы создать представление этих идентификаторов слов в векторах, нам нужно создать переменную встраивание и использовать tf.nn.embedding_lookup функцию следующим образом:

word_embeddings = tf.get_variable(“word_embeddings”,
    [vocabulary_size, embedding_size])
embedded_word_ids = tf.nn.embedding_lookup(word_embeddings, word_ids)

После этого тензор embedded_word_ids будет иметь форму [5, embedding_size] в нашем примере и содержать встраивания (embeddings) (плотные векторы)(dense vectors) для каждого из 5 слов. В конце обучения word_embeddings будет содержать встраивания для всех слов в словаре.

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

Визуализация встраиваний

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

Embedding Projector имеет три панели:

  • Панель данных (Data panel) в левом верхнем углу, где вы можете выбрать запуск, переменную встраивания и колонки данных (data columns) для задания цвета и меток точек.
  • Панель проекций (Projections panel) в левом нижнем углу, где вы можете выбрать тип проекции.
  • Панель инспектора (Inspector panel) справа, где вы можете искать конкретные точки и увидеть список ближайших соседей.

Проекции

Embedding Projector обеспечивает три способа уменьшения размерности набора данных.

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

  • PCA: линейный детерминированный алгоритм (метод главных компонент), который пытается захватить как можно большую часть изменчивости данных в как можно меньшем количестве измерений. PCA имеет тенденцию выделять крупномасштабную структуру данных, но может искажать локальное соседство. Embedding Projector вычисляет 10 главных компонентов, из которых вы можете выбрать два или три для просмотра.

  • Пользовательский (Custom): линейная проекция на горизонтальную и вертикальную оси, которую вы указываете с помощью меток в данных. Вы определяете горизонтальную ось, например, предоставляя текстовые шаблоны для "Левый" и "Правый". Embedding Projector находит все точки, метка которых соответствует "Левый" шаблону и вычисляет центр тяжести этого множества; аналогично для "Правый". Линия, проходящая через эти два центроида определяет горизонтальную ось. Вертикальная ось, аналогично, вычисляется по центроидам для точек, совпадающих с "Вверх" и "Вниз" текстовыми шаблонами.


Исследование (Exploration)

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

Иногда полезно ограничить вид подмножеством точек и выполнить прогнозы только по этим пунктам. Для этого вы можете выбрать точки несколькими способами:

  • После нажатия на точку также выбираются ее ближайшие соседи.
  • После поиска выбираются точки, соответствующие запросу.
  • Включение выделения, нажатие на точку и перетаскивание определяют выделяемую сферу.

Затем нажмите "Isolate nnn points" кнопку в верхней части Inspector панели на правой стороне. На следующем рисунке показаны 101 точка, выбранные и готовые, чтобы пользователь щелкнул "Isolate 101 points":

Выбор ближайших соседей “важных” в наборе данных встраиваний слов.

Фильтрация с пользовательской проекцией может быть весьма удобной. Ниже мы отфильтровали 100 ближайших соседей “politics” и спроецировали их на “worst” - “best” вектор в виде оси х. Ось у случайна. В результате справа находятся “ideas”, “science”, “perspective”, “journalism”, а слева “crisis”, “violence” и “conflict”.

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


Метаданные

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

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

Word\tFrequency
Airplane\t345
Car\t241
...

Предполагается, что порядок строк в файле метаданных соответствует порядку векторов в переменной встраивания, кроме заголовка. Следовательно, (i+1)-я строка в файле метаданных соответствует i-й строке переменной встраивания. Если файл метаданных TSV содержит только один столбец, мы не ожидаем строку заголовка и предполагаем, что каждая строка является меткой встраивания. Мы включаем это исключение, потому что оно соответствует обычно используемому "vocab file" формату.

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


Embedding мини-FAQ

"Встраивание (embedding)" - это действие или вещь? И то и другое. Люди говорят о встраивании слов в векторное пространство (действие) и о произведении встраиваний слов (вещи). Общим для обоих является понятие встраивания как отображения (mapping) дискретных объектов в векторах. Создание или применение этого отображения (mapping) - это действие, но само отображение (mapping) - это вещь.

Встраивания (embeddings) являются многомерными или низкоразмерными (high-dimensional или low-dimensional)? Это зависит от того с какой стороны расматривать. 300-мерное векторное пространство слов и фраз, например, часто называют низкоразмерным (и плотным) по сравнению с миллионами слов и фраз, которые оно может содержать. Но математически оно многомерное, поскольку показывает много свойств, что резко отличаются от того, что человек интуитивно знает о 2- и 3-мерных пространствах.

Является ли встраивание (embedding) тем же, что и слой встраивания (embedding layer)? Нет. слой встраивания (embedding layer) является частью нейронной сети, но встраивание (embedding) является более общей концепцией.


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

TensorFlow: базовая классификация

TensorFlow: классификация текста с отзывами к фильмам

Анатомия нейронных сетей

четверг, 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, базовое использование

среда, 19 декабря 2018 г.

TensorFlow Core: SavedModel CLI (интерфейс командной строки)

CLI для проверки и выполнения SavedModel

Вы можете использовать интерфейс командной строки SavedModel (CLI) для проверки и выполнения SavedModel. Например, вы можете использовать CLI для проверки SignatureDef модели. CLI позволяет быстро подтвердить, что ввод соотвествует Tensor dtype и форма соответствует требуемой в модели. Более того, если вы хотите протестировать свою модель, вы можете использовать CLI для проверки работоспособности: передавать входные данные в различных форматах (например, Python выражения), а затем извлекать вывод.

Установка интерфейса командной строки (CLI) для SavedModel

Вы можете установить сам TensorFlow одним из следующих способов:

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

Если вы установили TensorFlow через предварительно созданный бинарный файл TensorFlow, тогда CLI SavedModel уже установлен в вашей системе по пути bin\save_model_cli.

Если вы собрали TensorFlow из исходного кода, вы должны выполнить следующую дополнительную команду для сборки save_model_cli:

$ bazel build tensorflow/python/tools:saved_model_cli


Обзор команд

CLI SavedModel поддерживает следующие две команды на MetaGraphDef в SavedModel:

  • show, который показывает вычисление для MetaGraphDef в SavedModel.
  • run, который выполняет вычисления для MetaGraphDef.

show команда

SavedModel содержит один или несколько MetaGraphDef, идентифицированных по их наборам тегов. Перед началом обслуживания модели, у вас может возникнуть вопрос, какие SignatureDef есть в каждой модели и каковы их вводы и выводы. Команда show позволяет вам изучить содержимое SavedModel в иерархическом порядке. Вот синтаксис:

usage: saved_model_cli show [-h] --dir DIR [--all]
[--tag_set TAG_SET] [--signature_def SIGNATURE_DEF_KEY]

Например, следующая команда показывает все доступные наборы тегов MetaGraphDef в SavedModel:

$ saved_model_cli show --dir /tmp/saved_model_dir
The given SavedModel contains the following tag-sets:
serve
serve, gpu

Следующая команда показывает все доступные ключи SignatureDef в MetaGraphDef:

$ saved_model_cli show --dir /tmp/saved_model_dir --tag_set serve
The given SavedModel MetaGraphDef contains SignatureDefs with the
following keys:
SignatureDef key: "classify_x2_to_y3"
SignatureDef key: "classify_x_to_y"
SignatureDef key: "regress_x2_to_y3"
SignatureDef key: "regress_x_to_y"
SignatureDef key: "regress_x_to_y2"
SignatureDef key: "serving_default"

Если MetaGraphDef имеет несколько тегов в наборе тегов, необходимо указать все теги, где каждый тег разделен запятой. Например:

$ saved_model_cli show --dir /tmp/saved_model_dir --tag_set serve,gpu

Чтобы показать все вводы и выводы TensorInfo для определенного SignatureDef, введите ключ SignatureDef для параметра signature_def. Это очень полезно, когда вы хотите узнать значение ключа тензора, dtype и форму вводных тензоров для выполнение графа вычислений позже. Например:

$ saved_model_cli show --dir \
/tmp/saved_model_dir --tag_set serve --signature_def serving_default
The given SavedModel SignatureDef contains the following input(s):
  inputs['x'] tensor_info:
      dtype: DT_FLOAT
      shape: (-1, 1)
      name: x:0
The given SavedModel SignatureDef contains the following output(s):
  outputs['y'] tensor_info:
      dtype: DT_FLOAT
      shape: (-1, 1)
      name: y:0
Method name is: tensorflow/serving/predict

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

$ saved_model_cli show --dir /tmp/saved_model_dir --all
MetaGraphDef with tag-set: 'serve' contains the following SignatureDefs:

signature_def['classify_x2_to_y3']:
  The given SavedModel SignatureDef contains the following input(s):
    inputs['inputs'] tensor_info:
        dtype: DT_FLOAT
        shape: (-1, 1)
        name: x2:0
  The given SavedModel SignatureDef contains the following output(s):
    outputs['scores'] tensor_info:
        dtype: DT_FLOAT
        shape: (-1, 1)
        name: y3:0
  Method name is: tensorflow/serving/classify

...

signature_def['serving_default']:
  The given SavedModel SignatureDef contains the following input(s):
    inputs['x'] tensor_info:
        dtype: DT_FLOAT
        shape: (-1, 1)
        name: x:0
  The given SavedModel SignatureDef contains the following output(s):
    outputs['y'] tensor_info:
        dtype: DT_FLOAT
        shape: (-1, 1)
        name: y:0
  Method name is: tensorflow/serving/predict


run команда

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

usage: saved_model_cli run [-h] --dir DIR --tag_set TAG_SET 
                           --signature_def SIGNATURE_DEF_KEY 
                           [--inputs INPUTS]
                           [--input_exprs INPUT_EXPRS]
                           [--input_examples INPUT_EXAMPLES] 
                           [--outdir OUTDIR]
                           [--overwrite] [--tf_debug]

Команда run предоставляет следующие три способа передачи вводных данных в модель:

  • --inputs опция позволяет вам передавать numpy ndarray в файлах.
  • --input_exprs опция позволяет передавать Python выражения.
  • --input_examples опция позволяет передать tf.train.Example.

--inputs

Чтобы передать вводные данные в файлах, укажите параметр --inputs, который принимает следующий общий формат:

--inputs <INPUTS>

где <INPUTS> имеет один из следующих форматов:

  • <input_key>=<filename>
  • <input_key>=<filename>[<variable_name>]

Вы можете передать несколько INPUTS. Если вы передаете несколько вводов, используйте точку с запятой, чтобы отделить каждый из INPUTS.

saved_model_cli использует numpy.load для загрузки filename. filename может иметь любой из следующих форматов:

  • .npy
  • .npz
  • pickle формат

Файл .npy всегда содержит numpy ndarray. Поэтому при загрузке из файла .npy, контент будет напрямую назначен на указанный вводный тензор. Если вы укажете variable_name с этим файлом .npy, variable_name будет проигнорировано, и будет выдано предупреждение.

При загрузке из файла .npz(zip) вы можете при желании указать variable_name, чтобы определить переменную в zip-файле для загрузки ключа вводного тензора. Если вы не укажете variable_name, SavedModel CLI проверит, что в zip-файл включен только один файл, и загрузит его для указанного ключа вводного тензора.

При загрузке из pickle файла, если variable_name не указано в квадратных скобках, все, что находится внутри pickle файла, будет передано в указанный ключ тензора ввода. В противном случае SavedModel CLI примет словарь хранимый в pickle файле и значение, соответствующее variable_name будет использовано.

--input_exprs

Чтобы передать вводные данные через Python выражения, укажите параметр --input_exprs. Это может быть полезно, когда у вас нет файлов данных, лежащих поблизости, но вы хотите проверить модель с некоторыми простыми вводными данными, которые соответствуют dtype и форме SignatureDef модели. Например:

<input_key>=[[1],[2],[3]]

В дополнение к Python выражениям вы также можете передавать numpy функции. Например:

<input_key>=np.ones((32,32,3))

(обратите внимание, что модуль numpy уже доступен как np.)

--input_examples

Для передачи tf.train.Example в качестве вводных данных, укажите параметр --input_examples. Для каждого ключа ввода, он принимает список словарей, где каждый словарь является экземпляром tf.train.Example. Ключи словаря являются свойствами, а значения - это списки значений для каждого свойства. Например:

<input_key>=[{"age":[22,24],"education":["BS","MS"]}]

Сохранение вывода

По умолчанию SavedModel CLI записывает вывод в стандартный вывод. Если каталог передан параметру --outdir, выходные данные будут сохранены в виде файлов npy с именами ключей выходных тензоров из заданного каталога.

Используйте --overwrite, чтобы перезаписать существующие выходные файлы.

Интеграция отладчика TensorFlow (tfdbg)

Если установлена ​​опция --tf_debug, SavedModel CLI будет использовать TensorFlow Debugger (tfdbg) для просмотра промежуточных тензоров и времени выполнения графов или подграфов во время работы SavedModel.

Полные примеры run

Дано:

  • Ваша модель просто добавляет x1 и x2, чтобы получить выходные данные y.
  • Все тензоры в модели имеют форму (-1, 1).
  • У вас есть два файла npy:
    • /tmp/my_data1.npy, который содержит numpy ndarray [[1], [2], [3]].
    • /tmp/my_data2.npy, который содержит другой numpy ndarray [[0.5], [0.5], [0.5]].

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

$ saved_model_cli run --dir /tmp/saved_model_dir --tag_set serve \
--signature_def x1_x2_to_y \
--inputs 'x1=/tmp/my_data1.npy;x2=/tmp/my_data2.npy' \
--outdir /tmp/out
Result for output key y:
[[ 1.5]
 [ 2.5]
 [ 3.5]]

Давайте немного поменяем предыдущий пример. В этот раз вместо двух .npy файлов у вас есть .npz файл и pickle файл. Кроме того, вы хотите перезаписать любой существующий файл вывода. Вот команда:

$ saved_model_cli run --dir /tmp/saved_model_dir --tag_set serve \
--signature_def x1_x2_to_y \
--inputs 'x1=/tmp/my_data1.npz[x];x2=/tmp/my_data2.pkl' \
--outdir /tmp/out \
--overwrite
Result for output key y:
[[ 1.5]
 [ 2.5]
 [ 3.5]]

Вы можете задать python выражение вместо файла ввода. Например, следующая команда заменяет ввод x2 Python выражением:

$ saved_model_cli run --dir /tmp/saved_model_dir --tag_set serve \
--signature_def x1_x2_to_y --inputs x1=/tmp/my_data1.npz[x] \
--input_exprs 'x2=np.ones((3,1))'
Result for output key y:
[[ 2]
 [ 3]
 [ 4]]

Для того чтобы выполнить модель с включенным TensorFlow Debugger используйте следующую команду:

$ saved_model_cli run --dir /tmp/saved_model_dir --tag_set serve \
--signature_def serving_default --inputs x=/tmp/data.npz[x] --tf_debug


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

Основы TensorFlow Core

TensorFlow Core: тензоры (tensors)

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

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

tf.train.Saver предоставляет методы для сохранения и восстановления моделей. tf.saved_model.simple_save - это простой способ создания tf.saved_model, подходящей для этой цели. Оценщики (Estimators) автоматически сохраняют и восстанавливают переменные в model_dir.

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

Используйте SavedModel для сохранения и загрузки вашей модели - переменных, графа и метаданных графа. Это независимый от языка, восстанавливаемый, герметичный формат сериализации, позволяющий создавать более высокоуровневые системы и инструменты для производства, потребления и преобразования TensorFlow моделей. TensorFlow предоставляет несколько способов взаимодействия с SavedModel, включая tf.saved_model API, tf.estimator.Estimator и интерфейс командной строки.

Создание и загрузка SavedModel


Простое сохранение

Самый простой способ создать SavedModel - это использовать tf.saved_model.simple_save функцию:

simple_save(session,
            export_dir,
            inputs={"x": x, "y": y},
            outputs={"z": z})

Данная команда настраивает SavedModel, таким образом что она может быть загружена посредством TensorFlow serving и поддерживает Predict API. Чтобы получить доступ к API классификации, регрессии или множественного вывода, используйте API ручного создания SavedModel или tf.estimator.Estimator.

Создание SavedModel вручную

Если ваш вариант использования не охвачен tf.saved_model.simple_save, используйте ручной tf.saved_model.builder для создания SavedModel.

tf.saved_model.builder.SavedModelBuilder предоставляет функциональность для сохранения нескольких MetaGraphDef. MetaGraph - это граф потока данных, плюс связанные с ним переменные, активы (assets) и подписи. MetaGraphDef является буфером протокола представления MetaGraph. Подпись - это набор вводов и выводов из графа.

Если активы (assets) необходимо сохранить и записать или скопировать на диск, они могут быть предоставлены когда первый MetaGraphDef добавлен. Если несколько MetaGraphDef связанны с активом с тем же именем, сохраняется только первая версия.

Каждый MetaGraphDef, добавленный в SavedModel, должен быть аннотирован с пользовательскими тегами. Теги предоставляют средства для идентификации конкретных MetaGraphDef для загрузки и восстановления вместе с общим набором переменных и активов (assets). Эти теги обычно комментируют MetaGraphDef с его функциональностью (например, обслуживание или обучение), и, возможно, с аппаратно-специфичными аспектами (например, GPU).

Например, следующий код предлагает типичный способ использования SavedModelBuilder для создания SavedModel:

export_dir = ...
...
builder = tf.saved_model.builder.SavedModelBuilder(export_dir)
with tf.Session(graph=tf.Graph()) as sess:
  ...
  builder.add_meta_graph_and_variables(sess,
                                       [tag_constants.TRAINING],
                                       signature_def_map=foo_signatures,
                                       assets_collection=foo_assets,
                                       strip_default_attrs=True)
...
# Добавляем второй MetaGraphDef для вывода.
with tf.Session(graph=tf.Graph()) as sess:
  ...
  builder.add_meta_graph([tag_constants.SERVING], 
                         strip_default_attrs=True)
...
builder.save()

Прямая совместимость с помощью strip_default_attrs = True

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

tf.saved_model.builder.SavedModelBuilder позволяет пользователям контролировать, должны ли атрибуты по умолчанию быть удалены из NodeDefs при добавлении мета-графа в комплект SavedModel. И tf.saved_model.builder.SavedModelBuilder.add_meta_graph_and_variables, и tf.saved_model.builder.SavedModelBuilder.add_meta_graph методы принимают логический флаг strip_default_attrs, который управляет этим поведением.

Если strip_default_attrs имеет значение False, экспортированный tf.MetaGraphDef будет иметь атрибуты по умолчанию во всех его tf.NodeDef экземплярах. Это может нарушить совместимость с последовательностью событий, такой как следующая:

  • Существующая операция (Op) (Foo) обновлена, чтобы включить новый атрибут (T) со значением по умолчанию (bool) в версии 101.
  • Производитель модели, такой как "trainer binary" забирает это изменение (версия 101) в OpDef и реэкспортирует существующую модель, которая использует Op Foo.
  • Потребитель модели (например, Tensorflow Serving), использующий более старую версию двоичного файла (версия 100) не имеет атрибута T для Op Foo, но пытается импортировать эту модель. Потребитель модели не распознает атрибут T в NodeDef, который использует Op Foo и поэтому не может загрузить модель.
  • Если для параметра strip_default_attrs установлено значение True, производители моделей могут отказаться от любых аттрибутов со значениями по умолчанию в NodeDefs. Это помогает гарантировать, что недавно добавленные атрибуты со значениями по умолчанию не приводят к сбою попыток пользователей старых версий моделей загрузить модели, восстановленные с использованием новых тренировочных бинарных файлов.

Загрузка SavedModel в Python

Python-версия SavedModel tf.saved_model.loader предоставляет возможность загрузки и восстановления для SavedModel. Операция load требует следующей информации:

  • Сессия, в которой нужно восстановить определение графа и переменные.
  • Теги, используемые для идентификации загружаемого MetaGraphDef.
  • Местоположение (каталог) SavedModel.

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

export_dir = ...
...
with tf.Session(graph=tf.Graph()) as sess:
  tf.saved_model.loader.load(sess, [tag_constants.TRAINING], export_dir)
  ...

Загрузка SavedModel в C++

C++ версия SavedModel загрузчика предоставляет API для загрузки SavedModel из файла указанного пути, одновременно позволяя SessionOptions и RunOptions. Вы должны указать теги, связанные с загружаемым графом. Загруженная версия SavedModel называется SavedModelBundle и содержит MetaGraphDef и сессию, в которой он загружен.

const string export_dir = ...
SavedModelBundle bundle;
...
LoadSavedModel(session_options, run_options, export_dir, 
               {kSavedModelTagTrain},
               &bundle);

Загрузка и обслуживание SavedModel в TensorFlow serving

Вы можете легко загружать и обслуживать SavedModel с помощью TensorFlow Serving Model Server бинарного файла. Инструкции о том, как установить сервер или собрать его, если хотите.

Как только у вас будет Model Server, запустите его с помощью:

tensorflow_model_server --port=port-numbers \
--model_name=your-model-name --model_base_path=your_model_base_path

Установите флаги port и model_name на значения по вашему выбору. Ожидается, что флаг model_base_path будет находиться в базовом каталоге с каждой версией вашей модели, находящейся в подкаталоге с цифровым именем. Если у вас есть только единственная версия вашей модели, просто поместите ее в подкаталог, например:

  • Поместите модель в /tmp/model/0001
  • Установите для model_base_path значение /tmp/model

Храните разные версии вашей модели в подкаталогах с числовыми именами общего базового каталог. Например, предположим, что базовым каталогом является /tmp/model. Если у вас есть только одна версия вашей модели, сохраните ее в /tmp/model/0001. Если у вас есть две версии вашей модели, сохраните вторую версию в /tmp/model/0002 и так далее. Установите флаг --model-base_path равной базовому каталогу (/tmp/model, в этом примере). TensorFlow Model Server будет обслуживать модель в подкаталоге с наибольшим номером этого базового каталога.

Стандартные константы

SavedModel предлагает гибкость для построения и загрузки графов TensorFlow для разных вариантов использования. Для наиболее распространенных случаев использования API-интерфейсы SavedModel предоставляют набор констант в Python и C++, которые легко использовать повторно и совместно в разных инструментах.

Стандартные теги MetaGraphDef

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

Стандартные константы SignatureDef

SignatureDef - это буфер протокола, который определяет сигнатуру вычисления, поддерживаемую графом. Обычно используемые ключи ввода, ключи вывода и имена методов определены в:


Использование SavedModel с оценщиками (Estimators)

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

Чтобы подготовить Estimator для обслуживания (serving), вы должны экспортировать его в стандартный SavedModel формат. Далее объясняется, как:

  • Указывать выходные узлы и соответствующие API, которые могут быть обслуживаемы (Classify, Regress, или Predict).
  • Экспортировать свою модель в формат SavedModel.
  • Обслуживать модель с локального сервера и запрашивать прогнозы.

Подготовка вводов для обслуживания

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

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

Функция возвращает tf.estimator.export.ServingInputReceiver объект, который упаковывает заполнители и результирующие тензоры свойств вместе.

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

При написании такого serve_input_receiver_fn() вы должны пройти синтаксический анализ спецификация tf.parse_example, чтобы сообщить парсеру, какие имена функций следует ожидать и как сопоставить их с Tensor. Спецификация разбора принимает словарь из имен элементов и tf.FixedLenFeature, tf.VarLenFeature, и tf.SparseFeature. Обратите внимание, что эта спецификация анализа не должна включать любые метки или весовые столбцы, так как они не будут доступны во время обслуживания - в отличие от спецификации анализа, используемой в input_fn() во время тренировки.

В комбинации, тогда:

feature_spec = {'foo': tf.FixedLenFeature(...),
                'bar': tf.VarLenFeature(...)}

def serving_input_receiver_fn():
  """Получатель ввода, который ожидает сериализованный tf.Example."""
  serialized_tf_example = tf.placeholder(dtype=tf.string,
                                         shape=[default_batch_size],
                                         name='input_example_tensor')
  receiver_tensors = {'examples': serialized_tf_example}
  features = tf.parse_example(serialized_tf_example, feature_spec)
  return tf.estimator.export.ServingInputReceiver(features, 
                                                  receiver_tensors)

tf.estimator.export.build_parsing_serving_input_receiver_fn утилитная функция предоставляет приемник ввода для общих случаев.

Примечание. При обучении модели для обслуживания с использованием API Predict с локальным сервером, шаг разбора (parsing) не требуется, потому что модель будет получать необработанные данные свойств.

Даже если вам не требуется разбор или другая обработка ввода - то есть, если обслуживающая система будет напрямую передавать свойство Tensors - вы все равно должны предоставить serve_input_receiver_fn(), который создает заполнители для свойства Tensors и передает их. tf.estimator.export.build_raw_serving_input_receiver_fn утилита предоставляется для этого.

Если эти утилиты не соответствуют вашим потребностям, вы можете написать свою собственную serving_input_receiver_fn(). Один из случаев, когда это может быть необходимо, это если ваша тренировочная input_fn() включает некоторую логику предварительной обработки, которая должна быть пересмотрена во время обслуживания. Чтобы снизить риск перекоса при обучении, рекомендуется инкапсулировать такую ​​обработку в функцию, которая затем вызывается из input_fn() и serve_input_receiver_fn().

Обратите внимание, что serve_input_receiver_fn() также определяет input часть подписи. То есть при написании serve_input_receiver_fn(), вы должны сообщить парсеру, какие подписи ожидать и как сопоставить их с ожидаемыми входными данными вашей модели. Напротив, часть подписи output определяется моделью.

Указание выводных данных пользовательской модели

При написании пользовательской model_fn необходимо заполнить элемент export_outputs из tf.estimator.EstimatorSpec. Это словарь {name: output}, описывающий выходные сигнатуры, которые необходимо экспортировать и использовать во время обслуживания.

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

Каждое значение output должно быть объектом ExportOutput, таким как tf.estimator.export.ClassificationOutput, tf.estimator.export.RegressionOutput или tf.estimator.export.PredictOutput.

Эти выходные типы напрямую указывают на TensorFlow Serving API, и таким образом определяют, какие типы запросов будут выполнены.

Отметим, что в случае с несколькими заголовками будет генерироваться SignatureDef для каждого элемента словаря export_outputs, возвращенного из model_fn, названного с помощью тех же ключей. Эти SignatureDef отличаются только своими выходами, как предоставлено соответствующей записью ExportOutput. Входы всегда те, которые предоставляются serve_input_receiver_fn. Запрос на вывод может указывать голову по имени. Одна голова должна быть названа используя signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY указывающий, какой SignatureDef будет обслуживаться при запросе вывода не указывает ни одного.

Выполнение экспорта

Чтобы экспортировать натренированный Estimator, вызовите tf.estimator.Estimator.export_savedmodel с базовым путем экспорта и serve_input_receiver_fn.

estimator.export_savedmodel(export_dir_base, serving_input_receiver_fn,
                            strip_default_attrs=True)

Этот метод создает новый граф, сначала вызывая serve_input_receiver_fn(), чтобы получить свойство Tensor, а затем вызвать model_fn() Estimator для генерации графа модели на основе этих свойств. Он запускает новую Session и, по умолчанию, восстанавливает самую последнюю контрольную точку в ней. (Другая контрольная точка может быть передана, если это необходимо.) Наконец, он создает каталог экспорта с меткой времени под заданной export_dir_base (т. е. export_dir_base/<timestamp>), и записывает в него SavedModel, содержащую один MetaGraphDef, сохраненный из этой Session.

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

Обслуживание экспортированной модели локально

Для локального развертывания вы можете обслуживать свою модель, используя TensorFlow Serving, проект с открытым исходным кодом, который загружает SavedModel и предоставляет его в качестве gRPC сервиса.

Сначала установите TensorFlow Serving.

Затем соберите и запустите локальный сервер моделей, заменив $export_dir_base на путь к SavedModel, который вы экспортировали выше:

bazel build //tensorflow_serving/model_servers:tensorflow_model_server
bazel-bin/tensorflow_serving/model_servers/tensorflow_model_server \ 
--port=9000 --model_base_path=$export_dir_base

Теперь у вас есть сервер, который прослушивает запросы на вывод через gRPC по порту 9000.

Запрос прогнозов с локального сервера

Сервер отвечает на запросы gRPC в соответствии с PredictionService gRPC API определением сервиса. (Вложенные буферы протокола определены в различные соседних файлах).

Из API определения сервиса, gRPC фреймворк генерирует клиентские библиотеки на разных языках, обеспечивающих удаленный доступ к API. В проекте с использованием инструмента сборки Bazel, эти библиотеки создаются автоматически и предоставляются через такие зависимости (например, с использованием Python):

deps = [
    "//tensorflow_serving/apis:classification_proto_py_pb2",
    "//tensorflow_serving/apis:regression_proto_py_pb2",
    "//tensorflow_serving/apis:predict_proto_py_pb2",
    "//tensorflow_serving/apis:prediction_service_proto_py_pb2"
  ]

Python клиентский код может затем импортировать библиотеки:

from tensorflow_serving.apis import classification_pb2
from tensorflow_serving.apis import regression_pb2
from tensorflow_serving.apis import predict_pb2
from tensorflow_serving.apis import prediction_service_pb2

Отметитим: gnution_service_pb2 определяет службу в целом и поэтому всегда требуется. Однако типичному клиенту потребуется только один из classification_pb2, regression_pb2 или gnast_pb2, в зависимости от типа выполняемых запросов.

Отправка запроса gRPC выполняется путем сборки буфера протокола, содержащего данные запроса и передающего их в сервисную заглушку. Обратите внимание, как буфер протокола запроса создается пустым, а затем заполняется через API сгенерированного буфера протокола.

from grpc.beta import implementations

channel = implementations.insecure_channel(host, int(port))
stub = prediction_service_pb2.beta_create_PredictionService_stub(
                                                           channel)

request = classification_pb2.ClassificationRequest()
example = request.input.example_list.examples.add()
example.features.feature['x'].float_list.value.extend(
                                            image[0].astype(float))

result = stub.Classify(request, 10.0)  # 10 секунд таймаут

В этом примере возвращаемым результатом является буфер протокола ClassificationResponse.

Примечание. ClassificationRequest и RegressionRequest содержат буфер протокола tenorflow.serving.Input, который, в свою очередь, содержит список буферов протокола tenorflow.Example. PredictRequest, напротив, содержит соотвествие имен свойств и значений, закодированные с помощью TensorProto. Соответственно: при использовании API Classify и Regress, TensorFlow Serving передает сериализованные tf.Example в граф, поэтому ваша serve_input_receiver_fn() должна включать tf.parse_example() операцию. Однако при использовании универсального Predict API, TensorFlow Serving передает необработанные данные свойств в граф, поэтому должен быть выполнен проход через serve_input_receiver_fn().

Структура каталога SavedModel

Когда вы сохраняете модель в формате SavedModel, TensorFlow создает каталог SavedModel, состоящий из следующих подкаталогов и файлов:

assets/
assets.extra/
variables/
    variables.data-?????-of-?????
    variables.index
saved_model.pb|saved_model.pbtxt

, где:

  • assets - это подпапка, содержащая вспомогательные (внешние) файлы, такие как словари. Активы (assets) копируются в расположение SavedModel и могут быть прочитаны при загрузке определенного MetaGraphDef.
  • assets.extra - это подпапка, в которую могут входить высокоуровневые библиотеки и пользователи могут добавлять свои собственные активы, которые сосуществуют с моделью, но не загружены в граф. Эта подпапка не управляется библиотеками SavedModel.
  • variable - это подпапка, содержащая выходные данные из tf.train.Saver.
  • saved_model.pb или <код>saved_model.pbtxt- это буфер протокола SavedModel. Он включает определения графа в виде буферов протокола MetaGraphDef.

Одна SavedModel может представлять несколько графов. В этом случае все графы в SavedModel совместно используют единый набор контрольных точек (переменных) и активов. Например, следующая диаграмма показывает одну SavedModel, содержащую три элемента MetaGraphDef, все три из которых используют один и тот же набор контрольных точек и активов (assets):

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


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

Основы TensorFlow Core

TensorFlow Core: тензоры (tensors)

TensorFlow Core: переменные (variables)

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

TensorFlow Core: сохранение и восстановление переменных

TensorFlow переменные - это лучший способ представления общего постоянного состояния манипулируемого вашей программой. tf.train.Saver конструктор добавляет save и restore операции в граф для всех, или указанного списка, переменных в графе. Объект Saver предоставляет методы для запуска этих операций, указывая пути для файлов контрольных точек, в которые необходимо производить запись или из которых считывать.

Saver восстанавливает все переменные, уже определенные в вашей модели.

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

Внимание: файлы модели TensorFlow являются кодом. Будьте осторожны с ненадежным кодом.

Сохранение переменных

Создайте Saver с помощью tf.train.Saver() для управления всеми переменными в модели. Например, следующий фрагмент демонстрирует, как вызвать tf.train.Saver.save для сохранения переменных в файлах контрольных точек:

# Создаем несколько переменных.
v1 = tf.get_variable("v1", shape=[3], initializer = tf.zeros_initializer)
v2 = tf.get_variable("v2", shape=[5], initializer = tf.zeros_initializer)

inc_v1 = v1.assign(v1+1)
dec_v2 = v2.assign(v2-1)

# Добавляем операцию для инициализации переменных.
init_op = tf.global_variables_initializer()

# Добавляем операции для сохранения и восстановления всех переменных.
saver = tf.train.Saver()

# Затем, запускаем модель, инициализируем переменные, 
# выполняем некоторую работу, и сохраняем переменные на диск.
with tf.Session() as sess:
  sess.run(init_op)
  # Выполняем некоторую работу с моделью.
  inc_v1.op.run()
  dec_v2.op.run()
  # Сохраняем переменные на диск.
  save_path = saver.save(sess, "/tmp/model.ckpt")
  print("Model saved in path: %s" % save_path)

Восстановление переменных

tf.train.Saver не только сохраняет переменные в файлы контрольных точек, он также восстанавливает переменные. Обратите внимание, что при восстановлении переменных вам не требуется инициализировать их заранее. Например, следующий фрагмент кода демонстрирует как вызвать tf.train.Saver.restore метод для восстановления переменных из файлов контрольных точек:

tf.reset_default_graph()

# Создаем несколько переменных.
v1 = tf.get_variable("v1", shape=[3])
v2 = tf.get_variable("v2", shape=[5])

# Добавляем операции для сохранения и восстановления всех переменных.
saver = tf.train.Saver()

# Затем, запускаем модель, используем saver 
# для восстановления переменных с диска,
# и выполняем некторую работу с моделью.
with tf.Session() as sess:
  # Восстанавливаем переменные с диска.
  saver.restore(sess, "/tmp/model.ckpt")
  print("Model restored.")
  # Проверяем значения переменных
  print("v1 : %s" % v1.eval())
  print("v2 : %s" % v2.eval())

Примечание: Не существует физического файла с именем /tmp/model.ckpt. Это префикс для имен файлов, созданных для контрольной точки. Пользователи взаимодействуют только с префиксом вместо физических файлов контрольных точек.

Выбор переменных для сохранения и восстановления

Если вы не передаете аргументы в tf.train.Saver(), saver обрабатывает все переменные в графе. Каждая переменная сохраняется под именем, которое было передано когда переменная была создана.

Иногда полезно явно указать имена переменных в файлах контрольных точек. Например, вы могли обучить модель с переменной с именем"weights", значение которой вы хотите восстановить в переменную с именем "params".

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

Вы можете легко указать имена и переменные для сохранения или загрузки, передав в tf.train.Saver() конструктор любое из следующих:

  • Список переменных (которые будут храниться под своими именами).
  • Python словарь, в котором ключами являются имена для использования, а значениями являются переменные для управления.

Продолжая из примеров сохранения/восстановления, показанных ранее:

tf.reset_default_graph()
# Создаем несколько переменных.
v1 = tf.get_variable("v1", [3], initializer = tf.zeros_initializer)
v2 = tf.get_variable("v2", [5], initializer = tf.zeros_initializer)

# Добавляем операции для сохранения и восстановления только v2, 
# используя имя "v2"
saver = tf.train.Saver({"v2": v2})

# Используем saver объект после этого.
with tf.Session() as sess:
  # Инициализируем v1 ввиду того, что saver не будет инициализировать.
  v1.initializer.run()
  saver.restore(sess, "/tmp/model.ckpt")

  print("v1 : %s" % v1.eval())
  print("v2 : %s" % v2.eval())

Примечания:

  • Вы можете создать столько объектов Saver, сколько хотите, чтобы сохранять и восстанавливать различные подмножества переменных модели. Та же самая переменная может быть перечислена в нескольких объектах saver; ее значение изменяется только тогда, когда метод Saver.restore() запущен.

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

  • Чтобы проверить переменные в контрольной точке, вы можете использовать inspect_checkpoint библиотеку, в частности, функцию print_tensors_in_checkpoint_file.

  • По умолчанию Saver использует значение tf.Variable.name свойства для каждой переменной. Однако при создании объекта Saver вы можете при желании выбирать имена переменных в файлах контрольных точек.

Проверка переменных в контрольной точке

Мы можем быстро проверить переменные в контрольной точке с помощью inspect_checkpoint библиотеки.

Продолжая из примеров сохранения/восстановления, показанных ранее:

# импортируем inspect_checkpoint библиотеку
from tensorflow.python.tools import inspect_checkpoint as chkp

# печатаем все тензоры в файле контрольной точки
chkp.print_tensors_in_checkpoint_file("/tmp/model.ckpt", 
                                      tensor_name='', 
                                      all_tensors=True)

# tensor_name:  v1
# [ 1.  1.  1.]
# tensor_name:  v2
# [-1. -1. -1. -1. -1.]

# печатаем только тензор v1 в файле контрольной точки
chkp.print_tensors_in_checkpoint_file("/tmp/model.ckpt", 
                                      tensor_name='v1', 
                                      all_tensors=False)

# tensor_name:  v1
# [ 1.  1.  1.]

# печатаем только тензор v2 в файле контрольной точки
chkp.print_tensors_in_checkpoint_file("/tmp/model.ckpt", 
                                      tensor_name='v2', 
                                      all_tensors=False)

# tensor_name:  v2
# [-1. -1. -1. -1. -1.]


Читайте также другие статьи по этой теме в нашем блоге:

Основы TensorFlow Core

TensorFlow Core: тензоры (tensors)

TensorFlow Core: переменные (variables)

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

TensorFlow Core: графы и сессии

TensorFlow использует граф потока данных (dataflow graph) для представления вашего вычисления с точки зрения зависимостей между индивидуальными операциями. Это ведет к модели низкоуровневого программирования, в которой вы сначала определяете граф потока данных, а затем TensorFlow сессия выполняет части графа на наборе локальных и удаленных устройств.

Этот пост будет вам наиболее полезен, если вы намерены использовать модель низкоуровневого программирования напрямую. Высокоуровневые API, такие как tf.estimator.Estimator и Keras, скрывают детали графов и сессий от конечного пользователя, но этот пост может быть также полезен, если вы хотите понять как эти API реализованы.

Почему графы потоков данных?

Поток данных (Dataflow) - это общая модель программирования для параллельного вычисления. В графе потока данных узлы представляют элементы вычисления. Например, в TensorFlow графе, tf.matmul операция будет соотвествовать единственному узлу с двумя входящими краями (матрицами, которыми будет манипулирование) и одному выходящему узлу (результат умножения).

Поток данных (Dataflow) имеет несколько преимуществ, которые TensorFlow использует при исполнении вашей программы:

  • Параллелизм. Благодаря использованию явных краев для представления зависимостей между операциями, для системы облегчается задача определения операций, которые могут исполняться параллельно.
  • Распределенное исполнение. Благодаря использованию явных краев для представления значений, которые передаются между операциями, для TensorFlow становится возможным разделить на части вашу программу между несколькими устройствами (CPU, GPU, и TPU) присоединенными к разным машинам. TensorFlow вводит необходимое общение и координацию между устройствами.
  • Компиляция. TensorFlow XLA компилятор может использовать информацию в вашем графе потока данных для генерирования более быстрого кода, например, сплавливая воедино подходящие операции.
  • Возможность портирования. Граф потока данных - ээто независимое от языка представление кода в вашей модели. Вы можете построить граф потока данных в Python, сохранив его в SavedModel, и восстановить его в C++ программе для исполнения с низким уровнем задержки.

Что такое tf.Graph?

tf.Graph содержит два уникальных типа информации:

  • Структура графа. Узлы и края графа, отображающие как отдельные операции составляются вместе, но нет предписывающие как они должны быть использованы. Структура графа это как код сборки (код билда)(assembly code): его просмотр может открыть некоторую полезную информацию, но он не содержит всего необходимого контекста, который имеет код источника (source code).
  • Коллекции графа. TensorFlow предоставляет общий механизм для сохранения коллекций метаданных в tf.Graph. tf.add_to_collection функция позволяет вам связывать список объектов с ключом (где tf.GraphKeys определяет некоторые стандартные ключи), а tf.get_collection позволяет вам просматривать все коллекции, связанные с ключом. Многие части TensorFlow библиотеки используют эту возможность: например, когда вы создаете tf.Variable, она по умолчанию добавляется в коллекции представляющие "глобальные переменные" и "тренируемые переменные". Когда вы позже приходите для того чтобы создать tf.train.Saver или tf.train.Optimizer, переменные в этих коллекциях используются как аргументы по умолчанию.

Построение tf.Graph

Большинство TensorFlow программ стартуют с фазы создания графа потока данных. На этой фазе вы вызываете TensorFlow API функции, которые создают новые tf.Operation (узел) и tf.Tensor (край) объекты и добавляют их в экземпляр tf.Graph. TensorFlow предоставляет граф по умолчанию, который является неявным аргументом ко всем функциям API в том же контексте. Например:

  • Вызов tf.constant(42.0) создает единичную tf.Operation, которая производит значение 42.0, добавляет его в граф по умолчанию, и возвращает tf.Tensor, который представляет значение константы.
  • Вызов tf.matmul(x, y) создает единичную tf.Operation, которая умножает значения tf.Tensor объектов x и y, добавляет это в граф по умолчанию, и возвращает tf.Tensor, который представляет результат умножения.
  • Исполнение v = tf.Variable(0) добавляет в граф tf.Operation, который будет хранить записываемое значение тензора, которое сохраняется между вызовами tf.Session.run. tf.Variable объект оборачивает эту операцию, и может быть использован как тензор, который будет читать текущее значение хранимого значения. tf.Variable объект также имеет методы, такие как tf.Variable.assign и tf.Variable.assign_add, которые создают tf.Operation объекты, которые, при исполнении, обновляют хранимое значение.
  • Вызов tf.train.Optimizer.minimize будет добавлять операции и тензоры в граф по умолчанию, который вычисляет градиенты, и возвращает tf.Operation, которая, при исполнении, применяет эти градиенты к набору переменных.

Большинство программ полагаются исключительно на граф по умолчанию. Высокоуровневые API, такие как tf.estimator.Estimator API, управляют графом по умолчанию от вашего имени, и, например, могут создать различные графы для тренировки и оценки.

Следует отметить, что вызов большиства функций в TensorFlow API просто добавляет операции и тензоры в граф по умолчанию, но не выполняют на самом деле вычисление. Вместо этого, вы составляете эти функции до тех пор, пока у вас есть tf.Tensor или tf.Operation, который представляет все вычисление, такое как выполнение одного шага градиентного спуска, и затем передаете этот объект в tf.Session для выполнения вычисления.

Операции с именами

tf.Graph объект задает область имен (namespace) для tf.Operation объектов, которые он содержит. TensorFlow автоматически выбирает уникальное имя для каждой операции в вашем графе, но задание операциям описательных имен может сделать вашу программу проще для чтения и отладки. TensorFlow API предоставляет два способа переопределения имени операции:

  • Каждая API функция, которая создает новую tf.Operation или возвращает новый tf.Tensor принимает опциональный name аргумент. Например, tf.constant(42.0, name="answer") создает новую tf.Operation названную "answer" и возвращает tf.Tensor названный "answer:0". Если граф по умолчанию уже содержит операцию названную "answer", тогда TensorFlow добавит "_1", "_2", и так далее к имени, для того чтобы сделать имя уникальным.
  • tf.name_scope функция делает возможным добавлять префикс области видимости имен ко всем операциям, созданным в отдельном контексте. Текущий префикс области видимости имен - это "/"-разделенный список имен всех активных tf.name_scope контекстных менеджеров. Если область видимости имен уже была использована в текущем контексте, TensorFlow добавит "_1", "_2", и т.д. Например:

c_0 = tf.constant(0, name="c")  
# => операция названа "c"

# Уже существующие имена будут "уникализованы".
c_1 = tf.constant(2, name="c")  
# => операция названа "c_1"

# Области видимости имен добавляют префикс ко всем операциям, 
# созданным в том же контексте.
with tf.name_scope("outer"):
  c_2 = tf.constant(2, name="c")  
  # => операция названа "outer/c"

  # Области видимости имен складываются друг в друга
  # как пути в иерархической файловой системе.
  with tf.name_scope("inner"):
    c_3 = tf.constant(3, name="c")  
    # => операция названа "outer/inner/c"

  # Выход из конекста области видимости имен
  # будет возвращать предыдущий префикс.
  c_4 = tf.constant(4, name="c")  
  # => операция названа "outer/c_1"

  # Уже существующие области видимости имен будут "уникализованы".
  with tf.name_scope("inner"):
    c_5 = tf.constant(5, name="c")  
    # => операция названа "outer/inner_1/c"

Визуализатор графов использует области видимости имен для группирования операций и уменьшения визуальной сложности графа.

Отметим, что tf.Tensor объекты неявно названы согласно tf.Operation, которая производит этот тензор как вывод. Имя тензора имеет форму "<OP_NAME>:<i>", где:

  • "<OP_NAME>" - это имя операции, которая производит его.
  • "<i>" - это число, представляющее индекс этого тензора среди выводов операции.

Размещение операций на разных устройствах

Если вы хотите, чтобы ваша TensorFlow программа использовала несколько различных устройств, tf.device функция предоставляет удобный способ запросить, чтобы все операции, созданные в определенном контексте, были размещены на одном устройстве (или типе устройства).

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

/job:<JOB_NAME>/task:<TASK_INDEX>/device:<DEVICE_TYPE>:<DEVICE_INDEX>

где:

  • <JOB_NAME> - это алфавитно-числовая строка, которая не начинается с цифры.
  • <DEVICE_TYPE> - это зарегистрированный тип устройства (такой как GPU или CPU).
  • <TASK_INDEX> - неотрицательное число, представляющее индекс задачи в работе (job) с именем <JOB_NAME>.
  • <DEVICE_INDEX> - это неотрицательное число, представляющее индекс устройства, например, для разделения между разными GPU устройствами, используемыми в одном процессе.

Вам не требуется определять каждую часть спецификации устройства. Например, если вы исполняете программу в конфигурации с одной машиной и одним GPU, вы можете использовать tf.device, для того чтобы прикрепить некоторые операции к CPU и GPU:

# Операции созданные вне какого-либо контекста
# будут выполняться на "наиболее возможном" устройстве.
# Например, если у вас есть доступные GPU и CPU,
# а операция имеет GPU реализацию, тогда TensorFlow выберет GPU
weights = tf.random_normal(...)

with tf.device("/device:CPU:0"):
  # Операции, созданные в этом контексте,
  # будут прикрепленны к CPU.
  img = tf.decode_jpeg(tf.read_file("img.jpg"))

with tf.device("/device:GPU:0"):
  # Операции, созданные в этом контексте,
  # будут прикрепленны к GPU.
  result = tf.matmul(weights, img)

Если вы запускаете TensorFlow в типичной распределенной конфигурации, вы можете определить имя работы (job) и ID задачи (task), для того чтобы разместить переменные на задаче в параметре server работы (job) ("/job:ps"), а другие операции на задаче в работе исполнителя (worker job) ("/job:worker"):

with tf.device("/job:ps/task:0"):
  weights_1 = tf.Variable(tf.truncated_normal([784, 100]))
  biases_1 = tf.Variable(tf.zeroes([100]))

with tf.device("/job:ps/task:1"):
  weights_2 = tf.Variable(tf.truncated_normal([100, 10]))
  biases_2 = tf.Variable(tf.zeroes([10]))

with tf.device("/job:worker"):
  layer_1 = tf.matmul(train_batch, weights_1) + biases_1
  layer_2 = tf.matmul(train_batch, weights_2) + biases_2

tf.device дает вам большую долю гибкости для выбора размещения для отдельных операций или широких регионов TensorFlow графа. Во многих случаях, существуют простые эвристики, которые работают хорошо. Например, tf.train.replica_device_setter API может быть использован с tf.device для размещения операций для распределенной тренировки с параллельными данными. Например, следующий фрагмент кода показывает как tf.train.replica_device_setter применяет различные политики размещения tf.Variable объектов и других операций:

with tf.device(tf.train.replica_device_setter(ps_tasks=3)):
  # tf.Variable объекты, по умолчанию, размещены на задачах
  # "/job:ps" в круговой манере
  w_0 = tf.Variable(...)  # размещено на "/job:ps/task:0"
  b_0 = tf.Variable(...)  # размещено на "/job:ps/task:1"
  w_1 = tf.Variable(...)  # размещено на "/job:ps/task:2"
  b_1 = tf.Variable(...)  # размещено на "/job:ps/task:0"

  input_data = tf.placeholder(tf.float32)     
  # размещено на "/job:worker"

  layer_0 = tf.matmul(input_data, w_0) + b_0  
  # размещено на "/job:worker"

  layer_1 = tf.matmul(layer_0, w_1) + b_1     
  # размещено на "/job:worker"


Тензор-подобные объекты

Многие TensorFlow операции принимают один или более tf.Tensor объектов как аргументы. Например, tf.matmul принимает два tf.Tensor объекта, а tf.add_n принимает список n tf.Tensor объектов. Для удобства эти функции будут принимать тензор-подобный объект вместо tf.Tensor, и неявням образом преобразовывать его в tf.Tensor, используя tf.convert_to_tensor метод. Тензор-подобные объекты включают элементы следующих типов:

  • tf.Tensor
  • tf.Variable
  • numpy.ndarray
  • list (лист, список) (и списки тензор-подобных объектов)
  • Скалярные Python типы: bool, float, int, str

Вы можете регистрировать дополнительные тензор-подобные типы, используя tf.register_tensor_conversion_function.

Следует отметить, что по умолчанию TensorFlow будет создавать новый tf.Tensor каждый раз, когда вы используете тот же тензор-подобный объект. Если тензор-подобный объект большой (например, numpy.ndarray, содержащий набор тренировочных примеров) и вы используете его много раз, это может вызвать переполненние памяти. Чтобы избежать этого, вручную вызовите tf.convert_to_tensor на тензор-подобном объекте единожды и используйте возвращенный tf.Tensor вместо первоначального объкета.

Исполнение графа в tf.Session

TensorFlow использует tf.Session класс для представления соединения между клиентской программой - обычно Python программой, хотя схожий интерфейс доступен в других языках - и C++ рабочей средой исполнения. tf.Session объект предоставляет доступ к утройствам на локальной машине и удаленным устройствам, используя распределенную TensorFlow среду исполнения. Он также кеширует информацию о ваших tf.Graph, таким образом вы можете эффективно выполнять одни и те же вычисления много раз.

Создание tf.Session

Если вы используете низкоуровневое TensorFlow API, вы можете создать tf.Session для текущего графа по умолчанию следующим образом:

# Создаем внутрипроцессовую сессию по умолчанию
with tf.Session() as sess:
  # ...

# Cоздаем удаленную сессию
with tf.Session("grpc://example.org:2222"):
  # ...

Ввиду того, что tf.Session имеет физические ресурсы (такие как GPU и сетевые соединения), также обычно она используется как менеджер контекста (внутри with блока), который автоматически закрывает сессию, когда вы выходите из блока. Также возможно создать сессию без использования with блока, но вам следует явно вызывать tf.Session.close, когда вы заканчиваете использовать ее, для освобождения ресурсов.

Отметим: высокоуровневые API, такие как tf.train.MonitoredTrainingSession или tf.estimator.Estimator, будут создавать и управлять tf.Session для вас. Эти API принимают опциональные target и config аргументы (напрямую или как часть tf.estimator.RunConfig объекта), с тем же значением как описано выше.

tf.Session.init принимает три опциональных аргумента:

  • target. Если аргумент оставлен пустым (по умолчанию), сессия будет использовать устройства только на локальной машине. Однако, вы можете также определить grpc:// URL для определения адреса TensorFlow сервера, который дает сессии доступ ко всем устройствам на машинах, которые контролирует этот сервер. Например, в общей межграфовой конфигурации репликации, tf.Session подключается к tf.train.Server в том же процессе, что и клиент.
  • graph. По умолчанию, новая tf.Session будет привязана к текущему графу по умолчанию и способна выполнять операции только в нем. Если вы используете несколько графов в вашей программе, вы можете явно определить tf.Graph при создании сессии.
  • config. Этот аргумент позволяет вам определять tf.ConfigProto, который контролирует поведение сессии. Например, вот некоторые конфигурационные опции:
    • allow_soft_placement. Установите в True для включения "soft" алгоритма размещения устройства, которая игнорирует tf.device аннотации, которые пытаются размещать операции с GPU устройства предназначенные для CPU, и размещает их на CPU.
    • cluster_def. При использовании распределенного TensorFlow, эта опция позволяет вам определить какие машины использовать в вычислении, и предоставлять картирование (mapping) между имена работ (job names), индексами задач, и сетевыми адресами.
    • graph_options.optimizer_options. Предоставляет контроль над оптимизациями, которые TensorFlow выполняет в вашем графе перед его исполнением.
    • gpu_options.allow_growth. Установите в True, чтобы изменить аллокатор (выделитель) GPU памяти таким образом, чтобы он постепенно увеличивал размер занимаемой памяти, вместо занятия большей части памяти при сразу при запуске.

Использование tf.Session.run для выполнения операций

tf.Session.run метод является основным механизмом запуска tf.Operation или оценки tf.Tensor. Вы можете передать один или несколько tf.Operation или tf.Tensor объектов tf.Session.run, и TensorFlow выполнит операции, необходимые для вычисления результата.

tf.Session.run требует, чтобы вы указали список выборок, которые определяют возвращаемые значения и могут быть tf.Operation, tf.Tensor или тензор-подобного типа, такие как tf.Variable. Эти выборки определяют, какой подграф из всего tf.Graph должен быть исполнен для получения результата: это подграф, который содержит все операции, указанные в списке выборки плюс все операции, выходные данные которых используются для вычисления значения выборок. Например, следующий фрагмент кода показывает, как различные аргументы tf.Session.run приводят к выполнению различных подграфов:

x = tf.constant([[37.0, -23.0], [1.0, 4.0]])
w = tf.Variable(tf.random_uniform([2, 2]))
y = tf.matmul(x, w)
output = tf.nn.softmax(y)
init_op = w.initializer

with tf.Session() as sess:
  # Выполняем инициализатор на w.
  sess.run(init_op)

  # Оцениваем output. `sess.run(output)`вернет NumPy массив,
  # содержащий результат вычисления.
  print(sess.run(output))

  # Оцениваем y и output. Отметим, что y будет вычислено только единожды,
  # и его результат использован в обоих случаях:
  # чтобы вернуть y_val и как ввод для tf.nn.softmax() операции.
  # Оба y_val и output_val будут NumPy массивами.
  y_val, output_val = sess.run([y, output])

tf.Session.run также опционально принимает словарь вводов, который является картой (mapping) tf.Tensor объектов (обычно tf.placeholder тензоры) к значениям (как правило, Python скаляры, списки (lists), или NumPy массивы), которые будут заменены для этих тензоров в ходе исполнения. Например:

# Определяем placeholder. который ожидает вектор из трех
# нецельночисловых значений, и вычисление, которое зависит от него
x = tf.placeholder(tf.float32, shape=[3])
y = tf.square(x)

with tf.Session() as sess:
  # Передача значения изменяет результат
  # котороый возвращается, когда вы оцениваете y.
  print(sess.run(y, {x: [1.0, 2.0, 3.0]}))  # => "[1.0, 4.0, 9.0]"
  print(sess.run(y, {x: [0.0, 0.0, 5.0]}))  # => "[0.0, 0.0, 25.0]"

  # Вызывает tf.errors.InvalidArgumentError, 
  # потому что вы должны передавать значение для tf.placeholder()
  # при оценке тензора, который зависит от него.
  sess.run(y)

  # Вызывает ValueError, потому что форма 37.0
  # не соотвествует форме заполнителя x.
  sess.run(y, {x: 37.0})

tf.Session.run также принимает необязательный аргумент options, который позволяет вам указать параметры вызова и необязательный аргумент run_metadata, который позволяет собирать метаданные о выполнении. Например, вы можете использовать эти опции вместе, чтобы собрать информацию трассировки о выполнении:

y = tf.matmul([[37.0, -23.0], [1.0, 4.0]], tf.random_uniform([2, 2]))

with tf.Session() as sess:
  # Определяем опции для sess.run() вызова.
  options = tf.RunOptions()
  options.output_partition_graphs = True
  options.trace_level = tf.RunOptions.FULL_TRACE

  # Определяем контейнер для возвращенных метаданных.
  metadata = tf.RunMetadata()

  sess.run(y, options=options, run_metadata=metadata)

  # Печатаем подграфы, которые выполнены на каждом устройстве.
  print(metadata.partition_graphs)

  # Печатаем время каждой выполненной операции.
  print(metadata.step_stats)


Визуализация вашего графа

TensorFlow включает в себя инструменты, которые могут помочь вам понять код в графе. Визуализатор графа - это компонент TensorBoard, который отображает структуру вашего графа визуально в браузере. Самый простой способ создать визуализацию - передать tf.Graph при создании tf.summary.FileWriter:

# Строим граф.
x = tf.constant([[37.0, -23.0], [1.0, 4.0]])
w = tf.Variable(tf.random_uniform([2, 2]))
y = tf.matmul(x, w)
# ...
loss = ...
train_op = tf.train.AdagradOptimizer(0.01).minimize(loss)

with tf.Session() as sess:
  # sess.graph предоставляет доступ к графу, 
  # использованному в tf.Session.
  writer = tf.summary.FileWriter("/tmp/log/...", sess.graph)

  # Выполняем вычисление...
  for i in range(1000):
    sess.run(train_op)
    # ...

  writer.close()

Примечание: Если вы используете tf.estimator.Estimator, граф (и любые другие итоги) будут автоматически записаны в model_dir, который вы указали при создании оценщика (estimator).

Затем вы можете открыть журнал в tensorboard, перейти в "Graph" вкладку и увидеть высокоуровневую визуализацию структуры вашего графа. Обратите внимание, что типичный TensorFlow граф - особенно тренировочные графы с автоматически вычисляемыми градиентами - имеет слишком много узлов для одновременной визуализации. Визуализатор графов использует области имен для группировки связанных операций в «super» узлы. Вы можете нажать на оранжевый "+" кнопку на любом из этих супер узлов, чтобы развернуть подграф, находящийся внутри.


Программирование с несколькими графами

Примечание: При обучении модели, обычный способ организации вашего кода заключается в использовании одного графа для обучения вашей модели, и отдельного графа для оценки или выдачи прогноза с обученной моделью. Во многих случаях граф вывода будет отличаться от тренировочного графа: например, техники, такие как отсев (dropout) и пакетная нормализация используют разные операции в каждом случае. Кроме того, стандартные утилиты, такие как tf.train.Saver, используют имена tf.Variable объектов (имена которых основаны на подлежащей tf.Operation) для идентификации каждой переменной в сохраненной контрольной точке. При программировании таким образом, вы можете либо использовать отдельные Python процессы для построения и выполнения графов, либо вы можете использовать несколько графов в одном процессе. В этом посте описан вариант использования нескольких графов в одном процессе.

Как отмечалось выше, TensorFlow предоставляет «граф по умолчанию», который неявно передается для всех API функций в том же контексте. Для многих приложений одного графа достаточно. Тем не менее, TensorFlow также предоставляет методы для манипулирования графом по умолчанию, что может быть полезно в более сложных случаях использования. Например:

  • tf.Graph определяет пространство имен для tf.Operation объектов: каждая операция в одном графе должна иметь уникальное имя. TensorFlow будет "уникализовывать" имена операций путем добавления "_1", "_2" и т. д. к их именам, если запрошенное имя уже занято. Использование нескольких явно созданных графов дает вам больше контроля над тем, какое имя дается каждой операции.

  • В графе по умолчанию хранится информация о каждом tf.Operation и tf.Tensor, который когда-либо был добавлен в него. Если ваша программа создает большое количество неподключенных подграфов, более эффективным может быть использование другого tf.Graph для создания каждого подграфа, чтобы несвязанное состояние могло быть собрано сборщиком мусора.

Вы можете установить другой tf.Graph в качестве графа по умолчанию, используя tf.Graph.as_default менеджер контекста:

g_1 = tf.Graph()
with g_1.as_default():
  # Операции, созданные в этой области будут добавлены к g_1.
  c = tf.constant("Node in g_1")

  # Операции, созданные в этой области будут выполнять операции из g_1.
  sess_1 = tf.Session()

g_2 = tf.Graph()
with g_2.as_default():
  # Операции, созданные в этой области будут добавлены к g_2.
  d = tf.constant("Node in g_2")

# Альтернативно, вы можете передать граф при создании tf.Session:
# sess_2 будет выполнять ьоперации из g_2.
sess_2 = tf.Session(graph=g_2)

assert c.graph is g_1
assert sess_1.graph is g_1

assert d.graph is g_2
assert sess_2.graph is g_2

Для того чтобы инспектировать текущий граф по умолчанию, вызовите tf.get_default_graph, который вернет tf.Graph объект:

# Речатаем все операции в графе по умолчанию.
g = tf.get_default_graph()
print(g.get_operations())


Читайте также другие статьи по этой теме в нашем блоге:

Основы TensorFlow Core

TensorFlow Core: тензоры (tensors)

TensorFlow Core: переменные (variables)