День сообщества ML - 9 ноября! Присоединяйтесь к нам для обновления от TensorFlow, JAX, и многое другое Подробнее

Введение в графики и tf.функции

Посмотреть на TensorFlow.org Запускаем в Google Colab Посмотреть исходный код на GitHub Скачать блокнот

Обзор

Это руководство выходит за рамки TensorFlow и Keras, чтобы продемонстрировать, как работает TensorFlow. Если вместо этого вы хотите , чтобы немедленно приступить к работе с Keras, проверьте коллекцию Keras направляющих .

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

Это обзор большой картины , которая охватывает как tf.function позволяет перейти от нетерпеливого исполнения к исполнению графика. Для более полного описания tf.function , перейдите к tf.function руководства .

Что такое графики?

В трех предыдущих руководствах, вы запускали TensorFlow охотно. Это означает, что операции TensorFlow выполняются Python, операция за операцией, и возвращают результаты обратно в Python.

Несмотря на то, что активное выполнение имеет несколько уникальных преимуществ, выполнение графа обеспечивает переносимость вне Python и, как правило, обеспечивает лучшую производительность. Средство выполнения графа , что тензорные вычисления выполнены в виде TensorFlow графа, который иногда называют как tf.Graph или просто «граф.»

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

Так выглядит граф TensorFlow, представляющий двухслойную нейронную сеть, при визуализации в TensorBoard.

Простой график TensorFlow

Преимущества графиков

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

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

  • Статический вывод значения тензоров путем складывания постоянных узлов в вашем вычислении ( «константы»).
  • Отделяйте независимые части вычислений и разделяйте их между потоками или устройствами.
  • Упростите арифметические операции, исключив общие подвыражения.

Существует целая система оптимизации, Grappler , чтобы выполнить это и другие ускорения.

Короче говоря, графики чрезвычайно полезны , и пусть ваш TensorFlow быстро бегать, работать параллельно, и работать эффективно на нескольких устройствах.

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

Настраивать

import tensorflow as tf
import timeit
from datetime import datetime

Использование графиков

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

# Define a Python function.
def a_regular_function(x, y, b):
  x = tf.matmul(x, y)
  x = x + b
  return x

# `a_function_that_uses_a_graph` is a TensorFlow `Function`.
a_function_that_uses_a_graph = tf.function(a_regular_function)

# Make some tensors.
x1 = tf.constant([[1.0, 2.0]])
y1 = tf.constant([[2.0], [3.0]])
b1 = tf.constant(4.0)

orig_value = a_regular_function(x1, y1, b1).numpy()
# Call a `Function` like a Python function.
tf_function_value = a_function_that_uses_a_graph(x1, y1, b1).numpy()
assert(orig_value == tf_function_value)

На внешней стороне, а Function выглядит как обычная функция записи вы с помощью операции TensorFlow. Под , однако, это совсем другое. Function инкапсулирует несколько tf.Graph s за один API . То есть , как Function может дать вам преимущество выполнения графа , как скорость и развертывание.

tf.function относится к функции и всех других функций , которые он называет:

def inner_function(x, y, b):
  x = tf.matmul(x, y)
  x = x + b
  return x

# Use the decorator to make `outer_function` a `Function`.
@tf.function
def outer_function(x):
  y = tf.constant([[2.0], [3.0]])
  b = tf.constant(4.0)

  return inner_function(x, y, b)

# Note that the callable will create a graph that
# includes `inner_function` as well as `outer_function`.
outer_function(tf.constant([[1.0, 2.0]])).numpy()
array([[12.]], dtype=float32)

Если вы использовали TensorFlow 1.x, вы заметите , что ни разу не нужно определить Placeholder или tf.Session .

Преобразование функций Python в графики

Любая функция , которую вы пишете с TensorFlow будет содержать смесь встроенных операций ТФ и логики Python, например, if-then пункты, петли, break , return , по- continue , и многое другое. В то время как операции TensorFlow легко захвачены tf.Graph , Python-специфическая логические потребностями пройти дополнительный шаг, чтобы стать частью графа. tf.function использует библиотеку под названием Autograph ( tf.autograph ) для преобразования кода Python в граф-генерации кода.

def simple_relu(x):
  if tf.greater(x, 0):
    return x
  else:
    return 0

# `tf_simple_relu` is a TensorFlow `Function` that wraps `simple_relu`.
tf_simple_relu = tf.function(simple_relu)

print("First branch, with graph:", tf_simple_relu(tf.constant(1)).numpy())
print("Second branch, with graph:", tf_simple_relu(tf.constant(-1)).numpy())
First branch, with graph: 1
Second branch, with graph: 0

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

# This is the graph-generating output of AutoGraph.
print(tf.autograph.to_code(simple_relu))
def tf__simple_relu(x):
    with ag__.FunctionScope('simple_relu', '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 (do_return, retval_)

        def set_state(vars_):
            nonlocal retval_, do_return
            (do_return, retval_) = vars_

        def if_body():
            nonlocal retval_, do_return
            try:
                do_return = True
                retval_ = ag__.ld(x)
            except:
                do_return = False
                raise

        def else_body():
            nonlocal retval_, do_return
            try:
                do_return = True
                retval_ = 0
            except:
                do_return = False
                raise
        ag__.if_stmt(ag__.converted_call(ag__.ld(tf).greater, (ag__.ld(x), 0), None, fscope), if_body, else_body, get_state, set_state, ('do_return', 'retval_'), 2)
        return fscope.ret(retval_, do_return)
# This is the graph itself.
print(tf_simple_relu.get_concrete_function(tf.constant(1)).graph.as_graph_def())
node {
  name: "x"
  op: "Placeholder"
  attr {
    key: "_user_specified_name"
    value {
      s: "x"
    }
  }
  attr {
    key: "dtype"
    value {
      type: DT_INT32
    }
  }
  attr {
    key: "shape"
    value {
      shape {
      }
    }
  }
}
node {
  name: "Greater/y"
  op: "Const"
  attr {
    key: "dtype"
    value {
      type: DT_INT32
    }
  }
  attr {
    key: "value"
    value {
      tensor {
        dtype: DT_INT32
        tensor_shape {
        }
        int_val: 0
      }
    }
  }
}
node {
  name: "Greater"
  op: "Greater"
  input: "x"
  input: "Greater/y"
  attr {
    key: "T"
    value {
      type: DT_INT32
    }
  }
}
node {
  name: "cond"
  op: "StatelessIf"
  input: "Greater"
  input: "x"
  attr {
    key: "Tcond"
    value {
      type: DT_BOOL
    }
  }
  attr {
    key: "Tin"
    value {
      list {
        type: DT_INT32
      }
    }
  }
  attr {
    key: "Tout"
    value {
      list {
        type: DT_BOOL
        type: DT_INT32
      }
    }
  }
  attr {
    key: "_lower_using_switch_merge"
    value {
      b: true
    }
  }
  attr {
    key: "_read_only_resource_inputs"
    value {
      list {
      }
    }
  }
  attr {
    key: "else_branch"
    value {
      func {
        name: "cond_false_34"
      }
    }
  }
  attr {
    key: "output_shapes"
    value {
      list {
        shape {
        }
        shape {
        }
      }
    }
  }
  attr {
    key: "then_branch"
    value {
      func {
        name: "cond_true_33"
      }
    }
  }
}
node {
  name: "cond/Identity"
  op: "Identity"
  input: "cond"
  attr {
    key: "T"
    value {
      type: DT_BOOL
    }
  }
}
node {
  name: "cond/Identity_1"
  op: "Identity"
  input: "cond:1"
  attr {
    key: "T"
    value {
      type: DT_INT32
    }
  }
}
node {
  name: "Identity"
  op: "Identity"
  input: "cond/Identity_1"
  attr {
    key: "T"
    value {
      type: DT_INT32
    }
  }
}
library {
  function {
    signature {
      name: "cond_false_34"
      input_arg {
        name: "cond_placeholder"
        type: DT_INT32
      }
      output_arg {
        name: "cond_identity"
        type: DT_BOOL
      }
      output_arg {
        name: "cond_identity_1"
        type: DT_INT32
      }
    }
    node_def {
      name: "cond/Const"
      op: "Const"
      attr {
        key: "dtype"
        value {
          type: DT_BOOL
        }
      }
      attr {
        key: "value"
        value {
          tensor {
            dtype: DT_BOOL
            tensor_shape {
            }
            bool_val: true
          }
        }
      }
      experimental_debug_info {
        original_node_names: "cond/Const"
      }
    }
    node_def {
      name: "cond/Const_1"
      op: "Const"
      attr {
        key: "dtype"
        value {
          type: DT_BOOL
        }
      }
      attr {
        key: "value"
        value {
          tensor {
            dtype: DT_BOOL
            tensor_shape {
            }
            bool_val: true
          }
        }
      }
      experimental_debug_info {
        original_node_names: "cond/Const_1"
      }
    }
    node_def {
      name: "cond/Const_2"
      op: "Const"
      attr {
        key: "dtype"
        value {
          type: DT_INT32
        }
      }
      attr {
        key: "value"
        value {
          tensor {
            dtype: DT_INT32
            tensor_shape {
            }
            int_val: 0
          }
        }
      }
      experimental_debug_info {
        original_node_names: "cond/Const_2"
      }
    }
    node_def {
      name: "cond/Const_3"
      op: "Const"
      attr {
        key: "dtype"
        value {
          type: DT_BOOL
        }
      }
      attr {
        key: "value"
        value {
          tensor {
            dtype: DT_BOOL
            tensor_shape {
            }
            bool_val: true
          }
        }
      }
      experimental_debug_info {
        original_node_names: "cond/Const_3"
      }
    }
    node_def {
      name: "cond/Identity"
      op: "Identity"
      input: "cond/Const_3:output:0"
      attr {
        key: "T"
        value {
          type: DT_BOOL
        }
      }
      experimental_debug_info {
        original_node_names: "cond/Identity"
      }
    }
    node_def {
      name: "cond/Const_4"
      op: "Const"
      attr {
        key: "dtype"
        value {
          type: DT_INT32
        }
      }
      attr {
        key: "value"
        value {
          tensor {
            dtype: DT_INT32
            tensor_shape {
            }
            int_val: 0
          }
        }
      }
      experimental_debug_info {
        original_node_names: "cond/Const_4"
      }
    }
    node_def {
      name: "cond/Identity_1"
      op: "Identity"
      input: "cond/Const_4:output:0"
      attr {
        key: "T"
        value {
          type: DT_INT32
        }
      }
      experimental_debug_info {
        original_node_names: "cond/Identity_1"
      }
    }
    ret {
      key: "cond_identity"
      value: "cond/Identity:output:0"
    }
    ret {
      key: "cond_identity_1"
      value: "cond/Identity_1:output:0"
    }
    attr {
      key: "_construction_context"
      value {
        s: "kEagerRuntime"
      }
    }
    arg_attr {
      key: 0
      value {
        attr {
          key: "_output_shapes"
          value {
            list {
              shape {
              }
            }
          }
        }
      }
    }
  }
  function {
    signature {
      name: "cond_true_33"
      input_arg {
        name: "cond_identity_1_x"
        type: DT_INT32
      }
      output_arg {
        name: "cond_identity"
        type: DT_BOOL
      }
      output_arg {
        name: "cond_identity_1"
        type: DT_INT32
      }
    }
    node_def {
      name: "cond/Const"
      op: "Const"
      attr {
        key: "dtype"
        value {
          type: DT_BOOL
        }
      }
      attr {
        key: "value"
        value {
          tensor {
            dtype: DT_BOOL
            tensor_shape {
            }
            bool_val: true
          }
        }
      }
      experimental_debug_info {
        original_node_names: "cond/Const"
      }
    }
    node_def {
      name: "cond/Identity"
      op: "Identity"
      input: "cond/Const:output:0"
      attr {
        key: "T"
        value {
          type: DT_BOOL
        }
      }
      experimental_debug_info {
        original_node_names: "cond/Identity"
      }
    }
    node_def {
      name: "cond/Identity_1"
      op: "Identity"
      input: "cond_identity_1_x"
      attr {
        key: "T"
        value {
          type: DT_INT32
        }
      }
      experimental_debug_info {
        original_node_names: "cond/Identity_1"
      }
    }
    ret {
      key: "cond_identity"
      value: "cond/Identity:output:0"
    }
    ret {
      key: "cond_identity_1"
      value: "cond/Identity_1:output:0"
    }
    attr {
      key: "_construction_context"
      value {
        s: "kEagerRuntime"
      }
    }
    arg_attr {
      key: 0
      value {
        attr {
          key: "_output_shapes"
          value {
            list {
              shape {
              }
            }
          }
        }
      }
    }
  }
}
versions {
  producer: 808
  min_consumer: 12
}

Большая часть времени, tf.function будет работать без особых соображений. Тем не менее, есть некоторые оговорки, и руководство tf.function может помочь здесь, а также полной ссылки Автограф

Полиморфизм: одна Function , много график

tf.Graph специализируется на определенный тип входных данных (например, тензоры с определенной dtype или объектами с тем же id() ).

Каждый раз , когда вы вызываете Function с новым dtypes и формами в своих аргументах, Function создает новый tf.Graph для новых аргументов. В dtypes и форма о наличии tf.Graph входов «S известны как входная подпись или просто подпись.

В Function сохраняет tf.Graph , соответствующий этой подписи в ConcreteFunction . ConcreteFunction обертка вокруг tf.Graph .

@tf.function
def my_relu(x):
  return tf.maximum(0., x)

# `my_relu` creates new graphs as it observes more signatures.
print(my_relu(tf.constant(5.5)))
print(my_relu([1, -1]))
print(my_relu(tf.constant([3., -3.])))
tf.Tensor(5.5, shape=(), dtype=float32)
tf.Tensor([1. 0.], shape=(2,), dtype=float32)
tf.Tensor([3. 0.], shape=(2,), dtype=float32)

Если Function уже была вызвана с этой подписью, Function не создает новый tf.Graph .

# These two calls do *not* create new graphs.
print(my_relu(tf.constant(-2.5))) # Signature matches `tf.constant(5.5)`.
print(my_relu(tf.constant([-1., 1.]))) # Signature matches `tf.constant([3., -3.])`.
tf.Tensor(0.0, shape=(), dtype=float32)
tf.Tensor([0. 1.], shape=(2,), dtype=float32)

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

# There are three `ConcreteFunction`s (one for each graph) in `my_relu`.
# The `ConcreteFunction` also knows the return type and shape!
print(my_relu.pretty_printed_concrete_signatures())
my_relu(x)
  Args:
    x: float32 Tensor, shape=()
  Returns:
    float32 Tensor, shape=()

my_relu(x=[1, -1])
  Returns:
    float32 Tensor, shape=(2,)

my_relu(x)
  Args:
    x: float32 Tensor, shape=(2,)
  Returns:
    float32 Tensor, shape=(2,)

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

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

Выполнение графа против нетерпеливого выполнения

Код в Function может быть выполнен как охотно и в виде графика. По умолчанию, Function выполняет свой код в виде графика:

@tf.function
def get_MSE(y_true, y_pred):
  sq_diff = tf.pow(y_true - y_pred, 2)
  return tf.reduce_mean(sq_diff)
y_true = tf.random.uniform([5], maxval=10, dtype=tf.int32)
y_pred = tf.random.uniform([5], maxval=10, dtype=tf.int32)
print(y_true)
print(y_pred)
tf.Tensor([6 4 3 1 5], shape=(5,), dtype=int32)
tf.Tensor([6 7 9 9 1], shape=(5,), dtype=int32)
get_MSE(y_true, y_pred)
<tf.Tensor: shape=(), dtype=int32, numpy=25>

Для того, чтобы убедиться , что ваш Function графа «сек делает то же вычисление , как его эквивалент функции Python, вы можете сделать это выполнить с жадностью tf.config.run_functions_eagerly(True) . Это переключатель , который выключает Function способности «s для создания и запуск графиков, вместо выполнения кода в обычном режиме.

tf.config.run_functions_eagerly(True)
get_MSE(y_true, y_pred)
<tf.Tensor: shape=(), dtype=int32, numpy=25>
# Don't forget to set it back when you are done.
tf.config.run_functions_eagerly(False)

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

@tf.function
def get_MSE(y_true, y_pred):
  print("Calculating MSE!")
  sq_diff = tf.pow(y_true - y_pred, 2)
  return tf.reduce_mean(sq_diff)

Посмотрите, что напечатано:

error = get_MSE(y_true, y_pred)
error = get_MSE(y_true, y_pred)
error = get_MSE(y_true, y_pred)
Calculating MSE!

Удивительный результат? get_MSE печатается только один раз , даже если он был назван в три раза.

Чтобы объяснить, то print выполняется оператор , когда Function выполняется исходный код для создания графика в процессе , известном как «отслеживание» . Трассировка фиксируют операцию TensorFlow в графу, а print не учитываются в графике. Этот график затем выполняется для всех трех вызовов без когда - либо запустить код Python снова.

В качестве проверки работоспособности отключим выполнение графа для сравнения:

# Now, globally set everything to run eagerly to force eager execution.
tf.config.run_functions_eagerly(True)
# Observe what is printed below.
error = get_MSE(y_true, y_pred)
error = get_MSE(y_true, y_pred)
error = get_MSE(y_true, y_pred)
Calculating MSE!
Calculating MSE!
Calculating MSE!
tf.config.run_functions_eagerly(False)

print является побочным эффектом Python, и есть другие различия , которые вы должны знать при преобразовании функции в Function .

Нестрогое исполнение

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

  • Возвращаемое значение функции
  • Документированные хорошо известные побочные эффекты, такие как:
    • Операций ввода / вывода, как tf.print
    • Отладочные операции, такие как функции утверждают в tf.debugging
    • Мутации tf.Variable

Такое поведение обычно известно как «нестрогое выполнение» и отличается от активного выполнения, при котором проходят через все операции программы, необходимые или нет.

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

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

def unused_return_eager(x):
  # Get index 1 will fail when `len(x) == 1`
  tf.gather(x, [1]) # unused 
  return x

try:
  print(unused_return_eager(tf.constant([0.0])))
except tf.errors.InvalidArgumentError as e:
  # All operations are run during eager execution so an error is raised.
  print(f'{type(e).__name__}: {e}')
tf.Tensor([0.], shape=(1,), dtype=float32)
@tf.function
def unused_return_graph(x):
  tf.gather(x, [1]) # unused
  return x

# Only needed operations are run during graph exection. The error is not raised.
print(unused_return_graph(tf.constant([0.0])))
tf.Tensor([0.], shape=(1,), dtype=float32)

tf.function лучшие практики

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

Проектирование для tf.function может быть вашим лучшим выбором для записи графов-совместимых программ TensorFlow. Вот несколько советов:

  • Переключение между жадным и графика выполнения рано и часто с tf.config.run_functions_eagerly , чтобы точно определить , если / когда два режима расходятся.
  • Создание tf.Variable сек вне функции Python и изменить их на внутренней стороне . То же самое относится и к объектам , которые используют tf.Variable , как keras.layers , keras.Model с и tf.optimizers .
  • Избегайте написание функции , которые зависят от внешнего переменного Python , за исключением tf.Variable s и объектов Keras.
  • Предпочитайте писать функции, которые принимают на вход тензоры и другие типы TensorFlow. Вы можете перейти в других типах объектов , но будьте осторожны !
  • Включите столько вычислений , насколько это возможно в соответствии с tf.function максимального прироста производительности. Например, украсить весь тренировочный шаг или весь тренировочный цикл.

Видя ускорение

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

x = tf.random.uniform(shape=[10, 10], minval=-1, maxval=2, dtype=tf.dtypes.int32)

def power(x, y):
  result = tf.eye(10, dtype=tf.dtypes.int32)
  for _ in range(y):
    result = tf.matmul(x, result)
  return result
print("Eager execution:", timeit.timeit(lambda: power(x, 100), number=1000))
Eager execution: 2.286959676999686
power_as_graph = tf.function(power)
print("Graph execution:", timeit.timeit(lambda: power_as_graph(x, 100), number=1000))
Graph execution: 0.6817171659999985

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

Производительность и компромиссы

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

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

Когда является Function отслеживания?

Для того, чтобы выяснить , когда ваша Function прослеживает, добавьте print о его кода. Как правило, Function будет выполнять print заявление , каждый раз , когда он прослеживает.

@tf.function
def a_function_with_python_side_effect(x):
  print("Tracing!") # An eager-only side effect.
  return x * x + tf.constant(2)

# This is traced the first time.
print(a_function_with_python_side_effect(tf.constant(2)))
# The second time through, you won't see the side effect.
print(a_function_with_python_side_effect(tf.constant(3)))
Tracing!
tf.Tensor(6, shape=(), dtype=int32)
tf.Tensor(11, shape=(), dtype=int32)
# This retraces each time the Python argument changes,
# as a Python argument could be an epoch count or other
# hyperparameter.
print(a_function_with_python_side_effect(2))
print(a_function_with_python_side_effect(3))
Tracing!
tf.Tensor(6, shape=(), dtype=int32)
Tracing!
tf.Tensor(11, shape=(), dtype=int32)

Новые аргументы Python всегда запускают создание нового графика, отсюда и дополнительная трассировка.

Следующие шаги

Вы можете узнать больше о tf.function на опорном API страницы и следуя производительности лучше tf.function руководства.