Odpowiedz już dziś na lokalne wydarzenie TensorFlow Everywhere!
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 Function (na przykład przez zastosowanie dekoratora @tf.function ) jest jak podstawowa operacja TensorFlow: można ją wykonywać z niecierpliwością; możesz obliczyć gradienty; i tak dalej.

@tf.function  # The decorator converts `add` into a `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.003833446999806256
Function conv: 0.004606625999940661
Note how there's not much difference in performance for convolutions

Rysunek kalkowy

Ta sekcja pokazuje, jak Function działa pod maską, w tym szczegóły implementacji, które mogą ulec zmianie w przyszłości . Jednak gdy już zrozumiesz, dlaczego i kiedy odbywa się śledzenie, znacznie łatwiej tf.function efektywnie korzystać z tf.function !

Co to jest „śledzenie”?

Function uruchamia program na wykresie TensorFlow . Jednak tf.Graph nie może reprezentować wszystkich rzeczy, które można napisać w gorącym programie TensorFlow. Na przykład Python obsługuje polimorfizm, ale tf.Graph wymaga, aby jego dane wejściowe miały określony typ i wymiar danych. Możesz też wykonać zadania poboczne, takie jak odczytywanie argumentów wiersza poleceń, zgłaszanie błędu lub praca z bardziej złożonym obiektem Pythona; żadna z tych rzeczy nie może działać w tf.Graph .

Function wypełnia tę lukę, rozdzielając kod na dwa etapy:

1) W pierwszym etapie, zwanym „ śledzeniem ”, Function tworzy nowy tf.Graph . Kod Pythona działa normalnie, ale wszystkie operacje TensorFlow (takie jak dodanie dwóch Tensorów) są odroczone : są przechwytywane przez tf.Graph i nie są uruchamiane.

2) W drugim etapie tf.Graph jest tf.Graph zawierający wszystko, co zostało odroczone w pierwszym etapie. Ten etap jest znacznie szybszy niż etap śledzenia.

W zależności od swoich danych wejściowych Function nie zawsze będzie uruchamiać pierwszy etap, gdy zostanie wywołana. Zobacz „Zasady śledzenia” poniżej, aby lepiej zrozumieć, w jaki sposób decyduje o tym. Pominięcie pierwszego etapu i wykonanie tylko drugiego etapu zapewnia wysoką wydajność TensorFlow.

Gdy Function zdecyduje się na śledzenie, po etapie śledzenia następuje natychmiast drugi etap, więc wywołanie Function jednocześnie tworzy i uruchamia tf.Graph . Później zobaczysz, jak możesz uruchomić tylko etap śledzenia za pomocą funkcji get_concrete_function .

Kiedy przekazujemy argumenty różnych typów do Function , uruchamiane są oba etapy:

@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 wielokrotnie wywołujesz Function z tym samym typem argumentu, TensorFlow pominie etap śledzenia i 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: float32 Tensor, shape=()
  Returns:
    float32 Tensor, shape=()

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

double(a)
  Args:
    a: int32 Tensor, shape=()
  Returns:
    int32 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 jest surową, tf.Graph od języka, przenośną reprezentacją obliczeń TensorFlow.
  • ConcreteFunction otacza plik 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 .
  • Tracing tworzy tf.Graph i zawija go w ConcreteFunction , znanej również jako ślad.

Zasady śledzenia

Function określa, czy ponownie użyć śledzonej funkcji ConcreteFunction , obliczając klucz pamięci podręcznej z argumentów wejściowych i kwargs. Klucz pamięci podręcznej to klucz, który identyfikuje ConcreteFunction na podstawie argumentów wejściowych i kwargs wywołania Function , zgodnie z następującymi regułami (które mogą ulec zmianie):

  • 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, czyli sytuacja, w której Function tworzy więcej niż jeden ślad, 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

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)

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_162 as input #0(zero-based) was expected to be a string tensor but is a int32 tensor [Op:__inference_double_162]

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 1683, in _call_impl
    cancellation_manager)
  File "/tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/eager/function.py", line 1728, 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 są tworzone 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).

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 języku Python.

# 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.418865323 0.911027312 0.382812262 0.722406387 0.160078526]
[0.39597407 0.721625 0.365147263 0.618397713 0.158725053]
[0.376498967 0.617914855 0.34973979 0.550011516 0.157405376]
[0.359663159 0.549674571 0.336144745 0.500528812 0.156118125]
[0.344917297 0.500276268 0.324031234 0.462533 0.154862]
[0.331860244 0.462334394 0.313147396 0.43214643 0.15363577]
[0.320191294 0.431984901 0.303297669 0.407113552 0.152438238]
[0.309679836 0.406978786 0.294327527 0.386019081 0.151268333]
[0.300145775 0.385904342 0.286112964 0.36792323 0.150125]
[0.29144603 0.367824018 0.278553277 0.352173865 0.149007246]
[0.283465207 0.352086931 0.271565557 0.338302 0.147914127]
[0.276109159 0.338225 0.265080959 0.325960636 0.146844745]
[0.269300193 0.325891823 0.259041727 0.31488657 0.145798281]
[0.262973547 0.314824551 0.253399 0.304876 0.144773886]
[0.257074684 0.304819793 0.248111084 0.29576847 0.143770814]
[0.25155738 0.29571715 0.243142202 0.287435412 0.142788336]
[0.246382073 0.287388295 0.238461465 0.279772669 0.14182575]
[0.241514727 0.279729217 0.234042 0.272694677 0.140882403]
[0.23692593 0.272654444 0.229860321 0.266130418 0.139957651]
[0.232590035 0.266093045 0.225895762 0.260020494 0.139050901]
[0.228484616 0.259985656 0.222130179 0.254314691 0.13816157]
[0.224589869 0.254282087 0.218547404 0.248970196 0.137289107]
[0.220888361 0.248939633 0.215133116 0.243950352 0.13643299]
[0.217364609 0.243921608 0.211874455 0.23922351 0.135592714]
[0.214004785 0.239196405 0.208759964 0.234762147 0.134767786]
[0.210796505 0.234736577 0.20577924 0.230542287 0.133957744]
[0.207728744 0.230518073 0.202923 0.226542845 0.133162171]

<tf.Tensor: shape=(5,), dtype=float32, numpy=
array([0.20479149, 0.22651988, 0.20018281, 0.22274524, 0.13238062],
      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 przekonwertowanych 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 to jest zawinięcie danych jakotf.data.Dataset tak 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 10 nodes in its graph
train(<FlatMapDataset shapes: (<unknown>, <unknown>), types: (tf.int32, tf.int32)>) contains 10 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.04024231, 0.8750231 , 0.7828673 , 0.95796347],
        [0.6933379 , 1.4302992 , 1.0286844 , 1.2873636 ],
        [1.0946463 , 2.3919935 , 1.383383  , 1.6184174 ]],

       [[0.35952663, 0.8509462 , 0.04545867, 0.40333533],
        [0.43437874, 1.1436893 , 0.31203532, 0.4216336 ],
        [0.46808207, 1.8400187 , 0.3120687 , 1.1190954 ]]], dtype=float32)>

Ograniczenia

Function TensorFlow ma kilka ograniczeń projektowych, o których należy pamiętać podczas konwertowania funkcji Pythona na Function .

Wykonywanie efektów ubocznych Pythona

Efekty uboczne, takie jak drukowanie, dołączanie do list i mutowanie zmiennych globalnych, mogą zachowywać się nieoczekiwanie wewnątrz Function , czasami wykonując dwukrotnie lub nie wszystkie. 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 jest taka, aby unikać polegania na efektach ubocznych Pythona w swojej logice i używać ich tylko do debugowania śladów. W przeciwnym razie interfejsy API tf.data , takie jak tf.data , tf.print , tf.summary , tf.Variable.assign i tf.TensorArray są najlepszym sposobem na zapewnienie wykonania 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

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, nie można go zapisać za pomocą SavedModel i 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.

Zmiana zmiennych globalnych i bezpłatnych w Pythonie

Zmiana globalnych i wolnych zmiennych w Pythonie liczy się jako efekt uboczny Pythona, więc dzieje się to tylko podczas śledzenia.

external_list = []

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

side_effect(1)
side_effect(1)
side_effect(1)
# The list append only happened once!
assert len(external_list) == 1
Python side effect

Należy unikać mutowania kontenerów, takich jak listy, dykty i inne obiekty, które znajdują się poza Function . Zamiast tego użyj argumentów i obiektów TF. Na przykład sekcja „Akumulowanie wartości w pętli” zawiera przykład implementacji operacji podobnych do list.

W niektórych przypadkach możesz przechwytywać stan i manipulować nim, jeśli jest to tf.Variable . W ten sposób wagi modeli Keras są aktualizowane za pomocą powtarzających się wywołań tej samej funkcji ConcreteFunction .

Korzystanie z iteratorów i generatorów Pythona

Wiele funkcji Pythona, takich jak generatory i iteratory, polega na środowisku wykonawczym Pythona do śledzenia stanu. Ogólnie rzecz biorąc, chociaż konstrukcje te działają zgodnie z oczekiwaniami w trybie przyspieszonym, są przykładami efektów ubocznych Pythona i dlatego występują tylko podczas śledzenia.

@tf.function
def buggy_consume_next(iterator):
  tf.print("Value:", next(iterator))

iterator = iter([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: 1
Value: 1
Value: 1

Podobnie jak TensorFlow ma wyspecjalizowany tf.TensorArray dla konstrukcji list, ma wyspecjalizowany tf.data.Iterator dla konstrukcji iteracyjnych. Aby zapoznać się z omówieniem, zobacz sekcję dotyczącą Transformacji AutoGraph . Ponadto interfejs API tf.data może pomóc we wdrożeniu wzorców generatorów:

@tf.function
def good_consume_next(iterator):
  # This is ok, iterator is a tf.data.Iterator
  tf.print("Value:", next(iterator))

ds = tf.data.Dataset.from_tensor_slices([1, 2, 3])
iterator = iter(ds)
good_consume_next(iterator)
good_consume_next(iterator)
good_consume_next(iterator)
Value: 1
Value: 2
Value: 3

Usuwanie zmiennych tf. Między wywołaniami Function

Innym błędem, który możesz napotkać, jest zmienna zbierana bezużytecznie. Funkcja ConcreteFunction zachowuje WeakRefs tylko do zmiennych, które zamykają, więc musisz zachować odniesienie do wszystkich 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))

# The original variable object gets garbage collected, since there are no more
# references to it.
external_var = tf.Variable(4)
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-9a93d2e07632>", line 16, in <module>
    traced_f(4)
tensorflow.python.framework.errors_impl.FailedPreconditionError: 2 root error(s) found.
  (0) Failed precondition:  Error while reading resource variable _AnonymousVar3 from Container: localhost. This could mean that the variable was uninitialized. Not found: Resource localhost/_AnonymousVar3/N10tensorflow3VarE does not exist.
     [[node ReadVariableOp (defined at <ipython-input-1-9a93d2e07632>:4) ]]
  (1) Failed precondition:  Error while reading resource variable _AnonymousVar3 from Container: localhost. This could mean that the variable was uninitialized. Not found: Resource localhost/_AnonymousVar3/N10tensorflow3VarE does not exist.
     [[node ReadVariableOp (defined at <ipython-input-1-9a93d2e07632>:4) ]]
     [[ReadVariableOp/_2]]
0 successful operations.
0 derived errors ignored. [Op:__inference_f_782]

Function call stack:
f -> f


Znane problemy

Jeśli Function nie jest oceniana prawidłowo, błąd może wynikać z tych znanych problemów, które mają zostać naprawione w przyszłości.

W zależności od zmiennych globalnych i bezpłatnych Pythona

Function tworzy nową ConcreteFunction gdy jest wywoływana z nową wartością argumentu Pythona. Jednak nie robi tego dla zamknięcia Pythona, globalnych lub nielokalnych tej Function . Jeśli ich wartość zmieni się między wywołaniami Function , Function nadal będzie używać wartości, które miały, gdy była śledzona. Różni się to od działania zwykłych funkcji Pythona.

Z tego powodu zalecamy funkcjonalny styl programowania, który używa argumentów zamiast zamykania nazw zewnętrznych.

@tf.function
def buggy_add():
  return 1 + foo

@tf.function
def recommended_add(foo):
  return 1 + foo

foo = 1
print("Buggy:", buggy_add())
print("Correct:", recommended_add(foo))
Buggy: tf.Tensor(2, shape=(), dtype=int32)
Correct: tf.Tensor(2, shape=(), dtype=int32)

print("Updating the value of `foo` to 100!")
foo = 100
print("Buggy:", buggy_add())  # Did not change!
print("Correct:", recommended_add(foo))
Updating the value of `foo` to 100!
Buggy: tf.Tensor(2, shape=(), dtype=int32)
Correct: tf.Tensor(101, shape=(), dtype=int32)

Możesz zamknąć nazwy zewnętrzne, o ile nie zaktualizujesz ich wartości.

W zależności od obiektów Pythona

Zalecenie przekazywania obiektów Pythona jako argumentów do tf.function zawiera wiele znanych problemów, które powinny zostać naprawione w przyszłości. Ogólnie rzecz biorąc, możesz polegać na spójnym śledzeniu, jeśli używasz prymitywu Pythona lub struktury zgodnej z tf.nest jako argumentu lub przekazujesz inne wystąpienie obiektu do Function . Jednak Function nie utworzy nowego śladu, gdy przekażesz ten sam obiekt i zmieni tylko jego atrybuty .

class SimpleModel(tf.Module):
  def __init__(self):
    # These values are *not* tf.Variables.
    self.bias = 0.
    self.weight = 2.

@tf.function
def evaluate(model, x):
  return model.weight * x + model.bias

simple_model = SimpleModel()
x = tf.constant(10.)
print(evaluate(simple_model, x))
tf.Tensor(20.0, shape=(), dtype=float32)

print("Adding bias!")
simple_model.bias += 5.0
print(evaluate(simple_model, x))  # Didn't change :(
Adding bias!
tf.Tensor(20.0, shape=(), dtype=float32)

Użycie tej samej Function do oceny zaktualizowanej instancji modelu będzie błędne, ponieważ zaktualizowany model ma ten sam klucz pamięci podręcznej, co oryginalny model.

Z tego powodu zalecamy napisanie Function aby uniknąć uzależnienia od zmiennych atrybutów obiektu lub tworzyć nowe obiekty.

Jeśli nie jest to możliwe, jednym obejściem jest tworzenie nowych Function każdym razem, gdy modyfikujesz obiekt, aby wymusić odtworzenie:

def evaluate(model, x):
  return model.weight * x + model.bias

new_model = SimpleModel()
evaluate_no_bias = tf.function(evaluate).get_concrete_function(new_model, x)
# Don't pass in `new_model`, `Function` already captured its state during tracing.
print(evaluate_no_bias(x))
tf.Tensor(20.0, shape=(), dtype=float32)

print("Adding bias!")
new_model.bias += 5.0
# Create new Function and ConcreteFunction since you modified new_model.
evaluate_with_bias = tf.function(evaluate).get_concrete_function(new_model, x)
print(evaluate_with_bias(x)) # Don't pass in `new_model`.
Adding bias!
tf.Tensor(25.0, shape=(), dtype=float32)

Ponieważ odtwarzanie może być kosztowne , możesz użyć tf.Variable s jako atrybutów obiektu, które można zmutować (ale nie zmienić, uwaga!) W celu uzyskania podobnego efektu bez konieczności tf.Variable .

class BetterModel:

  def __init__(self):
    self.bias = tf.Variable(0.)
    self.weight = tf.Variable(2.)

@tf.function
def evaluate(model, x):
  return model.weight * x + model.bias

better_model = BetterModel()
print(evaluate(better_model, x))
tf.Tensor(20.0, shape=(), dtype=float32)

print("Adding bias!")
better_model.bias.assign_add(5.0)  # Note: instead of better_model.bias += 5
print(evaluate(better_model, x))  # This works!
Adding bias!
tf.Tensor(25.0, shape=(), dtype=float32)

Tworzenie zmiennych tf.Variables

Function obsługuje tworzenie zmiennych tylko raz, przy pierwszym wywołaniu, a następnie ponowne ich użycie. Nie można tworzyć tf.Variables w nowych śladach. Tworzenie nowych zmiennych w kolejnych wywołaniach jest obecnie niedozwolone, ale będzie w przyszłości.

Przykład:

@tf.function
def f(x):
  v = tf.Variable(1.0)
  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-8a0913e250e0>", line 7, in <module>
    f(1.0)
ValueError: in user code:

    <ipython-input-1-8a0913e250e0>: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:731 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)

Używanie z wieloma optymalizatorami Keras

Możesz napotkać ValueError: tf.function-decorated function tried to create variables on non-first call. podczas używania więcej niż jednego optymalizatora Keras z tf.function . Ten błąd występuje, ponieważ optymalizatory wewnętrznie tworzą tf.Variables gdy stosują gradienty po raz pierwszy.

opt1 = tf.keras.optimizers.Adam(learning_rate = 1e-2)
opt2 = tf.keras.optimizers.Adam(learning_rate = 1e-3)

@tf.function
def train_step(w, x, y, optimizer):
   with tf.GradientTape() as tape:
       L = tf.reduce_sum(tf.square(w*x - y))
   gradients = tape.gradient(L, [w])
   optimizer.apply_gradients(zip(gradients, [w]))

w = tf.Variable(2.)
x = tf.constant([-1.])
y = tf.constant([2.])

train_step(w, x, y, opt1)
print("Calling `train_step` with different optimizer...")
with assert_raises(ValueError):
  train_step(w, x, y, opt2)
Calling `train_step` with different optimizer...
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-d3d3937dbf1a>", line 18, in <module>
    train_step(w, x, y, opt2)
ValueError: in user code:

    <ipython-input-1-d3d3937dbf1a>:9 train_step  *
        optimizer.apply_gradients(zip(gradients, [w]))
    /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/keras/optimizer_v2/optimizer_v2.py:604 apply_gradients  **
        self._create_all_weights(var_list)
    /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/keras/optimizer_v2/optimizer_v2.py:781 _create_all_weights
        _ = self.iterations
    /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/keras/optimizer_v2/optimizer_v2.py:788 __getattribute__
        return super(OptimizerV2, self).__getattribute__(name)
    /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/keras/optimizer_v2/optimizer_v2.py:926 iterations
        aggregation=tf_variables.VariableAggregation.ONLY_FIRST_REPLICA)
    /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/keras/optimizer_v2/optimizer_v2.py:1132 add_weight
        aggregation=aggregation)
    /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/training/tracking/base.py:810 _add_variable_with_custom_getter
        **kwargs_for_getter)
    /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/keras/engine/base_layer_utils.py:142 make_variable
        shape=variable_shape if variable_shape else None)
    /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/ops/variables.py:260 __call__
        return cls._variable_v1_call(*args, **kwargs)
    /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/ops/variables.py:221 _variable_v1_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:731 invalid_creator_scope
        "tf.function-decorated function tried to create "

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


Jeśli musisz zmienić optymalizator podczas uczenia, obejściem jest utworzenie nowej Function dla każdego optymalizatora, wywołując bezpośrednio ConcreteFunction .

opt1 = tf.keras.optimizers.Adam(learning_rate = 1e-2)
opt2 = tf.keras.optimizers.Adam(learning_rate = 1e-3)

# Not a tf.function.
def train_step(w, x, y, optimizer):
   with tf.GradientTape() as tape:
       L = tf.reduce_sum(tf.square(w*x - y))
   gradients = tape.gradient(L, [w])
   optimizer.apply_gradients(zip(gradients, [w]))

w = tf.Variable(2.)
x = tf.constant([-1.])
y = tf.constant([2.])

# Make a new Function and ConcreteFunction for each optimizer.
train_step_1 = tf.function(train_step).get_concrete_function(w, x, y, opt1)
train_step_2 = tf.function(train_step).get_concrete_function(w, x, y, opt2)
for i in range(10):
  if i % 2 == 0:
    train_step_1(w, x, y) # `opt1` is not used as a parameter. 
  else:
    train_step_2(w, x, y) # `opt2` is not used as a parameter.

Używanie z wieloma modelami Keras

Możesz również napotkać ValueError: tf.function-decorated function tried to create variables on non-first call. podczas przekazywania różnych instancji modelu do tej samej Function .

Ten błąd występuje, ponieważ modele Keras (które nie mają zdefiniowanego kształtu wejściowego ) i warstwy Keras tworzą tf.Variables przy pierwszym wywołaniu. Możesz próbować zainicjować te zmienne wewnątrz Function , która została już wywołana. Aby uniknąć tego błędu, spróbuj wywołać model.build(input_shape) celu zainicjowania wszystkich wag przed model.build(input_shape) modelu.

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 .