Повышение производительности тензорфлова без гибкости

Повышение производительности тензорфлова без гибкости

13 августа 2025 г.

Обзор контента

  • Настраивать
  • Основы
  • Использование
  • Трассировка
  • Контроль обращения
  • Получение конкретных функций
  • Получение графиков
  • Отладка
  • Автограф -преобразование
  • Условия
  • Петли

В TensorFlow 2,Жесткое исполнениевключен по умолчанию. Пользовательский интерфейс является интуитивно понятным и гибким (выполнять одноразовые операции намного проще и быстрее), но это может произойти за счет производительности и развертываемости.

Вы можете использоватьtf.functionсделать графики из ваших программ. Это инструмент преобразования, который создает независимые от Python графики DataFlow из вашего кода Python. Это поможет вам создать производительные и портативные модели, и это требуется для использованияSavedModelПолем

Это руководство поможет вам осмыслить, какtf.functionРаботает под капотом, так что вы можете эффективно использовать его.

Основные выводы и рекомендации:

  • Отладка в нетерпеливом режиме, затем украсьте с помощью@tf.functionПолем
  • Не полагайтесь на побочные эффекты Python, такие как мутация объекта или приложения списка.
  • tf.functionлучше всего работает с Tensorflow Ops; Numpy и Python Calls преобразуются в константы.

Настраивать

import tensorflow as tf

2024-08-15 02:57:28.958444: E external/local_xla/xla/stream_executor/cuda/cuda_fft.cc:485] Unable to register cuFFT factory: Attempting to register factory for plugin cuFFT when one has already been registered
2024-08-15 02:57:28.979712: E external/local_xla/xla/stream_executor/cuda/cuda_dnn.cc:8454] Unable to register cuDNN factory: Attempting to register factory for plugin cuDNN when one has already been registered
2024-08-15 02:57:28.986177: E external/local_xla/xla/stream_executor/cuda/cuda_blas.cc:1452] Unable to register cuBLAS factory: Attempting to register factory for plugin cuBLAS when one has already been registered

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

import traceback
import contextlib

# Some helper code to demonstrate the kinds of errors you might encounter.
@contextlib.contextmanager
def assert_raises(error_class):
  try:
    yield
  except error_class as e:
    print('Caught expected exception \n  {}:'.format(error_class))
    traceback.print_exc(limit=2)
  except Exception as e:
    raise e
  else:
    raise Exception('Expected {} to be raised but no error was raised!'.format(
        error_class))

Основы

Использование

Аtf.functionчто вы определяете (например, путем применения@tf.functionДекоратор) похож на операцию с основной тензором: вы можете выполнить его с нетерпением; Вы можете вычислить градиенты; и так далее.

@tf.function  # The decorator converts `add` into a `PolymorphicFunction`.
def add(a, b):
  return a + b

add(tf.ones([2, 2]), tf.ones([2, 2]))  #  [[2., 2.], [2., 2.]]

WARNING: All log messages before absl::InitializeLog() is called are written to STDERR
I0000 00:00:1723690651.607368  167534 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355
I0000 00:00:1723690651.611235  167534 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355
I0000 00:00:1723690651.614398  167534 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355
I0000 00:00:1723690651.618234  167534 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355
I0000 00:00:1723690651.629890  167534 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355
I0000 00:00:1723690651.633433  167534 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355
I0000 00:00:1723690651.636337  167534 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355
I0000 00:00:1723690651.639748  167534 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355
I0000 00:00:1723690651.643233  167534 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355
I0000 00:00:1723690651.646588  167534 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355
I0000 00:00:1723690651.649526  167534 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355
I0000 00:00:1723690651.652949  167534 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355
I0000 00:00:1723690652.865955  167534 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355
I0000 00:00:1723690652.868101  167534 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355
I0000 00:00:1723690652.870112  167534 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355
I0000 00:00:1723690652.872121  167534 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355
I0000 00:00:1723690652.874165  167534 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355
I0000 00:00:1723690652.876153  167534 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355
I0000 00:00:1723690652.878068  167534 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355
I0000 00:00:1723690652.879960  167534 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355
I0000 00:00:1723690652.881883  167534 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355
I0000 00:00:1723690652.883841  167534 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355
I0000 00:00:1723690652.885768  167534 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355
I0000 00:00:1723690652.887660  167534 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355
I0000 00:00:1723690652.926250  167534 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355
I0000 00:00:1723690652.928321  167534 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355
I0000 00:00:1723690652.930298  167534 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355
I0000 00:00:1723690652.932288  167534 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355
I0000 00:00:1723690652.934241  167534 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355
I0000 00:00:1723690652.936253  167534 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355
I0000 00:00:1723690652.938172  167534 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355
I0000 00:00:1723690652.940080  167534 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355
I0000 00:00:1723690652.942041  167534 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355
I0000 00:00:1723690652.944593  167534 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355
I0000 00:00:1723690652.946947  167534 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355
I0000 00:00:1723690652.949245  167534 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355
<tf.Tensor: shape=(2, 2), dtype=float32, numpy=
array([[2., 2.],
       [2., 2.]], dtype=float32)>

v = tf.Variable(1.0)
with tf.GradientTape() as tape:
  result = add(v, 1.0)
tape.gradient(result, v)

<tf.Tensor: shape=(), dtype=float32, numpy=1.0>

Вы можете использоватьtf.functionS внутри другихtf.functionс

@tf.function
def dense_layer(x, w, b):
  return add(tf.matmul(x, w), b)

dense_layer(tf.ones([3, 2]), tf.ones([2, 2]), tf.ones([2]))

<tf.Tensor: shape=(3, 2), dtype=float32, numpy=
array([[3., 3.],
       [3., 3.],
       [3., 3.]], dtype=float32)>

tf.functionS может быть быстрее, чем нетерпеливый код, особенно для графиков со многими небольшими операциями. Но для графиков с несколькими дорогими операциями (например, свертыми) вы можете не увидеть много ускорения.

import timeit
conv_layer = tf.keras.layers.Conv2D(100, 3)

@tf.function
def conv_fn(image):
  return conv_layer(image)

image = tf.zeros([1, 200, 200, 100])
# Warm up
conv_layer(image); conv_fn(image)
print("Eager conv:", timeit.timeit(lambda: conv_layer(image), number=10))
print("Function conv:", timeit.timeit(lambda: conv_fn(image), number=10))
print("Note how there's not much difference in performance for convolutions")

W0000 00:00:1723690654.228267  167534 gpu_timer.cc:114] Skipping the delay kernel, measurement accuracy will be reduced
W0000 00:00:1723690654.285525  167534 gpu_timer.cc:114] Skipping the delay kernel, measurement accuracy will be reduced
W0000 00:00:1723690654.290477  167534 gpu_timer.cc:114] Skipping the delay kernel, measurement accuracy will be reduced
W0000 00:00:1723690654.295072  167534 gpu_timer.cc:114] Skipping the delay kernel, measurement accuracy will be reduced
W0000 00:00:1723690654.299820  167534 gpu_timer.cc:114] Skipping the delay kernel, measurement accuracy will be reduced
W0000 00:00:1723690654.304580  167534 gpu_timer.cc:114] Skipping the delay kernel, measurement accuracy will be reduced
W0000 00:00:1723690654.322737  167534 gpu_timer.cc:114] Skipping the delay kernel, measurement accuracy will be reduced
W0000 00:00:1723690654.327483  167534 gpu_timer.cc:114] Skipping the delay kernel, measurement accuracy will be reduced
W0000 00:00:1723690654.332646  167534 gpu_timer.cc:114] Skipping the delay kernel, measurement accuracy will be reduced
W0000 00:00:1723690654.337747  167534 gpu_timer.cc:114] Skipping the delay kernel, measurement accuracy will be reduced
W0000 00:00:1723690654.343046  167534 gpu_timer.cc:114] Skipping the delay kernel, measurement accuracy will be reduced
W0000 00:00:1723690654.347480  167534 gpu_timer.cc:114] Skipping the delay kernel, measurement accuracy will be reduced
W0000 00:00:1723690654.361780  167534 gpu_timer.cc:114] Skipping the delay kernel, measurement accuracy will be reduced
W0000 00:00:1723690654.370325  167534 gpu_timer.cc:114] Skipping the delay kernel, measurement accuracy will be reduced
W0000 00:00:1723690654.381185  167534 gpu_timer.cc:114] Skipping the delay kernel, measurement accuracy will be reduced
W0000 00:00:1723690654.405763  167534 gpu_timer.cc:114] Skipping the delay kernel, measurement accuracy will be reduced
Eager conv: 0.011224052000216034
Function conv: 0.005400947000453016
Note how there's not much difference in performance for convolutions

Трассировка

Этот раздел раскрывает, какtf.functionРаботает под капотом, включая детали реализациикоторый может измениться в будущемПолем Однако, как только вы поймете, почему и когда происходит отслеживание, это намного проще использоватьtf.functionЭффективно!

Что такое «трассировка»? Функция TF. Запускает вашу программу на графике TensorFlow. Тем не менее, TF.Graph не может представлять все вещи, которые вы пишете в нетерпеливой программе TensorFlow. Например, Python поддерживает полиморфизм, но TF.Graph требует, чтобы его входы имели указанный тип данных и измерение. Или вы можете выполнять побочные задачи, такие как чтение аргументов командной строки, повышение ошибки или работа с более сложным объектом Python; Ни одна из этих вещей не может работать в TF.Graph.

TF.Function Brids этот пробел, разделяя ваш код на два этапа:

  1. На первом этапе, называемом «трассировкой», tf.function создает новый TF.Graph. Код Python работает нормально, но все операции TensorFlow (например, добавление двух тензоров) отложены: они захватываются TF.Graph и не запускаются.
  2. На втором этапе TF.Graph, который содержит все, что было отложено на первом этапе, запускается. Этот этап намного быстрее, чем этап трассировки.

В зависимости от его входов, tf.function не всегда будет работать на первом этапе, когда она будет вызвана. См. «Правила отслеживания» ниже, чтобы лучше понять, как это делает это решимость. Пропустить первый этап и только выполнение второго этапа - это то, что дает вам высокую производительность Tensorflow.

Когда tf.function действительно решает отслеживать, за этапом трассировки сразу же следует второй этап, поэтому вызывая функцию TF. Оба создает и запускает TF.Graph. Позже вы увидите, как вы можете запустить только этап трассировки с помощью get_concrete_function.

Когда вы передаете аргументы разных типов в функцию TF, оба этапа запускаются:

@tf.function
def double(a):
  print("Tracing with", a)
  return a + a

print(double(tf.constant(1)))
print()
print(double(tf.constant(1.1)))
print()
print(double(tf.constant("a")))
print()

Tracing with Tensor("a:0", shape=(), dtype=int32)
tf.Tensor(2, shape=(), dtype=int32)

Tracing with Tensor("a:0", shape=(), dtype=float32)
tf.Tensor(2.2, shape=(), dtype=float32)

Tracing with Tensor("a:0", shape=(), dtype=string)
tf.Tensor(b'aa', shape=(), dtype=string)

Обратите внимание, что если вы неоднократно вызываетеtf.functionС тем же типом аргумента, Tensorflow пропустит стадию трассировки и повторно использует ранее прослеженный график, поскольку сгенерированный график будет идентичным.

# This doesn't print 'Tracing with ...'
print(double(tf.constant("b")))

tf.Tensor(b'bb', shape=(), dtype=string)

Вы можете использоватьpretty_printed_concrete_signatures()Чтобы увидеть все доступные следы:

print(double.pretty_printed_concrete_signatures())

Input Parameters:
  a (POSITIONAL_OR_KEYWORD): TensorSpec(shape=(), dtype=tf.int32, name=None)
Output Type:
  TensorSpec(shape=(), dtype=tf.int32, name=None)
Captures:
  None

Input Parameters:
  a (POSITIONAL_OR_KEYWORD): TensorSpec(shape=(), dtype=tf.float32, name=None)
Output Type:
  TensorSpec(shape=(), dtype=tf.float32, name=None)
Captures:
  None

Input Parameters:
  a (POSITIONAL_OR_KEYWORD): TensorSpec(shape=(), dtype=tf.string, name=None)
Output Type:
  TensorSpec(shape=(), dtype=tf.string, name=None)
Captures:
  None

Пока вы видели этоtf.functionСоздает кэшированный, динамический рассеянный слой над логикой трассировки графика Tensorflow. Быть более конкретным в терминологии:

  • Аtf.Graphявляется необработанным, агрессивным языком, портативным представлением вычисления с тензорфлоу.
  • Трассировка - это процесс, посредством которого новыйtf.GraphS генерируется из кода Python.
  • Экземплярtf.Graphспециализируется на конкретных типах ввода, которыми он был прослежен. Различные типы требуют обращения.
  • Каждый прослеживаетсяtf.Graphимеет соответствующийConcreteFunctionПолем
  • Аtf.functionуправляет кэшемConcreteFunctionS и выбирает правильный для ваших входов.
  • tf.functionОкутает функцию Python, которая будет прослежена, возвращаяtf.types.experimental.PolymorphicFunctionобъект.

Правила трассировки

Когда называется, аtf.functionсначала оценивает тип каждого входного аргумента, используяtf.types.experimental.TraceTypeкаждого аргумента. Это используется для построенияtf.types.experimental.FunctionTypeописывая подпись желаемогоConcreteFunctionПолем Мы сравниваем этоFunctionTypeвFunctionTypeS существующихConcreteFunctionс Если совпадениеConcreteFunctionнайдено, звонок отправляется на него. Если совпадение не найдено, новыйConcreteFunctionпрослежены на желаемоеFunctionTypeПолем

Если найдено несколько совпадений, выбрана самая специфическая подпись. Сопоставление осуществляетсяподтинг, например, как обычные вызовы функций в C ++ или Java. Например,TensorShape([1, 2])является подтипомTensorShape([None, None])и так звонок к функции TF.TensorShape([1, 2])может быть отправлен вConcreteFunctionпроизводится сTensorShape([None, None])но если аConcreteFunctionсTensorShape([1, None])Также существует, тогда это будет приоритет, так как это более конкретно.

АTraceTypeопределяется из входных аргументов следующим образом:

  • ДляTensor, тип параметризованTensorS.dtypeиshape; Ранные формы представляют собой подтип небегальных фигур; Фиксированные измерения являются подтипом неизвестных измерений

  • ДляVariable, тип похож наTensor, но также включает в себя уникальный идентификатор ресурса переменной, необходимый для правильного управления проводом зависимостей

  • Для примитивных значений Python тип соответствуетценитьсам Например,TraceTypeзначения3являетсяLiteralTraceType<3>, нетintПолем

  • Для Python упорядочил контейнеры, такие какlistиtupleи т. д. тип параметризован типами их элементов; Например, тип[1, 2]являетсяListTraceType<LiteralTraceType<1>, LiteralTraceType<2>>и тип для[2, 1]являетсяListTraceType<LiteralTraceType<2>, LiteralTraceType<1>> which is different.

  • Для отображений Python, таких какdictТип также является отображением из тех же клавиш, но по типам значений вместо фактических значений. Например, тип{1: 2, 3: 4}, являетсяMappingTraceType<<KeyValue<1, LiteralTraceType<2>>>, <KeyValue<3, LiteralTraceType<4>>>>Полем Однако, в отличие от упорядоченных контейнеров,{1: 2, 3: 4}и{3: 4, 1: 2}имеют эквивалентные типы.

  • Для объектов Python, которые реализуют__tf_tracing_type__Метод, тип - это то, что этот метод возвращает.

  • Для любых других объектов Python тип является общимTraceTypeи соответствующий предшествующий

    • Сначала он проверяет, является ли объект тем же объектом, который используется в предыдущем трассировке (с помощью Pythonid()илиis) Обратите внимание, что это все равно будет соответствовать, если объект изменился, поэтому, если вы используете объекты Python какtf.functionаргументы лучше всего использоватьнеизменени
    • Далее он проверяет, равен ли объект объекту, используемому в предыдущем трассировке (с помощью Python==)

    Обратите внимание, что эта процедура только сохраняетSleedRefдля объекта и, следовательно, работает только до тех пор, пока объект находится в объеме/не удален.

Примечание:TraceTypeоснован наtf.functionвходные параметры, поэтому изменения в глобальном исвободные переменныеОдно не создаст нового следа. Видетьэтот разделдля рекомендуемых практик при работе с Python Global и Free переменными.

Контроль обращения

Поездка, когда вашtf.functionСоздает более одного трассировки, помогает гарантировать, что TensorFlow генерирует правильные графики для каждого набора входов. Однако трассировка - это дорогая операция! Если тыtf.functionПосложняет новый график для каждого вызова, вы обнаружите, что ваш код выполняется медленнее, чем если бы вы не использовалиtf.functionПолем

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

Пройти фиксированнуюinput_signatureкtf.function

Это силыtf.functionограничить себя только однимtf.types.experimental.FunctionTypeсостоит из типов, перечисленныхinput_signatureПолем Звонки, которые не могут быть отправлены на этоFunctionTypeвыбросит ошибку.

@tf.function(input_signature=(tf.TensorSpec(shape=[None], dtype=tf.int32),))
def next_collatz(x):
  print("Tracing with", x)
  return tf.where(x % 2 == 0, x // 2, 3 * x + 1)

print(next_collatz(tf.constant([1, 2])))
# You specified a 1-D tensor in the input signature, so this should fail.
with assert_raises(TypeError):
  next_collatz(tf.constant([[1, 2], [3, 4]]))

# You specified an int32 dtype in the input signature, so this should fail.
with assert_raises(TypeError):
  next_collatz(tf.constant([1.0, 2.0]))

Tracing with Tensor("x:0", shape=(None,), dtype=int32)
tf.Tensor([4 1], shape=(2,), dtype=int32)
Caught expected exception 
  <class 'TypeError'>:
Caught expected exception 
  <class 'TypeError'>:
Traceback (most recent call last):
  File "/tmpfs/tmp/ipykernel_167534/3551158538.py", line 8, in assert_raises
    yield
  File "/tmpfs/tmp/ipykernel_167534/3657259638.py", line 9, in <module>
    next_collatz(tf.constant([[1, 2], [3, 4]]))
TypeError: Binding inputs to tf.function failed due to `Can not cast TensorSpec(shape=(2, 2), dtype=tf.int32, name=None) to TensorSpec(shape=(None,), dtype=tf.int32, name=None)`. Received args: (<tf.Tensor: shape=(2, 2), dtype=int32, numpy=
array([[1, 2],
       [3, 4]], dtype=int32)>,) and kwargs: {} for signature: (x: TensorSpec(shape=(None,), dtype=tf.int32, name=None)).
Traceback (most recent call last):
  File "/tmpfs/tmp/ipykernel_167534/3551158538.py", line 8, in assert_raises
    yield
  File "/tmpfs/tmp/ipykernel_167534/3657259638.py", line 13, in <module>
    next_collatz(tf.constant([1.0, 2.0]))
TypeError: Binding inputs to tf.function failed due to `Can not cast TensorSpec(shape=(2,), dtype=tf.float32, name=None) to TensorSpec(shape=(None,), dtype=tf.int32, name=None)`. Received args: (<tf.Tensor: shape=(2,), dtype=float32, numpy=array([1., 2.], dtype=float32)>,) and kwargs: {} for signature: (x: TensorSpec(shape=(None,), dtype=tf.int32, name=None)).

Используйте неизвестные измерения для гибкости

Поскольку TensorFlow соответствует тензорам в зависимости от их формы, используяNoneразмер как подстановочный знак позволитtf.functionS для повторного использования трассов для ввода переменного размера. Вход переменного размера может произойти, если у вас есть последовательности разной длины или изображения разных размеров для каждой партии. Вы можете проверитьТрансформаториГлубокая мечтаУчебные пособия для примеров.

@tf.function(input_signature=(tf.TensorSpec(shape=[None], dtype=tf.int32),))
def g(x):
  print('Tracing with', x)
  return x

# No retrace!
print(g(tf.constant([1, 2, 3])))
print(g(tf.constant([1, 2, 3, 4, 5])))

Tracing with Tensor("x:0", shape=(None,), dtype=int32)
tf.Tensor([1 2 3], shape=(3,), dtype=int32)
tf.Tensor([1 2 3 4 5], shape=(5,), dtype=int32)

Использоватьreduce_retracingдля автоматической гибкости

Когдаreduce_retracingвключено,tf.functionАвтоматически идентифицирует супертипы типов входов, которые он наблюдает и выбирает автоматически отслеживать более обобщенные графики. Это менее эффективно, чем установлениеinput_signatureнепосредственно, но полезно, когда необходимо поддерживать многие типы.

@tf.function(reduce_retracing=True)
def g(x):
  print('Tracing with', x)
  return x

# Traces once.
print(g(tf.constant([1, 2, 3])))

# Traces again, but more generalized this time.
print(g(tf.constant([1, 2, 3, 4, 5])))

# No more tracing!
print(g(tf.constant([1, 2, 3, 4, 5, 6, 7])))
print(g(tf.constant([1, 2, 3, 4, 5, 6, 7, 8, 9])))

Tracing with Tensor("x:0", shape=(3,), dtype=int32)
tf.Tensor([1 2 3], shape=(3,), dtype=int32)
Tracing with Tensor("x:0", shape=(None,), dtype=int32)
tf.Tensor([1 2 3 4 5], shape=(5,), dtype=int32)
tf.Tensor([1 2 3 4 5 6 7], shape=(7,), dtype=int32)
tf.Tensor([1 2 3 4 5 6 7 8 9], shape=(9,), dtype=int32)

Пройти тензоры вместо литералов Python

Часто аргументы Python используются для контроля гиперпараметров и конструкций графика - например,, например,num_layers=10илиtraining=Trueилиnonlinearity='relu'Полем Таким образом, если аргумент Python изменится, имеет смысл, что вам придется просматривать график.

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

def train_one_step():
  pass

@tf.function
def train(num_steps):
  print("Tracing with num_steps = ", num_steps)
  tf.print("Executing with num_steps = ", num_steps)
  for _ in tf.range(num_steps):
    train_one_step()

print("Retracing occurs for different Python arguments.")
train(num_steps=10)
train(num_steps=20)

print()
print("Traces are reused for Tensor arguments.")
train(num_steps=tf.constant(10))
train(num_steps=tf.constant(20))

Retracing occurs for different Python arguments.
Tracing with num_steps =  10
Executing with num_steps =  10
Tracing with num_steps =  20
Executing with num_steps =  20

Traces are reused for Tensor arguments.
Tracing with num_steps =  Tensor("num_steps:0", shape=(), dtype=int32)
Executing with num_steps =  10
Executing with num_steps =  20

Если вам нужно затопить обработку, создайте новыйtf.functionПолем Отдельныйtf.functionОбъекты гарантированы, чтобы не делиться следами.

def f():
  print('Tracing!')
  tf.print('Executing')

tf.function(f)()
tf.function(f)()

Tracing!
Executing
Tracing!
Executing

Используйте протокол трассировки

Там, где это возможно, вы должны предпочесть преобразовать тип Python вtf.experimental.ExtensionTypeвместо. Более того,TraceTypeизExtensionTypeявляетсяtf.TypeSpecсвязан с этим. Поэтому, если необходимо, вы можете просто переопределить по умолчаниюtf.TypeSpecЧтобы взять под контрольExtensionTypeS.Tracing ProtocolПолем СмНастройка типа ExtensionTypeраздел вТипы расширенияРуководство для деталей.

В противном случае для прямого контроля над тем, когдаtf.functionдолжен просматривать в отношении конкретного типа Python, вы можете реализоватьTracing Protocolдля этого самостоятельно.

@tf.function
def get_mixed_flavor(fruit_a, fruit_b):
  return fruit_a.flavor + fruit_b.flavor

class Fruit:
  flavor = tf.constant([0, 0])

class Apple(Fruit):
  flavor = tf.constant([1, 2])

class Mango(Fruit):
  flavor = tf.constant([3, 4])

# As described in the above rules, a generic TraceType for `Apple` and `Mango`
# is generated (and a corresponding ConcreteFunction is traced) but it fails to
# match the second function call since the first pair of Apple() and Mango()
# have gone out out of scope by then and deleted.
get_mixed_flavor(Apple(), Mango()) # Traces a new concrete function
get_mixed_flavor(Apple(), Mango()) # Traces a new concrete function again

# However, each subclass of the `Fruit` class has a fixed flavor, and you
# can reuse an existing traced concrete function if it was the same
# subclass. Avoiding such unnecessary tracing of concrete functions
# can have significant performance benefits.

class FruitTraceType(tf.types.experimental.TraceType):
  def __init__(self, fruit):
    self.fruit_type = type(fruit)
    self.fruit_value = fruit

  def is_subtype_of(self, other):
      # True if self subtypes `other` and `other`'s type matches FruitTraceType.
      return (type(other) is FruitTraceType and
              self.fruit_type is other.fruit_type)

  def most_specific_common_supertype(self, others):
      # `self` is the specific common supertype if all input types match it.
      return self if all(self == other for other in others) else None

  def placeholder_value(self, placeholder_context=None):
      # Use the fruit itself instead of the type for correct tracing.
      return self.fruit_value

  def __eq__(self, other):
    return type(other) is FruitTraceType and self.fruit_type == other.fruit_type

  def __hash__(self):
    return hash(self.fruit_type)

class FruitWithTraceType:

  def __tf_tracing_type__(self, context):
    return FruitTraceType(self)

class AppleWithTraceType(FruitWithTraceType):
  flavor = tf.constant([1, 2])

class MangoWithTraceType(FruitWithTraceType):
  flavor = tf.constant([3, 4])

# Now if you try calling it again:
get_mixed_flavor(AppleWithTraceType(), MangoWithTraceType()) # Traces a new concrete function
get_mixed_flavor(AppleWithTraceType(), MangoWithTraceType()) # Re-uses the traced concrete function

<tf.Tensor: shape=(2,), dtype=int32, numpy=array([4, 6], dtype=int32)>

Получение конкретных функций

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

print("Obtaining concrete trace")
double_strings = double.get_concrete_function(tf.constant("a"))
print("Executing traced function")
print(double_strings(tf.constant("a")))
print(double_strings(a=tf.constant("b")))

Obtaining concrete trace
Executing traced function
tf.Tensor(b'aa', shape=(), dtype=string)
tf.Tensor(b'bb', shape=(), dtype=string)

# You can also call get_concrete_function on an InputSpec
double_strings_from_inputspec = double.get_concrete_function(tf.TensorSpec(shape=[], dtype=tf.string))
print(double_strings_from_inputspec(tf.constant("c")))

tf.Tensor(b'cc', shape=(), dtype=string)

ПечатьConcreteFunctionОтображает резюме его входных аргументов (с типами) и его выходного типа.

print(double_strings)

ConcreteFunction Input Parameters:
  a (POSITIONAL_OR_KEYWORD): TensorSpec(shape=(), dtype=tf.string, name=None)
Output Type:
  TensorSpec(shape=(), dtype=tf.string, name=None)
Captures:
  None

Вы также можете напрямую получить подпись конкретной функции.

print(double_strings.function_type)

(a: TensorSpec(shape=(), dtype=tf.string, name=None)) -> TensorSpec(shape=(), dtype=tf.string, name=None)

Использование конкретного трассировки с несовместимыми типами вынесет ошибку

with assert_raises(tf.errors.InvalidArgumentError):
  double_strings(tf.constant(1))

Caught expected exception 
  <class 'tensorflow.python.framework.errors_impl.InvalidArgumentError'>:
Traceback (most recent call last):
  File "/tmpfs/src/tf_docs_env/lib/python3.9/site-packages/tensorflow/python/eager/polymorphic_function/function_type_utils.py", line 442, in bind_function_inputs
    bound_arguments = function_type.bind_with_defaults(
  File "/tmpfs/src/tf_docs_env/lib/python3.9/site-packages/tensorflow/core/function/polymorphism/function_type.py", line 277, in bind_with_defaults
    with_default_args[arg_name] = constraint.cast(
TypeError: Can not cast TensorSpec(shape=(), dtype=tf.int32, name=None) to TensorSpec(shape=(), dtype=tf.string, name=None)

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/tmpfs/src/tf_docs_env/lib/python3.9/site-packages/tensorflow/python/eager/polymorphic_function/concrete_function.py", line 1179, in _call_impl
    return self._call_with_structured_signature(args, kwargs)
  File "/tmpfs/src/tf_docs_env/lib/python3.9/site-packages/tensorflow/python/eager/polymorphic_function/concrete_function.py", line 1259, in _call_with_structured_signature
    function_type_utils.canonicalize_function_inputs(
TypeError: Binding inputs to tf.function failed due to `Can not cast TensorSpec(shape=(), dtype=tf.int32, name=None) to TensorSpec(shape=(), dtype=tf.string, name=None)`. Received args: (<tf.Tensor: shape=(), dtype=int32, numpy=1>,) and kwargs: {} for signature: (a: TensorSpec(shape=(), dtype=tf.string, name=None)) -> TensorSpec(shape=(), dtype=tf.string, name=None).

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/tmpfs/tmp/ipykernel_167534/3551158538.py", line 8, in assert_raises
    yield
  File "/tmpfs/tmp/ipykernel_167534/3196284684.py", line 2, in <module>
    double_strings(tf.constant(1))
tensorflow.python.framework.errors_impl.InvalidArgumentError: cannot compute __inference_double_189 as input #0(zero-based) was expected to be a string tensor but is a int32 tensor [Op:__inference_double_189]

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

@tf.function
def pow(a, b):
  return a ** b

square = pow.get_concrete_function(a=tf.TensorSpec(None, tf.float32), b=2)
print(square)

ConcreteFunction Input Parameters:
  a (POSITIONAL_OR_KEYWORD): TensorSpec(shape=<unknown>, dtype=tf.float32, name=None)
  b (POSITIONAL_OR_KEYWORD): Literal[2]
Output Type:
  TensorSpec(shape=<unknown>, dtype=tf.float32, name=None)
Captures:
  None

assert square(tf.constant(10.0)) == 100

with assert_raises(TypeError):
  square(tf.constant(10.0), b=3)

Caught expected exception 
  <class 'TypeError'>:
Traceback (most recent call last):
  File "/tmpfs/src/tf_docs_env/lib/python3.9/site-packages/tensorflow/python/eager/polymorphic_function/function_type_utils.py", line 442, in bind_function_inputs
    bound_arguments = function_type.bind_with_defaults(
  File "/tmpfs/src/tf_docs_env/lib/python3.9/site-packages/tensorflow/core/function/polymorphism/function_type.py", line 277, in bind_with_defaults
    with_default_args[arg_name] = constraint.cast(
ValueError: Can not cast 3 to Literal[2]

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/tmpfs/src/tf_docs_env/lib/python3.9/site-packages/tensorflow/python/eager/polymorphic_function/concrete_function.py", line 1179, in _call_impl
    return self._call_with_structured_signature(args, kwargs)
  File "/tmpfs/src/tf_docs_env/lib/python3.9/site-packages/tensorflow/python/eager/polymorphic_function/concrete_function.py", line 1259, in _call_with_structured_signature
    function_type_utils.canonicalize_function_inputs(
TypeError: Binding inputs to tf.function failed due to `Can not cast 3 to Literal[2]`. Received args: (<tf.Tensor: shape=(), dtype=float32, numpy=10.0>,) and kwargs: {'b': 3} for signature: (a: TensorSpec(shape=<unknown>, dtype=tf.float32, name=None), b: Literal[2]) -> TensorSpec(shape=<unknown>, dtype=tf.float32, name=None).

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/tmpfs/src/tf_docs_env/lib/python3.9/site-packages/tensorflow/python/eager/polymorphic_function/concrete_function.py", line 1182, in _call_impl
    return self._call_with_flat_signature(args, kwargs)
  File "/tmpfs/src/tf_docs_env/lib/python3.9/site-packages/tensorflow/python/eager/polymorphic_function/concrete_function.py", line 1233, in _call_with_flat_signature
    raise TypeError(f"{self._flat_signature_summary()} got unexpected "
TypeError: pow(a) got unexpected keyword arguments: b.

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/tmpfs/tmp/ipykernel_167534/3551158538.py", line 8, in assert_raises
    yield
  File "/tmpfs/tmp/ipykernel_167534/2310937119.py", line 4, in <module>
    square(tf.constant(10.0), b=3)
TypeError: Binding inputs to tf.function failed due to `Can not cast 3 to Literal[2]`. Received args: (<tf.Tensor: shape=(), dtype=float32, numpy=10.0>,) and kwargs: {'b': 3} for signature: (a: TensorSpec(shape=<unknown>, dtype=tf.float32, name=None), b: Literal[2]) -> TensorSpec(shape=<unknown>, dtype=tf.float32, name=None).
Fallback to flat signature also failed due to: pow(a) got unexpected keyword arguments: b.

Получение графиков

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

graph = double_strings.graph
for node in graph.as_graph_def().node:
  print(f'{node.input} -> {node.name}')

[] -> a
['a', 'a'] -> add
['add'] -> Identity

На самом деле,tf.GraphS не подлежат напрямую. Мы на самом деле используемtf.types.experimental.AtomicFunctionЧтобы выполнить вычисления, описанныеtf.GraphПолем Вы можете получить доступ кAtomicFunctionописывая отслеживаниеtf.Graphи назовите это напрямую вместоConcreteFunction:

atomic_fn = double_strings.inference_fn
atomic_fn(tf.constant("a"))

<tf.Tensor: shape=(), dtype=string, numpy=b'aa'>

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

Отладка

В общем, код отладки проще в нетерпеливом режиме, чем внутриtf.functionПолем Вы должны убедиться, что ваш код выполняет без ошибок в нетерпеливом режиме перед украшением с помощьюtf.functionПолем Чтобы помочь в процессе отладки, вы можете позвонитьtf.config.run_functions_eagerly(True)чтобы глобально отключить и реконструироватьtf.functionПолем

При отслеживании проблем, которые появляются только внутриtf.function, вот несколько советов:

  • Простой старый питонprintВызовы выполняются только во время отслеживания, помогая вам отслеживать, когда ваша функция прослеживается.
  • tf.printВызовы будут выполняться каждый раз и могут помочь вам отслеживать промежуточные значения во время выполнения.
  • tf.debugging.enable_check_numericsэто простой способ отслеживать, где создаются NAN и Inf.
  • pdb(Отладчик Python) может помочь вам понять, что происходит во время трассировки. (Предостережение:pdbВернут вас в исходный код, преобразованный автографами.)

Автограф -преобразование

Автограф - это библиотека, которая по умолчанию вtf.function, и преобразует подмножество свинцового кода в Python в график, совместимый с Tensorflow Ops. Это включает в себя поток управления, какifВforВwhileПолем

Tensorflow Ops нравитсяtf.condиtf.while_loopПродолжайте работать, но поток управления часто легче писать и понимать, когда написано на Python.

# A simple loop

@tf.function
def f(x):
  while tf.reduce_sum(x) > 1:
    tf.print(x)
    x = tf.tanh(x)
  return x

f(tf.random.uniform([5]))

[0.722626925 0.640327692 0.725044 0.904435039 0.868018746]
[0.61853379 0.565122604 0.620023966 0.718450606 0.700366139]
[0.550106347 0.511768281 0.551144719 0.615948677 0.604600191]
[0.500599921 0.471321791 0.501377642 0.548301 0.540314913]
[0.462588847 0.439266682 0.463199914 0.499245733 0.493226349]
[0.432191819 0.413036436 0.432688653 0.461523771 0.456773371]
[0.407151431 0.391047835 0.407565802 0.431325316 0.427450746]
[0.386051297 0.372263193 0.386403859 0.406428277 0.403188676]
[0.367951065 0.355969697 0.368255854 0.38543576 0.382673979]
[0.352198243 0.341659099 0.352465183 0.367418766 0.365027398]
[0.338323593 0.328957736 0.33856 0.351731867 0.349634588]
[0.325979948 0.317583948 0.326191217 0.337910533 0.336051434]
[0.314903945 0.307320684 0.315094262 0.325610697 0.323947728]
[0.304891765 0.297997624 0.30506441 0.314571291 0.313072115]
[0.295782804 0.289479077 0.29594034 0.304590017 0.303229302]
[0.287448555 0.281655282 0.287593067 0.295507431 0.294265062]
[0.279784769 0.274436355 0.279917955 0.287195921 0.286055595]
[0.272705853 0.267748028 0.272829145 0.279551893 0.278500348]
[0.266140789 0.261528105 0.266255379 0.272490293 0.271516532]
[0.26003018 0.255724251 0.260137022 0.265940517 0.265035421]
[0.254323781 0.250291914 0.254423678 0.259843439 0.258999288]
[0.248978764 0.245193034 0.249072418 0.25414905 0.253359258]
[0.243958414 0.240394741 0.244046524 0.248814836 0.248073786]
[0.239231125 0.235868543 0.239314198 0.243804231 0.24310714]
[0.234769359 0.231589615 0.234847859 0.239085764 0.238428399]
[0.230549142 0.227536201 0.230623439 0.234632015 0.234010741]
[0.226549357 0.223689109 0.22661984 0.23041907 0.229830697]
[0.222751439 0.220031396 0.222818434 0.226425976 0.225867674]
[0.21913895 0.216548 0.219202697 0.222634196 0.222103462]
[0.215697214 0.213225439 0.215757981 0.219027311 0.218521982]
[0.212413162 0.210051686 0.212471202 0.215590775 0.215108871]
[0.209275112 0.207015961 0.209330618 0.212311521 0.211851314]
[0.206272557 0.204108506 0.206325665 0.209177911 0.20873782]
[0.203395993 0.201320544 0.203446865 0.206179485 0.20575805]
[0.200636819 0.198644072 0.200685605 0.203306749 0.202902704]
<tf.Tensor: shape=(5,), dtype=float32, numpy=
array([0.19798723, 0.19607186, 0.19803411, 0.20055115, 0.20016332],
      dtype=float32)>

Если вам любопытно, вы можете осмотреть создает автограф кода.

print(tf.autograph.to_code(f.python_function))

def tf__f(x):
    with ag__.FunctionScope('f', 'fscope', ag__.ConversionOptions(recursive=True, user_requested=True, optional_features=(), internal_convert_user_code=True)) as fscope:
        do_return = False
        retval_ = ag__.UndefinedReturnValue()

        def get_state():
            return (x,)

        def set_state(vars_):
            nonlocal x
            (x,) = vars_

        def loop_body():
            nonlocal x
            ag__.converted_call(ag__.ld(tf).print, (ag__.ld(x),), None, fscope)
            x = ag__.converted_call(ag__.ld(tf).tanh, (ag__.ld(x),), None, fscope)

        def loop_test():
            return ag__.converted_call(ag__.ld(tf).reduce_sum, (ag__.ld(x),), None, fscope) > 1
        ag__.while_stmt(loop_test, loop_body, get_state, set_state, ('x',), {})
        try:
            do_return = True
            retval_ = ag__.ld(x)
        except:
            do_return = False
            raise
        return fscope.ret(retval_, do_return)

Условия

Автограф преобразует некоторыеif <condition>заявления в эквивалентномtf.condвызовы Эта замена сделана, если<condition>это тензор. В противном случаеifЗаявление выполняется как условное Python.

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

tf.condТреки и добавляет обе ветви условного к графику, динамически выбрав ветвь во время выполнения. Трагин может иметь непреднамеренные побочные эффекты; проверитьЭффекты отслеживания автографовДля получения дополнительной информации.

@tf.function
def fizzbuzz(n):
  for i in tf.range(1, n + 1):
    print('Tracing for loop')
    if i % 15 == 0:
      print('Tracing fizzbuzz branch')
      tf.print('fizzbuzz')
    elif i % 3 == 0:
      print('Tracing fizz branch')
      tf.print('fizz')
    elif i % 5 == 0:
      print('Tracing buzz branch')
      tf.print('buzz')
    else:
      print('Tracing default branch')
      tf.print(i)

fizzbuzz(tf.constant(5))
fizzbuzz(tf.constant(20))

Tracing for loop
Tracing fizzbuzz branch
Tracing fizz branch
Tracing buzz branch
Tracing default branch
1
2
fizz
4
buzz
1
2
fizz
4
buzz
fizz
7
8
fizz
buzz
11
fizz
13
14
fizzbuzz
16
17
fizz
19
buzz

Увидетьсправочная документацияДля дополнительных ограничений на автограф-конвертированные операторы.

Петли

Автограф преобразует некоторыеforиwhileЗаявления в эквивалентном петле Tensorflow, какtf.while_loopПолем Если не конвертировано,forилиwhileЦикл выполняется как петля Python.

Эта замена сделана в следующих ситуациях:

  • for x in y: еслиyТенсор, конвертируется вtf.while_loopПолем В особом случае, гдеyявляется аtf.data.Dataset, комбинацияtf.data.Datasetops генерируются.
  • while <condition>: если<condition>Тенсор, конвертируется вtf.while_loopПолем

Цикл Python выполняется во время трассировки, добавляя дополнительные операции вtf.Graphдля каждой итерации петли.

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

Увидетьсправочная документацияДля дополнительных ограничений на автограф-конвертированныеforиwhileзаявления.

Зацикливание по данным Python

Распространенной ловушки является переход по данным Python/Numpy в рамкахtf.functionПолем Этот цикл будет выполняться во время процесса отслеживания, добавив копию вашей модели вtf.GraphДля каждой итерации петли.

Если вы хотите обернуть всю тренировочную петлю вtf.function, самый безопасный способ сделать это - обернуть ваши данные какtf.data.DatasetТак что автограф динамически развернет петлю обучения.

def measure_graph_size(f, *args):
  g = f.get_concrete_function(*args).graph
  print("{}({}) contains {} nodes in its graph".format(
      f.__name__, ', '.join(map(str, args)), len(g.as_graph_def().node)))

@tf.function
def train(dataset):
  loss = tf.constant(0)
  for x, y in dataset:
    loss += tf.abs(y - x) # Some dummy computation.
  return loss

small_data = [(1, 1)] * 3
big_data = [(1, 1)] * 10
measure_graph_size(train, small_data)
measure_graph_size(train, big_data)

measure_graph_size(train, tf.data.Dataset.from_generator(
    lambda: small_data, (tf.int32, tf.int32)))
measure_graph_size(train, tf.data.Dataset.from_generator(
    lambda: big_data, (tf.int32, tf.int32)))

train([(1, 1), (1, 1), (1, 1)]) contains 11 nodes in its graph
train([(1, 1), (1, 1), (1, 1), (1, 1), (1, 1), (1, 1), (1, 1), (1, 1), (1, 1), (1, 1)]) contains 32 nodes in its graph
train(<_FlatMapDataset element_spec=(TensorSpec(shape=<unknown>, dtype=tf.int32, name=None), TensorSpec(shape=<unknown>, dtype=tf.int32, name=None))>) contains 6 nodes in its graph
train(<_FlatMapDataset element_spec=(TensorSpec(shape=<unknown>, dtype=tf.int32, name=None), TensorSpec(shape=<unknown>, dtype=tf.int32, name=None))>) contains 6 nodes in its graph

При обертывании данных Python/Numpy в наборе данных, помните оtf.data.Dataset.from_generatorпротивtf.data.Dataset.from_tensor_slicesПолем Первый будет хранить данные в Python и принести их черезtf.py_functionкоторые могут иметь последствия для производительности, тогда как последний будет объединять копию данных как одного большогоtf.constant()Узел на графике, который может иметь последствия для памяти.

Чтение данных из файлов черезTFRecordDatasetВCsvDatasetи т. д. - наиболее эффективный способ потребления данных, так как тогда сам Tensorflow может управлять асинхронной загрузкой и предварительной выборкой данных, без необходимости вовлечения Python. Чтобы узнать больше, увидетьtf.data: Построить входные трубопроводы TensorFlow.

Накопление значений в цикле

Общим шаблоном является накопление промежуточных значений из цикла. Обычно это достигается путем добавления в список Python или добавления записей в словарь Python. Однако, поскольку это побочные эффекты Python, они не будут работать, как ожидалось в динамически развертываемом цикле. Использоватьtf.TensorArrayЧтобы накопить результаты динамически развернутой петли.

batch_size = 2
seq_len = 3
feature_size = 4

def rnn_step(inp, state):
  return inp + state

@tf.function
def dynamic_rnn(rnn_step, input_data, initial_state):
  # [batch, time, features] -> [time, batch, features]
  input_data = tf.transpose(input_data, [1, 0, 2])
  max_seq_len = input_data.shape[0]

  states = tf.TensorArray(tf.float32, size=max_seq_len)
  state = initial_state
  for i in tf.range(max_seq_len):
    state = rnn_step(input_data[i], state)
    states = states.write(i, state)
  return tf.transpose(states.stack(), [1, 0, 2])

dynamic_rnn(rnn_step,
            tf.random.uniform([batch_size, seq_len, feature_size]),
            tf.zeros([batch_size, feature_size]))

<tf.Tensor: shape=(2, 3, 4), dtype=float32, numpy=
array([[[0.98815036, 0.8358947 , 0.15233278, 0.58257985],
        [1.7802314 , 1.215749  , 0.6186948 , 0.9416343 ],
        [2.1005788 , 1.2919371 , 1.1675987 , 1.4443643 ]],

       [[0.751495  , 0.8949536 , 0.16761959, 0.45424747],
        [0.9617816 , 1.7412133 , 0.37147725, 0.7925167 ],
        [1.655664  , 1.9362986 , 1.1732976 , 1.12577   ]]], dtype=float32)>

Первоначально опубликовано наTensorflowВеб -сайт, эта статья появляется здесь под новым заголовком и имеет лицензию в CC на 4.0. Образцы кода, разделенные по лицензии Apache 2.0.


Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE