Dzień Społeczności ML jest 9 listopada! Dołącz do nas na aktualizacje z TensorFlow Jax i więcej Dowiedz się więcej

TensorFlow 1.x a TensorFlow 2 — zachowania i interfejsy API

Zobacz na TensorFlow.org Uruchom w Google Colab Zobacz na GitHub Pobierz notatnik

Pod maską TensorFlow 2 podąża za zasadniczo innym paradygmatem programowania niż TF1.x.

W tym przewodniku opisano podstawowe różnice między programami TF1.x i TF2 pod względem zachowań i interfejsów API oraz ich związek z podróżą migracji.

Ogólne podsumowanie głównych zmian

Zasadniczo TF1.x i TF2 używają innego zestawu zachowań środowiska uruchomieniowego wokół wykonywania (chętne w TF2), zmiennych, przepływu sterowania, kształtów tensorów i porównań równości tensorów. Aby był zgodny z TF2, Twój kod musi być zgodny z pełnym zestawem zachowań TF2. Podczas migracji, można włączyć lub wyłączyć większość z tych zachowań indywidualnie przez tf.compat.v1.enable_* lub tf.compat.v1.disable_* API. Jedynym wyjątkiem jest usuwanie kolekcji, które jest efektem ubocznym włączania/wyłączania gorliwego wykonywania.

Na wysokim poziomie TensorFlow 2:

  • Usuwa redundantny API .
  • Sprawia API bardziej spójny - na przykład Unified RNNs i Unified optymalizatorów .
  • Preferuje funkcje ponad sesji i lepiej integruje się ze środowiskiem wykonawczym Pythona z Eager wykonanie włączoną domyślnie wraz z tf.function który zapewnia automatyczne zależności sterujących dla wykresy i zestawienia.
  • Deprecates globalnych wykres zbiory .
  • Zmienna zmienia semantyki współbieżności przy użyciu ResourceVariables nad ReferenceVariables .
  • Obsługuje funkcję oparte i różniczkowalna flow control (Kontrola przepływu v2).
  • Upraszcza API TensorShape przytrzymywać int s zamiast tf.compat.v1.Dimension obiektów.
  • Aktualizuje mechanikę równości tensorów. W TF1.x == operator na tensorów i zmiennych kontrole dla równości odniesienia obiektu. W TF2 sprawdza równość wartości. Dodatkowo tensory / zmienne nie są już hashable, ale można dostać hashable referencje obiektów do nich poprzez var.ref() , jeśli chcesz je wykorzystać w zestawach lub jako dict kluczy.

Poniższe sekcje zawierają więcej kontekstu na temat różnic między TF1.xi TF2. Aby dowiedzieć się więcej o procesie projektowania za TF2, przeczytaj RFC oraz dokumenty projektowe .

Oczyszczanie API

Wiele API są albo poszedł lub przeniesiony w TF2. Niektóre z głównych zmian należą usuwania tf.app , tf.flags i tf.logging na rzecz teraz open source ABSL-Py , znajdowania nowego domu projektów, które mieszkały w tf.contrib i oczyszczania główną tf.* Nazw przez przesuwając rzadziej używane funkcje w podpakiety jak tf.math . Niektóre interfejsy API zostały zastąpione ich ekwiwalenty - TF2 tf.summary , tf.keras.metrics i tf.keras.optimizers .

tf.compat.v1 : Legacy i kompatybilności Punkty końcowe API

Symbole pod tf.compat i tf.compat.v1 nazw nie są uważane TF2 API. Te przestrzenie nazw uwidaczniają mieszankę symboli zgodności, a także starsze punkty końcowe interfejsu API z TF 1.x. Mają one na celu ułatwienie migracji z TF1.x do TF2. Jednak, jak żaden z tych compat.v1 API są idiomatyczne API TF2, nie używać ich do pisania zupełnie nowy kod TF2.

Poszczególne tf.compat.v1 symbole mogą być kompatybilne TF2 bo nadal pracować nawet z TF2 zachowania włączona (takich jak tf.compat.v1.losses.mean_squared_error ), podczas gdy inne są niezgodne z TF2 (takich jak tf.compat.v1.metrics.accuracy ). Wiele compat.v1 symbole (choć nie wszystkie) zawiera dedykowany informacje migracji w ich dokumentacji, która wyjaśnia ich stopień kompatybilności z TF2 zachowań, a także w jaki sposób przenieść je do TF2 API.

Skrypt aktualizacji TF2 może odwzorowywać wiele compat.v1 symbole API do świadczeń równoważnych API TF2, w przypadku gdy są one aliasy lub posiadających te same argumenty, ale o innej kolejności. Możesz również użyć skryptu aktualizacji, aby automatycznie zmienić nazwy interfejsów API TF1.x.

Interfejsy API fałszywych znajomych

Istnieje zestaw „false-przyjaciel” symboli znaleźć w TF2 tf nazw (nie pod compat.v1 ), które faktycznie ignorować TF2 zachowań under-the-kaptur, i / lub nie są w pełni kompatybilne z pełnym zestawem zachowań TF2. W związku z tym te interfejsy API mogą źle działać z kodem TF2, potencjalnie w cichy sposób.

  • tf.estimator.* : Estymatory tworzyć wykresy i wykorzystanie i sesje pod maską. W związku z tym nie należy ich uważać za kompatybilne z TF2. Jeśli w kodzie działają estymatory, nie używa on zachowań TF2.
  • keras.Model.model_to_estimator(...) : To tworzy prognozy pod maską, która jak wspomniano powyżej, nie jest kompatybilny z TF2.
  • tf.Graph().as_default() : Ta TF1.x wchodzi zachowań wykres i nie przestrzegać standardowych TF2 kompatybilne tf.function zachowań. Kod, który wprowadzi takie wykresy, zazwyczaj uruchamia je za pośrednictwem sesji i nie powinien być uważany za zgodny z TF2.
  • tf.feature_column.* API kolumna funkcji generalnie opierają się na TF1 stylu tf.compat.v1.get_variable tworzenia zmiennej i zakładamy, że będzie dostępny utworzone poprzez zmienne globalne kolekcji. Ponieważ TF2 nie obsługuje kolekcji, interfejsy API mogą nie działać poprawnie podczas ich uruchamiania z włączonymi zachowaniami TF2.

Inne zmiany API

  • TF2 wyposażony znaczne ulepszenia w stosunku do algorytmu umieszczania urządzenia, które powoduje, że wykorzystanie tf.colocate_with zbędne. Jeśli usunięcie go powoduje obniżyć wydajność należy zgłosić błąd .

  • Wymień wszystkie wykorzystanie tf.v1.ConfigProto równoważnych funkcji z tf.config .

Chętna egzekucja

TF1.x wymagane ręczne zszyć razem to drzewo składniowe (wykresu) przez co tf.* Wywołań API, a następnie ręcznie skompilować drzewo składniowe przekazując komplet tensorów wyjściowych i tensorów wejściowych do session.run rozmowy. TF2 wykonuje się chętnie (jak zwykle robi to Python) i sprawia, że ​​wykresy i sesje przypominają szczegóły implementacji.

Jednym godnym uwagi produktem ubocznym jest to, że chętnie wykonanie tf.control_dependencies nie jest już konieczne, ponieważ wszystkie linie kodu wykonać w kolejności (w tf.function , kod ze skutkami ubocznymi w kolejności sporządzi pisemne).

Nigdy więcej globalnych

TF1.x w dużym stopniu opierał się na niejawnych globalnych przestrzeniach nazw i kolekcjach. Kiedy zadzwoniłeś tf.Variable , byłoby umieścić w kolekcji w domyślnym wykresie, i to tam pozostać, nawet jeśli straciłem zmiennej Pythona skierowaną do niego. Następnie można odzyskać tę tf.Variable , ale tylko wtedy, gdy wiedział, że nazwa została stworzona za pomocą. Było to trudne do zrobienia, jeśli nie kontrolowałeś tworzenia zmiennej. W rezultacie mnożyły się wszelkiego rodzaju mechanizmy, które próbowały pomóc w ponownym odnalezieniu zmiennych, a frameworki w celu znalezienia zmiennych utworzonych przez użytkownika. Niektóre z nich to: zmienne zakresy, globalne zbiory, pomocnika metod takich jak tf.get_global_step i tf.global_variables_initializer , optymalizujące niejawnie informatyki gradienty nad wszystkimi zmiennymi wyszkolić, i tak dalej. TF2 eliminuje wszystkich tych mechanizmów ( Zmienne 2,0 RFC ) na rzecz mechanizmu domyślne - śledzenie zmiennych. Jeśli stracić z tf.Variable , robi śmieci zebrane.

Wymóg, aby śledzić zmienne tworzy jakąś dodatkową pracę, ale za pomocą narzędzi takich jak podkładki modelowania i zachowań takich ukrytych obiektowych zbiorów zmiennych w tf.Module s i tf.keras.layers.Layer s , ciężar jest zminimalizowane.

Funkcje, a nie sesje

session.run połączenie jest prawie jak wywołanie funkcji: można określić wejść i funkcji na miano i wrócisz zestaw wyjść. W TF2, można ozdobić za pomocą funkcji Pythona tf.function oznaczyć go do kompilacji JIT tak że TensorFlow uruchamia go jako jednym wykresie ( Functions 2.0 RFC ). Ten mechanizm pozwala TF2 na uzyskanie wszystkich zalet trybu wykresu:

  • Wydajność: funkcję można zoptymalizować (przycinanie węzłów, fuzja jądra itp.)
  • Przenośność: Funkcja ta może być eksportowane / powrotnie przywiezione ( SavedModel 2.0 RFC ), co pozwala na ponowne wykorzystanie i udostępnianie modułowe funkcje TensorFlow.
# TF1.x
outputs = session.run(f(placeholder), feed_dict={placeholder: input})
# TF2
outputs = f(input)

Dzięki możliwości swobodnego przeplatania kodu Pythona i TensorFlow możesz wykorzystać ekspresję Pythona. Jednak przenośny TensorFlow wykonuje się w kontekstach bez interpretera Pythona, takiego jak mobile, C++ i JavaScript. Aby uniknąć przepisywania kodu podczas dodawania tf.function użyć autograf przekonwertować podzbiór konstrukcji Pythona do ich odpowiedników TensorFlow:

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

AutoGraph obsługuje dowolne zagnieżdżenia przepływu sterowania, co umożliwia wydajną i zwięzłą implementację wielu złożonych programów ML, takich jak modele sekwencji, uczenie się ze wzmocnieniem, niestandardowe pętle szkoleniowe i wiele innych.

Adaptacja do zmian w zachowaniu TF 2.x

Migracja do TF2 jest zakończona dopiero po migracji do pełnego zestawu zachowań TF2. Pełny zestaw zachowań może być włączona lub wyłączona poprzez tf.compat.v1.enable_v2_behaviors i tf.compat.v1.disable_v2_behaviors . Poniższe sekcje szczegółowo omawiają każdą poważną zmianę zachowania.

Korzystanie tf.function s

Największe zmiany w swoich programach podczas migracji mogą pochodzić z podstawowego modelu programowania paradygmatu z wykresami i sesje chętny wykonania i tf.function . Zapoznaj się z prowadnic migracji TF2 , aby dowiedzieć się więcej o przeprowadzce z API, które są niezgodne z upragnieniem wykonania i tf.function do interfejsów API, które są z nim kompatybilne.

Poniżej przedstawiamy kilka wzorów wspólnego programu nie przywiązane do jednego API, które mogą powodować problemy podczas przełączania z tf.Graph s i tf.compat.v1.Session s chętny do wykonania z tf.function s.

Wzorzec 1: Manipulacja obiektami Pythona i tworzenie zmiennych, które mają być wykonywane tylko raz, są uruchamiane wiele razy

W programach TF1.x, które opierają się na wykresach i sesjach, zwykle oczekuje się, że cała logika Pythona w twoim programie zostanie uruchomiona tylko raz. Jednak z upragnieniem wykonania i tf.function to jest sprawiedliwe, by oczekiwać, że logika Python będą prowadzone co najmniej raz, ale być może więcej razy (albo wielokrotnie niecierpliwie, lub kilka razy w różnych tf.function śladów). Czasami tf.function nawet śladowe dwukrotnie tego samego wejścia powoduje nieoczekiwane zachowanie (patrz przykład 1 i 2). Zapoznaj się z tf.function przewodnika po więcej szczegółów.

Przykład 1: Tworzenie zmiennych

Rozważmy poniższy przykład, w którym funkcja tworzy zmienną po wywołaniu:

def f():
  v = tf.Variable(1.0)
  return v

with tf.Graph().as_default():
  with tf.compat.v1.Session() as sess:
    res = f()
    sess.run(tf.compat.v1.global_variables_initializer())
    sess.run(res)

Jednakże naiwnego owijania powyższej funkcji, który zawiera zmienny tworzenia z tf.function nie jest dozwolone. tf.function obsługuje tylko singleton projekty zmienne dotyczące pierwszego naboru . Aby to wymusić, gdy tf.function wykryje tworzenie zmiennej w pierwszym wywołaniu, spróbuje ponownie śledzić i zgłosi błąd, jeśli w drugim śledzeniu wystąpi tworzenie zmiennej.

@tf.function
def f():
  print("trace") # This will print twice because the python body is run twice
  v = tf.Variable(1.0)
  return v

try:
  f()
except ValueError as e:
  print(e)

Obejście to buforowanie i ponowne użycie zmiennej po jej utworzeniu w pierwszym wywołaniu.

class Model(tf.Module):
  def __init__(self):
    self.v = None

  @tf.function
  def __call__(self):
    print("trace") # This will print twice because the python body is run twice
    if self.v is None:
      self.v = tf.Variable(0)
    return self.v

m = Model()
m()

Przykład 2: Out-of-zakresie Tensory powodu tf.function odtworzyć

Jak wykazano w przykładzie 1, tf.function się odtworzyć w przypadku wykrycia w zmiennych tworzenia pierwszego połączenia. Może to spowodować dodatkowe zamieszanie, ponieważ dwa obrysy utworzą dwa wykresy. Gdy drugi wykres z retracingu próbuje uzyskać dostęp do Tensora z wykresu wygenerowanego podczas pierwszego śledzenia, Tensorflow zgłosi błąd, skarżąc się, że Tensor jest poza zakresem. Aby zademonstrować scenariusz, poniższy kod tworzy zbiór danych na pierwszym tf.function rozmowy. To działałoby zgodnie z oczekiwaniami.

class Model(tf.Module):
  def __init__(self):
    self.dataset = None

  @tf.function
  def __call__(self):
    print("trace") # This will print once: only traced once
    if self.dataset is None:
      self.dataset = tf.data.Dataset.from_tensors([1, 2, 3])
    it = iter(self.dataset)
    return next(it)

m = Model()
m()

Jeśli jednak również próbować utworzyć zmienną na pierwszym tf.function rozmowy kod będzie podnieść błąd twierdząc, że zestaw danych jest poza zakresem. Dzieje się tak, ponieważ zbiór danych znajduje się na pierwszym wykresie, podczas gdy drugi wykres również próbuje uzyskać do niego dostęp.

class Model(tf.Module):
  def __init__(self):
    self.v = None
    self.dataset = None

  @tf.function
  def __call__(self):
    print("trace") # This will print twice because the python body is run twice
    if self.v is None:
      self.v = tf.Variable(0)
    if self.dataset is None:
      self.dataset = tf.data.Dataset.from_tensors([1, 2, 3])
    it = iter(self.dataset)
    return [self.v, next(it)]

m = Model()
try:
  m()
except TypeError as e:
  print(e) # <tf.Tensor ...> is out of scope and cannot be used here.

Najbardziej rozwiązanie straightfoward jest zapewnienie, że stworzenie zbioru danych i tworzenie zmiennej są zarówno na zewnątrz tf.funciton rozmowy. Na przykład:

class Model(tf.Module):
  def __init__(self):
    self.v = None
    self.dataset = None

  def initialize(self):
    if self.dataset is None:
      self.dataset = tf.data.Dataset.from_tensors([1, 2, 3])
    if self.v is None:
      self.v = tf.Variable(0)

  @tf.function
  def __call__(self):
    it = iter(self.dataset)
    return [self.v, next(it)]

m = Model()
m.initialize()
m()

Jednak czasami to nie do uniknięcia, aby utworzyć zmienne tf.function (takie jak zmienne wrzutowych w niektórych Keras TF optymalizujące ). Mimo to, możemy po prostu przesunąć poza tworzenie zestawu danych z tf.function rozmowy. Dlatego, że możemy liczyć na to, ponieważ tf.function otrzyma zestaw danych jako wejście niejawny i oba wykresy do niego dostęp prawidłowo.

class Model(tf.Module):
  def __init__(self):
    self.v = None
    self.dataset = None

  def initialize(self):
    if self.dataset is None:
      self.dataset = tf.data.Dataset.from_tensors([1, 2, 3])

  @tf.function
  def __call__(self):
    if self.v is None:
      self.v = tf.Variable(0)
    it = iter(self.dataset)
    return [self.v, next(it)]

m = Model()
m.initialize()
m()

Przykład 3: Nieoczekiwane odtworzenie obiektu Tensorflow z powodu użycia dict

tf.function ma bardzo słabe wsparcie dla efektów ubocznych, takich jak pyton dołączając do listy lub zaznaczenie / dodanie do słownika. Więcej szczegółów w „lepszą wydajność z tf.function” . W poniższym przykładzie kod używa słowników do buforowania zestawów danych i iteratorów. Dla tego samego klucza każde wywołanie modelu zwróci ten sam iterator zestawu danych.

class Model(tf.Module):
  def __init__(self):
    self.datasets = {}
    self.iterators = {}

  def __call__(self, key):
    if key not in self.datasets:
      self.datasets[key] = tf.compat.v1.data.Dataset.from_tensor_slices([1, 2, 3])
      self.iterators[key] = self.datasets[key].make_initializable_iterator()
    return self.iterators[key]

with tf.Graph().as_default():
  with tf.compat.v1.Session() as sess:
    m = Model()
    it = m('a')
    sess.run(it.initializer)
    for _ in range(3):
      print(sess.run(it.get_next())) # prints 1, 2, 3

Jednak powyższy wzór nie będzie pracy jak oczekiwano w tf.function . Podczas śledzenia, tf.function zignoruje efekt uboczny pyton dodawania słowników. Zamiast tego zapamiętuje tylko utworzenie nowego zestawu danych i iteratora. W rezultacie każde wywołanie modelu zawsze zwróci nowy iterator. Ten problem jest trudny do zauważenia, chyba że wyniki liczbowe lub wydajność są wystarczająco znaczące. Dlatego zalecamy użytkownikom, aby myśleć o kodzie przed owijając starannie tf.function naiwnie na kodzie Pythona.

class Model(tf.Module):
  def __init__(self):
    self.datasets = {}
    self.iterators = {}

  @tf.function
  def __call__(self, key):
    if key not in self.datasets:
      self.datasets[key] = tf.data.Dataset.from_tensor_slices([1, 2, 3])
      self.iterators[key] = iter(self.datasets[key])
    return self.iterators[key]

m = Model()
for _ in range(3):
  print(next(m('a'))) # prints 1, 1, 1

Możemy użyć tf.init_scope podnieść zbiór danych i iterator tworzenie poza wykresem, aby osiągnąć oczekiwane zachowanie:

class Model(tf.Module):
  def __init__(self):
    self.datasets = {}
    self.iterators = {}

  @tf.function
  def __call__(self, key):
    if key not in self.datasets:
      # Lifts ops out of function-building graphs
      with tf.init_scope():
        self.datasets[key] = tf.data.Dataset.from_tensor_slices([1, 2, 3])
        self.iterators[key] = iter(self.datasets[key])
    return self.iterators[key]

m = Model()
for _ in range(3):
  print(next(m('a'))) # prints 1, 2, 3

Ogólną zasadą jest unikanie polegania na efektach ubocznych Pythona w swojej logice i używanie ich tylko do debugowania śladów.

Przykład 4: Manipulowanie globalną listą Pythona

Poniższy kod TF1.x używa globalnej listy strat, której używa tylko do utrzymywania listy strat wygenerowanych przez bieżący krok uczenia. Zwróć uwagę, że logika Pythona, która dodaje straty do listy, zostanie wywołana tylko raz, niezależnie od tego, ile kroków treningowych jest uruchamiana dla sesji.

all_losses = []

class Model():
  def __call__(...):
    ...
    all_losses.append(regularization_loss)
    all_losses.append(label_loss_a)
    all_losses.append(label_loss_b)
    ...

g = tf.Graph()
with g.as_default():
  ...
  # initialize all objects
  model = Model()
  optimizer = ...
  ...
  # train step
  model(...)
  total_loss = tf.reduce_sum(all_losses)
  optimizer.minimize(total_loss)
  ...
...
sess = tf.compat.v1.Session(graph=g)
sess.run(...)  

Jeśli jednak ta logika Pythona zostanie naiwnie zmapowana do TF2 z gorliwym wykonaniem, globalna lista strat będzie miała dodawane nowe wartości w każdym kroku uczenia. Oznacza to, że kod kroku treningowego, który wcześniej oczekiwał, że lista będzie zawierała tylko straty z bieżącego kroku treningowego, teraz faktycznie widzi listę strat ze wszystkich dotychczasowych kroków treningowych. Jest to niezamierzona zmiana zachowania i lista będzie musiała zostać wyczyszczona na początku każdego kroku lub umieszczona lokalnie na etapie szkolenia.

all_losses = []

class Model():
  def __call__(...):
    ...
    all_losses.append(regularization_loss)
    all_losses.append(label_loss_a)
    all_losses.append(label_loss_b)
    ...

# initialize all objects
model = Model()
optimizer = ...

def train_step(...)
  ...
  model(...)
  total_loss = tf.reduce_sum(all_losses) # global list is never cleared,
  # Accidentally accumulates sum loss across all training steps
  optimizer.minimize(total_loss)
  ...

Wzorzec 2: Symboliczny tensor, który ma być przeliczany na każdym kroku w TF1.x, jest przypadkowo buforowany z początkową wartością podczas przełączania na eager.

Ten wzór zwykle wywołuje kod dyskretnie źle się zachowywać podczas wykonywania chętnie poza tf.functions, ale podnosi InaccessibleTensorError jeśli buforowanie wartość początkowa występuje wewnątrz tf.function . Należy jednak pamiętać, że w celu uniknięcia wzór 1 powyżej was często nieumyślnie struktury kodu w taki sposób, że wartość początkowa będzie buforowanie wydarzyć poza jakimkolwiek tf.function , który byłby w stanie podnieść błąd. Zachowaj więc szczególną ostrożność, jeśli wiesz, że Twój program może być podatny na ten wzorzec.

Ogólnym rozwiązaniem tego wzorca jest restrukturyzacja kodu lub użycie funkcji wywoływalnych Pythona, jeśli to konieczne, aby upewnić się, że wartość jest przeliczana za każdym razem, a nie przypadkowo buforowana.

Przykład 1: Szybkość uczenia/hiperparametr/itd. harmonogramy zależne od kroku globalnego

W poniższym fragmencie kodu, oczekuje się, że za każdym razem, gdy sesja jest prowadzony najnowszego global_step wartość zostanie odczytany, a nowy kurs nauki będą obliczane.

g = tf.Graph()
with g.as_default():
  ...
  global_step = tf.Variable(0)
  learning_rate = 1.0 / global_step
  opt = tf.compat.v1.train.GradientDescentOptimizer(learning_rate)
  ...
  global_step.assign_add(1)
...
sess = tf.compat.v1.Session(graph=g)
sess.run(...)

Jednak próbując przełączyć się na gorliwy, uważaj, aby nie skończyć z szybkością uczenia się obliczaną tylko raz, a następnie ponownie wykorzystaną, zamiast postępować zgodnie z zamierzonym harmonogramem:

global_step = tf.Variable(0)
learning_rate = 1.0 / global_step # Wrong! Only computed once!
opt = tf.keras.optimizers.SGD(learning_rate)

def train_step(...):
  ...
  opt.apply_gradients(...)
  global_step.assign_add(1)
  ...

Ponieważ ten konkretny przykład jest wspólny wzór i optymalizujące powinna być inicjowana tylko raz, a nie na każdym etapie kształcenia, TF2 optymalizujące wsparcie tf.keras.optimizers.schedules.LearningRateSchedule harmonogramy lub callables Python jako argumenty dla szybkości uczenia się i innych hiperparametrów.

Przykład 2: Symboliczne inicjalizacje liczb losowych przypisane jako atrybuty obiektu, a następnie ponownie użyte za pomocą wskaźnika są przypadkowo buforowane po przełączeniu na eager

Rozważmy następujący NoiseAdder moduł:

class NoiseAdder(tf.Module):
  def __init__(shape, mean):
    self.noise_distribution = tf.random.normal(shape=shape, mean=mean)
    self.trainable_scale = tf.Variable(1.0, trainable=True)

  def add_noise(input):
    return (self.noise_distribution + input) * self.trainable_scale

Użycie go w następujący sposób w TF1.x obliczy nowy tensor losowego szumu za każdym razem, gdy uruchamiana jest sesja:

g = tf.Graph()
with g.as_default():
  ...
  # initialize all variable-containing objects
  noise_adder = NoiseAdder(shape, mean)
  ...
  # computation pass
  x_with_noise = noise_adder.add_noise(x)
  ...
...
sess = tf.compat.v1.Session(graph=g)
sess.run(...)

Jednak w TF2 inicjowania noise_adder na początku spowoduje noise_distribution być obliczane tylko raz i dostać zamrożone na wszystkich etapach kształcenia:

...
# initialize all variable-containing objects
noise_adder = NoiseAdder(shape, mean) # Freezes `self.noise_distribution`!
...
# computation pass
x_with_noise = noise_adder.add_noise(x)
...

Aby rozwiązać ten problem, Refactor NoiseAdder zadzwonić tf.random.normal każdym razem potrzebna jest nowa losowa tensor, zamiast odnoszenia się do tego samego obiektu tensora każdym razem.

class NoiseAdder(tf.Module):
  def __init__(shape, mean):
    self.noise_distribution = lambda: tf.random.normal(shape=shape, mean=mean)
    self.trainable_scale = tf.Variable(1.0, trainable=True)

  def add_noise(input):
    return (self.noise_distribution() + input) * self.trainable_scale

Wzorzec 3: Kod TF1.x bezpośrednio opiera się na tensorach i wyszukuje tensory według nazwy

Często testy kodu TF1.x polegają na sprawdzeniu, jakie tensory lub operacje są obecne na grafie. W niektórych rzadkich przypadkach kod modelowania będzie również opierał się na tych wyszukiwaniach według nazwy.

Tensor imiona nie są generowane podczas wykonywania chętnie poza tf.function w ogóle, więc wszystkie zwyczaje tf.Tensor.name musi się zdarzyć wewnątrz tf.function . Należy pamiętać, że rzeczywiste nazwy tworzone są bardzo prawdopodobnie różnią się między TF1.x i TF2 nawet w obrębie tej samej tf.function i gwarancje API nie zapewniają stabilność generowanych nazw w całej wersji TF.

Wzorzec 4: Sesja TF1.x selektywnie uruchamia tylko część wygenerowanego wykresu

W TF1.x można skonstruować wykres, a następnie wybrać tylko selektywne uruchamianie tylko jego podzbioru w sesji, wybierając zestaw danych wejściowych i wyjściowych, które nie wymagają uruchamiania każdej operacji na wykresie.

Na przykład, możesz mieć zarówno generator i wnętrze dyskryminator jednym wykresie i używać oddzielnych tf.compat.v1.Session.run połączeń na przemian tylko szkolenie dyskryminator lub tylko szkolenie generator.

W TF2, ze względu na zależności automatycznej kontroli w tf.function i chętnie realizacji, nie ma selektywne przycinanie tf.function śladów. Pełny wykres zawierający wszystkie aktualizacje zmiennych dostanie uruchomić nawet jeśli, na przykład, tylko wyjście dyskryminatora lub generator jest wyjście z tf.function .

Więc trzeba by użyć wielokrotności tf.function s zawierających różne części programu, lub warunkowego argument do tf.function że oddział na tak aby wykonać tylko to, co rzeczywiście chcesz mieć bieg.

Usuwanie kolekcji

Kiedy chętny wykonanie jest włączona, wykres kolekcja związane compat.v1 API (w tym tych, które czytają lub zapisują do zbiorów pod maską takie jak tf.compat.v1.trainable_variables ) nie są już dostępne. Niektórzy mogą podnieść ValueError s, podczas gdy inni mogą dyskretnie zwrotu pustych list.

Najbardziej standardowe wykorzystanie zbiorów w TF1.x jest utrzymanie inicjatorów, globalny krok, wagi straty regularyzacji straty wyjściowych modelu i zmiennych aktualizacje, które muszą być prowadzone tak jak od BatchNormalization warstwach.

Aby obsłużyć każde z tych standardowych zastosowań:

  1. Inicjatory — ignoruj. Ręczna inicjalizacja zmiennych nie jest wymagana przy włączonym przyspieszonym wykonywaniu.
  2. Globalny krok - Zobacz dokumentację tf.compat.v1.train.get_or_create_global_step dla migracji instrukcji.
  3. Ciężarki - Mapa swoje modele do tf.Module s / tf.keras.layers.Layer s / tf.keras.Model s postępując według wskazówek w podręczniku modelu mapowania , a następnie użyć ich odpowiednie mechanizmy śledzenia wagi, takich jak tf.module.trainable_variables .
  4. Straty regularyzacji - mapa swoje modele do tf.Module s / tf.keras.layers.Layer s / tf.keras.Model s postępując według wskazówek w podręczniku modelu mapowania , a następnie użyć tf.keras.losses . Alternatywnie możesz również ręcznie śledzić straty związane z regularyzacją.
  5. Straty wyjściowe Model - Wykorzystanie tf.keras.Model mechanizmy zarządzania utrata lub oddzielnie śledzić swoje straty bez używania kolekcji.
  6. Aktualizacje wagi — zignoruj ​​tę kolekcję. Marzą wykonanie i tf.function (z autografem i auto-control-zależnościami) oznacza wszystkie aktualizacje zmiennych będzie się uruchamiać automatycznie. Tak więc nie będziesz musiał jawnie uruchamiać wszystkich aktualizacji wagi na końcu, ale pamiętaj, że oznacza to, że aktualizacje wagi mogą nastąpić w innym czasie niż w kodzie TF1.x, w zależności od tego, jak używałeś zależności kontrolnych.
  7. Podsumowania - Patrz API przewodnika podsumowania migrujące .

Bardziej złożone użycie kolekcji (takie jak używanie kolekcji niestandardowych) może wymagać refaktoryzacji kodu, aby utrzymać własne sklepy globalne lub w ogóle nie polegać na sklepach globalnych.

ResourceVariables zamiast ReferenceVariables

ResourceVariables mieć mocniejsze gwarancje spójności odczytu niż ReferenceVariables . Prowadzi to do bardziej przewidywalnych, łatwiejszych do uzasadnienia w semantyce tego, czy będziesz obserwować wynik poprzedniego zapisu podczas używania zmiennych. Ta zmiana jest bardzo mało prawdopodobna, aby spowodować, że istniejący kod będzie zgłaszał błędy lub dyskretnie się zepsuł.

Jednak jest to możliwe, choć mało prawdopodobne, że te mocniejsze gwarancje spójności może zwiększyć zużycie pamięci programu konkretnego. Proszę złożyć problem , jeśli okaże się, że jest to przypadek. Ponadto, jeśli masz testy jednostkowe oparte na dokładnych porównaniach ciągów z nazwami operatorów na wykresie odpowiadającym odczytom zmiennych, pamiętaj, że włączenie zmiennych zasobów może nieznacznie zmienić nazwy tych operatorów.

Aby wyizolować wpływ tej zmiany zachowań w kodzie, jeśli chętny wykonanie jest wyłączona można użyć tf.compat.v1.disable_resource_variables() i tf.compat.v1.enable_resource_variables() , aby wyłączyć lub włączyć globalnie tę zmianę zachowania. ResourceVariables zawsze być stosowane, jeżeli chętny wykonanie jest włączony.

Sterowanie przepływem v2

W TF1.x, ops sterowania przepływem, takie jak tf.cond i tf.while_loop rolki niskopoziomowych ops takie jak Switch , Merge TF2 ulepszona kontroli funkcjonalnej ops przepływu, które są wykonywane z wykorzystaniem oddzielnych itp tf.function śladów dla każdej gałęzi i wsparcia zróżnicowanie wyższego rzędu.

Aby wyizolować wpływ tej zmiany zachowań w kodzie, jeśli chętny wykonanie jest wyłączona można użyć tf.compat.v1.disable_control_flow_v2() i tf.compat.v1.enable_control_flow_v2() , aby wyłączyć lub włączyć globalnie tę zmianę zachowania. Przepływ sterowania v2 można jednak wyłączyć tylko wtedy, gdy przyspieszone wykonywanie jest również wyłączone. Jeśli jest włączona, zawsze będzie używany przepływ sterowania v2.

Ta zmiana zachowania może radykalnie zmienić strukturę wygenerowanych programów TF, które używają przepływu sterowania, ponieważ będą one zawierać kilka zagnieżdżonych śladów funkcji zamiast jednego płaskiego wykresu. Tak więc każdy kod, który jest wysoce zależny od dokładnej semantyki produkowanych śladów, może wymagać pewnej modyfikacji. To zawiera:

  • Kod oparty na nazwach operatorów i tensorów
  • Kod odwołujący się do tensorów utworzonych w gałęzi przepływu sterowania TensorFlow spoza tej gałęzi. Jest prawdopodobne, aby produkować InaccessibleTensorError

Ta zmiana zachowanie ma być wydajność neutralnego na pozytywne, ale jeśli napotkasz problem, gdzie kontrola przepływu v2 wykonuje gorzej dla ciebie niż kontrolą TF1.x przepływają następnie proszę złożyć problem ze schodami rozrodu.

Zmiany w zachowaniu API TensorShape

TensorShape klasa została uproszczona do hold int s, zamiast tf.compat.v1.Dimension obiektów. Więc nie ma potrzeby, aby zadzwonić .value uzyskać int .

Indywidualne tf.compat.v1.Dimension obiekty są nadal dostępne z tf.TensorShape.dims .

Aby wyizolować wpływ tej zmiany zachowań w kodzie, można użyć tf.compat.v1.disable_v2_tensorshape() i tf.compat.v1.enable_v2_tensorshape() globalnie wyłączyć lub włączyć tę zmianę zachowania.

Poniżej przedstawiono różnice między TF1.x i TF2.

import tensorflow as tf
# Create a shape and choose an index
i = 0
shape = tf.TensorShape([16, None, 256])
shape
TensorShape([16, None, 256])

Jeśli miałeś to w TF1.x:

value = shape[i].value

Następnie zrób to w TF2:

value = shape[i]
value
16

Jeśli miałeś to w TF1.x:

for dim in shape:
    value = dim.value
    print(value)

Następnie zrób to w TF2:

for value in shape:
  print(value)
16
None
256

Jeśli miałeś to w TF1.x (lub użyłeś innej metody wymiarowania):

dim = shape[i]
dim.assert_is_compatible_with(other_dim)

Następnie zrób to w TF2:

other_dim = 16
Dimension = tf.compat.v1.Dimension

if shape.rank is None:
  dim = Dimension(None)
else:
  dim = shape.dims[i]
dim.is_compatible_with(other_dim) # or any other dimension method
True
shape = tf.TensorShape(None)

if shape:
  dim = shape.dims[i]
  dim.is_compatible_with(other_dim) # or any other dimension method

Wartość logiczna wartość tf.TensorShape jest True jeśli ranga jest znana, False inaczej.

print(bool(tf.TensorShape([])))      # Scalar
print(bool(tf.TensorShape([0])))     # 0-length vector
print(bool(tf.TensorShape([1])))     # 1-length vector
print(bool(tf.TensorShape([None])))  # Unknown-length vector
print(bool(tf.TensorShape([1, 10, 100])))       # 3D tensor
print(bool(tf.TensorShape([None, None, None]))) # 3D tensor with no known dimensions
print()
print(bool(tf.TensorShape(None)))  # A tensor with unknown rank.
True
True
True
True
True
True

False

Potencjalne błędy spowodowane zmianami TensorShape

Zmiany zachowania TensorShape raczej nie spowodują dyskretnego złamania kodu. Jednak można zobaczyć kształt związane Kod zaczynają podnosić AttributeError s jako int s i None s nie mają te same atrybuty, które tf.compat.v1.Dimension s zrobienia. Poniżej przedstawiamy kilka przykładów tych AttributeError s:

try:
  # Create a shape and choose an index
  shape = tf.TensorShape([16, None, 256])
  value = shape[0].value
except AttributeError as e:
  # 'int' object has no attribute 'value'
  print(e)
'int' object has no attribute 'value'
try:
  # Create a shape and choose an index
  shape = tf.TensorShape([16, None, 256])
  dim = shape[1]
  other_dim = shape[2]
  dim.assert_is_compatible_with(other_dim)
except AttributeError as e:
  # 'NoneType' object has no attribute 'assert_is_compatible_with'
  print(e)
'NoneType' object has no attribute 'assert_is_compatible_with'

Równość tensora według wartości

Binarny == i != Operatorzy na zmiennych i tensorów zostały zmienione, aby porównać pod względem wartości w TF2 zamiast porównywania przez odniesienie obiektu jak w TF1.x. Ponadto tensory i zmienne nie są już bezpośrednio haszowane ani używane w zestawach lub kluczach dyktujących, ponieważ może nie być możliwe ich haszowanie według wartości. Zamiast tego narazić .ref() metody, które można użyć, aby uzyskać hashable odniesienie do tensora lub zmiennej.

Aby wyizolować wpływ tej zmiany zachowania, można użyć tf.compat.v1.disable_tensor_equality() i tf.compat.v1.enable_tensor_equality() globalnie wyłączyć lub włączyć tę zmianę zachowania.

Na przykład, w TF1.x dwie zmienne o tej samej wartości zwróci false podczas korzystania z == operatora:

tf.compat.v1.disable_tensor_equality()
x = tf.Variable(0.0)
y = tf.Variable(0.0)

x == y
False

Podczas gdy w TF2 z włączonym tensor sprawdza równość, x == y powróci True .

tf.compat.v1.enable_tensor_equality()
x = tf.Variable(0.0)
y = tf.Variable(0.0)

x == y
<tf.Tensor: shape=(), dtype=bool, numpy=True>

Tak więc, w TF2, jeśli trzeba porównać ze względu na cel odnieść pewnością stosowanie is i is not

tf.compat.v1.enable_tensor_equality()
x = tf.Variable(0.0)
y = tf.Variable(0.0)

x is y
False

Tensory i zmienne haszujące

Z zachowaniami TF1.x użyłeś aby móc bezpośrednio dodać zmienne i tensorów do struktur danych, które wymagają hashowania, takich jak set i dict kluczy.

tf.compat.v1.disable_tensor_equality()
x = tf.Variable(0.0)
set([x, tf.constant(2.0)])
{<tf.Variable 'Variable:0' shape=() dtype=float32, numpy=0.0>,
 <tf.Tensor: shape=(), dtype=float32, numpy=2.0>}

Jednak w TF2 z włączonym tensor równość, tensory i zmienne są unhashable powodu == i != Semantyka operatora zmieniające kontroli równości wartości.

tf.compat.v1.enable_tensor_equality()
x = tf.Variable(0.0)

try:
  set([x, tf.constant(2.0)])
except TypeError as e:
  # TypeError: Variable is unhashable. Instead, use tensor.ref() as the key.
  print(e)
Variable is unhashable. Instead, use tensor.ref() as the key.

Tak więc, w TF2 jeśli trzeba użyć tensora lub zmienne przedmiotów jak klucze lub set treści, można użyć tensor.ref() , aby uzyskać hashable odniesienia, który może być używany jako klucz:

tf.compat.v1.enable_tensor_equality()
x = tf.Variable(0.0)

tensor_set = set([x.ref(), tf.constant(2.0).ref()])
assert x.ref() in tensor_set

tensor_set
{<Reference wrapping <tf.Variable 'Variable:0' shape=() dtype=float32, numpy=0.0>>,
 <Reference wrapping <tf.Tensor: shape=(), dtype=float32, numpy=2.0>>}

W razie potrzeby, można również uzyskać tensor lub zmienną z odniesieniem za pomocą reference.deref() :

referenced_var = x.ref().deref()
assert referenced_var is x
referenced_var
<tf.Variable 'Variable:0' shape=() dtype=float32, numpy=0.0>

Zasoby i dalsze czytanie