Merken Sie den Termin vor! Google I / O kehrt vom 18. bis 20. Mai zurück Registrieren Sie sich jetzt
Diese Seite wurde von der Cloud Translation API übersetzt.
Switch to English

Effektiver TensorFlow 2

In TensorFlow 2.0 wurden mehrere Änderungen vorgenommen, um die Produktivität von TensorFlow-Benutzern zu steigern. TensorFlow 2.0 entfernt redundante APIs , macht APIs konsistenter ( Unified RNNs , Unified Optimizers ) und lässt sich mit Eager-Ausführung besser in die Python-Laufzeit integrieren.

Viele RFCs haben die Änderungen erklärt, die bei der Herstellung von TensorFlow 2.0 vorgenommen wurden. Dieser Leitfaden enthält eine Vision, wie die Entwicklung in TensorFlow 2.0 aussehen sollte. Es wird davon ausgegangen, dass Sie mit TensorFlow 1.x vertraut sind.

Eine kurze Zusammenfassung der wichtigsten Änderungen

API-Bereinigung

Viele APIs sind in TF 2.0 entweder weg oder verschoben . Zu den wichtigsten Änderungen gehören das Entfernen von tf.app , tf.flags und tf.logging zugunsten der jetzt Open-Source- Absl-py , das Wiederherstellen von Projekten, die in tf.contrib lebten, und das Bereinigen des Haupt- tf.* -Namensraums von Verschieben weniger verwendeter Funktionen in Unterpakete wie tf.math . Einige APIs wurden durch ihre 2.0-Entsprechungen ersetzt - tf.summary , tf.keras.metrics und tf.keras.optimizers . Der einfachste Weg, diese Umbenennungen automatisch anzuwenden, ist die Verwendung des v2-Upgrade-Skripts .

Eifrige Ausführung

Bei TensorFlow 1.X müssen Benutzer einen abstrakten Syntaxbaum (das Diagramm) manuell zusammenfügen, indem sie tf.* API-Aufrufe ausführen. Anschließend müssen Benutzer den abstrakten Syntaxbaum manuell kompilieren, indem sie eine Reihe von Ausgabe- und Eingabe-Tensoren an einen session.run() . TensorFlow 2.0 wird eifrig ausgeführt (wie es Python normalerweise tut) und in 2.0 sollten sich Diagramme und Sitzungen wie Implementierungsdetails anfühlen.

Ein bemerkenswertes Nebenprodukt der eifrigen Ausführung ist, dass tf.control_dependencies() nicht mehr erforderlich ist, da alle Codezeilen der Reihe nach ausgeführt werden (innerhalb einer tf.function wird Code mit Nebenwirkungen in der angegebenen Reihenfolge ausgeführt).

Keine Globalen mehr

TensorFlow 1.X stützte sich stark auf implizit globale Namespaces. Wenn Sie tf.Variable() , wird es in das Standarddiagramm tf.Variable() und bleibt dort, auch wenn Sie den Überblick über die darauf verweisende Python-Variable verloren haben. Sie könnten diese tf.Variable dann tf.Variable , aber nur, wenn Sie den Namen kennen, mit dem sie erstellt wurde. Dies war schwierig, wenn Sie nicht die Kontrolle über die Erstellung der Variablen hatten. Infolgedessen vermehrten sich alle Arten von Mechanismen, um Benutzern zu helfen, ihre Variablen wieder zu finden, und Frameworks, um vom Benutzer erstellte Variablen zu finden: Variablenbereiche, globale Sammlungen, tf.get_global_step() wie tf.get_global_step() , tf.global_variables_initializer() , Optimierer berechnen implizit Gradienten über alle trainierbaren Variablen und so weiter. TensorFlow 2.0 eliminiert alle diese Mechanismen ( Variablen 2.0 RFC ) zugunsten des Standardmechanismus: Behalten Sie Ihre Variablen im Auge! Wenn Sie den Überblick über eine tf.Variable verlieren, wird der Müll gesammelt.

Das Erfordernis, Variablen zu verfolgen, schafft zusätzliche Arbeit für den Benutzer, aber mit Keras-Objekten (siehe unten) wird die Belastung minimiert.

Funktionen, keine Sitzungen

Ein Aufruf von session.run() ähnelt fast einem Funktionsaufruf: Sie geben die Eingaben und die aufzurufende Funktion an und erhalten eine Reihe von Ausgaben zurück. In TensorFlow 2.0 können Sie eine Python-Funktion mit tf.function() dekorieren, um sie für die JIT-Kompilierung zu markieren, sodass TensorFlow sie als einzelnes Diagramm tf.function() ( Functions 2.0 RFC ). Mit diesem Mechanismus kann TensorFlow 2.0 alle Vorteile des Grafikmodus nutzen:

  • Leistung: Die Funktion kann optimiert werden (Knotenbereinigung, Kernelfusion usw.)
  • Portabilität: Die Funktion kann exportiert / erneut importiert werden ( SavedModel 2.0 RFC ), sodass Benutzer modulare TensorFlow-Funktionen wiederverwenden und gemeinsam nutzen können.
# TensorFlow 1.X
outputs = session.run(f(placeholder), feed_dict={placeholder: input})
# TensorFlow 2.0
outputs = f(input)

Mit der Möglichkeit, Python- und TensorFlow-Code frei zu verteilen, können Benutzer die Ausdruckskraft von Python nutzen. Portable TensorFlow wird jedoch in Kontexten ohne Python-Interpreter wie Mobile, C ++ und JavaScript ausgeführt. Damit Benutzer beim Hinzufügen von @tf.function ihren Code neu schreiben @tf.function , konvertiert AutoGraph eine Teilmenge der Python-Konstrukte in ihre TensorFlow-Entsprechungen:

  • for / while -> tf.while_loop ( break und continue werden unterstützt)
  • if -> tf.cond
  • for _ in dataset -> dataset.reduce

AutoGraph unterstützt beliebige Verschachtelungen des Kontrollflusses, wodurch viele komplexe ML-Programme wie Sequenzmodelle, Verstärkungslernen, benutzerdefinierte Trainingsschleifen und mehr performant und präzise implementiert werden können.

Empfehlungen für idiomatischen TensorFlow 2.0

Refaktorieren Sie Ihren Code in kleinere Funktionen

Ein gängiges Verwendungsmuster in TensorFlow 1.X war die Strategie "Küchenspüle", bei der die Vereinigung aller möglichen Berechnungen präventiv festgelegt und anschließend ausgewählte Tensoren über session.run() ausgewertet wurden. In TensorFlow 2.0 sollten Benutzer ihren Code in kleinere Funktionen umgestalten, die nach Bedarf aufgerufen werden. Im Allgemeinen ist es nicht erforderlich, jede dieser kleineren Funktionen mit tf.function zu dekorieren. Verwenden Sie tf.function nur, um tf.function auf hoher Ebene zu dekorieren - zum Beispiel einen Trainingsschritt oder den Vorwärtsdurchlauf Ihres Modells.

Verwenden Sie Keras-Ebenen und -Modelle, um Variablen zu verwalten

Keras-Modelle und -Ebenen bieten die praktischen variables und trainable_variables Eigenschaften, mit denen alle abhängigen Variablen rekursiv erfasst werden. Dies macht es einfach, Variablen lokal dort zu verwalten, wo sie verwendet werden.

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.

mit der Keras-Version:

# 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, ...]

Keras-Layer / -Modelle erben von tf.train.Checkpointable und sind in @tf.function , wodurch es möglich ist, SavedModels direkt von Keras-Objekten zu überprüfen oder zu exportieren. Sie müssen nicht unbedingt die .fit() API von Keras verwenden, um diese Integrationen nutzen zu können.

Hier ist ein Beispiel für ein Transferlernen, das zeigt, wie Keras das Sammeln einer Teilmenge relevanter Variablen vereinfacht. Angenommen, Sie trainieren ein mehrköpfiges Modell mit einem gemeinsam genutzten Kofferraum:

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)

Kombinieren Sie tf.data.Datasets und @ tf.function

Wenn Sie über Trainingsdaten iterieren, die in den Speicher passen, können Sie die reguläre Python-Iteration verwenden. Andernfalls isttf.data.Dataset der beste Weg, um Trainingsdaten von der Festplatte zu streamen. Datensätze sind Iterables (keine Iteratoren) und funktionieren wie andere Python-Iterables im Eager-Modus. Sie können die asynchronen Prefetching- / Streaming-Funktionen für Datasets vollständig nutzen, indem Sie Ihren Code in tf.function() , wodurch die Python-Iteration durch die entsprechenden Diagrammoperationen mit AutoGraph ersetzt wird.

@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))

Wenn Sie die Keras .fit() , müssen Sie sich keine Gedanken über die Iteration von Datasets machen.

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

Nutzen Sie AutoGraph mit Python-Steuerungsfluss

AutoGraph bietet eine Möglichkeit, den datenabhängigen Steuerungsfluss in Entsprechungen im Grafikmodus wie tf.cond und tf.while_loop .

Ein häufiger Ort, an dem datenabhängiger Kontrollfluss auftritt, sind Sequenzmodelle. tf.keras.layers.RNN eine RNN-Zelle, sodass Sie die Wiederholung entweder statisch oder dynamisch abrollen können. Zur Demonstration können Sie das dynamische Abrollen wie folgt neu implementieren:

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

Eine detailliertere Übersicht über die Funktionen von AutoGraph finden Sie in der Anleitung .

tf.metrics aggregiert Daten und tf.summary protokolliert sie

Verwenden tf.summary.(scalar|histogram|...) zum Protokollieren von Zusammenfassungen tf.summary.(scalar|histogram|...) und leiten Sie es mithilfe eines Kontextmanagers an einen Writer weiter. (Wenn Sie den Kontextmanager weglassen, geschieht nichts.) Im Gegensatz zu TF 1.x werden die Zusammenfassungen direkt an den Writer ausgegeben. Es gibt keine separate "Merge" add_summary() und keinen separaten Aufruf von add_summary() bedeutet, dass der step auf der Aufrufseite angegeben werden muss.

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

Verwenden Sie tf.metrics , um Daten zu aggregieren, bevor Sie sie als Zusammenfassungen tf.metrics . Metriken sind statusbehaftet: Sie akkumulieren Werte und geben ein kumulatives Ergebnis zurück, wenn Sie .result() aufrufen. Löschen Sie akkumulierte Werte mit .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)

Visualisieren Sie die generierten Zusammenfassungen, indem Sie TensorBoard auf das Zusammenfassungsprotokollverzeichnis richten:

tensorboard --logdir /tmp/summaries

Verwenden Sie beim Debuggen tf.config.experimental_run_functions_eagerly ()

In TensorFlow 2.0 können Sie mit der Eager-Ausführung den Code Schritt für Schritt ausführen, um Formen, Datentypen und Werte zu überprüfen. Bestimmte APIs, wie tf.function . tf.keras tf.function , tf.keras usw., verwenden die tf.keras für Leistung und Portabilität. Verwenden tf.config.experimental_run_functions_eagerly(True) beim Debuggen tf.config.experimental_run_functions_eagerly(True) , um die Eager-Ausführung in diesem Code zu verwenden.

Beispielsweise:

@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]

Dies funktioniert auch in Keras-Modellen und anderen APIs, die die Eager-Ausführung unterstützen:

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]))