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

Efektywny TensorFlow 2

W TensorFlow 2.0 wprowadzono wiele zmian, które zwiększają produktywność użytkowników TensorFlow. TensorFlow 2.0 usuwa nadmiarowe interfejsy API , sprawia, że ​​interfejsy API są bardziej spójne ( ujednolicone sieci RNN , ujednolicone optymalizatory ) i lepiej integrują się ze środowiskiem wykonawczym Pythona dzięki szybkiemu wykonywaniu .

W wielu dokumentach RFC wyjaśniono zmiany, jakie zaszły przy tworzeniu TensorFlow 2.0. Ten przewodnik przedstawia wizję tego, jak powinien wyglądać programowanie w TensorFlow 2.0. Zakłada się, że znasz już TensorFlow 1.x.

Krótkie podsumowanie głównych zmian

Oczyszczanie API

Wiele interfejsów API zniknęło lub zostało przeniesionych w TF 2.0. Niektóre z głównych zmian obejmują usunięcie tf.app , tf.flags i tf.logging na rzecz obecnie otwartego absl-py , ponowne udostępnienie projektów, które tf.contrib w tf.contrib i wyczyszczenie głównej przestrzeni nazw tf.* Przez przenoszenie rzadziej używanych funkcji do podpakietów, takich jak tf.math . Niektóre interfejsy API zostały zastąpione ich odpowiednikami w wersji 2.0 - tf.summary , tf.keras.metrics i tf.keras.optimizers . Najłatwiejszym sposobem automatycznego zastosowania tych zmian jest użycie skryptu aktualizacji v2 .

Chętna egzekucja

TensorFlow 1.X wymaga od użytkowników ręcznego zszywania abstrakcyjnego drzewa składni (wykresu) za pomocą wywołań API tf.* . Następnie wymaga od użytkowników ręcznego skompilowania abstrakcyjnego drzewa składni przez przekazanie zestawu tensorów wyjściowych i wejściowych do wywołania session.run() . TensorFlow 2.0 wykonuje się ochoczo (jak zwykle robi to Python), aw wersji 2.0 wykresy i sesje powinny przypominać szczegóły implementacji.

Jednym z godnych uwagi produktów ubocznych tf.control_dependencies() wykonania jest to, że tf.control_dependencies() nie jest już wymagana, ponieważ wszystkie wiersze kodu są wykonywane w kolejności (w ramach funkcji tf.function Kod z efektami ubocznymi jest wykonywany w kolejności zapisanej).

Żadnych więcej globali

TensorFlow 1.X w dużym stopniu opierał się na niejawnie globalnych przestrzeniach nazw. Gdy tf.Variable() , zostanie on umieszczony na domyślnym wykresie i pozostanie tam, nawet jeśli zgubisz wskazującą na niego zmienną Pythona. Możesz wtedy odzyskać tę tf.Variable , ale tylko wtedy, gdy znasz nazwę, pod jaką została utworzona. Było to trudne, jeśli nie miałeś kontroli nad tworzeniem zmiennej. W rezultacie rozprzestrzeniły się różnego rodzaju mechanizmy, które miały pomóc użytkownikom w ponownym znalezieniu ich zmiennych, a dla platform w znalezieniu zmiennych utworzonych przez użytkowników: zakresy zmiennych, kolekcje globalne, metody pomocnicze, takie jak tf.get_global_step() , tf.global_variables_initializer() , optymalizatory niejawnie obliczające gradienty dla wszystkich możliwych do nauczenia zmiennych i tak dalej. TensorFlow 2.0 eliminuje wszystkie te mechanizmy ( Variables 2.0 RFC ) na korzyść mechanizmu domyślnego: Śledź swoje zmienne! Jeśli zgubisz tf.Variable , zostanie ona tf.Variable .

Wymóg śledzenia zmiennych powoduje dodatkową pracę dla użytkownika, ale w przypadku obiektów Keras (patrz poniżej) obciążenie jest zminimalizowane.

Funkcje, a nie sesje

Wywołanie session.run() jest prawie jak wywołanie funkcji: określasz dane wejściowe i funkcję, która ma zostać wywołana, i otrzymujesz zestaw wyników. W TensorFlow 2.0 można ozdobić funkcję Pythona za pomocą tf.function() aby oznaczyć ją do kompilacji JIT, tak aby TensorFlow uruchamiał ją jako pojedynczy wykres ( funkcje 2.0 RFC ). Ten mechanizm pozwala TensorFlow 2.0 na uzyskanie wszystkich zalet trybu graficznego:

  • Wydajność: funkcję można zoptymalizować (przycinanie węzłów, fuzja jądra itp.)
  • Przenośność: funkcję można eksportować / ponownie importować ( SavedModel 2.0 RFC ), co pozwala użytkownikom na ponowne wykorzystanie i udostępnianie modułowych funkcji TensorFlow.
# TensorFlow 1.X
outputs = session.run(f(placeholder), feed_dict={placeholder: input})
# TensorFlow 2.0
outputs = f(input)

Dzięki możliwości swobodnego przeplatania kodu Python i TensorFlow użytkownicy mogą skorzystać z ekspresji języka Python. Ale przenośny TensorFlow działa w kontekstach bez interpretera Pythona, takich jak mobile, C ++ i JavaScript. Aby pomóc użytkownikom uniknąć konieczności przepisywania kodu podczas dodawania @tf.function , AutoGraph konwertuje podzbiór konstrukcji Pythona na ich odpowiedniki TensorFlow:

  • for / while -> tf.while_loop (obsługiwane są break i continue )
  • if -> tf.cond
  • for _ in dataset -> dataset.reduce

AutoGraph obsługuje dowolne zagnieżdżenia przepływu sterowania, co umożliwia wydajne i zwięzłe wdrażanie wielu złożonych programów ML, takich jak modele sekwencyjne, uczenie się ze wzmocnieniami, niestandardowe pętle szkoleniowe i inne.

Zalecenia dotyczące idiomatycznego TensorFlow 2.0

Refaktoryzuj swój kod na mniejsze funkcje

Powszechnym wzorcem użycia w TensorFlow 1.X była strategia „zlewozmywaka”, w której suma wszystkich możliwych obliczeń została zaplanowana z wyprzedzeniem, a następnie wybrane tensory zostały ocenione za pomocą session.run() . W TensorFlow 2.0 użytkownicy powinni refaktoryzować swój kod na mniejsze funkcje, które są wywoływane w razie potrzeby. Ogólnie rzecz biorąc, nie ma potrzeby ozdabiania każdej z tych mniejszych funkcji wartością tf.function ; używaj tf.function tylko do udekorowania obliczeń wysokiego poziomu - na przykład jednego kroku treningu lub przejścia modelu do przodu.

Użyj warstw i modeli Keras do zarządzania zmiennymi

Modele i warstwy Keras oferują wygodne variables i właściwości trainable_variables , które rekurencyjnie gromadzą wszystkie zmienne zależne. Ułatwia to zarządzanie zmiennymi lokalnie tam, gdzie są używane.

Kontrast:

def dense(x, W, b):
  return tf.nn.sigmoid(tf.matmul(x, W) + b)

@tf.function
def multilayer_perceptron(x, w0, b0, w1, b1, w2, b2 ...):
  x = dense(x, w0, b0)
  x = dense(x, w1, b1)
  x = dense(x, w2, b2)
  ...

# You still have to manage w_i and b_i, and their shapes are defined far away from the code.

z wersją Keras:

# Each layer can be called, with a signature equivalent to linear(x)
layers = [tf.keras.layers.Dense(hidden_size, activation=tf.nn.sigmoid) for _ in range(n)]
perceptron = tf.keras.Sequential(layers)

# layers[3].trainable_variables => returns [w3, b3]
# perceptron.trainable_variables => returns [w0, b0, ...]

Warstwy / modele Keras dziedziczą z tf.train.Checkpointable i są zintegrowane z @tf.function , co umożliwia bezpośrednie kontrolowanie lub eksportowanie SavedModels z obiektów Keras. Nie musisz koniecznie używać interfejsu API .fit() Keras, aby skorzystać z tych integracji.

Oto przykład nauki transferu, który pokazuje, jak Keras ułatwia zbieranie podzbioru odpowiednich zmiennych. Załóżmy, że trenujesz model wielogłowy ze wspólnym pniem:

trunk = tf.keras.Sequential([...])
head1 = tf.keras.Sequential([...])
head2 = tf.keras.Sequential([...])

path1 = tf.keras.Sequential([trunk, head1])
path2 = tf.keras.Sequential([trunk, head2])

# Train on primary dataset
for x, y in main_dataset:
  with tf.GradientTape() as tape:
    # training=True is only needed if there are layers with different
    # behavior during training versus inference (e.g. Dropout).
    prediction = path1(x, training=True)
    loss = loss_fn_head1(prediction, y)
  # Simultaneously optimize trunk and head1 weights.
  gradients = tape.gradient(loss, path1.trainable_variables)
  optimizer.apply_gradients(zip(gradients, path1.trainable_variables))

# Fine-tune second head, reusing the trunk
for x, y in small_dataset:
  with tf.GradientTape() as tape:
    # training=True is only needed if there are layers with different
    # behavior during training versus inference (e.g. Dropout).
    prediction = path2(x, training=True)
    loss = loss_fn_head2(prediction, y)
  # Only optimize head2 weights, not trunk weights
  gradients = tape.gradient(loss, head2.trainable_variables)
  optimizer.apply_gradients(zip(gradients, head2.trainable_variables))

# You can publish just the trunk computation for other people to reuse.
tf.saved_model.save(trunk, output_path)

Połącz tf.data.Datasets i @ tf.function

Podczas iteracji na danych treningowych, które mieszczą się w pamięci, możesz swobodnie używać zwykłej iteracji Pythona. W przeciwnym razie tf.data.Dataset jest najlepszym sposobem przesyłania strumieniowego danych szkoleniowych z dysku. Zestawy danych są iterowalne (nie iteratorami) i działają tak samo jak inne iterowalne elementy Pythona w trybie Eager. Możesz w pełni wykorzystać funkcje asynchronicznego pobierania / przesyłania strumieniowego zestawu danych, opakowując kod w tf.function() , który zastępuje iterację w języku Python równoważnymi operacjami na wykresie przy użyciu funkcji AutoGraph.

@tf.function
def train(model, dataset, optimizer):
  for x, y in dataset:
    with tf.GradientTape() as tape:
      # training=True is only needed if there are layers with different
      # behavior during training versus inference (e.g. Dropout).
      prediction = model(x, training=True)
      loss = loss_fn(prediction, y)
    gradients = tape.gradient(loss, model.trainable_variables)
    optimizer.apply_gradients(zip(gradients, model.trainable_variables))

Jeśli używasz interfejsu API Keras .fit() , nie będziesz musiał martwić się o iterację zestawu danych.

model.compile(optimizer=optimizer, loss=loss_fn)
model.fit(dataset)

Skorzystaj z funkcji AutoGraph z przepływem sterowania w języku Python

AutoGraph zapewnia sposób konwertowania przepływu sterowania zależnego od danych na odpowiedniki w trybie wykresu, takie jak tf.cond i tf.while_loop .

Jednym z typowych miejsc, w których pojawia się przepływ sterowania zależny od danych, są modele sekwencyjne. tf.keras.layers.RNN otacza komórkę RNN, umożliwiając statyczne lub dynamiczne rozwijanie powtarzania. Dla celów demonstracyjnych możesz ponownie zaimplementować dynamiczne rozwijanie w następujący sposób:

class DynamicRNN(tf.keras.Model):

  def __init__(self, rnn_cell):
    super(DynamicRNN, self).__init__(self)
    self.cell = rnn_cell

  def call(self, input_data):
    # [batch, time, features] -> [time, batch, features]
    input_data = tf.transpose(input_data, [1, 0, 2])
    outputs = tf.TensorArray(tf.float32, input_data.shape[0])
    state = self.cell.zero_state(input_data.shape[1], dtype=tf.float32)
    for i in tf.range(input_data.shape[0]):
      output, state = self.cell(input_data[i], state)
      outputs = outputs.write(i, output)
    return tf.transpose(outputs.stack(), [1, 0, 2]), state

Bardziej szczegółowy przegląd funkcji AutoGraph można znaleźć w przewodniku .

tf.metrics agreguje dane, a tf.summary je rejestruje

Aby rejestrować podsumowania, użyj tf.summary.(scalar|histogram|...) i przekieruj je do pisarza za pomocą menedżera kontekstu. (Jeśli pominiesz menedżera kontekstu, nic się nie dzieje.) W przeciwieństwie do TF 1.x, podsumowania są emitowane bezpośrednio do autora; nie ma oddzielnej add_summary() "merge" i oddzielnego add_summary() , co oznacza, że ​​wartość step musi być podana na stronie wywołania.

summary_writer = tf.summary.create_file_writer('/tmp/summaries')
with summary_writer.as_default():
  tf.summary.scalar('loss', 0.1, step=42)

Aby zagregować dane przed zarejestrowaniem ich jako podsumowań, użyj tf.metrics . Metryki są stanowe: gromadzą wartości i zwracają skumulowany wynik po wywołaniu .result() . Wyczyść skumulowane wartości za pomocą .reset_states() .

def train(model, optimizer, dataset, log_freq=10):
  avg_loss = tf.keras.metrics.Mean(name='loss', dtype=tf.float32)
  for images, labels in dataset:
    loss = train_step(model, optimizer, images, labels)
    avg_loss.update_state(loss)
    if tf.equal(optimizer.iterations % log_freq, 0):
      tf.summary.scalar('loss', avg_loss.result(), step=optimizer.iterations)
      avg_loss.reset_states()

def test(model, test_x, test_y, step_num):
  # training=False is only needed if there are layers with different
  # behavior during training versus inference (e.g. Dropout).
  loss = loss_fn(model(test_x, training=False), test_y)
  tf.summary.scalar('loss', loss, step=step_num)

train_summary_writer = tf.summary.create_file_writer('/tmp/summaries/train')
test_summary_writer = tf.summary.create_file_writer('/tmp/summaries/test')

with train_summary_writer.as_default():
  train(model, optimizer, dataset)

with test_summary_writer.as_default():
  test(model, test_x, test_y, optimizer.iterations)

Wizualizuj wygenerowane podsumowania, wskazując TensorBoard na katalog dziennika podsumowań:

tensorboard --logdir /tmp/summaries

Użyj tf.config.experimental_run_functions_eagerly () podczas debugowania

W TensorFlow 2.0 zachłanne wykonanie umożliwia uruchamianie kodu krok po kroku w celu sprawdzenia kształtów, typów danych i wartości. Niektóre interfejsy API, takie jak tf.function , tf.keras itp., Są zaprojektowane do korzystania z wykonywania wykresów w celu zapewnienia wydajności i przenośności. Podczas debugowania użyj tf.config.experimental_run_functions_eagerly(True) aby użyć tf.config.experimental_run_functions_eagerly(True) wykonywania w tym kodzie.

Na przykład:

@tf.function
def f(x):
  if x > 0:
    import pdb
    pdb.set_trace()
    x = x + 1
  return x

tf.config.experimental_run_functions_eagerly(True)
f(tf.constant(1))
>>> f()
-> x = x + 1
(Pdb) l
  6     @tf.function
  7     def f(x):
  8       if x > 0:
  9         import pdb
 10         pdb.set_trace()
 11  ->     x = x + 1
 12       return x
 13
 14     tf.config.experimental_run_functions_eagerly(True)
 15     f(tf.constant(1))
[EOF]

Działa to również w modelach Keras i innych interfejsach API, które obsługują przyspieszone wykonywanie:

class CustomModel(tf.keras.models.Model):

  @tf.function
  def call(self, input_data):
    if tf.reduce_mean(input_data) > 0:
      return input_data
    else:
      import pdb
      pdb.set_trace()
      return input_data // 2


tf.config.experimental_run_functions_eagerly(True)
model = CustomModel()
model(tf.constant([-2, -4]))
>>> call()
-> return input_data // 2
(Pdb) l
 10         if tf.reduce_mean(input_data) > 0:
 11           return input_data
 12         else:
 13           import pdb
 14           pdb.set_trace()
 15  ->       return input_data // 2
 16
 17
 18     tf.config.experimental_run_functions_eagerly(True)
 19     model = CustomModel()
 20     model(tf.constant([-2, -4]))