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

среда, 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)

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

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

TensorFlow переменная (variable) - это лучший способ представления общедоступного, хранимого состояния, используемого вашей программой.

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

Внутри себя tf.Variable хранит постоянный тензор. Определенные операции позволяют вам читать и изменять значения этого тензора. Эти изменения видимы в нескольких tf.Session, таким образом несколько исполняемых потоков могут видеть одни и те же значения для tf.Variable.

Создание переменной

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

Для создания переменной с tf.get_variable, просто предоставьте название и форму:

my_variable = tf.get_variable("my_variable", [1, 2, 3])

Этот код создает переменную, названную "my_variable", которая является трехмерным тензором с формой [1, 2, 3]. Эта переменная будет по умолчанию иметь dtype (тип данных) tf.float32 и ее первоначальное значение будет задано случайным образом, посредством tf.glorot_uniform_initializer.

Вы также можете опционально задать тип данных (dtype) и инициализатор (initializer) для tf.get_variable. Например:

my_int_variable = tf.get_variable("my_int_variable", [1, 2, 3], 
                                  dtype=tf.int32,
                                  initializer=tf.zeros_initializer)

TensorFlow предоставляет много удобных инициализаторов. С другой стороны, вы можете инициализировать tf.Variable значением tf.Tensor. Например:

other_variable = tf.get_variable("other_variable", dtype=tf.int32,
                                 initializer=tf.constant([23, 42]))

Отметим, что в случае, когда в качестве инициализатора используется tf.Tensor, вам не следует определять форму переменной, ввиду того, что эта форма будет взята из тензора, которым инициализируется переменная.

Коллекции переменных

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

По умолчанию каждая tf.Variable располагается в следующих двух коллекциях:

  • tf.GraphKeys.GLOBAL_VARIABLES - переменные, которые могут быть общими для нескольких устройств
  • tf.GraphKeys.TRAINABLE_VARIABLES - переменные, для которых TensorFlow будет вычислять градиенты

Если вы не хотите, чтобы переменная была тренируема, добавьте ее в коллекцию tf.GraphKeys.LOCAL_VARIABLES. Например, следующий код показывает как добавить переменную, названную my_local, в эту коллекцию:

my_local = tf.get_variable("my_local", shape=(),
                           collections=[tf.GraphKeys.LOCAL_VARIABLES])

Альтернативно, вы можете определить trainable=False как аргумент к tf.get_variable:

my_non_trainable = tf.get_variable("my_non_trainable",
                                   shape=(),
                                   trainable=False)

Вы также можете использовать свои собственные коллекции. Любая строка - это валидное имя коллекции, и нет необходимости явным образом создавать коллекцию. Для того чтобы добавить переменную (или любой другой объект) в коллекцию, после создания переменной вызовите tf.add_to_collection. Например, следующий код добавляет существующую переменную my_local в коллекцию, названную my_collection_name:

tf.add_to_collection("my_collection_name", my_local)

А чтобы извлечь список всех переменных (или других объектов), размещенных вами в коллекции, можно использовать:

tf.get_collection("my_collection_name")

Размещение на устройствах (Device placement)

Как и любую другую TensorFlow операцию, вы можете разместить переменные на определенных устройствах. Например, следующий код создает переменную, названную v, и размещает ее на втором GPU устройстве:

with tf.device("/device:GPU:1"):
  v = tf.get_variable("v", [1])

Это особенно важно для переменных - быть на правильном устройстве в распределенных установках. Случайное размещение переменных на воркерах (workers) (коде программы, непосредственно выполняющем задачу) вместо помещения переменной в параметре servers, например, может значительно замедлить тренировку или, в худшем случае, позволит каждому воркеру беспечно продвигаться вперед с его собственной копией каждой переменной. По этой причине мы предоставляем tf.train.replica_device_setter, который может автоматически размещать переменные в параметре servers. Например:

cluster_spec = {
    "ps": ["ps0:2222", "ps1:2222"],
    "worker": ["worker0:2222", "worker1:2222", "worker2:2222"]}
with tf.device(tf.train.replica_device_setter(cluster=cluster_spec)):
  v = tf.get_variable("v", shape=[20, 20])  
  # эта переменная размещена в параметре server
  # посредством replica_device_setter


Инициализация переменных

До того как вы сможете использовать переменную, она должна быть инициализирована. Если вы программируете в низкоуровневом TensorFlow API (то есть вы явным образом создаете собственные графы и сессии), вы должны явным образом инициализировать переменные. Большинство высокоуровневых фреймворков, таких как tf.contrib.slim, tf.estimator.Estimator и Keras автоматически инициализируют переменные для вас перед тренировкой модели.

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

Для того чтобы инициализировать все тренируемые переменные в один заход, перед началом тренировки, вызовите tf.global_variables_initializer(). Эта функция возвращает единственную операцию, ответственную за инициализацию всех переменных в tf.GraphKeys.GLOBAL_VARIABLES коллекции. Выполнение этой операции инициализирует все переменные. Например:

session.run(tf.global_variables_initializer())
# Теперь все переменные инициализированы.

Если вам не требуется самим инициализировать переменные, вы можете выполнить операцию инициализатора переменной. Например:

session.run(my_variable.initializer)

Может возникнуть потребность узнать - какие переменные до сих пор не инициализированы. Например, следующий код печатает имена всех переменных, которые еще не были инициализированы:

print(session.run(tf.report_uninitialized_variables()))

Отметим, что, по умолчанию, tf.global_variables_initializer не определяет порядок в котором переменные инициализированы. Поэтому, если первоначальное значение переменной зависит от значения другой переменной, есть вероятность, что вы получите ошибку. В любое время, когда вы используете значение переменной в контексте, где не все переменные инициализированы (скажем, если вы используете значение переменной во время инициализации другой переменной), лучше использовать variable.initialized_value() вместо переменной:

v = tf.get_variable("v", shape=(), initializer=tf.zeros_initializer())
w = tf.get_variable("w", initializer=v.initialized_value() + 1)


Использование переменных

Для того чтобы использовать значение tf.Variable в TensorFlow графе, просто рассматривайте его как обычный tf.Tensor:

v = tf.get_variable("v", shape=(), initializer=tf.zeros_initializer())
w = v + 1  
# w - это tf.Tensor, который вычислен на основании значения v.
# В любом случае, когда переменная использована в выражении
# она автоматически преобразована в tf.Tensor, 
# представляющий ее значение

Для того чтобы назначить значение переменной, используйте методы assign, assign_add, и содружественные с ними в tf.Variable классе. Например, вот как можно вызывать эти методы:

v = tf.get_variable("v", shape=(), initializer=tf.zeros_initializer())
assignment = v.assign_add(1)
tf.global_variables_initializer().run()
sess.run(assignment)  # или assignment.op.run(), или assignment.eval()

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

Ввиду того, что переменные изменяемы (mutable), иногда полезно знать какая версия значения переменной была использована в любой конкретной точке времени. Для того чтобы принудительно заставлять перечитывать значение переменной после того как что-то произошло, вы можете использовать tf.Variable.read_value. Например:

v = tf.get_variable("v", shape=(), initializer=tf.zeros_initializer())
assignment = v.assign_add(1)
with tf.control_dependencies([assignment]):
  w = v.read_value()  
  # w гаранитированно отражает значение v
  # после assign_add операции


Предоставление общего доступа к переменным (Sharing variables)

TensorFlow поддерживает два способа предоставления общего доступа к переменным:

  • Явная передача tf.Variable объектов.
  • Неявное обертывание tf.Variable объектов в tf.variable_scope объекты.

Хотя код, который явным образом передает переменные, чище и понятнее, иногда удобно писать TensorFlow функции, которые неявно используют переменные в своих реализациях. Большинство функиональных слоев из tf.layers используют этот подход, также как все tf.metrics, и некоторые другие библиотечные утилиты.

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

Например, напишем функцию, которая создает сверточный / relu слой:

def conv_relu(input, kernel_shape, bias_shape):
    # Создаем переменную с именем "weights".
    weights = tf.get_variable("weights", kernel_shape,
        initializer=tf.random_normal_initializer())
    # Создаем переменную с именем "biases".
    biases = tf.get_variable("biases", bias_shape,
        initializer=tf.constant_initializer(0.0))
    conv = tf.nn.conv2d(input, weights,
        strides=[1, 1, 1, 1], padding='SAME')
    return tf.nn.relu(conv + biases)

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

input1 = tf.random_normal([1,10,10,32])
input2 = tf.random_normal([1,20,20,32])
x = conv_relu(input1, kernel_shape=[5, 5, 32, 32], bias_shape=[32])
x = conv_relu(x, kernel_shape=[5, 5, 32, 32], bias_shape = [32])
# Такой код не удастся.

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

def my_image_filter(input_images):
    with tf.variable_scope("conv1"):
        # Переменные, созданные здесь, будут названы 
        # "conv1/weights", "conv1/biases".
        relu1 = conv_relu(input_images, [5, 5, 32, 32], [32])
    with tf.variable_scope("conv2"):
        # Переменные, созданные здесь, будут названы 
        # "conv2/weights", "conv2/biases".
        return conv_relu(relu1, [5, 5, 32, 32], [32])

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

with tf.variable_scope("model"):
  output1 = my_image_filter(input1)
with tf.variable_scope("model", reuse=True):
  output2 = my_image_filter(input2)

Вы можете также вызвать scope.reuse_variables(), чтобы запустить переиспользование:

with tf.variable_scope("model") as scope:
  output1 = my_image_filter(input1)
  scope.reuse_variables()
  output2 = my_image_filter(input2)

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

with tf.variable_scope("model") as scope:
  output1 = my_image_filter(input1)
with tf.variable_scope(scope, reuse=True):
  output2 = my_image_filter(input2)


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

Основы TensorFlow Core

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

суббота, 1 декабря 2018 г.

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

TensorFlow, как следует из названия (tensor flow - поток тнзоров) - это фреймворк для определения и выполнения вычислений, включающих тензоры. Тензор - это обобщение векторов и матриц к потенциально более высоким измерениям. Внутри, TensorFlow представляет тензоры как n-мерные массивы базовых типов данных.

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

tf.Tensor имеет следующие свойства:

  • тип данных (float32, int32, или string, например)
  • форма (shape)

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

Основные типы тензоров следующие: .

  • tf.Variable
  • tf.constant
  • tf.placeholder
  • tf.SparseTensor

За исключением tf.Variable, значение тензора неизменно, это означает, что в контексте одного исполнения тензоры имеют единственное значение. Однако, оценка одного и того же тензора дважды может вернуть разные значения; например, когда тензор может быть результатом чтения данных с диска или генерации случайного числа.

Разряд (Rank)

Разряд tf.Tensor объекта - это количество его измерений. Синонимы разряда в данном случае - это порядок, степень, размерность. Следует отметить, что разряд в TensorFlow - это не то же самое, что и разряд матрицы в математике. Как показывает следующая таблица каждый разряд в TensorFlow соответствует различным математическим сущностям:

РазрядМатематическая сущность
0Скаляр (только величина)
1Вектор (величина и направление)
2Матрица (таблица чисел)
33-Тензор (куб чисел)
nn-Тензор (n-мерный тензор)

Разряд 0

Следующий код демонстрирует создание нескольких переменных 0 разряда:

mammal = tf.Variable("Elephant", tf.string)
ignition = tf.Variable(451, tf.int16)
floating = tf.Variable(3.14159265359, tf.float64)
its_complicated = tf.Variable(12.3 - 4.85j, tf.complex64)

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

Разряд 1

Для того чтобы создать объект tf.Tensor 1 разряда, можно передать список элементов, как первоначальное значение. Например:

mystr = tf.Variable(["Hello"], tf.string)
cool_numbers  = tf.Variable([3.14159, 2.71828], tf.float32)
first_primes = tf.Variable([2, 3, 5, 7, 11], tf.int32)
its_very_complicated = tf.Variable([12.3 - 4.85j, 7.5 - 6.23j], 
                                   tf.complex64)

Более высокие разряды

tf.Tensor объект 2 разряда состоит по крайней мере из одного ряда (row) и одной колонки (cell):

mymat = tf.Variable([[7],[11]], tf.int16)
myxor = tf.Variable([[False, True],[True, False]], tf.bool)
linear_squares = tf.Variable([[4], [9], [16], [25]], tf.int32)
squarish_squares = tf.Variable([ [4, 9], [16, 25] ], tf.int32)
rank_of_squares = tf.rank(squarish_squares)
mymatC = tf.Variable([[7],[11]], tf.int32)

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

my_image = tf.zeros([10, 299, 299, 3])  
# пакет x высота x ширина x цвет

Получение разряда tf.Tensor объекта

Для того чтобы определить разряд tf.Tensor объекта вызываем tf.rank метод. Например, следующий метод программно определяет разряд tf.Tensor объекта, созданного в предыдущей секции:

r = tf.rank(my_image) 
# после исполнения графа, r будет содержать значение 4

Ссылка на tf.Tensor слои

Ввиду того, что tf.Tensor - это n-мерный массив ячеек, для того чтобы получить доступ к единичной ячейке в tf.Tensor вам необходимо определить n индексы.

Для тензора 0 разряда (скаляр) индексы не требуются, ввиду того, что это единственное число.

Для тензора 1 разряда (вектор), передавая единственный индекса позволяет получить доступ к числу.

my_scalar = my_vector[2]

Отметим, что индекс переданный в [] сам может быть tf.Tensor скаляром, если вы хотите динамически выбирать элемент из вектора.

Для тензоров разряда 2 или выше ситуация более интересная. Для tf.Tensor разряда 2 передача двух чисел возвращает скаляр:

my_scalar = my_matrix[1, 2]

Передача единственного числа, однако, возвращает подвектор матрицы:

my_row_vector = my_matrix[2]
my_column_vector = my_matrix[:, 3]

: нотация - это python синтаксис для получения слоев - "оставить это измерение целиком". Это полезно для тензоров высоких рангов, ввиду того что это позволяет получать доступ к субвекторам, субматрицам и даже другим субтензорам.

Форма (Shape)

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

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

РазрядФормаРазмер измеренияПример
0[]0-D0-D тензор. Скаляр.
1[D0]1-D1-D тензор с формой [5].
2[D0,D1]2-D2-D тензор с формой [3,4].
3[D0,D1,D2]3-D3-D тензор с формой [1,4,3].
n[D0,D1,... Dn-1]n-Dn-D тензор с формой [D0, D1, ... Dn-1].

Формы могут быть представлены через Python листы(lists) / таплы(tuples) чисел, или с tf.TensorShape.

Получение формы tf.Tensor объекта

Существует два способа получения формы tf.Tensor объекта. В ходе построения графа, часто требуется спрашивать что уже известно о форме тензора. Это может быть сделано посредством метода shape tf.Tensor объекта. Этот метод возвращает TensorShape объект, который является удобным путем представления частично-определенных форм (ввиду того что при построении графа не все формы могут быть полностью известны).

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

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

zeros = tf.zeros(my_matrix.shape[1])

Изменение формы tf.Tensor объекта

Количество элементов в тензоре - это продукт размеров всех его форм. Количество элементов скаляра всегда 1. Ввиду того что часто существует много различных форм, которые имеют одинаковое количество элементов, зачастую удобно иметь возможность изменять форму tf.Tensor, сохраняя его элементы фиксированными. Этого можно достигнуть с tf.reshape.

Следующие примеры показывают как менять форму (переформировать)(reshape) тензоров:

При исполнении кода с русскоязычным комментарием не забывайте удалять комментарий либо выставлять строку
# -*- coding: utf-8 -*-
в начале скрипта.

rank_three_tensor = tf.ones([3, 4, 5])

# Переформируем существующее содержимое
# в 6x10 матрицу
matrix = tf.reshape(rank_three_tensor, [6, 10])  

# Переформируем существующее содержимое
# в 3x20 матрицу.
# -1 сообщает методу reshape
# вычислить размер этого измерения.

matrixB = tf.reshape(matrix, [3, -1]) 


# Переформируем существующее содержимое
# в 4x3x5 тензор.

matrixAlt = tf.reshape(matrixB, [4, 3, -1])  

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

yet_another = tf.reshape(matrixAlt, [13, 2, -1])  # ERROR!

Типы данных (Data types)

В дополнение к размерности, тензоры имеют типы данных (tf.DType).

Невозможно иметь tf.Tensor с более чем одним типом данных. Возможно, однако, сериализовать произвольные структуры данных как строки и сохранить их в tf.Tensor.

Также доступно менять (cast) tf.Tensors из одного типа данных в другой, используя tf.cast:

# Переводим константный цельночисловой тензор в нецельночисловой.
float_tensor = tf.cast(tf.constant([1, 2, 3]), dtype=tf.float32)

Для того чтобы выяснить текущий тип данных тензора используйте Tensor.dtype свойство.

При создании tf.Tensor из python объекта можно опционально задать тип данных. Если тип данных не задан TensorFlow выберет тип данных, который может представлять ваши данные. TensorFlow преобразует Python целый числа (integer) в tf.int32, Python нецелые числа (float) в tf.float32. В остальных случаях TensorFlow использует правила, которые использует numpy при преобразовании массивов.

Оценка тензоров

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

Простейший способ оценить тензор - это использовать Tensor.eval метод. Например:

constant = tf.constant([1, 2, 3])
tensor = constant * constant
print(tensor.eval())

eval метод работает только когда дефолтная tf.Session активна.

Tensor.eval возвращает numpy массив с тем же содержимым, что и тензор.

Иногда, невозможно оценить tf.Tensor без контекста, потому что его значение может зависеть от динамической информации, которая не доступна. Например, тензоры, которые зависят от заполнителей не могут быть оценены без предоставления значения для этого заполнителя.

p = tf.placeholder(tf.float32)
t = p + 1.0

# Данная операция не удастся,
# ввиду того, что заполнитель не получил значения.
t.eval() 

# Эта операция удастся, потому что мы передали значение для заполнителя.
t.eval(feed_dict={p:2.0})  

Отметим, что доступно наполнение любого tf.Tensor, а не только заполнителей.

Другие конструкции моделей могут сделать оценку tf.Tensor сложной. TensorFlow не может напрямую оценивать tf.Tensor определенный внутри функций или конструкции вне потока контроля. Если tf.Tensor зависит от значения из очереди, оценка tf.Tensor будет работать только когда что-либо пришло из очереди, в ином случае оценка зависнет. При работе с очередями вызывайте tf.train.start_queue_runners до оценки любого tf.Tensor.

Печать тензоров

Для целей отладки вам может потребоваться напечатать значение tf.Tensor. Хотя tfdbg предоставляет поддержку сложной отладки, TensorFlow также имеет операцию для печати значения tf.Tensor напрямую.

Следующий способ редко используется для печати tf.Tensor:

t = <<какая-либо tensorflow операция>>
print(t)  
# Это напечатает символический тензор, когда граф построен.
# Этот тензор не имеет значения в этом контексте.

Этот код печатает tf.Tensor объект (который представляет отложенное вычисление), а не его значение. Вместо этого TensorFlow предоставляет tf.Print операцию, которая возвращает ее первый тензор аргумент не измененным, в то время как печатает набор тензоров, переданных вторым аргументом.

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

t = <<какая-либо tensorflow операция>>
tf.Print(t, [t])  # Это ничего не делает

# Здесь мы используем значение, возвращенное tf.Print
t = tf.Print(t, [t])  

# Теперь, когда result оценен, значение `t` будет напечатано.
result = t + 1  

Когда вы оцениваете result, вы оцениваете все от чего зависит result. Ввиду того, что result зависит от t, оценка t имеет сторонний эффект печати его значения (старое значение t), t оказывается напечатан.