Ta strona została przetłumaczona przez Cloud Translation API.
Switch to English

Lepsza wydajność dzięki funkcji tf

Zobacz na TensorFlow.org Uruchom w Google Colab Wyświetl źródło na GitHub Pobierz notatnik

W TensorFlow 2 przyspieszone wykonywanie jest domyślnie włączone. Interfejs użytkownika jest intuicyjny i elastyczny (wykonywanie jednorazowych operacji jest znacznie łatwiejsze i szybsze), ale może to odbywać się kosztem wydajności i możliwości wdrażania.

Możesz użyć tf.function do tworzenia wykresów ze swoich programów. Jest to narzędzie do transformacji, które tworzy niezależne od Pythona wykresy przepływu danych z kodu Pythona. Pomoże to w tworzeniu wydajnych i przenośnych modeli i jest wymagane do korzystania z SavedModel .

Ten przewodnik pomoże Ci tf.function jak tf.function działa pod maską, abyś mógł z niej efektywnie korzystać.

Główne wnioski i zalecenia to:

  • Debuguj w trybie @tf.function , a następnie udekoruj za pomocą @tf.function .
  • Nie polegaj na efektach ubocznych Pythona, takich jak mutacja obiektu lub dołączanie listy.
  • tf.function działa najlepiej z operacjami TensorFlow; Wywołania NumPy i Python są konwertowane na stałe.

Ustawiać

import tensorflow as tf

Zdefiniuj funkcję pomocniczą, aby zademonstrować rodzaje błędów, które możesz napotkać:

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))

Podstawy

Stosowanie

Zdefiniowana przez Ciebie Function przypomina podstawową operację TensorFlow: możesz ją wykonywać chętnie; możesz obliczyć gradienty; i tak dalej.

@tf.function
def add(a, b):
  return a + b

add(tf.ones([2, 2]), tf.ones([2, 2]))  #  [[2., 2.], [2., 2.]]
<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>

Można użyć Function s wewnątrz innych Function y.

@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)>

Function mogą być szybsze niż chętny kod, szczególnie w przypadku wykresów z wieloma małymi operacjami. Ale w przypadku wykresów z kilkoma drogimi operacjami (takimi jak zwoje), możesz nie zauważyć dużego przyspieszenia.

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")
Eager conv: 0.002407395000091128
Function conv: 0.004000883000117028
Note how there's not much difference in performance for convolutions

Rysunek kalkowy

Dynamiczne typowanie w Pythonie oznacza, że ​​możesz wywoływać funkcje z różnymi typami argumentów, a Python może zrobić coś innego w każdym scenariuszu.

Jednak do utworzenia wykresu dtypes wymagane są statyczne dtypes i wymiary kształtu. tf.function tę lukę, opakowując funkcję Pythona w celu utworzenia obiektu Function . Na podstawie danych wejściowych Function wybiera odpowiedni wykres dla danych danych wejściowych, odtwarzając funkcję Pythona w razie potrzeby. Gdy zrozumiesz, dlaczego i kiedy ma miejsce śledzenie, znacznie łatwiej tf.function efektywnie korzystać z tf.function !

Możesz wywołać Function z argumentami różnych typów, aby zobaczyć to polimorficzne zachowanie w akcji.

@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)


Zwróć uwagę, że jeśli będziesz wielokrotnie wywoływać Function z tym samym typem argumentu, TensorFlow ponownie użyje wcześniej prześledzonego wykresu, ponieważ wygenerowany wykres byłby identyczny.

# This doesn't print 'Tracing with ...'
print(double(tf.constant("b")))
tf.Tensor(b'bb', shape=(), dtype=string)

Możesz użyć pretty_printed_concrete_signatures() aby zobaczyć wszystkie dostępne ślady:

print(double.pretty_printed_concrete_signatures())
double(a)
  Args:
    a: int32 Tensor, shape=()
  Returns:
    int32 Tensor, shape=()

double(a)
  Args:
    a: float32 Tensor, shape=()
  Returns:
    float32 Tensor, shape=()

double(a)
  Args:
    a: string Tensor, shape=()
  Returns:
    string Tensor, shape=()

Do tej pory widzieliście, że tf.function tworzy buforowaną, dynamiczną warstwę wysyłania na podstawie logiki śledzenia wykresów TensorFlow. Aby być bardziej szczegółowym na temat terminologii:

  • tf.Graph to surowa, tf.Graph od języka, przenośna reprezentacja obliczeń.
  • ConcreteFunction jest chętnie wykonującym się opakowaniem wokół tf.Graph .
  • Function zarządza pamięcią podręczną ConcreteFunction si wybiera właściwą dla danych wejściowych.
  • tf.function funkcję Pythona, zwracając obiekt Function .

Uzyskanie konkretnych funkcji

Za każdym razem, gdy śledzona jest funkcja, tworzona jest nowa konkretna funkcja. Możesz bezpośrednio uzyskać konkretną funkcję, używając 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")))
Tracing with Tensor("a:0", shape=(), dtype=string)
tf.Tensor(b'cc', shape=(), dtype=string)

(Następująca zmiana jest dostępna w TensorFlow nightly i będzie dostępna w TensorFlow 2.3.)

Drukowanie ConcreteFunction wyświetla podsumowanie jej argumentów wejściowych (z typami) i typ wyjściowy.

print(double_strings)
ConcreteFunction double(a)
  Args:
    a: string Tensor, shape=()
  Returns:
    string Tensor, shape=()

Możesz także bezpośrednio pobrać podpis konkretnej funkcji.

print(double_strings.structured_input_signature)
print(double_strings.structured_outputs)
((TensorSpec(shape=(), dtype=tf.string, name='a'),), {})
Tensor("Identity:0", shape=(), dtype=string)

Użycie konkretnego śledzenia z niekompatybilnymi typami spowoduje zgłoszenie błędu

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 "<ipython-input-1-73d0ca52e838>", line 8, in assert_raises
    yield
  File "<ipython-input-1-e4e2860a4364>", line 2, in <module>
    double_strings(tf.constant(1))
tensorflow.python.framework.errors_impl.InvalidArgumentError: cannot compute __inference_double_168 as input #0(zero-based) was expected to be a string tensor but is a int32 tensor [Op:__inference_double_168]

Możesz zauważyć, że argumenty Pythona są traktowane w specjalny sposób w sygnaturze wejściowej konkretnej funkcji. Przed TensorFlow 2.3 argumenty Pythona były po prostu usuwane z sygnatury konkretnej funkcji. Począwszy od TensorFlow 2.3, argumenty Pythona pozostają w sygnaturze, ale są ograniczone do przyjęcia wartości ustawionej podczas śledzenia.

@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 pow(a, b=2)
  Args:
    a: float32 Tensor, shape=<unknown>
  Returns:
    float32 Tensor, shape=<unknown>

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.6/site-packages/tensorflow/python/eager/function.py", line 1669, in _call_impl
    cancellation_manager)
  File "/tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/eager/function.py", line 1714, in _call_with_flat_signature
    self._flat_signature_summary(), ", ".join(sorted(kwargs))))
TypeError: pow(a) got unexpected keyword arguments: b.

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<ipython-input-1-73d0ca52e838>", line 8, in assert_raises
    yield
  File "<ipython-input-1-d163f3d206cb>", line 4, in <module>
    square(tf.constant(10.0), b=3)
TypeError: ConcreteFunction pow(a, b) was constructed with int value 2 in b, but was called with int value 3

Uzyskanie wykresów

Każda konkretna funkcja jest wywoływalnym opakowaniem wokół tf.Graph . Chociaż pobieranie rzeczywistego obiektu tf.Graph nie jest czymś, co normalnie musisz robić, możesz go łatwo uzyskać z dowolnej konkretnej funkcji.

graph = double_strings.graph
for node in graph.as_graph_def().node:
  print(f'{node.input} -> {node.name}')
[] -> a
['a', 'a'] -> add
['add'] -> Identity

Debugowanie

Ogólnie rzecz biorąc, debugowanie kodu jest łatwiejsze w tf.function trybie niż wewnątrz tf.function . Powinieneś upewnić się, że twój kod wykonuje się bezbłędnie w trybie tf.function przed dekorowaniem za pomocą tf.function . Aby pomóc w procesie debugowania, możesz wywołać tf.config.run_functions_eagerly(True) aby globalnie wyłączyć i ponownie tf.function .

Podczas wyszukiwania problemów, które pojawiają się tylko w tf.function , oto kilka wskazówek:

  • Zwykłe, stare wywołania print Pythonie są wykonywane tylko podczas śledzenia, pomagając w wyśledzeniu, kiedy funkcja zostaje (ponownie) śledzona.
  • tf.print będą wykonywane za każdym razem i mogą pomóc w śledzeniu wartości pośrednich podczas wykonywania.
  • tf.debugging.enable_check_numerics to łatwy sposób na śledzenie, gdzie tworzone są NaN i Inf.
  • pdb może pomóc ci zrozumieć, co się dzieje podczas śledzenia. (Uwaga: PDB przeniesie Cię do kodu źródłowego przekształconego przez AutoGraph).

Śledzenie semantyki

Zasady dotyczące klucza pamięci podręcznej

Function określa, czy ponownie użyć śledzonej funkcji konkretnej, obliczając klucz pamięci podręcznej z argumentów wejściowych i kwargs.

  • Klucz wygenerowany dla argumentu tf.Tensor to jego kształt i typ.
  • Począwszy od TensorFlow 2.3, kluczem wygenerowanym dla argumentu tf.Variable jest jego id() .
  • Kluczem wygenerowanym dla prymitywu Pythona jest jego wartość. Klucz wygenerowany dla zagnieżdżonych dict s, list s, tuple s, namedtuple s i attr s to spłaszczona krotka. (W wyniku tego spłaszczenia wywołanie konkretnej funkcji z inną strukturą zagnieżdżenia niż ta używana podczas śledzenia spowoduje błąd TypeError).
  • W przypadku wszystkich innych typów Pythona klucze są oparte na obiekcie id() dzięki czemu metody są śledzone niezależnie dla każdej instancji klasy.

Kontrolowanie odtwarzania

Retracing pomaga zapewnić, że TensorFlow generuje prawidłowe wykresy dla każdego zestawu danych wejściowych. Jednak śledzenie jest kosztowną operacją! Jeśli twoja Function odtworzy nowy wykres dla każdego wywołania, przekonasz się, że twój kod wykonuje się wolniej niż gdybyś nie używał tf.function .

Aby kontrolować zachowanie śledzenia, można użyć następujących technik:

  • Określ input_signature w tf.function aby ograniczyć śledzenie.
@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])))
# We specified a 1-D tensor in the input signature, so this should fail.
with assert_raises(ValueError):
  next_collatz(tf.constant([[1, 2], [3, 4]]))

# We specified an int32 dtype in the input signature, so this should fail.
with assert_raises(ValueError):
  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 'ValueError'>:
Caught expected exception 
  <class 'ValueError'>:

Traceback (most recent call last):
  File "<ipython-input-1-73d0ca52e838>", line 8, in assert_raises
    yield
  File "<ipython-input-1-20f544b8adbf>", line 9, in <module>
    next_collatz(tf.constant([[1, 2], [3, 4]]))
ValueError: Python inputs incompatible with input_signature:
  inputs: (
    tf.Tensor(
[[1 2]
 [3 4]], shape=(2, 2), dtype=int32))
  input_signature: (
    TensorSpec(shape=(None,), dtype=tf.int32, name=None))
Traceback (most recent call last):
  File "<ipython-input-1-73d0ca52e838>", line 8, in assert_raises
    yield
  File "<ipython-input-1-20f544b8adbf>", line 13, in <module>
    next_collatz(tf.constant([1.0, 2.0]))
ValueError: Python inputs incompatible with input_signature:
  inputs: (
    tf.Tensor([1. 2.], shape=(2,), dtype=float32))
  input_signature: (
    TensorSpec(shape=(None,), dtype=tf.int32, name=None))

  • Określ wymiar [Brak] w tf.TensorSpec aby zapewnić elastyczność ponownego wykorzystania śledzenia.

    Ponieważ TensorFlow dopasowuje tensory na podstawie ich kształtu, użycie wymiaru None jako symbolu wieloznacznego umożliwi Function s ponowne wykorzystanie śladów dla danych wejściowych o zmiennej wielkości. Dane wejściowe o zmiennej wielkości mogą wystąpić, jeśli masz sekwencje o różnej długości lub obrazy o różnych rozmiarach dla każdej partii (zobacz na przykład samouczki Transformer i Deep Dream ).

@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)

  • Rzutuj argumenty Pythona na Tensory, aby zredukować odtwarzanie.

    Często argumenty Pythona są używane do kontrolowania hiperparametrów i konstrukcji wykresów - na przykład num_layers=10 lub training=True lub nonlinearity='relu' . Więc jeśli argument Pythona się zmieni, ma sens, że musiałbyś odtworzyć wykres.

    Jednak możliwe jest, że argument Pythona nie jest używany do sterowania konstrukcją wykresu. W takich przypadkach zmiana wartości Pythona może wywołać niepotrzebne odtworzenie. Weźmy na przykład tę pętlę szkoleniową, którą AutoGraph będzie dynamicznie rozwijać. Pomimo wielu śladów, wygenerowany wykres jest w rzeczywistości identyczny, więc odtworzenie nie jest konieczne.

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

Jeśli chcesz wymusić odtworzenie, utwórz nową Function . Gwarantuje się, że oddzielne obiekty Function nie będą współużytkować śladów.

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

tf.function(f)()
tf.function(f)()
Tracing!
Executing
Tracing!
Executing

Efekty uboczne Pythona

Efekty uboczne Pythona, takie jak drukowanie, dołączanie do list i mutacje globalne, występują tylko przy pierwszym wywołaniu Function z zestawem danych wejściowych. Następnie wyśledzony tf.Graph jest ponownie wykonywany, bez wykonywania kodu Pythona.

Ogólna zasada mówi, że do debugowania śladów należy używać tylko efektów ubocznych Pythona. W przeciwnym razie operacje tf.Variable.assign , takie jak tf.Variable.assign , tf.print i tf.summary są najlepszym sposobem na zapewnienie śledzenia i wykonywania kodu przez środowisko wykonawcze TensorFlow przy każdym wywołaniu.

@tf.function
def f(x):
  print("Traced with", x)
  tf.print("Executed with", x)

f(1)
f(1)
f(2)
Traced with 1
Executed with 1
Executed with 1
Traced with 2
Executed with 2

Wiele funkcji Pythona, takich jak generatory i iteratory, polega na środowisku wykonawczym Pythona, aby śledzić stan. Ogólnie rzecz biorąc, podczas gdy te konstrukcje działają zgodnie z oczekiwaniami w trybie przyspieszonym, wewnątrz Function może wydarzyć się wiele nieoczekiwanych rzeczy.

Aby podać jeden przykład, zaawansowanie stanu iteratora jest efektem ubocznym Pythona i dlatego ma miejsce tylko podczas śledzenia.

external_var = tf.Variable(0)
@tf.function
def buggy_consume_next(iterator):
  external_var.assign_add(next(iterator))
  tf.print("Value of external_var:", external_var)

iterator = iter([0, 1, 2, 3])
buggy_consume_next(iterator)
# This reuses the first value from the iterator, rather than consuming the next value.
buggy_consume_next(iterator)
buggy_consume_next(iterator)
Value of external_var: 0
Value of external_var: 0
Value of external_var: 0

Niektóre konstrukcje iteracji są obsługiwane przez AutoGraph. Zobacz sekcję poświęconą transformacjom AutoGraph, aby zapoznać się z omówieniem.

Jeśli chcesz wykonać kod Pythona podczas każdego wywołania Function , tf.py_function jest tf.py_function wyjściową. Wadą tf.py_function jest to, że nie jest przenośny ani szczególnie wydajny, ani nie działa dobrze w konfiguracjach rozproszonych (multi-GPU, TPU). Ponadto, ponieważ tf.py_function musi być podłączony do wykresu, tf.py_function wszystkie wejścia / wyjścia na tensory.

Interfejsy API, takie jak tf.gather , tf.stack i tf.TensorArray mogą pomóc we wdrożeniu typowych wzorców pętli w natywnym TensorFlow.

external_list = []

def side_effect(x):
  print('Python side effect')
  external_list.append(x)

@tf.function
def f(x):
  tf.py_function(side_effect, inp=[x], Tout=[])

f(1)
f(1)
f(1)
# The list append happens all three times!
assert len(external_list) == 3
# The list contains tf.constant(1), not 1, because py_function casts everything to tensors.
assert external_list[0].numpy() == 1
Python side effect
Python side effect
Python side effect

Zmienne

Podczas tworzenia nowej tf.Variable w funkcji może wystąpić błąd. Ten błąd chroni przed rozbieżnością w zachowaniu w przypadku powtarzających się wywołań: w trybie przyspieszonym funkcja tworzy nową zmienną przy każdym wywołaniu, ale w Function nowa zmienna może nie zostać utworzona z powodu ponownego użycia śledzenia.

@tf.function
def f(x):
  v = tf.Variable(1.0)
  v.assign_add(x)
  return v

with assert_raises(ValueError):
  f(1.0)
Caught expected exception 
  <class 'ValueError'>:

Traceback (most recent call last):
  File "<ipython-input-1-73d0ca52e838>", line 8, in assert_raises
    yield
  File "<ipython-input-1-73e410646579>", line 8, in <module>
    f(1.0)
ValueError: in user code:

    <ipython-input-1-73e410646579>:3 f  *
        v = tf.Variable(1.0)
    /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/ops/variables.py:262 __call__  **
        return cls._variable_v2_call(*args, **kwargs)
    /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/ops/variables.py:256 _variable_v2_call
        shape=shape)
    /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/ops/variables.py:67 getter
        return captured_getter(captured_previous, **kwargs)
    /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/eager/def_function.py:702 invalid_creator_scope
        "tf.function-decorated function tried to create "

    ValueError: tf.function-decorated function tried to create variables on non-first call.


Możesz tworzyć zmienne wewnątrz Function o ile te zmienne są tworzone tylko przy pierwszym wykonaniu funkcji.

class Count(tf.Module):
  def __init__(self):
    self.count = None

  @tf.function
  def __call__(self):
    if self.count is None:
      self.count = tf.Variable(0)
    return self.count.assign_add(1)

c = Count()
print(c())
print(c())
tf.Tensor(1, shape=(), dtype=int32)
tf.Tensor(2, shape=(), dtype=int32)

Innym błędem, który możesz napotkać, jest zmienna zbierana jako śmieci. W przeciwieństwie do zwykłych funkcji Pythona, konkretne funkcje zachowują WeakRefs tylko do zmiennych, nad którymi się zamykają, więc należy zachować odniesienia do wszelkich zmiennych.

external_var = tf.Variable(3)
@tf.function
def f(x):
  return x * external_var

traced_f = f.get_concrete_function(4)
print("Calling concrete function...")
print(traced_f(4))

del external_var
print()
print("Calling concrete function after garbage collecting its closed Variable...")
with assert_raises(tf.errors.FailedPreconditionError):
  traced_f(4)
Calling concrete function...
tf.Tensor(12, shape=(), dtype=int32)

Calling concrete function after garbage collecting its closed Variable...
Caught expected exception 
  <class 'tensorflow.python.framework.errors_impl.FailedPreconditionError'>:

Traceback (most recent call last):
  File "<ipython-input-1-73d0ca52e838>", line 8, in assert_raises
    yield
  File "<ipython-input-1-304a18524b57>", line 14, in <module>
    traced_f(4)
tensorflow.python.framework.errors_impl.FailedPreconditionError: 2 root error(s) found.
  (0) Failed precondition:  Error while reading resource variable _AnonymousVar4 from Container: localhost. This could mean that the variable was uninitialized. Not found: Resource localhost/_AnonymousVar4/N10tensorflow3VarE does not exist.
     [[node ReadVariableOp (defined at <ipython-input-1-304a18524b57>:4) ]]
  (1) Failed precondition:  Error while reading resource variable _AnonymousVar4 from Container: localhost. This could mean that the variable was uninitialized. Not found: Resource localhost/_AnonymousVar4/N10tensorflow3VarE does not exist.
     [[node ReadVariableOp (defined at <ipython-input-1-304a18524b57>:4) ]]
     [[ReadVariableOp/_2]]
0 successful operations.
0 derived errors ignored. [Op:__inference_f_514]

Function call stack:
f -> f


Transformacje AutoGraph

AutoGraph to biblioteka, która jest domyślnie włączona w tf.function i przekształca podzbiór chętnego kodu Pythona na zgodne z grafami operacje TensorFlow. Obejmuje to przepływ sterowania, taki jak if , for , while .

Operacje TensorFlow, takie jak tf.cond i tf.while_loop nadal działają, ale przepływ sterowania jest często łatwiejszy do napisania i zrozumienia, gdy jest napisany w Pythonie.

# 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.224704742 0.895507693 0.0398198366 0.98112452 0.278468847]
[0.220997646 0.71410346 0.0397988036 0.753552318 0.271487355]
[0.217468739 0.61324358 0.0397778042 0.637263417 0.265008271]
[0.214104146 0.546406269 0.0397568382 0.563033342 0.258973926]
[0.210891485 0.497821957 0.0397359058 0.510224521 0.253335565]
[0.207819641 0.460402519 0.0397150069 0.470120102 0.248051569]
[0.204878598 0.430412233 0.0396941416 0.438296348 0.243086234]
[0.202059314 0.405665785 0.039673306 0.412231296 0.2384087]
[0.199353606 0.384786367 0.039652504 0.39036563 0.23399213]
[0.196754038 0.366856933 0.0396317355 0.371675402 0.229813099]
[0.194253832 0.351239443 0.039611 0.355456293 0.225851]
[0.191846803 0.337474287 0.0395902954 0.341205537 0.222087651]
[0.189527303 0.325220674 0.0395696238 0.3285532 0.218506947]
[0.187290132 0.314219803 0.0395489857 0.317220151 0.215094551]
[0.185130537 0.304271102 0.0395283774 0.30699119 0.211837649]
[0.183044136 0.295216352 0.0395078026 0.297697395 0.208724767]
[0.181026861 0.286928833 0.0394872613 0.289204 0.205745578]

<tf.Tensor: shape=(5,), dtype=float32, numpy=
array([0.17907499, 0.27930567, 0.03946675, 0.281402  , 0.20289075],
      dtype=float32)>

Jeśli jesteś ciekawy, możesz sprawdzić kod generowany przez autograf.

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)


Warunki

AutoGraph skonwertuje niektóre instrukcje if <condition> na odpowiadające tf.cond wywołania tf.cond . To podstawienie jest wykonywane, jeśli <condition> jest tensorem. W przeciwnym if instrukcja if jest wykonywana jako warunkowa w Pythonie.

Warunek Pythona jest wykonywany podczas śledzenia, więc dokładnie jedna gałąź warunku zostanie dodana do wykresu. Bez AutoGraph ten śledzony wykres nie byłby w stanie przyjąć alternatywnej gałęzi, jeśli istnieje przepływ sterowania zależny od danych.

tf.cond śledzi i dodaje obie gałęzie warunku do wykresu, dynamicznie wybierając gałąź w czasie wykonywania. Śledzenie może mieć niezamierzone skutki uboczne; więcej informacji można znaleźć w sekcji Efekty śledzenia AutoGraph .

@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

Zapoznaj się z dokumentacją referencyjną, aby uzyskać dodatkowe ograniczenia dotyczące instrukcji if konwertowanych metodą AutoGraph.

Pętle

AutoGraph skonwertuje niektóre instrukcje for i while na równoważne operacje pętli TensorFlow, takie jak tf.while_loop . Jeśli nie przeprowadzeniu modyfikacji for lub while pętla jest wykonany w postaci pętli Pythona.

Ta zamiana ma miejsce w następujących sytuacjach:

Pętla Pythona jest wykonywana podczas śledzenia, dodając dodatkowe tf.Graph do tf.Graph dla każdej iteracji pętli.

Pętla TensorFlow śledzi treść pętli i dynamicznie wybiera liczbę iteracji do uruchomienia w czasie wykonywania. Treść pętli pojawia się tylko raz w wygenerowanym tf.Graph .

Zobacz dokumentację referencyjną, aby uzyskać dodatkowe ograniczenia dotyczące instrukcji AutoGraph konwertowanych for i while .

Pętla przez dane Pythona

Częstą tf.function jest tf.function danych Python / Numpy w tf.function . Ta pętla będzie wykonywana podczas procesu śledzenia, dodając kopię twojego modelu do tf.Graph dla każdej iteracji pętli.

Jeśli chcesz zawinąć całą pętlę szkoleniową w tf.function , najbezpieczniejszym sposobem na zrobienie tego jest zawinięcie danych jakotf.data.Dataset , aby AutoGraph dynamicznie rozwijał pętlę szkoleniową.

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 shapes: (<unknown>, <unknown>), types: (tf.int32, tf.int32)>) contains 8 nodes in its graph
train(<FlatMapDataset shapes: (<unknown>, <unknown>), types: (tf.int32, tf.int32)>) contains 8 nodes in its graph

Podczas pakowania danych Python / Numpy w tf.data.Dataset.from_generator danych należy pamiętać o tf.data.Dataset.from_generator i tf.data.Dataset.from_tensors . Pierwsza z nich zachowa dane w Pythonie i tf.py_function je przez tf.py_function co może mieć wpływ na wydajność, podczas gdy druga będzie tf.constant() kopię danych jako jeden duży węzeł tf.constant() na wykresie, co może mieć wpływ na pamięć.

Odczyt danych z plików przez TFRecordDataset / CsvDataset / etc. jest najbardziej efektywnym sposobem wykorzystania danych, ponieważ wtedy sam TensorFlow może zarządzać asynchronicznym ładowaniem i wstępnym pobieraniem danych bez konieczności angażowania języka Python. Aby dowiedzieć się więcej, zobacz przewodnik tf.data .

Gromadzenie wartości w pętli

Powszechnym wzorcem jest gromadzenie wartości pośrednich z pętli. Zwykle dokonuje się tego poprzez dołączenie do listy Pythona lub dodanie wpisów do słownika Pythona. Jednak ponieważ są to efekty uboczne Pythona, nie będą one działać zgodnie z oczekiwaniami w dynamicznie rozwijanej pętli. Użyj tf.TensorArray do gromadzenia wyników z dynamicznie rozwijanej pętli.

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.9854791 , 0.5162524 , 0.14062047, 0.04950547],
        [1.8820469 , 0.67421603, 0.40786874, 0.7679055 ],
        [2.8815444 , 1.1567757 , 1.0627073 , 0.8880433 ]],

       [[0.94119024, 0.19776726, 0.24890792, 0.4663092 ],
        [1.4591933 , 1.123581  , 0.35438073, 1.4392309 ],
        [2.0026946 , 1.9165647 , 0.37988353, 1.8128917 ]]], dtype=float32)>

Dalsza lektura

Aby dowiedzieć się, jak eksportować i ładować Function , zobacz przewodnik SavedModel . Aby dowiedzieć się więcej na temat optymalizacji wykresów, które są wykonywane po śledzeniu, zobacz przewodnik dotyczący grapplera . Aby dowiedzieć się, jak zoptymalizować potok danych i profilować model, zobacz przewodnik Profiler .