Wprowadzenie do wykresów i funkcji tf

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.

Prosty wykres TensorFlow

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:

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:

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 .