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

Wprowadzenie do wykresów i funkcji

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

Wprowadzenie do wykresów i funkcji tf.function

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 w celu uzyskania wykresów oraz w jaki sposób są one przechowywane i reprezentowane oraz jak możesz ich używać do przyspieszania i eksportowania modeli.

To jest krótkie wprowadzenie; Pełne wprowadzenie do tych pojęć znajduje się w przewodniku po tf.function .

Co to są wykresy?

W poprzednich trzech przewodnikach widzieliście, że TensorFlow działa chętnie . Oznacza to, że operacje TensorFlow są wykonywane przez Python, operacja po operacji i zwracają wyniki z powrotem do Pythona. Chętny TensorFlow korzysta z zalet procesorów graficznych, umożliwiając umieszczanie zmiennych, tensorów, a nawet operacji na GPU i TPU. Jest również łatwy do debugowania.

Niektórzy użytkownicy mogą nigdy nie chcieć opuszczać Pythona.

Jednak uruchomienie TensorFlow op-by-op w Pythonie zapobiega wielu akceleracjom dostępnym w innym przypadku. Jeśli możesz wyodrębnić obliczenia tensorowe z Pythona, możesz przekształcić je w wykres .

Wykresy 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 prosty dwuwarstwowy wykres wizualizowany w TensorBoard.

dwuwarstwowy wykres tensorflow

Zalety wykresów

Dzięki wykresowi masz dużą elastyczność. Możesz używać wykresu TensorFlow 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 wykonywać transformacje, takie jak:

  • Statycznie wnioskuj wartość tensorów przez zawijanie stałych węzłów w swoich obliczeniach („zawijanie na stałe”) .
  • Oddzielne części składowe obliczeń, które są niezależne, i rozdziel je między wątki lub urządzenia.
  • Uprość operacje arytmetyczne, eliminując typowe podwyrażenia.

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 konstruować wykresy, gdy ich potrzebujesz.

Śledzenie wykresów

Sposób tworzenia wykresu w TensorFlow polega na użyciu tf.function , jako bezpośredniego wywołania lub dekoratora.

import tensorflow as tf
import timeit
from datetime import datetime
# Define a Python function
def function_to_get_faster(x, y, b):
  x = tf.matmul(x, y)
  x = x + b
  return x

# Create a `Function` object that contains a graph
a_function_that_uses_a_graph = tf.function(function_to_get_faster)

# Make some tensors
x1 = tf.constant([[1.0, 2.0]])
y1 = tf.constant([[2.0], [3.0]])
b1 = tf.constant(4.0)

# It just works!
a_function_that_uses_a_graph(x1, y1, b1).numpy()
array([[12.]], dtype=float32)

tf.function oparte na tf.function wywołania Pythona, które działają tak samo, jak ich odpowiedniki w Pythonie. Mają określoną klasę ( python.eager.def_function.Function ), ale dla Ciebie zachowują się tak samo, jak wersja bez śledzenia.

tf.function rekurencyjnie śledzi każdą funkcję Pythona, którą wywołuje.

def inner_function(x, y, b):
  x = tf.matmul(x, y)
  x = x + b
  return x

# Use the decorator
@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.Sesssion Placeholder ani tf.Sesssion .

Kontrola przepływu i skutki uboczne

Sterowanie przepływem i pętle są domyślnie konwertowane na TensorFlow za pośrednictwem tf.autograph . Autograf używa kombinacji metod, w tym standaryzacji konstrukcji pętli, rozwijania i manipulacji AST .

def my_function(x):
  if tf.reduce_sum(x) <= 1:
    return x * x
  else:
    return x-1

a_function = tf.function(my_function)

print("First branch, with graph:", a_function(tf.constant(1.0)).numpy())
print("Second branch, with graph:", a_function(tf.constant([5.0, 5.0])).numpy())
First branch, with graph: 1.0
Second branch, with graph: [4. 4.]

Możesz bezpośrednio wywołać konwersję Autograph, aby zobaczyć, jak Python jest konwertowany na operacje TensorFlow. Jest to przeważnie nieczytelne, ale widać transformację.

# Don't read the output too carefully.
print(tf.autograph.to_code(my_function))
def tf__my_function(x):
    with ag__.FunctionScope('my_function', '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 do_return, retval_
            (do_return, retval_) = vars_

        def if_body():
            nonlocal do_return, retval_
            try:
                do_return = True
                retval_ = (ag__.ld(x) * ag__.ld(x))
            except:
                do_return = False
                raise

        def else_body():
            nonlocal do_return, retval_
            try:
                do_return = True
                retval_ = (ag__.ld(x) - 1)
            except:
                do_return = False
                raise
        ag__.if_stmt((ag__.converted_call(ag__.ld(tf).reduce_sum, (ag__.ld(x),), None, fscope) <= 1), if_body, else_body, get_state, set_state, ('do_return', 'retval_'), 2)
        return fscope.ret(retval_, do_return)


Autograf automatycznie konwertuje klauzule if-then , pętle, break , return , continue i inne.

W większości przypadków Autograf będzie działał bez specjalnych uwag. Istnieją jednak pewne zastrzeżenia, a przewodnik po funkcjach tf. Może tu pomóc, a także kompletne odniesienie do autografu

Widząc przyspieszenie

Samo umieszczenie funkcji używającej tensora w tf.function nie przyspiesza automatycznie kodu. W przypadku małych funkcji wywoływanych kilka razy na jednej maszynie, narzut związany z wywołaniem wykresu lub fragmentu wykresu może dominować w czasie wykonywania. Ponadto, jeśli większość obliczeń była już wykonywana na akceleratorze, na przykład stosy zwojów obciążonych GPU, przyspieszenie wykresu nie będzie duże.

W przypadku skomplikowanych obliczeń wykresy mogą zapewnić znaczne przyspieszenie. Dzieje się tak, ponieważ wykresy zmniejszają komunikację Pythona z urządzeniem i powodują pewne przyspieszenia.

Ten kod razy kilka uruchomień na kilku małych gęstych warstwach.

# Create an oveerride model to classify pictures
class SequentialModel(tf.keras.Model):
  def __init__(self, **kwargs):
    super(SequentialModel, self).__init__(**kwargs)
    self.flatten = tf.keras.layers.Flatten(input_shape=(28, 28))
    self.dense_1 = tf.keras.layers.Dense(128, activation="relu")
    self.dropout = tf.keras.layers.Dropout(0.2)
    self.dense_2 = tf.keras.layers.Dense(10)

  def call(self, x):
    x = self.flatten(x)
    x = self.dense_1(x)
    x = self.dropout(x)
    x = self.dense_2(x)
    return x

input_data = tf.random.uniform([60, 28, 28])

eager_model = SequentialModel()
graph_model = tf.function(eager_model)

print("Eager time:", timeit.timeit(lambda: eager_model(input_data), number=10000))
print("Graph time:", timeit.timeit(lambda: graph_model(input_data), number=10000))

Eager time: 4.800515108999889
Graph time: 2.0497353999999177

Funkcje polimorficzne

Podczas śledzenia funkcji tworzysz obiekt Function który jest polimorficzny . Funkcja polimorficzna to funkcja wywoływana w Pythonie, która zawiera kilka konkretnych wykresów funkcji za jednym interfejsem API.

Można użyć tej Function na wszystkich różnych rodzajów dtypes i kształtach. Za każdym razem, gdy wywołujesz ją z nową sygnaturą argumentów, oryginalna funkcja zostaje odtworzona z nowymi argumentami. Function przechowuje następnie tf.Graph odpowiadający temu tf.Graph w concrete_function . Jeśli funkcja została już prześledzona za pomocą tego rodzaju argumentu, po prostu otrzymujesz wstępnie śledzony wykres.

Zatem koncepcyjnie:

  • tf.Graph to surowa, przenośna struktura danych opisująca obliczenia
  • Function to buforowanie, śledzenie i wysyłanie przez ConcreteFunctions
  • ConcreteFunction jest chętnie kompatybilnym opakowaniem wokół wykresu, które umożliwia wykonanie wykresu z Pythona

Sprawdzanie funkcji polimorficznych

Możesz sprawdzić a_function , który jest wynikiem wywołania tf.function w funkcji Python my_function . W tym przykładzie wywołanie a_function z trzema rodzajami argumentów skutkuje trzema różnymi konkretnymi funkcjami.

print(a_function)

print("Calling a `Function`:")
print("Int:", a_function(tf.constant(2)))
print("Float:", a_function(tf.constant(2.0)))
print("Rank-1 tensor of floats", a_function(tf.constant([2.0, 2.0, 2.0])))
<tensorflow.python.eager.def_function.Function object at 0x7fcbcf7c6b70>
Calling a `Function`:
Int: tf.Tensor(1, shape=(), dtype=int32)
Float: tf.Tensor(1.0, shape=(), dtype=float32)
Rank-1 tensor of floats tf.Tensor([1. 1. 1.], shape=(3,), dtype=float32)

# Get the concrete function that works on floats
print("Inspecting concrete functions")
print("Concrete function for float:")
print(a_function.get_concrete_function(tf.TensorSpec(shape=[], dtype=tf.float32)))
print("Concrete function for tensor of floats:")
print(a_function.get_concrete_function(tf.constant([2.0, 2.0, 2.0])))

Inspecting concrete functions
Concrete function for float:
ConcreteFunction my_function(x)
  Args:
    x: float32 Tensor, shape=()
  Returns:
    float32 Tensor, shape=()
Concrete function for tensor of floats:
ConcreteFunction my_function(x)
  Args:
    x: float32 Tensor, shape=(3,)
  Returns:
    float32 Tensor, shape=(3,)

# Concrete functions are callable
# Note: You won't normally do this, but instead just call the containing `Function`
cf = a_function.get_concrete_function(tf.constant(2))
print("Directly calling a concrete function:", cf(tf.constant(2)))
Directly calling a concrete function: tf.Tensor(1, shape=(), dtype=int32)

W tym przykładzie widzisz dość daleko w stosie. O ile nie zarządzasz specjalnie śledzeniem, zwykle nie musisz bezpośrednio wywoływać konkretnych funkcji, jak pokazano tutaj.

Wracając do gorliwej egzekucji

Możesz zobaczyć długie ślady stosu, szczególnie te, które odwołują się do tf.Graph lub with tf.Graph().as_default() . Oznacza to, że prawdopodobnie pracujesz w kontekście wykresu. Podstawowe funkcje TensorFlow używają kontekstów grafowych, takich jak model.fit() .

Często znacznie łatwiej jest debugować przyspieszone wykonanie. Ślady stosu powinny być stosunkowo krótkie i łatwe do zrozumienia.

W sytuacjach, gdy wykres utrudnia debugowanie, możesz powrócić do używania przyspieszonego wykonywania do debugowania.

Oto sposoby na upewnienie się, że chętnie biegasz:

  • Wywołaj modele i warstwy bezpośrednio jako wywoływane

  • Podczas korzystania z Keras compile / fit, w czasie kompilacji użyj model.compile(run_eagerly=True)

  • Ustaw tryb wykonywania globalnego za pomocą tf.config.run_functions_eagerly(True)

Używanie run_eagerly=True

# Define an identity layer with an eager side effect
class EagerLayer(tf.keras.layers.Layer):
  def __init__(self, **kwargs):
    super(EagerLayer, self).__init__(**kwargs)
    # Do some kind of initialization here

  def call(self, inputs):
    print("\nCurrently running eagerly", str(datetime.now()))
    return inputs
# Create an override model to classify pictures, adding the custom layer
class SequentialModel(tf.keras.Model):
  def __init__(self):
    super(SequentialModel, self).__init__()
    self.flatten = tf.keras.layers.Flatten(input_shape=(28, 28))
    self.dense_1 = tf.keras.layers.Dense(128, activation="relu")
    self.dropout = tf.keras.layers.Dropout(0.2)
    self.dense_2 = tf.keras.layers.Dense(10)
    self.eager = EagerLayer()

  def call(self, x):
    x = self.flatten(x)
    x = self.dense_1(x)
    x = self.dropout(x)
    x = self.dense_2(x)
    return self.eager(x)

# Create an instance of this model
model = SequentialModel()

# Generate some nonsense pictures and labels
input_data = tf.random.uniform([60, 28, 28])
labels = tf.random.uniform([60])

loss_fn = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)

Najpierw skompiluj model bez chęci. Zwróć uwagę, że model nie jest śledzony; pomimo swojej nazwy compile tylko ustawia funkcje strat, optymalizację i inne parametry treningowe.

model.compile(run_eagerly=False, loss=loss_fn)

Teraz wywołaj fit i zobacz, że funkcja jest prześledzona (dwukrotnie), a następnie pożądany efekt nigdy się nie powtórzy.

model.fit(input_data, labels, epochs=3)
Epoch 1/3

Currently running eagerly 2020-09-12 01:21:25.249359

Currently running eagerly 2020-09-12 01:21:25.366135
2/2 [==============================] - 0s 2ms/step - loss: 0.9820
Epoch 2/3
2/2 [==============================] - 0s 1ms/step - loss: 0.0020
Epoch 3/3
2/2 [==============================] - 0s 1ms/step - loss: 0.0018

<tensorflow.python.keras.callbacks.History at 0x7fcb1c033ac8>

Jeśli jednak masz ochotę choćby na jedną epokę, możesz dwukrotnie zobaczyć ten chciwy efekt uboczny.

print("Running eagerly")
# When compiling the model, set it to run eagerly
model.compile(run_eagerly=True, loss=loss_fn)

model.fit(input_data, labels, epochs=1)

Running eagerly

Currently running eagerly 2020-09-12 01:21:25.560620
1/2 [==============>...............] - ETA: 0s - loss: 0.0014
Currently running eagerly 2020-09-12 01:21:25.581654
2/2 [==============================] - 0s 5ms/step - loss: 7.7140e-04

<tensorflow.python.keras.callbacks.History at 0x7fcb1c09ea58>

Korzystanie run_functions_eagerly

Możesz także ustawić wszystko globalnie, aby działało chętnie. Zauważ, że działa to tylko wtedy, gdy prześledzisz ponownie; śledzone funkcje pozostaną śledzone i będą działać jako wykres.

# Now, globally set everything to run eagerly
tf.config.run_functions_eagerly(True)
print("Run all functions eagerly.")

# First, trace the model, triggering the side effect
polymorphic_function = tf.function(model)

# It was traced...
print(polymorphic_function.get_concrete_function(input_data))

# But when you run the function again, the side effect happens (both times).
result = polymorphic_function(input_data)
result = polymorphic_function(input_data)
Run all functions eagerly.

Currently running eagerly 2020-09-12 01:21:25.608077
ConcreteFunction function(self)
  Args:
    self: float32 Tensor, shape=(60, 28, 28)
  Returns:
    float32 Tensor, shape=(60, 10)

Currently running eagerly 2020-09-12 01:21:25.612328

Currently running eagerly 2020-09-12 01:21:25.613704

# Don't forget to set it back when you are done
tf.config.experimental_run_functions_eagerly(False)

WARNING:tensorflow:From <ipython-input-17-782fe9ce7b18>:2: experimental_run_functions_eagerly (from tensorflow.python.eager.def_function) is deprecated and will be removed in a future version.
Instructions for updating:
Use `tf.config.run_functions_eagerly` instead of the experimental version.

Śledzenie i wydajność

Śledzenie kosztuje trochę narzutów. Chociaż śledzenie małych funkcji jest szybkie, śledzenie dużych modeli może zająć zauważalny czas zegara ściennego. Ta inwestycja jest zwykle szybko zwracana dzięki zwiększeniu wydajności, ale ważne jest, aby mieć świadomość, że kilka pierwszych epok treningu 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. W tej sekcji przewodnika po funkcjach tf. Omówiono, jak ustawić specyfikacje wejścia i jak używać argumentów tensora, aby uniknąć odtwarzania. Jeśli zauważysz, że osiągasz wyjątkowo słabe wyniki, dobrze jest sprawdzić, czy przypadkowo nie odtwarzasz.

Możesz dodać efekt uboczny, który ma tylko ochotę (na przykład wydrukowanie argumentu Pythona), aby zobaczyć, kiedy funkcja jest śledzona. Tutaj widzisz dodatkowe odtwarzanie, ponieważ nowe argumenty Pythona zawsze wyzwalają odtwarzanie.

# Use @tf.function decorator
@tf.function
def a_function_with_python_side_effect(x):
  print("Tracing!")  # This eager
  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)))

# 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)
tf.Tensor(11, shape=(), dtype=int32)
Tracing!
tf.Tensor(6, shape=(), dtype=int32)
Tracing!
tf.Tensor(11, shape=(), dtype=int32)

Następne kroki

Możesz przeczytać bardziej szczegółową dyskusję zarówno na tf.function z tf.function interfejsie API tf.function iw przewodniku .