![]() | ![]() | ![]() | ![]() |
Przegląd
Ten przewodnik znajduje się pod powierzchnią TensorFlow i Keras, aby zobaczyć, jak działa TensorFlow. Jeśli zamiast tego chcesz od razu zacząć korzystać z Keras, zapoznaj się z naszą kolekcją przewodników po Keras .
W tym przewodniku dowiesz się, w jaki sposób TensorFlow umożliwia wprowadzanie prostych zmian w kodzie, aby uzyskać wykresy, w jaki sposób wykresy są przechowywane i reprezentowane oraz jak możesz ich używać do przyspieszania modeli.
To jest ogólny przegląd, który omawia, w jaki sposób tf.function
pozwala na przełączanie się z wykonywania zachłannego do wykonywania wykresów. tf.function
specyfikacja funkcji tf.function
znajduje się w przewodniku po funkcji tf.function
.
Co to są wykresy?
W poprzednich trzech przewodnikach widzieliście, że TensorFlow działa z niecierpliwością . Oznacza to, że operacje TensorFlow są wykonywane przez Python, operacja po operacji i zwracają wyniki z powrotem do Pythona.
Chociaż gorliwe wykonywanie ma kilka unikalnych zalet, wykonywanie wykresów umożliwia przenoszenie poza Python i zwykle zapewnia lepszą wydajność. Wykonywanie wykresów oznacza, że obliczenia tensorowe są wykonywane jako wykres TensorFlow , czasami nazywany tf.Graph
lub po prostu „wykresem”.
Grafy to struktury danych zawierające zestaw obiektów tf.Operation
, które reprezentują jednostki obliczeniowe; i obiekty tf.Tensor
, które reprezentują jednostki danych przepływające między operacjami. Są zdefiniowane w kontekście tf.Graph
. Ponieważ te wykresy są strukturami danych, można je zapisywać, uruchamiać i przywracać bez oryginalnego kodu Pythona.
Tak wygląda wykres TensorFlow przedstawiający dwuwarstwową sieć neuronową po wizualizacji w TensorBoard.
Zalety wykresów
Dzięki wykresowi masz dużą elastyczność. Wykresu TensorFlow można używać w środowiskach, które nie mają interpretera języka Python, takich jak aplikacje mobilne, urządzenia wbudowane i serwery zaplecza. TensorFlow używa wykresów jako formatu zapisanych modeli podczas eksportowania ich z języka Python.
Wykresy są również łatwo optymalizowane, co pozwala kompilatorowi na dokonywanie transformacji, takich jak:
- Statycznie wnioskuj wartość tensorów przez zawijanie stałych węzłów w swoich obliczeniach („zawijanie na stałe”) .
- Oddzielne części podrzędne obliczenia, które są niezależne, i rozdziel je między wątki lub urządzenia.
- Uprość operacje arytmetyczne, eliminując typowe 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ą TensorFlow działać szybko , równolegle i wydajnie na wielu urządzeniach .
Jednak nadal chcesz dla wygody zdefiniować nasze modele uczenia maszynowego (lub inne obliczenia) w Pythonie, a następnie automatycznie tworzyć wykresy, gdy ich potrzebujesz.
Korzystanie z wykresów
Tworzysz i uruchamiasz wykres w TensorFlow używając tf.function
, jako bezpośrednie wywołanie lub jako dekorator. tf.function
przyjmuje zwykłą funkcję jako dane wejściowe i zwraca Function
. Function
to obiekt wywoływany w Pythonie, który buduje wykresy TensorFlow na podstawie funkcji Pythona. Użyć Function
w taki sam sposób jak jego odpowiednik Python.
import tensorflow as tf
import timeit
from datetime import datetime
# 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. Jednak pod spodem jest zupełnie inaczej . Function
hermetyzuje kilka tf.Graph
za jednym API . W ten sposób Function
może zapewnić korzyści wynikające z wykonywania wykresów , takie jak szybkość i możliwość wdrażania .
tf.function
odnosi się do 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 trzeba było definiować tf.Session
Placeholder
ani tf.Session
.
Konwersja funkcji Pythona na wykresy
Każda funkcja, którą napiszesz za pomocą TensorFlow, będzie zawierała mieszankę natywnych 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
wykorzystuje bibliotekę 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ł bezpośrednio przeglądać wykresy, możesz sprawdzić dane wyjściowe, aby zobaczyć dokładne wyniki. Nie są one łatwe do odczytania, więc nie musisz zbyt uważnie patrzeć!
# 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 (retval_, do_return) def set_state(vars_): nonlocal retval_, do_return (retval_, do_return) = vars_ def if_body(): nonlocal retval_, do_return try: do_return = True retval_ = ag__.ld(x) 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, ('retval_', 'do_return'), 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_INT32 type: DT_BOOL } } } 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_INT32 } } } node { name: "cond/Identity_1" op: "Identity" input: "cond:1" attr { key: "T" value { type: DT_BOOL } } } node { name: "Identity" op: "Identity" input: "cond/Identity" 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_INT32 } output_arg { name: "cond_identity_1" type: DT_BOOL } } node_def { name: "cond/Const" op: "Const" attr { key: "dtype" value { type: DT_INT32 } } attr { key: "value" value { tensor { dtype: DT_INT32 tensor_shape { } int_val: 0 } } } experimental_debug_info { original_node_names: "cond/Const" } } 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 } } } experimental_debug_info { original_node_names: "cond/Const_1" } } node_def { name: "cond/Const_2" op: "Const" attr { key: "dtype" value { type: DT_BOOL } } attr { key: "value" value { tensor { dtype: DT_BOOL tensor_shape { } bool_val: true } } } experimental_debug_info { original_node_names: "cond/Const_2" } } node_def { name: "cond/Const_3" op: "Const" attr { key: "dtype" value { type: DT_INT32 } } attr { key: "value" value { tensor { dtype: DT_INT32 tensor_shape { } int_val: 0 } } } experimental_debug_info { original_node_names: "cond/Const_3" } } node_def { name: "cond/Identity" op: "Identity" input: "cond/Const_3:output:0" attr { key: "T" value { type: DT_INT32 } } experimental_debug_info { original_node_names: "cond/Identity" } } node_def { name: "cond/Const_4" op: "Const" attr { key: "dtype" value { type: DT_BOOL } } attr { key: "value" value { tensor { dtype: DT_BOOL tensor_shape { } bool_val: true } } } experimental_debug_info { original_node_names: "cond/Const_4" } } node_def { name: "cond/Identity_1" op: "Identity" input: "cond/Const_4:output:0" attr { key: "T" value { type: DT_BOOL } } experimental_debug_info { original_node_names: "cond/Identity_1" } } ret { key: "cond_identity" value: "cond/Identity:output:0" } ret { key: "cond_identity_1" value: "cond/Identity_1:output:0" } arg_attr { key: 0 value { attr { key: "_output_shapes" value { list { shape { } } } } } } } function { signature { name: "cond_true_33" input_arg { name: "cond_identity_x" type: DT_INT32 } output_arg { name: "cond_identity" type: DT_INT32 } output_arg { name: "cond_identity_1" type: DT_BOOL } } node_def { name: "cond/Identity" op: "Identity" input: "cond_identity_x" attr { key: "T" value { type: DT_INT32 } } experimental_debug_info { original_node_names: "cond/Identity" } } 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 } } } experimental_debug_info { original_node_names: "cond/Const" } } node_def { name: "cond/Identity_1" op: "Identity" input: "cond/Const:output:0" attr { key: "T" value { type: DT_BOOL } } experimental_debug_info { original_node_names: "cond/Identity_1" } } ret { key: "cond_identity" value: "cond/Identity:output:0" } ret { key: "cond_identity_1" value: "cond/Identity_1:output:0" } arg_attr { key: 0 value { attr { key: "_output_shapes" value { list { shape { } } } } } } } } versions { producer: 561 min_consumer: 12 }
W większości przypadków tf.function
będzie działać bez specjalnych uwag. Istnieją jednak pewne zastrzeżenia, a przewodnik po funkcjach tf. Może w tym pomóc, a także pełne odniesienie do AutoGraph
Polimorfizm: jedna Function
, wiele wykresów
tf.Graph
specjalizuje się do określonego typu danych wejściowych (na przykład, z określonym tensorydtype
lub przedmiotów z 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 tf.Graph
są znane jako sygnatura wejściowa lub po prostu sygnatura .
Function
przechowuje tf.Graph
odpowiadający temu podpisowi 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 sees 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 tym podpisem, 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 poparty wieloma wykresami, możemy powiedzieć, że Function
jest polimorficzna . Umożliwia to obsługę większej liczby typów danych wejściowych niż pojedynczy tf.Graph
, a także optymalizację każdego tf.Graph
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=[1, -1]) Returns: float32 Tensor, shape=(2,) my_relu(x) Args: x: float32 Tensor, shape=(2,) Returns: float32 Tensor, shape=(2,) my_relu(x) Args: x: float32 Tensor, shape=() Returns: float32 Tensor, shape=()
Korzystanie z funkcji tf.function
Do tej pory widzieliście, jak można przekonwertować funkcję Pythona na wykres po prostu używając tf.function
jako dekoratora lub wrappera. Jednak w praktyce tf.function
działanie funkcji tf.function
Może być trudne! W kolejnych sekcjach dowiesz się, jak sprawić, by kod działał zgodnie z oczekiwaniami za pomocą tf.function
.
Wykonywanie wykresów a szybkie wykonanie
Kod w Function
może być wykonywany zarówno chętnie, jak i jako wykres. 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 8 6 3 8], shape=(5,), dtype=int32) tf.Tensor([9 8 3 6 1], shape=(5,), dtype=int32)
get_MSE(y_true, y_pred)
<tf.Tensor: shape=(), dtype=int32, numpy=26>
Aby sprawdzić, czy Function
„s wykres robi tego samego obliczenia jak jego odpowiednik funkcji Python, możemy wykonać to chętnie z tf.config.run_functions_eagerly(True)
. Jest to przełącznik, który wyłącza 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=26>
# Don't forget to set it back when you are done.
tf.config.run_functions_eagerly(False)
Jednak Function
może zachowywać się inaczej w przypadku wykresu i przyspieszonego wykonywania. Funkcja print
języku Python jest jednym z przykładów różnic między tymi dwoma trybami. Zobaczmy, co się stanie, gdy wstawimy instrukcję print
do naszej funkcji i będziemy ją wielokrotnie wywoływać.
@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
drukowane 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” . Śledzenie przechwytuje operacje TensorFlow do wykresu, a print
nie jest przechwytywany na wykresie. Ten wykres jest następnie wykonywany dla wszystkich trzech wywołań bez ponownego uruchamiania kodu Pythona .
Aby sprawdzić poczytalność, 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 należy pamiętać podczas konwertowania funkcji na Function
.
tf.function
najlepsze praktyki
Przyzwyczajenie się do zachowania Function
może zająć trochę czasu. Aby szybko rozpocząć pracę, początkujący użytkownicy powinni bawić się dekorowaniem funkcji zabawek za pomocą funkcji @tf.function
aby uzyskać doświadczenie w przechodzeniu od chętnego do wykonywania wykresów.
Projektowanie pod kątem tf.function
może być najlepszymtf.function
na pisanie programów TensorFlow obsługujących wykresy. Oto kilka porad:
- Przełączaj się między
tf.config.run_functions_eagerly
itf.config.run_functions_eagerly
wykonywaniem wykresów wcześnie i często za pomocątf.config.run_functions_eagerly
aby wskazać, czy / kiedy te dwa tryby się rozchodzą. - Utwórz
tf.Variable
poza funkcją Pythona i zmodyfikuj je wewnątrz. To samo dotyczy obiektów używającychtf.Variable
, takich jakkeras.layers
,keras.Model
s itf.optimizers
. - Unikaj pisania funkcji, które zależą od zewnętrznych zmiennych Pythona , z wyjątkiem obiektów
tf.Variables
i Keras. - Wolę pisać funkcje, które przyjmują tensory i inne typy TensorFlow jako dane wejściowe. Możesz przekazać inne typy obiektów, ale bądź ostrożny !
- Uwzględnij jak najwięcej obliczeń w ramach 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 narzut związany z wywołaniem 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: 1.9166935040000226
power_as_graph = tf.function(power)
print("Graph execution:", timeit.timeit(lambda: power_as_graph(x, 100), number=1000))
Graph execution: 0.5355543040000157
tf.function
jest powszechnie używany do przyspieszania pętli treningowych, jak widać tutaj w przypadku Keras.
Wydajność i kompromisy
Wykresy mogą przyspieszyć twój kod, ale proces ich tworzenia wiąże się z pewnym narzutem. 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 ważne jest, aby mieć świadomość, że kilka pierwszych kroków dowolnego uczenia dużego modelu może być wolniejsze z powodu śledzenia.
Bez względu na to, jak duży jest Twój model, chcesz uniknąć częstego śledzenia. Przewodnik po tf.function
omawia, jak ustawić specyfikacje wejścia i jak używać argumentów tensora, aby uniknąć odtworzenia. Jeśli zauważysz, że osiągasz wyjątkowo słabe wyniki, dobrze jest sprawdzić, czy przypadkowo nie odtwarzasz.
Kiedy jest śledzenie Function
?
Aby dowiedzieć się, kiedy Function
śledzi, dodaj instrukcję print
do jej kodu. Zasadą jest, że Function
wykona instrukcję print
każdym razem, gdy będzie ś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)
Tutaj widzisz dodatkowe śledzenie, ponieważ nowe argumenty Pythona zawsze powodują utworzenie nowego wykresu.
Następne kroki
Bardziej szczegółowe omówienie można znaleźć zarówno na tf.function
z tf.function
interfejsie API tf.function
iw przewodniku .