Zobacz na TensorFlow.org | Uruchom w Google Colab | Wyświetl źródło na GitHub | Pobierz notatnik |
Przegląd
Ten przewodnik zagłębia się w temat TensorFlow i Keras, aby zademonstrować, jak działa TensorFlow. Jeśli zamiast tego chcesz od razu zacząć korzystać z Keras, zapoznaj się z kolekcją przewodników Keras .
W tym przewodniku dowiesz się, w jaki sposób TensorFlow umożliwia wprowadzanie prostych zmian w kodzie w celu uzyskania wykresów, jak są one przechowywane i reprezentowane oraz jak możesz ich używać do przyspieszania modeli.
Jest to ogólny przegląd, który opisuje, w jaki sposób tf.function
pozwala przełączyć się z szybkiego wykonywania do wykonywania wykresów. Aby uzyskać pełniejszą specyfikację tf.function
, przejdź do przewodnika tf.function
.
Czym są wykresy?
W poprzednich trzech przewodnikach chętnie korzystałeś z TensorFlow . Oznacza to, że operacje TensorFlow są wykonywane przez Pythona, operacja po operacji i zwracają wyniki z powrotem do Pythona.
Podczas gdy szybkie wykonywanie ma kilka unikalnych zalet, wykonywanie grafów umożliwia przenoszenie poza Python i zwykle zapewnia lepszą wydajność. Wykonanie wykresu oznacza, że obliczenia tensorowe są wykonywane jako wykres TensorFlow , czasami nazywany tf.Graph
lub po prostu „grafem”.
Wykresy to struktury danych zawierające zestaw obiektów tf.Operation
, które reprezentują jednostki obliczeniowe; oraz obiekty tf.Tensor
, które reprezentują jednostki danych przepływających między operacjami. Są one zdefiniowane w kontekście tf.Graph
. Ponieważ te wykresy są strukturami danych, można je zapisywać, uruchamiać i odtwarzać bez oryginalnego kodu Pythona.
Tak wygląda wykres TensorFlow reprezentujący dwuwarstwową sieć neuronową w wizualizacji w TensorBoard.
Korzyści z wykresów
Dzięki wykresowi masz dużą elastyczność. Możesz używać swojego wykresu TensorFlow w środowiskach, które nie mają interpretera Pythona, takich jak aplikacje mobilne, urządzenia osadzone i serwery zaplecza. TensorFlow używa wykresów jako formatu zapisanych modeli podczas eksportowania ich z Pythona.
Wykresy są również łatwo optymalizowane, co pozwala kompilatorowi na wykonywanie przekształceń, takich jak:
- Statycznie wywnioskuj wartość tensorów, składając stałe węzły w obliczeniach („składanie stałe”) .
- Oddziel niezależne części obliczeń i podziel je między wątki lub urządzenia.
- Uprość operacje arytmetyczne, eliminując wspólne wyrażenia podrzędne.
Istnieje cały system optymalizacji Grappler , który wykonuje to i inne przyspieszenia.
Krótko mówiąc, wykresy są niezwykle przydatne i pozwalają Twojemu TensorFlow działać szybko , działać równolegle i wydajnie na wielu urządzeniach .
Jednak nadal chcesz zdefiniować modele uczenia maszynowego (lub inne obliczenia) w Pythonie dla wygody, a następnie automatycznie konstruować wykresy, gdy ich potrzebujesz.
Ustawiać
import tensorflow as tf
import timeit
from datetime import datetime
Korzystanie z wykresów
Tworzysz i uruchamiasz wykres w TensorFlow za pomocą tf.function
, jako bezpośrednie wywołanie lub jako dekorator. tf.function
przyjmuje zwykłą funkcję jako dane wejściowe i zwraca Function
. Function
to wywoływalna funkcja Pythona, która buduje wykresy TensorFlow z funkcji Pythona. Używasz Function
w taki sam sposób, jak jej odpowiednika w Pythonie.
# Define a Python function.
def a_regular_function(x, y, b):
x = tf.matmul(x, y)
x = x + b
return x
# `a_function_that_uses_a_graph` is a TensorFlow `Function`.
a_function_that_uses_a_graph = tf.function(a_regular_function)
# Make some tensors.
x1 = tf.constant([[1.0, 2.0]])
y1 = tf.constant([[2.0], [3.0]])
b1 = tf.constant(4.0)
orig_value = a_regular_function(x1, y1, b1).numpy()
# Call a `Function` like a Python function.
tf_function_value = a_function_that_uses_a_graph(x1, y1, b1).numpy()
assert(orig_value == tf_function_value)
Z zewnątrz Function
wygląda jak zwykła funkcja, którą piszesz za pomocą operacji TensorFlow. Pod spodem jest jednak zupełnie inaczej . Function
hermetyzuje kilka tf.Graph
za jednym API . W ten sposób Function
może zapewnić Ci korzyści z wykonywania wykresów , takie jak szybkość i możliwość wdrożenia.
tf.function
dotyczy funkcji i wszystkich innych funkcji, które wywołuje :
def inner_function(x, y, b):
x = tf.matmul(x, y)
x = x + b
return x
# Use the decorator to make `outer_function` a `Function`.
@tf.function
def outer_function(x):
y = tf.constant([[2.0], [3.0]])
b = tf.constant(4.0)
return inner_function(x, y, b)
# Note that the callable will create a graph that
# includes `inner_function` as well as `outer_function`.
outer_function(tf.constant([[1.0, 2.0]])).numpy()
array([[12.]], dtype=float32)
Jeśli korzystałeś z TensorFlow 1.x, zauważysz, że w żadnym momencie nie musiałeś definiować elementu Placeholder
ani tf.Session
.
Konwersja funkcji Pythona na wykresy
Każda funkcja, którą piszesz za pomocą TensorFlow, będzie zawierała mieszankę wbudowanych operacji TF i logiki Pythona, takich jak klauzule if-then
, pętle, break
, return
, continue
i inne. Podczas gdy operacje TensorFlow są łatwo przechwytywane przez tf.Graph
, logika specyficzna dla Pythona musi przejść dodatkowy krok, aby stać się częścią wykresu. tf.function
używa biblioteki o nazwie AutoGraph ( tf.autograph
) do konwersji kodu Pythona na kod generujący wykresy.
def simple_relu(x):
if tf.greater(x, 0):
return x
else:
return 0
# `tf_simple_relu` is a TensorFlow `Function` that wraps `simple_relu`.
tf_simple_relu = tf.function(simple_relu)
print("First branch, with graph:", tf_simple_relu(tf.constant(1)).numpy())
print("Second branch, with graph:", tf_simple_relu(tf.constant(-1)).numpy())
First branch, with graph: 1 Second branch, with graph: 0
Chociaż jest mało prawdopodobne, że będziesz musiał przeglądać wykresy bezpośrednio, możesz sprawdzić wyniki, aby sprawdzić dokładne wyniki. Nie są one łatwe do odczytania, więc nie musisz patrzeć zbyt uważnie!
# This is the graph-generating output of AutoGraph.
print(tf.autograph.to_code(simple_relu))
def tf__simple_relu(x): with ag__.FunctionScope('simple_relu', 'fscope', ag__.ConversionOptions(recursive=True, user_requested=True, optional_features=(), internal_convert_user_code=True)) as fscope: do_return = False retval_ = ag__.UndefinedReturnValue() def get_state(): return (do_return, retval_) def set_state(vars_): nonlocal retval_, do_return (do_return, retval_) = vars_ def if_body(): nonlocal retval_, do_return try: do_return = True retval_ = ag__.ld(x) except: do_return = False raise def else_body(): nonlocal retval_, do_return try: do_return = True retval_ = 0 except: do_return = False raise ag__.if_stmt(ag__.converted_call(ag__.ld(tf).greater, (ag__.ld(x), 0), None, fscope), if_body, else_body, get_state, set_state, ('do_return', 'retval_'), 2) return fscope.ret(retval_, do_return)
# This is the graph itself.
print(tf_simple_relu.get_concrete_function(tf.constant(1)).graph.as_graph_def())
node { name: "x" op: "Placeholder" attr { key: "_user_specified_name" value { s: "x" } } attr { key: "dtype" value { type: DT_INT32 } } attr { key: "shape" value { shape { } } } } node { name: "Greater/y" op: "Const" attr { key: "dtype" value { type: DT_INT32 } } attr { key: "value" value { tensor { dtype: DT_INT32 tensor_shape { } int_val: 0 } } } } node { name: "Greater" op: "Greater" input: "x" input: "Greater/y" attr { key: "T" value { type: DT_INT32 } } } node { name: "cond" op: "StatelessIf" input: "Greater" input: "x" attr { key: "Tcond" value { type: DT_BOOL } } attr { key: "Tin" value { list { type: DT_INT32 } } } attr { key: "Tout" value { list { type: DT_BOOL type: DT_INT32 } } } attr { key: "_lower_using_switch_merge" value { b: true } } attr { key: "_read_only_resource_inputs" value { list { } } } attr { key: "else_branch" value { func { name: "cond_false_34" } } } attr { key: "output_shapes" value { list { shape { } shape { } } } } attr { key: "then_branch" value { func { name: "cond_true_33" } } } } node { name: "cond/Identity" op: "Identity" input: "cond" attr { key: "T" value { type: DT_BOOL } } } node { name: "cond/Identity_1" op: "Identity" input: "cond:1" attr { key: "T" value { type: DT_INT32 } } } node { name: "Identity" op: "Identity" input: "cond/Identity_1" attr { key: "T" value { type: DT_INT32 } } } library { function { signature { name: "cond_false_34" input_arg { name: "cond_placeholder" type: DT_INT32 } output_arg { name: "cond_identity" type: DT_BOOL } output_arg { name: "cond_identity_1" type: DT_INT32 } } node_def { name: "cond/Const" op: "Const" attr { key: "dtype" value { type: DT_BOOL } } attr { key: "value" value { tensor { dtype: DT_BOOL tensor_shape { } bool_val: true } } } } node_def { name: "cond/Const_1" op: "Const" attr { key: "dtype" value { type: DT_BOOL } } attr { key: "value" value { tensor { dtype: DT_BOOL tensor_shape { } bool_val: true } } } } node_def { name: "cond/Const_2" op: "Const" attr { key: "dtype" value { type: DT_INT32 } } attr { key: "value" value { tensor { dtype: DT_INT32 tensor_shape { } int_val: 0 } } } } node_def { name: "cond/Const_3" op: "Const" attr { key: "dtype" value { type: DT_BOOL } } attr { key: "value" value { tensor { dtype: DT_BOOL tensor_shape { } bool_val: true } } } } node_def { name: "cond/Identity" op: "Identity" input: "cond/Const_3:output:0" attr { key: "T" value { type: DT_BOOL } } } node_def { name: "cond/Const_4" op: "Const" attr { key: "dtype" value { type: DT_INT32 } } attr { key: "value" value { tensor { dtype: DT_INT32 tensor_shape { } int_val: 0 } } } } node_def { name: "cond/Identity_1" op: "Identity" input: "cond/Const_4:output:0" attr { key: "T" value { type: DT_INT32 } } } ret { key: "cond_identity" value: "cond/Identity:output:0" } ret { key: "cond_identity_1" value: "cond/Identity_1:output:0" } attr { key: "_construction_context" value { s: "kEagerRuntime" } } arg_attr { key: 0 value { attr { key: "_output_shapes" value { list { shape { } } } } } } } function { signature { name: "cond_true_33" input_arg { name: "cond_identity_1_x" type: DT_INT32 } output_arg { name: "cond_identity" type: DT_BOOL } output_arg { name: "cond_identity_1" type: DT_INT32 } } node_def { name: "cond/Const" op: "Const" attr { key: "dtype" value { type: DT_BOOL } } attr { key: "value" value { tensor { dtype: DT_BOOL tensor_shape { } bool_val: true } } } } node_def { name: "cond/Identity" op: "Identity" input: "cond/Const:output:0" attr { key: "T" value { type: DT_BOOL } } } node_def { name: "cond/Identity_1" op: "Identity" input: "cond_identity_1_x" attr { key: "T" value { type: DT_INT32 } } } ret { key: "cond_identity" value: "cond/Identity:output:0" } ret { key: "cond_identity_1" value: "cond/Identity_1:output:0" } attr { key: "_construction_context" value { s: "kEagerRuntime" } } arg_attr { key: 0 value { attr { key: "_output_shapes" value { list { shape { } } } } } } } } versions { producer: 898 min_consumer: 12 }
W większości przypadków tf.function
będzie działać bez specjalnych uwarunkowań. Istnieją jednak pewne zastrzeżenia, a przewodnik tf.function może tutaj pomóc, a także pełne odniesienie do AutoGraph
Polimorfizm: jedna Function
, wiele grafów
tf.Graph
jest wyspecjalizowany do określonego typu danych wejściowych (na przykład tensory o określonym typie dtype
lub obiekty o tym samym id()
).
Za każdym razem, gdy wywołujesz Function
z nowymi dtypes
i kształtami w swoich argumentach, Function
tworzy nowy tf.Graph
dla nowych argumentów. dtypes
i kształty danych wejściowych tf.Graph
są znane jako sygnatura wejściowa lub po prostu sygnatura .
Function
przechowuje tf.Graph
odpowiadający tej sygnaturze w ConcreteFunction
. ConcreteFunction
to opakowanie wokół tf.Graph
.
@tf.function
def my_relu(x):
return tf.maximum(0., x)
# `my_relu` creates new graphs as it observes more signatures.
print(my_relu(tf.constant(5.5)))
print(my_relu([1, -1]))
print(my_relu(tf.constant([3., -3.])))
tf.Tensor(5.5, shape=(), dtype=float32) tf.Tensor([1. 0.], shape=(2,), dtype=float32) tf.Tensor([3. 0.], shape=(2,), dtype=float32)
Jeśli Function
została już wywołana z tą sygnaturą, Function
nie tworzy nowego tf.Graph
.
# These two calls do *not* create new graphs.
print(my_relu(tf.constant(-2.5))) # Signature matches `tf.constant(5.5)`.
print(my_relu(tf.constant([-1., 1.]))) # Signature matches `tf.constant([3., -3.])`.
tf.Tensor(0.0, shape=(), dtype=float32) tf.Tensor([0. 1.], shape=(2,), dtype=float32)
Ponieważ jest wspierany przez wiele wykresów, Function
jest polimorficzny . Umożliwia to obsługę większej liczby typów danych wejściowych niż może reprezentować pojedynczy tf.Graph
, a także optymalizację każdego tf.Graph
w celu uzyskania lepszej wydajności.
# There are three `ConcreteFunction`s (one for each graph) in `my_relu`.
# The `ConcreteFunction` also knows the return type and shape!
print(my_relu.pretty_printed_concrete_signatures())
my_relu(x) Args: x: float32 Tensor, shape=() Returns: float32 Tensor, shape=() my_relu(x=[1, -1]) Returns: float32 Tensor, shape=(2,) my_relu(x) Args: x: float32 Tensor, shape=(2,) Returns: float32 Tensor, shape=(2,)
Korzystanie tf.function
Do tej pory nauczyłeś się konwertować funkcję Pythona na wykres, używając po prostu tf.function
jako dekoratora lub opakowania. Ale w praktyce poprawne działanie tf.function
może być trudne! W kolejnych sekcjach dowiesz się, jak sprawić, by kod działał zgodnie z oczekiwaniami za pomocą tf.function
.
Wykonanie wykresu a gorliwe wykonanie
Kod w Function
można wykonać zarówno chętnie, jak i w postaci grafu. Domyślnie Function
wykonuje swój kod jako wykres:
@tf.function
def get_MSE(y_true, y_pred):
sq_diff = tf.pow(y_true - y_pred, 2)
return tf.reduce_mean(sq_diff)
y_true = tf.random.uniform([5], maxval=10, dtype=tf.int32)
y_pred = tf.random.uniform([5], maxval=10, dtype=tf.int32)
print(y_true)
print(y_pred)
tf.Tensor([1 0 4 4 7], shape=(5,), dtype=int32) tf.Tensor([3 6 3 0 6], shape=(5,), dtype=int32)
get_MSE(y_true, y_pred)
<tf.Tensor: shape=(), dtype=int32, numpy=11>
Aby sprawdzić, czy wykres Function
wykonuje te same obliczenia, co jego odpowiednik funkcji w Pythonie, możesz sprawić, by wykonywał się chętnie za pomocą tf.config.run_functions_eagerly(True)
. Jest to przełącznik, który wyłącza zdolność Function
do tworzenia i uruchamiania wykresów , zamiast normalnego wykonywania kodu.
tf.config.run_functions_eagerly(True)
get_MSE(y_true, y_pred)
<tf.Tensor: shape=(), dtype=int32, numpy=11>
# Don't forget to set it back when you are done.
tf.config.run_functions_eagerly(False)
Jednak Function
może zachowywać się inaczej pod wykresem i gorliwym wykonaniem. Funkcja print
Pythona jest jednym z przykładów różnic między tymi dwoma trybami. Sprawdźmy, co się dzieje, gdy wstawiasz instrukcję print
do swojej funkcji i wywołujesz ją wielokrotnie.
@tf.function
def get_MSE(y_true, y_pred):
print("Calculating MSE!")
sq_diff = tf.pow(y_true - y_pred, 2)
return tf.reduce_mean(sq_diff)
Obserwuj, co jest drukowane:
error = get_MSE(y_true, y_pred)
error = get_MSE(y_true, y_pred)
error = get_MSE(y_true, y_pred)
Calculating MSE!
Czy wynik jest zaskakujący? get_MSE
wydrukowane tylko raz, mimo że zostało wywołane trzy razy.
Aby wyjaśnić, instrukcja print
jest wykonywana, gdy Function
uruchamia oryginalny kod w celu utworzenia wykresu w procesie znanym jako „śledzenie” . Tracing przechwytuje operacje TensorFlow na wykresie, a print
nie jest przechwytywane na wykresie. Ten wykres jest następnie wykonywany dla wszystkich trzech wywołań bez ponownego uruchamiania kodu Pythona .
Aby sprawdzić rozsądek, wyłączmy wykonywanie wykresów, aby porównać:
# Now, globally set everything to run eagerly to force eager execution.
tf.config.run_functions_eagerly(True)
# Observe what is printed below.
error = get_MSE(y_true, y_pred)
error = get_MSE(y_true, y_pred)
error = get_MSE(y_true, y_pred)
Calculating MSE! Calculating MSE! Calculating MSE!
tf.config.run_functions_eagerly(False)
print
jest efektem ubocznym Pythona i istnieją inne różnice , o których powinieneś wiedzieć podczas konwertowania funkcji na Function
. Dowiedz się więcej w sekcji Ograniczenia przewodnika Lepsza wydajność dzięki tf.function .
Nieścisła egzekucja
Wykonanie wykresu wykonuje tylko operacje niezbędne do uzyskania obserwowalnych efektów, które obejmują:
- Zwracana wartość funkcji
- Udokumentowane dobrze znane skutki uboczne, takie jak:
- Operacje wejścia/wyjścia, takie jak
tf.print
- Operacje debugowania, takie jak funkcje attach w
tf.debugging
- Mutacje
tf.Variable
- Operacje wejścia/wyjścia, takie jak
To zachowanie jest zwykle znane jako „wykonywanie nieścisłe” i różni się od przyspieszonego wykonywania, które przechodzi przez wszystkie operacje programu, potrzebne lub nie.
W szczególności sprawdzanie błędów w czasie wykonywania nie liczy się jako obserwowalny efekt. Jeśli operacja zostanie pominięta, ponieważ jest niepotrzebna, nie może wywołać żadnych błędów w czasie wykonywania.
W poniższym przykładzie „niepotrzebna” operacja tf.gather
jest pomijana podczas wykonywania wykresu, więc błąd czasu wykonywania InvalidArgumentError
nie jest zgłaszany, ponieważ miałby miejsce w przypadku gorliwego wykonywania. Nie należy polegać na zgłaszaniu błędu podczas wykonywania wykresu.
def unused_return_eager(x):
# Get index 1 will fail when `len(x) == 1`
tf.gather(x, [1]) # unused
return x
try:
print(unused_return_eager(tf.constant([0.0])))
except tf.errors.InvalidArgumentError as e:
# All operations are run during eager execution so an error is raised.
print(f'{type(e).__name__}: {e}')
tf.Tensor([0.], shape=(1,), dtype=float32)
@tf.function
def unused_return_graph(x):
tf.gather(x, [1]) # unused
return x
# Only needed operations are run during graph exection. The error is not raised.
print(unused_return_graph(tf.constant([0.0])))
tf.Tensor([0.], shape=(1,), dtype=float32)
najlepsze praktyki tf.function
Przyzwyczajenie się do zachowania Function
może zająć trochę czasu. Aby szybko rozpocząć, nowi użytkownicy powinni bawić się dekorowaniem funkcji zabawek za pomocą @tf.function
, aby uzyskać doświadczenie w przechodzeniu od chętnego do wykonywania wykresów.
Projektowanie dla tf.function
może być najlepszym rozwiązaniem do pisania programów TensorFlow zgodnych z wykresami. Oto kilka porad:
- Przełączaj się między szybkim i szybkim wykonywaniem wykresu za pomocą
tf.config.run_functions_eagerly
, aby określić, czy/ kiedy te dwa tryby się rozchodzą. - Utwórz
tf.Variable
s poza funkcją Pythona i zmodyfikuj je wewnątrz. To samo dotyczy obiektów korzystającychtf.Variable
, takich jakkeras.layers
,keras.Model
s itf.optimizers
. - Unikaj pisania funkcji, które zależą od zewnętrznych zmiennych Pythona , z wyjątkiem
tf.Variable
s i Keras. - Wolę pisać funkcje, które pobierają tensory i inne typy TensorFlow jako dane wejściowe. Możesz podać inne typy obiektów, ale bądź ostrożny !
- Uwzględnij jak najwięcej obliczeń w funkcji
tf.function
., aby zmaksymalizować wzrost wydajności. Na przykład udekoruj cały krok treningowy lub całą pętlę treningową.
Widząc przyspieszenie
tf.function
zwykle poprawia wydajność twojego kodu, ale stopień przyspieszenia zależy od rodzaju wykonywanych obliczeń. Małe obliczenia mogą być zdominowane przez koszty wywołania wykresu. Możesz zmierzyć różnicę w wydajności w następujący sposób:
x = tf.random.uniform(shape=[10, 10], minval=-1, maxval=2, dtype=tf.dtypes.int32)
def power(x, y):
result = tf.eye(10, dtype=tf.dtypes.int32)
for _ in range(y):
result = tf.matmul(x, result)
return result
print("Eager execution:", timeit.timeit(lambda: power(x, 100), number=1000))
Eager execution: 2.5637862179974036
power_as_graph = tf.function(power)
print("Graph execution:", timeit.timeit(lambda: power_as_graph(x, 100), number=1000))
Graph execution: 0.6832536700021592
tf.function
jest powszechnie używana do przyspieszania pętli treningowych i możesz dowiedzieć się więcej na ten temat w artykule Pisanie pętli treningowej od podstaw za pomocą Keras.
Wydajność i kompromisy
Wykresy mogą przyspieszyć Twój kod, ale proces ich tworzenia wiąże się z pewnym obciążeniem. W przypadku niektórych funkcji utworzenie wykresu zajmuje więcej czasu niż wykonanie wykresu. Ta inwestycja jest zwykle szybko zwracana wraz ze wzrostem wydajności kolejnych wykonań, ale należy mieć świadomość, że kilka pierwszych kroków dowolnego trenowania dużych modeli może być wolniejszych z powodu śledzenia.
Bez względu na to, jak duży jest Twój model, chcesz uniknąć częstego śledzenia. W przewodniku po tf.function
omówiono sposób ustawiania specyfikacji wejściowych i używania argumentów tensora, aby uniknąć cofania się. Jeśli okaże się, że uzyskujesz wyjątkowo niską wydajność, dobrym pomysłem jest sprawdzenie, czy przypadkowo cofasz się.
Kiedy jest śledzenie Function
?
Aby dowiedzieć się, kiedy Function
śledzi, dodaj instrukcję print
do jej kodu. Z reguły Function
wykona instrukcję print
za każdym razem, gdy ją śledzi.
@tf.function
def a_function_with_python_side_effect(x):
print("Tracing!") # An eager-only side effect.
return x * x + tf.constant(2)
# This is traced the first time.
print(a_function_with_python_side_effect(tf.constant(2)))
# The second time through, you won't see the side effect.
print(a_function_with_python_side_effect(tf.constant(3)))
Tracing! tf.Tensor(6, shape=(), dtype=int32) tf.Tensor(11, shape=(), dtype=int32)
# This retraces each time the Python argument changes,
# as a Python argument could be an epoch count or other
# hyperparameter.
print(a_function_with_python_side_effect(2))
print(a_function_with_python_side_effect(3))
Tracing! tf.Tensor(6, shape=(), dtype=int32) Tracing! tf.Tensor(11, shape=(), dtype=int32)
Nowe argumenty Pythona zawsze powodują utworzenie nowego wykresu, stąd dodatkowe śledzenie.
Następne kroki
Więcej informacji o tf.function
można znaleźć na stronie z informacjami o interfejsie API oraz w przewodniku Lepsza wydajność dzięki tf.function
.