Эта страница была переведа с помощью Cloud Translation API.
Switch to English

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

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

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

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

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

Это краткое введение; полное введение в эти понятия см. в руководстве по tf.function .

Какие графики?

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

Для некоторых пользователей вам может никогда не понадобиться или не захотеть покидать Python

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

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

Вот как выглядит простой двухслойный график при визуализации в TensorBoard.

двухслойный тензорный граф

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

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

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

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

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

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

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

Трассировка графов

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

 import tensorflow as tf
import timeit
from datetime import datetime
 
 # Define a Python function
def function_to_get_faster(x, y, b):
  x = tf.matmul(x, y)
  x = x + b
  return x

# Create a `Function` object that contains a graph
a_function_that_uses_a_graph = tf.function(function_to_get_faster)

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

# It just works!
a_function_that_uses_a_graph(x1, y1, b1).numpy()
 
array([[12.]], dtype=float32)

tf.function tf.function - это вызываемые в Python функции, которые работают так же, как их эквиваленты в Python. У них есть определенный класс ( python.eager.def_function.Function ), но для вас они действуют как неотслеживаемая версия.

tf.function рекурсивно отслеживает любую функцию Python, которую она вызывает.

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

# Use the decorator
@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.Sesssion .

Контроль потока и побочные эффекты

Управление потоком и циклы по умолчанию преобразуются в tf.autograph через tf.autograph . Autograph использует комбинацию методов, включая стандартизацию конструкций цикла, развертывание и манипулирование AST .

 def my_function(x):
  if tf.reduce_sum(x) <= 1:
    return x * x
  else:
    return x-1

a_function = tf.function(my_function)

print("First branch, with graph:", a_function(tf.constant(1.0)).numpy())
print("Second branch, with graph:", a_function(tf.constant([5.0, 5.0])).numpy())
 
First branch, with graph: 1.0
Second branch, with graph: [4. 4.]

Вы можете напрямую вызвать преобразование Autograph, чтобы увидеть, как Python преобразуется в операции TensorFlow. Это, в основном, нечитаемо, но вы можете видеть трансформацию.

 # Don't read the output too carefully.
print(tf.autograph.to_code(my_function))
 
def tf__my_function(x):
    with ag__.FunctionScope('my_function', '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 (retval_, do_return)

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

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

        def else_body():
            nonlocal retval_, do_return
            try:
                do_return = True
                retval_ = (ag__.ld(x) - 1)
            except:
                do_return = False
                raise
        ag__.if_stmt((ag__.converted_call(ag__.ld(tf).reduce_sum, (ag__.ld(x),), None, fscope) <= 1), if_body, else_body, get_state, set_state, ('retval_', 'do_return'), 2)
        return fscope.ret(retval_, do_return)


Автограф автоматически преобразует предложения if-then , циклы, break , return , continue и многое другое.

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

Видя скорость

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

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

Этот код несколько раз работает на небольших плотных слоях.

 # Create an oveerride model to classify pictures
class SequentialModel(tf.keras.Model):
  def __init__(self, **kwargs):
    super(SequentialModel, self).__init__(**kwargs)
    self.flatten = tf.keras.layers.Flatten(input_shape=(28, 28))
    self.dense_1 = tf.keras.layers.Dense(128, activation="relu")
    self.dropout = tf.keras.layers.Dropout(0.2)
    self.dense_2 = tf.keras.layers.Dense(10)

  def call(self, x):
    x = self.flatten(x)
    x = self.dense_1(x)
    x = self.dropout(x)
    x = self.dense_2(x)
    return x

input_data = tf.random.uniform([60, 28, 28])

eager_model = SequentialModel()
graph_model = tf.function(eager_model)

print("Eager time:", timeit.timeit(lambda: eager_model(input_data), number=10000))
print("Graph time:", timeit.timeit(lambda: graph_model(input_data), number=10000))

 
Eager time: 4.9249971129993355
Graph time: 2.026840765000088

Полиморфные функции

Когда вы отслеживаете функцию, вы создаете объект Function полиморфный . Полиморфная функция - это вызываемый Python, который инкапсулирует несколько графиков конкретных функций за одним API.

Вы можете использовать эту Function на всех различных видах dtypes и форм. Каждый раз, когда вы вызываете его с новой сигнатурой аргумента, исходная функция пересматривается с новыми аргументами. Затем Function сохраняет tf.Graph соответствующий этой трассировке, в concrete_function . Если функция уже отслежена с таким аргументом, вы просто получите свой предварительно прослеженный график.

Концептуально, тогда:

  • tf.Graph - это необработанная, переносимая структура данных, описывающая вычисления
  • Function - это кэширование, трассировка, диспетчер над ConcreteFunctions
  • ConcreteFunction - совместимая с нетерпением оболочка вокруг графа, которая позволяет вам выполнить граф из Python

Проверка полиморфных функций

Вы можете проверить a_function , которая является результатом вызова функции tf.function в Python-функции my_function . В этом примере вызов a_function с тремя типами аргументов приводит к трем различным конкретным функциям.

 print(a_function)

print("Calling a `Function`:")
print("Int:", a_function(tf.constant(2)))
print("Float:", a_function(tf.constant(2.0)))
print("Rank-1 tensor of floats", a_function(tf.constant([2.0, 2.0, 2.0])))
 
<tensorflow.python.eager.def_function.Function object at 0x7f466417bf60>
Calling a `Function`:
Int: tf.Tensor(1, shape=(), dtype=int32)
Float: tf.Tensor(1.0, shape=(), dtype=float32)
Rank-1 tensor of floats tf.Tensor([1. 1. 1.], shape=(3,), dtype=float32)

 # Get the concrete function that works on floats
print("Inspecting concrete functions")
print("Concrete function for float:")
print(a_function.get_concrete_function(tf.TensorSpec(shape=[], dtype=tf.float32)))
print("Concrete function for tensor of floats:")
print(a_function.get_concrete_function(tf.constant([2.0, 2.0, 2.0])))

 
Inspecting concrete functions
Concrete function for float:
ConcreteFunction my_function(x)
  Args:
    x: float32 Tensor, shape=()
  Returns:
    float32 Tensor, shape=()
Concrete function for tensor of floats:
ConcreteFunction my_function(x)
  Args:
    x: float32 Tensor, shape=(3,)
  Returns:
    float32 Tensor, shape=(3,)

 # Concrete functions are callable
# Note: You won't normally do this, but instead just call the containing `Function`
cf = a_function.get_concrete_function(tf.constant(2))
print("Directly calling a concrete function:", cf(tf.constant(2)))
 
Directly calling a concrete function: tf.Tensor(1, shape=(), dtype=int32)

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

Возвращаясь к нетерпеливому исполнению

Вы можете посмотреть на длинные трассировки стека, особенно те, которые ссылаются на tf.Graph или with tf.Graph().as_default() . Это означает, что вы, вероятно, работаете в контексте графа. Основные функции в TensorFlow используют контексты графа, например, model.fit() 's model.fit() .

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

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

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

  • Назовите модели и слои напрямую как вызываемые

  • При использовании Keras compile / fit, во время компиляции используйте model.compile(run_eagerly=True)

  • Установить глобальный режим выполнения через tf.config.run_functions_eagerly(True)

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

 # Define an identity layer with an eager side effect
class EagerLayer(tf.keras.layers.Layer):
  def __init__(self, **kwargs):
    super(EagerLayer, self).__init__(**kwargs)
    # Do some kind of initialization here

  def call(self, inputs):
    print("\nCurrently running eagerly", str(datetime.now()))
    return inputs
 
 # Create an override model to classify pictures, adding the custom layer
class SequentialModel(tf.keras.Model):
  def __init__(self):
    super(SequentialModel, self).__init__()
    self.flatten = tf.keras.layers.Flatten(input_shape=(28, 28))
    self.dense_1 = tf.keras.layers.Dense(128, activation="relu")
    self.dropout = tf.keras.layers.Dropout(0.2)
    self.dense_2 = tf.keras.layers.Dense(10)
    self.eager = EagerLayer()

  def call(self, x):
    x = self.flatten(x)
    x = self.dense_1(x)
    x = self.dropout(x)
    x = self.dense_2(x)
    return self.eager(x)

# Create an instance of this model
model = SequentialModel()

# Generate some nonsense pictures and labels
input_data = tf.random.uniform([60, 28, 28])
labels = tf.random.uniform([60])

loss_fn = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)
 

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

 model.compile(run_eagerly=False, loss=loss_fn)
 

Теперь вызовите fit и увидите, что функция отслеживается (дважды), и затем эффект нетерпения больше никогда не запускается.

 model.fit(input_data, labels, epochs=3)
 
Epoch 1/3

Currently running eagerly 2020-08-04 01:22:21.848492

Currently running eagerly 2020-08-04 01:22:21.955102
2/2 [==============================] - 0s 1ms/step - loss: 1.4056
Epoch 2/3
2/2 [==============================] - 0s 1ms/step - loss: 0.0037
Epoch 3/3
2/2 [==============================] - 0s 1ms/step - loss: 0.0019

<tensorflow.python.keras.callbacks.History at 0x7f45f03c5630>

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

 print("Running eagerly")
# When compiling the model, set it to run eagerly
model.compile(run_eagerly=True, loss=loss_fn)

model.fit(input_data, labels, epochs=1)

 
Running eagerly

Currently running eagerly 2020-08-04 01:22:22.152979
1/2 [==============>...............] - ETA: 0s - loss: 8.7806e-04
Currently running eagerly 2020-08-04 01:22:22.173295
2/2 [==============================] - 0s 5ms/step - loss: 4.6877e-04

<tensorflow.python.keras.callbacks.History at 0x7f45f0312940>

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

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

 # Now, globally set everything to run eagerly
tf.config.run_functions_eagerly(True)
print("Run all functions eagerly.")

# First, trace the model, triggering the side effect
polymorphic_function = tf.function(model)

# It was traced...
print(polymorphic_function.get_concrete_function(input_data))

# But when you run the function again, the side effect happens (both times).
result = polymorphic_function(input_data)
result = polymorphic_function(input_data)
 
Run all functions eagerly.

Currently running eagerly 2020-08-04 01:22:22.202726
ConcreteFunction function(self)
  Args:
    self: float32 Tensor, shape=(60, 28, 28)
  Returns:
    float32 Tensor, shape=(60, 10)

Currently running eagerly 2020-08-04 01:22:22.206521

Currently running eagerly 2020-08-04 01:22:22.207818

 # Don't forget to set it back when you are done
tf.config.experimental_run_functions_eagerly(False)

 
WARNING:tensorflow:From <ipython-input-17-782fe9ce7b18>:2: experimental_run_functions_eagerly (from tensorflow.python.eager.def_function) is deprecated and will be removed in a future version.
Instructions for updating:
Use `tf.config.run_functions_eagerly` instead of the experimental version.

Трассировка и производительность

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

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

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

 # Use @tf.function decorator
@tf.function
def a_function_with_python_side_effect(x):
  print("Tracing!")  # This eager
  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)))

# This retraces each time the Python argument chances
# 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)
tf.Tensor(11, shape=(), dtype=int32)
Tracing!
tf.Tensor(6, shape=(), dtype=int32)
Tracing!
tf.Tensor(11, shape=(), dtype=int32)

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

Более подробное обсуждение можно прочитать как на справочной странице API tf.function и в руководстве .