Diese Seite wurde von der Cloud Translation API übersetzt.
Switch to English

Bessere Leistung mit tf.function

Ansicht auf TensorFlow.org In Google Colab ausführen Quelle auf GitHub anzeigen Notizbuch herunterladen

In TensorFlow 2 ist die eifrige Ausführung standardmäßig aktiviert. Die Benutzeroberfläche ist intuitiv und flexibel (einmalige Vorgänge sind viel einfacher und schneller), dies kann jedoch zu Lasten der Leistung und der Bereitstellbarkeit gehen.

Mit tf.function können tf.function aus Ihren Programmen Diagramme tf.function . Es ist ein Transformationstool, das Python-unabhängige Datenflussdiagramme aus Ihrem Python-Code erstellt. Auf diese Weise können Sie leistungsfähige und tragbare Modelle erstellen, und SavedModel .

Diese Anleitung hilft Ihnen bei der Konzeption der Funktionsweise von tf.function unter der Haube, damit Sie sie effektiv nutzen können.

Die wichtigsten Erkenntnisse und Empfehlungen sind:

  • Debuggen Sie im eifrigen Modus und dekorieren Sie dann mit @tf.function .
  • Verlassen Sie sich nicht auf Python-Nebenwirkungen wie Objektmutation oder Listenanhänge.
  • tf.function funktioniert am besten mit TensorFlow-Operationen. NumPy- und Python-Aufrufe werden in Konstanten konvertiert.

Konfiguration

 import tensorflow as tf
 

Definieren Sie eine Hilfsfunktion, um die Arten von Fehlern zu demonstrieren, auf die Sie möglicherweise stoßen:

 import traceback
import contextlib

# Some helper code to demonstrate the kinds of errors you might encounter.
@contextlib.contextmanager
def assert_raises(error_class):
  try:
    yield
  except error_class as e:
    print('Caught expected exception \n  {}:'.format(error_class))
    traceback.print_exc(limit=2)
  except Exception as e:
    raise e
  else:
    raise Exception('Expected {} to be raised but no error was raised!'.format(
        error_class))
 

Grundlagen

Verwendung

Eine von Ihnen definierte Function ähnelt einer TensorFlow-Kernoperation: Sie können sie eifrig ausführen. Sie können Farbverläufe berechnen. und so weiter.

 @tf.function
def add(a, b):
  return a + b

add(tf.ones([2, 2]), tf.ones([2, 2]))  #  [[2., 2.], [2., 2.]]
 
<tf.Tensor: shape=(2, 2), dtype=float32, numpy=
array([[2., 2.],
       [2., 2.]], dtype=float32)>
 v = tf.Variable(1.0)
with tf.GradientTape() as tape:
  result = add(v, 1.0)
tape.gradient(result, v)
 
<tf.Tensor: shape=(), dtype=float32, numpy=1.0>

Sie können Function in anderen Function .

 @tf.function
def dense_layer(x, w, b):
  return add(tf.matmul(x, w), b)

dense_layer(tf.ones([3, 2]), tf.ones([2, 2]), tf.ones([2]))
 
<tf.Tensor: shape=(3, 2), dtype=float32, numpy=
array([[3., 3.],
       [3., 3.],
       [3., 3.]], dtype=float32)>

Function können schneller sein als eifriger Code, insbesondere für Diagramme mit vielen kleinen Operationen. Bei Diagrammen mit einigen teuren Operationen (z. B. Windungen) ist die Beschleunigung möglicherweise nicht sehr hoch.

 import timeit
conv_layer = tf.keras.layers.Conv2D(100, 3)

@tf.function
def conv_fn(image):
  return conv_layer(image)

image = tf.zeros([1, 200, 200, 100])
# warm up
conv_layer(image); conv_fn(image)
print("Eager conv:", timeit.timeit(lambda: conv_layer(image), number=10))
print("Function conv:", timeit.timeit(lambda: conv_fn(image), number=10))
print("Note how there's not much difference in performance for convolutions")

 
Eager conv: 0.0023194860004878137
Function conv: 0.0036776439992536325
Note how there's not much difference in performance for convolutions

Rückverfolgung

Durch die dynamische Typisierung von Python können Sie Funktionen mit einer Vielzahl von Argumenttypen aufrufen, und Python kann in jedem Szenario etwas anderes ausführen.

Zum Erstellen eines TensorFlow-Diagramms sind jedoch statische d- dtypes und dtypes erforderlich. tf.function diese Lücke, indem eine Python-Funktion tf.function , um ein Function Objekt zu erstellen. Basierend auf den angegebenen Eingaben wählt die Function das entsprechende Diagramm für die angegebenen Eingaben aus und verfolgt die Python-Funktion nach Bedarf. Sobald Sie verstanden haben, warum und wann die Ablaufverfolgung stattfindet, ist es viel einfacher, tf.function effektiv zu nutzen!

Sie können eine Function mit Argumenten unterschiedlichen Typs aufrufen, um dieses polymorphe Verhalten in Aktion zu sehen.

 @tf.function
def double(a):
  print("Tracing with", a)
  return a + a

print(double(tf.constant(1)))
print()
print(double(tf.constant(1.1)))
print()
print(double(tf.constant("a")))
print()

 
Tracing with Tensor("a:0", shape=(), dtype=int32)
tf.Tensor(2, shape=(), dtype=int32)

Tracing with Tensor("a:0", shape=(), dtype=float32)
tf.Tensor(2.2, shape=(), dtype=float32)

Tracing with Tensor("a:0", shape=(), dtype=string)
tf.Tensor(b'aa', shape=(), dtype=string)


Beachten Sie, dass TensorFlow ein zuvor verfolgtes Diagramm wiederverwendet, wenn Sie wiederholt eine Function mit demselben Argumenttyp aufrufen, da das generierte Diagramm identisch wäre.

 # This doesn't print 'Tracing with ...'
print(double(tf.constant("b")))
 
tf.Tensor(b'bb', shape=(), dtype=string)

(Die folgende Änderung ist in TensorFlow jede Nacht verfügbar und wird in TensorFlow 2.3 verfügbar sein.)

Sie können pretty_printed_concrete_signatures() , um alle verfügbaren Traces pretty_printed_concrete_signatures() :

 print(double.pretty_printed_concrete_signatures())
 
double(a)
  Args:
    a: string Tensor, shape=()
  Returns:
    string Tensor, shape=()

double(a)
  Args:
    a: int32 Tensor, shape=()
  Returns:
    int32 Tensor, shape=()

double(a)
  Args:
    a: float32 Tensor, shape=()
  Returns:
    float32 Tensor, shape=()

Bisher haben Sie gesehen, dass tf.function eine zwischengespeicherte, dynamische Versandebene über der Graph-Tracing-Logik von TensorFlow erstellt. Genauer gesagt:

  • Ein tf.Graph ist die rohe, tf.Graph , tragbare Darstellung Ihrer Berechnung.
  • Eine ConcreteFunction ist ein eifrig ausführender Wrapper um einen tf.Graph .
  • Eine Function verwaltet einen Cache mit ConcreteFunction s und wählt den richtigen für Ihre Eingaben aus.
  • tf.function eine Python-Funktion und gibt ein Function Objekt zurück.

Konkrete Funktionen erhalten

Jedes Mal, wenn eine Funktion verfolgt wird, wird eine neue konkrete Funktion erstellt. Mit get_concrete_function können Sie direkt eine konkrete Funktion get_concrete_function .

 print("Obtaining concrete trace")
double_strings = double.get_concrete_function(tf.constant("a"))
print("Executing traced function")
print(double_strings(tf.constant("a")))
print(double_strings(a=tf.constant("b")))

 
Obtaining concrete trace
Executing traced function
tf.Tensor(b'aa', shape=(), dtype=string)
tf.Tensor(b'bb', shape=(), dtype=string)

 # You can also call get_concrete_function on an InputSpec
double_strings_from_inputspec = double.get_concrete_function(tf.TensorSpec(shape=[], dtype=tf.string))
print(double_strings_from_inputspec(tf.constant("c")))
 
Tracing with Tensor("a:0", shape=(), dtype=string)
tf.Tensor(b'cc', shape=(), dtype=string)

(Die folgende Änderung ist in TensorFlow jede Nacht verfügbar und wird in TensorFlow 2.3 verfügbar sein.)

Beim Drucken einer ConcreteFunction wird eine Zusammenfassung ihrer Eingabeargumente (mit Typen) und ihres Ausgabetyps angezeigt.

 print(double_strings)
 
ConcreteFunction double(a)
  Args:
    a: string Tensor, shape=()
  Returns:
    string Tensor, shape=()

Sie können die Signatur einer konkreten Funktion auch direkt abrufen.

 print(double_strings.structured_input_signature)
print(double_strings.structured_outputs)
 
((TensorSpec(shape=(), dtype=tf.string, name='a'),), {})
Tensor("Identity:0", shape=(), dtype=string)

Die Verwendung einer konkreten Ablaufverfolgung mit inkompatiblen Typen führt zu einem Fehler

 with assert_raises(tf.errors.InvalidArgumentError):
  double_strings(tf.constant(1))
 
Caught expected exception 
  <class 'tensorflow.python.framework.errors_impl.InvalidArgumentError'>:

Traceback (most recent call last):
  File "<ipython-input-3-73d0ca52e838>", line 8, in assert_raises
    yield
  File "<ipython-input-15-e4e2860a4364>", line 2, in <module>
    double_strings(tf.constant(1))
tensorflow.python.framework.errors_impl.InvalidArgumentError: cannot compute __inference_double_168 as input #0(zero-based) was expected to be a string tensor but is a int32 tensor [Op:__inference_double_168]

Möglicherweise stellen Sie fest, dass Python-Argumente in der Eingabesignatur einer konkreten Funktion eine Sonderbehandlung erhalten. Vor TensorFlow 2.3 wurden Python-Argumente einfach aus der Signatur der konkreten Funktion entfernt. Ab TensorFlow 2.3 verbleiben Python-Argumente in der Signatur, müssen jedoch den während der Ablaufverfolgung festgelegten Wert annehmen.

 @tf.function
def pow(a, b):
  return a ** b

square = pow.get_concrete_function(a=tf.TensorSpec(None, tf.float32), b=2)
print(square)
 
ConcreteFunction pow(a, b=2)
  Args:
    a: float32 Tensor, shape=<unknown>
  Returns:
    float32 Tensor, shape=<unknown>

 assert square(tf.constant(10.0)) == 100

with assert_raises(TypeError):
  square(tf.constant(10.0), b=3)
 
Caught expected exception 
  <class 'TypeError'>:

Traceback (most recent call last):
  File "/tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/eager/function.py", line 1669, in _call_impl
    cancellation_manager)
  File "/tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/eager/function.py", line 1714, in _call_with_flat_signature
    self._flat_signature_summary(), ", ".join(sorted(kwargs))))
TypeError: pow(a) got unexpected keyword arguments: b.

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<ipython-input-3-73d0ca52e838>", line 8, in assert_raises
    yield
  File "<ipython-input-17-d163f3d206cb>", line 4, in <module>
    square(tf.constant(10.0), b=3)
TypeError: ConcreteFunction pow(a, b) was constructed with int value 2 in b, but was called with int value 3

Diagramme erhalten

Jede konkrete Funktion ist ein aufrufbarer Wrapper um einen tf.Graph . Obwohl das Abrufen des eigentlichen tf.Graph Objekts normalerweise nicht erforderlich ist, können Sie es problemlos von jeder konkreten Funktion tf.Graph .

 graph = double_strings.graph
for node in graph.as_graph_def().node:
  print(f'{node.input} -> {node.name}')

 
[] -> a
['a', 'a'] -> add
['add'] -> Identity

Debuggen

Im Allgemeinen ist das Debuggen von Code im eifrigen Modus einfacher als in tf.function . Sie sollten sicherstellen, dass Ihr Code im Eifrig-Modus fehlerfrei ausgeführt wird, bevor Sie mit tf.function . Um den Debugging-Prozess zu unterstützen, können Sie tf.config.run_functions_eagerly(True) aufrufen, um tf.config.run_functions_eagerly(True) global zu deaktivieren und wieder zu tf.function .

Wenn Sie Probleme aufspüren, die nur innerhalb von tf.function , finden Sie hier einige Tipps:

  • Einfache alte Python- print werden nur während der Ablaufverfolgung ausgeführt, sodass Sie feststellen können, wann Ihre Funktion (erneut) verfolgt wird.
  • tf.print Aufrufe werden jedes Mal ausgeführt und können Ihnen helfen, Zwischenwerte während der Ausführung aufzuspüren.
  • tf.debugging.enable_check_numerics einfache Weise feststellen, wo NaNs und Inf erstellt werden.
  • pdb kann Ihnen helfen zu verstehen, was während der Ablaufverfolgung vor sich geht. (Vorsichtsmaßnahme: PDB versetzt Sie in AutoGraph-transformierten Quellcode.)

Semantik verfolgen

Cache-Schlüsselregeln

Eine Function bestimmt, ob eine verfolgte konkrete Funktion wiederverwendet werden soll, indem ein Cache-Schlüssel aus den Argumenten und Warnungen einer Eingabe berechnet wird.

  • Der für ein tf.Tensor Argument generierte Schlüssel ist seine Form und sein Typ.
  • Ab TensorFlow 2.3 wird für ein tf.Variable Argument der Schlüssel id() generiert.
  • Der für ein Python-Grundelement generierte Schlüssel ist sein Wert. Der Schlüssel, der für verschachtelte dict , list , tuple , namedtuple tuple und attr ist das abgeflachte Tupel. (Infolge dieser Abflachung führt das Aufrufen einer konkreten Funktion mit einer anderen Verschachtelungsstruktur als der während der Ablaufverfolgung verwendeten zu einem TypeError.)
  • Bei allen anderen Python-Typen basieren die Schlüssel auf der Objekt- id() sodass Methoden für jede Instanz einer Klasse unabhängig voneinander verfolgt werden.

Rückverfolgung steuern

Durch das Retracing wird sichergestellt, dass TensorFlow für jeden Satz von Eingaben korrekte Diagramme generiert. Die Rückverfolgung ist jedoch eine teure Operation! Wenn Ihre Function bei jedem Aufruf ein neues Diagramm tf.function , werden Sie feststellen, dass Ihr Code langsamer ausgeführt wird, als wenn Sie tf.function nicht verwendet tf.function .

Um das Ablaufverfolgungsverhalten zu steuern, können Sie die folgenden Techniken verwenden:

 @tf.function(input_signature=(tf.TensorSpec(shape=[None], dtype=tf.int32),))
def next_collatz(x):
  print("Tracing with", x)
  return tf.where(x % 2 == 0, x // 2, 3 * x + 1)

print(next_collatz(tf.constant([1, 2])))
# We specified a 1-D tensor in the input signature, so this should fail.
with assert_raises(ValueError):
  next_collatz(tf.constant([[1, 2], [3, 4]]))

# We specified an int32 dtype in the input signature, so this should fail.
with assert_raises(ValueError):
  next_collatz(tf.constant([1.0, 2.0]))

 
Tracing with Tensor("x:0", shape=(None,), dtype=int32)
tf.Tensor([4 1], shape=(2,), dtype=int32)
Caught expected exception 
  <class 'ValueError'>:
Caught expected exception 
  <class 'ValueError'>:

Traceback (most recent call last):
  File "<ipython-input-3-73d0ca52e838>", line 8, in assert_raises
    yield
  File "<ipython-input-19-20f544b8adbf>", line 9, in <module>
    next_collatz(tf.constant([[1, 2], [3, 4]]))
ValueError: Python inputs incompatible with input_signature:
  inputs: (
    tf.Tensor(
[[1 2]
 [3 4]], shape=(2, 2), dtype=int32))
  input_signature: (
    TensorSpec(shape=(None,), dtype=tf.int32, name=None))
Traceback (most recent call last):
  File "<ipython-input-3-73d0ca52e838>", line 8, in assert_raises
    yield
  File "<ipython-input-19-20f544b8adbf>", line 13, in <module>
    next_collatz(tf.constant([1.0, 2.0]))
ValueError: Python inputs incompatible with input_signature:
  inputs: (
    tf.Tensor([1. 2.], shape=(2,), dtype=float32))
  input_signature: (
    TensorSpec(shape=(None,), dtype=tf.int32, name=None))

  • tf.TensorSpec in tf.TensorSpec eine Dimension [None] an, um Flexibilität bei der Wiederverwendung von tf.TensorSpec zu ermöglichen.

    Da TensorFlow Spiele Tensoren auf ihre Form basiert, eine Verwendung None ermöglicht Dimension als Wildcard - Function s Wiederverwendung Spuren für variabler Größe Eingang. Eingaben mit variabler Größe können auftreten, wenn Sie Sequenzen unterschiedlicher Länge oder Bilder unterschiedlicher Größe für jeden Stapel haben (siehe z. B. Tutorials zu Transformer und Deep Dream ).

 @tf.function(input_signature=(tf.TensorSpec(shape=[None], dtype=tf.int32),))
def g(x):
  print('Tracing with', x)
  return x

# No retrace!
print(g(tf.constant([1, 2, 3])))
print(g(tf.constant([1, 2, 3, 4, 5])))

 
Tracing with Tensor("x:0", shape=(None,), dtype=int32)
tf.Tensor([1 2 3], shape=(3,), dtype=int32)
tf.Tensor([1 2 3 4 5], shape=(5,), dtype=int32)

  • Übertragen Sie Python-Argumente auf Tensoren, um das Retracing zu reduzieren.

    Oft werden Python-Argumente verwendet, um Hyperparameter und Diagrammkonstruktionen zu steuern - zum Beispiel num_layers=10 oder training=True oder nonlinearity='relu' . Wenn sich das Python-Argument ändert, ist es sinnvoll, das Diagramm zurückzuverfolgen.

    Es ist jedoch möglich, dass ein Python-Argument nicht zur Steuerung der Diagrammkonstruktion verwendet wird. In diesen Fällen kann eine Änderung des Python-Werts ein unnötiges Retracing auslösen. Nehmen Sie zum Beispiel diese Trainingsschleife, die AutoGraph dynamisch abrollt. Trotz der mehreren Spuren ist das generierte Diagramm tatsächlich identisch, sodass ein erneutes Verfolgen nicht erforderlich ist.

 def train_one_step():
  pass

@tf.function
def train(num_steps):
  print("Tracing with num_steps = ", num_steps)
  tf.print("Executing with num_steps = ", num_steps)
  for _ in tf.range(num_steps):
    train_one_step()

print("Retracing occurs for different Python arguments.")
train(num_steps=10)
train(num_steps=20)

print()
print("Traces are reused for Tensor arguments.")
train(num_steps=tf.constant(10))
train(num_steps=tf.constant(20))
 
Retracing occurs for different Python arguments.
Tracing with num_steps =  10
Executing with num_steps =  10
Tracing with num_steps =  20
Executing with num_steps =  20

Traces are reused for Tensor arguments.
Tracing with num_steps =  Tensor("num_steps:0", shape=(), dtype=int32)
Executing with num_steps =  10
Executing with num_steps =  20

Wenn Sie das Zurückverfolgen erzwingen müssen, erstellen Sie eine neue Function . Separate Function teilen garantiert keine Spuren.

 def f():
  print('Tracing!')
  tf.print('Executing')

tf.function(f)()
tf.function(f)()
 
Tracing!
Executing
Tracing!
Executing

Python Nebenwirkungen

Python-Nebenwirkungen wie Drucken, Anhängen an Listen und Mutieren von Globals treten nur auf, wenn Sie zum ersten Mal eine Function mit einer Reihe von Eingaben aufrufen. Anschließend wird der verfolgte tf.Graph ausgeführt, ohne den Python-Code auszuführen.

Die allgemeine Faustregel lautet, nur Python-Nebenwirkungen zum Debuggen Ihrer Traces zu verwenden. Andernfalls sind TensorFlow- tf.Variable.assign wie tf.Variable.assign , tf.print und tf.summary der beste Weg, um sicherzustellen, dass Ihr Code bei jedem Aufruf von der TensorFlow-Laufzeit verfolgt und ausgeführt wird.

 @tf.function
def f(x):
  print("Traced with", x)
  tf.print("Executed with", x)

f(1)
f(1)
f(2)

 
Traced with 1
Executed with 1
Executed with 1
Traced with 2
Executed with 2

Viele Python-Funktionen, wie z. B. Generatoren und Iteratoren, basieren auf der Python-Laufzeit, um den Status zu verfolgen. Während diese Konstrukte im eifrigen Modus wie erwartet Function können im Allgemeinen viele unerwartete Dinge innerhalb einer Function passieren.

Zum Beispiel ist das Vorrücken des Iteratorstatus ein Python-Nebeneffekt und tritt daher nur während der Ablaufverfolgung auf.

 external_var = tf.Variable(0)
@tf.function
def buggy_consume_next(iterator):
  external_var.assign_add(next(iterator))
  tf.print("Value of external_var:", external_var)

iterator = iter([0, 1, 2, 3])
buggy_consume_next(iterator)
# This reuses the first value from the iterator, rather than consuming the next value.
buggy_consume_next(iterator)
buggy_consume_next(iterator)

 
Value of external_var: 0
Value of external_var: 0
Value of external_var: 0

Einige Iterationskonstrukte werden von AutoGraph unterstützt. Eine Übersicht finden Sie im Abschnitt über AutoGraph-Transformationen .

Wenn Sie Python-Code bei jedem Aufruf einer Function tf.py_function ist tf.py_function eine Exit-Luke. Der Nachteil von tf.py_function ist, dass es weder portabel oder besonders leistungsfähig ist noch in verteilten Setups (Multi-GPU, TPU) gut funktioniert. Da tf.py_function in den Graphen tf.py_function muss, werden alle Ein- / Ausgänge in Tensoren umgewandelt.

APIs wie tf.gather , tf.stack und tf.TensorArray können Ihnen dabei helfen, allgemeine Schleifenmuster in nativem TensorFlow zu implementieren.

 external_list = []

def side_effect(x):
  print('Python side effect')
  external_list.append(x)

@tf.function
def f(x):
  tf.py_function(side_effect, inp=[x], Tout=[])

f(1)
f(1)
f(1)
# The list append happens all three times!
assert len(external_list) == 3
# The list contains tf.constant(1), not 1, because py_function casts everything to tensors.
assert external_list[0].numpy() == 1

 
Python side effect
Python side effect
Python side effect

Variablen

Beim Erstellen einer neuen tf.Variable in einer Funktion kann ein Fehler auftreten. Dieser Fehler schützt vor Verhaltensabweichungen bei wiederholten Aufrufen: Im Eifersuchtsmodus erstellt eine Funktion bei jedem Aufruf eine neue Variable. In einer Function wird jedoch möglicherweise aufgrund der Wiederverwendung von Ablaufverfolgungen keine neue Variable erstellt.

 @tf.function
def f(x):
  v = tf.Variable(1.0)
  v.assign_add(x)
  return v

with assert_raises(ValueError):
  f(1.0)
 
Caught expected exception 
  <class 'ValueError'>:

Traceback (most recent call last):
  File "<ipython-input-3-73d0ca52e838>", line 8, in assert_raises
    yield
  File "<ipython-input-26-73e410646579>", line 8, in <module>
    f(1.0)
ValueError: in user code:

    <ipython-input-26-73e410646579>:3 f  *
        v = tf.Variable(1.0)
    /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/ops/variables.py:262 __call__  **
        return cls._variable_v2_call(*args, **kwargs)
    /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/ops/variables.py:256 _variable_v2_call
        shape=shape)
    /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/ops/variables.py:67 getter
        return captured_getter(captured_previous, **kwargs)
    /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/eager/def_function.py:702 invalid_creator_scope
        "tf.function-decorated function tried to create "

    ValueError: tf.function-decorated function tried to create variables on non-first call.


Sie können Variablen innerhalb einer Function erstellen, solange diese Variablen nur beim ersten Ausführen der Funktion erstellt werden.

 class Count(tf.Module):
  def __init__(self):
    self.count = None
  
  @tf.function
  def __call__(self):
    if self.count is None:
      self.count = tf.Variable(0)
    return self.count.assign_add(1)

c = Count()
print(c())
print(c())
 
tf.Tensor(1, shape=(), dtype=int32)
tf.Tensor(2, shape=(), dtype=int32)

Ein weiterer Fehler, auf den Sie möglicherweise stoßen, ist eine durch Müll gesammelte Variable. Im Gegensatz zu normalen Python-Funktionen behalten konkrete Funktionen WeakRefs nur für die Variablen bei, über die sie schließen. Daher müssen Sie einen Verweis auf alle Variablen beibehalten.

 external_var = tf.Variable(3)
@tf.function
def f(x):
  return x * external_var

traced_f = f.get_concrete_function(4)
print("Calling concrete function...")
print(traced_f(4))

del external_var
print()
print("Calling concrete function after garbage collecting its closed Variable...")
with assert_raises(tf.errors.FailedPreconditionError):
  traced_f(4)
 
Calling concrete function...
tf.Tensor(12, shape=(), dtype=int32)

Calling concrete function after garbage collecting its closed Variable...
Caught expected exception 
  <class 'tensorflow.python.framework.errors_impl.FailedPreconditionError'>:

Traceback (most recent call last):
  File "<ipython-input-3-73d0ca52e838>", line 8, in assert_raises
    yield
  File "<ipython-input-28-304a18524b57>", line 14, in <module>
    traced_f(4)
tensorflow.python.framework.errors_impl.FailedPreconditionError: 2 root error(s) found.
  (0) Failed precondition:  Error while reading resource variable _AnonymousVar4 from Container: localhost. This could mean that the variable was uninitialized. Not found: Resource localhost/_AnonymousVar4/N10tensorflow3VarE does not exist.
     [[node ReadVariableOp (defined at <ipython-input-28-304a18524b57>:4) ]]
     [[ReadVariableOp/_2]]
  (1) Failed precondition:  Error while reading resource variable _AnonymousVar4 from Container: localhost. This could mean that the variable was uninitialized. Not found: Resource localhost/_AnonymousVar4/N10tensorflow3VarE does not exist.
     [[node ReadVariableOp (defined at <ipython-input-28-304a18524b57>:4) ]]
0 successful operations.
0 derived errors ignored. [Op:__inference_f_514]

Function call stack:
f -> f


AutoGraph-Transformationen

AutoGraph ist eine Bibliothek, die standardmäßig in tf.function ist und eine Teilmenge von Python-Eifrig-Code in tf.function TensorFlow- tf.function umwandelt. Dies schließt den Kontrollfluss ein, wie if , for , while .

TensorFlow- tf.cond wie tf.cond und tf.while_loop weiterhin, aber der Kontrollfluss ist in Python oft einfacher zu schreiben und zu verstehen.

 # Simple loop

@tf.function
def f(x):
  while tf.reduce_sum(x) > 1:
    tf.print(x)
    x = tf.tanh(x)
  return x

f(tf.random.uniform([5]))
 
[0.448926926 0.896036148 0.703306437 0.446930766 0.20440042]
[0.421016544 0.714362323 0.6064623 0.419372857 0.201600626]
[0.397786468 0.613405049 0.541632056 0.396401972 0.198913112]
[0.378053397 0.546519518 0.494222373 0.376866162 0.196330562]
[0.361015767 0.497907132 0.457561225 0.359982818 0.1938463]
[0.346108437 0.460469633 0.428094476 0.3451989 0.191454232]
[0.332919776 0.43046692 0.403727621 0.332110822 0.189148799]
[0.321141869 0.405711472 0.383133948 0.320416152 0.18692489]
[0.310539037 0.384825289 0.365426034 0.309883147 0.184777796]
[0.300927401 0.366890609 0.349984437 0.300330788 0.182703182]
[0.292161077 0.351268977 0.336361736 0.291615278 0.180697069]
[0.284122646 0.337500453 0.324225426 0.283620834 0.178755745]
[0.276716352 0.325244069 0.313322544 0.276252925 0.176875815]
[0.269863278 0.314240903 0.303456694 0.269433528 0.175054088]
[0.263497591 0.304290265 0.294472754 0.263097644 0.17328763]
[0.257564 0.295233846 0.2862463 0.257190555 0.171573699]
[0.25201565 0.286944896 0.278676242 0.25166589 0.169909731]
[0.246812463 0.279320478 0.271679461 0.246483982 0.168293342]
[0.24192 0.272276044 0.265186876 0.241610721 0.166722313]
[0.237308443 0.265741408 0.259140551 0.237016559 0.165194541]
[0.23295185 0.25965777 0.253491491 0.232675791 0.163708091]
[0.228827521 0.253975391 0.248197898 0.228565902 0.162261128]
[0.224915475 0.248651937 0.243223906 0.224667087 0.160851941]
[0.221198082 0.243651047 0.238538548 0.220961839 0.159478888]
[0.217659682 0.238941342 0.23411487 0.217434615 0.158140466]
[0.214286327 0.23449555 0.229929343 0.214071587 0.156835243]
[0.211065561 0.230289876 0.225961298 0.210860386 0.155561864]
[0.207986191 0.226303399 0.222192511 0.207789883 0.154319063]
[0.20503816 0.222517684 0.2186068 0.204850093 0.153105617]

<tf.Tensor: shape=(5,), dtype=float32, numpy=
array([0.20221236, 0.2189164 , 0.21518978, 0.20203198, 0.15192041],
      dtype=float32)>

Wenn Sie neugierig sind, können Sie den Code überprüfen, den das Autogramm generiert.

 print(tf.autograph.to_code(f.python_function))
 
def tf__f(x):
    with ag__.FunctionScope('f', '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 (x,)

        def set_state(vars_):
            nonlocal x
            (x,) = vars_

        def loop_body():
            nonlocal x
            ag__.converted_call(ag__.ld(tf).print, (ag__.ld(x),), None, fscope)
            x = ag__.converted_call(ag__.ld(tf).tanh, (ag__.ld(x),), None, fscope)

        def loop_test():
            return (ag__.converted_call(ag__.ld(tf).reduce_sum, (ag__.ld(x),), None, fscope) > 1)
        ag__.while_stmt(loop_test, loop_body, get_state, set_state, ('x',), {})
        try:
            do_return = True
            retval_ = ag__.ld(x)
        except:
            do_return = False
            raise
        return fscope.ret(retval_, do_return)


Bedingungen

AutoGraph konvertiert einige if <condition> tf.cond in die entsprechenden tf.cond Aufrufe. Diese Ersetzung erfolgt, wenn <condition> ein Tensor ist. Andernfalls wird die if Anweisung als Python-Bedingung ausgeführt.

Eine Python-Bedingung wird während der Ablaufverfolgung ausgeführt, sodass genau ein Zweig der Bedingung zum Diagramm hinzugefügt wird. Ohne AutoGraph könnte dieses verfolgte Diagramm den alternativen Zweig nicht übernehmen, wenn ein datenabhängiger Kontrollfluss besteht.

tf.cond verfolgt und fügt beide Zweige der Bedingung zum Diagramm hinzu und wählt zur Ausführungszeit dynamisch einen Zweig aus. Die Rückverfolgung kann unbeabsichtigte Nebenwirkungen haben. Weitere Informationen finden Sie unter AutoGraph-Ablaufverfolgungseffekte .

 @tf.function
def fizzbuzz(n):
  for i in tf.range(1, n + 1):
    print('Tracing for loop')
    if i % 15 == 0:
      print('Tracing fizzbuzz branch')
      tf.print('fizzbuzz')
    elif i % 3 == 0:
      print('Tracing fizz branch')
      tf.print('fizz')
    elif i % 5 == 0:
      print('Tracing buzz branch')
      tf.print('buzz')
    else:
      print('Tracing default branch')
      tf.print(i)

fizzbuzz(tf.constant(5))
fizzbuzz(tf.constant(20))
 
Tracing for loop
Tracing fizzbuzz branch
Tracing fizz branch
Tracing buzz branch
Tracing default branch
1
2
fizz
4
buzz
1
2
fizz
4
buzz
fizz
7
8
fizz
buzz
11
fizz
13
14
fizzbuzz
16
17
fizz
19
buzz

Weitere Einschränkungen für AutoGraph-konvertierte if-Anweisungen finden Sie in der Referenzdokumentation .

Schleifen

Autograph wird einige konvertieren for und while Aussagen in die äquivalente TensorFlow ops Looping, wie tf.while_loop . Wenn nicht konvertiert, wird die for oder while Schleife als Python-Schleife ausgeführt.

Diese Ersetzung erfolgt in folgenden Situationen:

  • for x in y : Wenn y ein Tensor ist, konvertiere in tf.while_loop . In dem speziellen Fall, in dem y ein tf.data.Dataset , wird eine Kombination von tf.data.Dataset generiert.
  • while <condition> : Wenn <condition> ein Tensor ist, konvertieren Sie in tf.while_loop .

Eine Python-Schleife wird während der Ablaufverfolgung ausgeführt und fügt dem tf.Graph für jede Iteration der Schleife zusätzliche tf.Graph .

Eine TensorFlow-Schleife verfolgt den Hauptteil der Schleife und wählt dynamisch aus, wie viele Iterationen zur Ausführungszeit ausgeführt werden sollen. Der Schleifenkörper erscheint nur einmal im generierten tf.Graph .

Finden Sie in der Referenzdokumentation für zusätzliche Einschränkungen für Autogramm konvertiert for und while Aussagen.

Durchlaufen von Python-Daten

Eine häufige Gefahr besteht darin, Python / Numpy-Daten innerhalb einer tf.function . Diese Schleife wird während des Ablaufverfolgungsprozesses ausgeführt und fügt dem tf.Graph für jede Iteration der Schleife eine Kopie Ihres Modells tf.Graph .

Wenn Sie die gesamte Trainingsschleife in tf.function , ist es am sichersten, Ihre Daten als tf.data.Dataset damit AutoGraph die Trainingsschleife dynamisch abrollt.

 def measure_graph_size(f, *args):
  g = f.get_concrete_function(*args).graph
  print("{}({}) contains {} nodes in its graph".format(
      f.__name__, ', '.join(map(str, args)), len(g.as_graph_def().node)))

@tf.function
def train(dataset):
  loss = tf.constant(0)
  for x, y in dataset:
    loss += tf.abs(y - x) # Some dummy computation.
  return loss

small_data = [(1, 1)] * 3
big_data = [(1, 1)] * 10
measure_graph_size(train, small_data)
measure_graph_size(train, big_data)

measure_graph_size(train, tf.data.Dataset.from_generator(
    lambda: small_data, (tf.int32, tf.int32)))
measure_graph_size(train, tf.data.Dataset.from_generator(
    lambda: big_data, (tf.int32, tf.int32)))
 
train([(1, 1), (1, 1), (1, 1)]) contains 11 nodes in its graph
train([(1, 1), (1, 1), (1, 1), (1, 1), (1, 1), (1, 1), (1, 1), (1, 1), (1, 1), (1, 1)]) contains 32 nodes in its graph
train(<FlatMapDataset shapes: (<unknown>, <unknown>), types: (tf.int32, tf.int32)>) contains 8 nodes in its graph
train(<FlatMapDataset shapes: (<unknown>, <unknown>), types: (tf.int32, tf.int32)>) contains 8 nodes in its graph

Beachten Sie beim Umschließen von Python / Numpy-Daten in ein Dataset tf.data.Dataset.from_generator Vergleich zu tf.data.Dataset.from_tensors . Ersteres behält die Daten in Python bei und tf.py_function sie über tf.py_function was Auswirkungen auf die Leistung haben kann, während letzteres eine Kopie der Daten als einen großen tf.constant() -Knoten im Diagramm tf.constant() , was Auswirkungen auf den Speicher haben kann.

Lesen von Daten aus Dateien über TFRecordDataset / CsvDataset / etc. ist die effektivste Methode, um Daten zu konsumieren, da TensorFlow selbst das asynchrone Laden und Vorabrufen von Daten verwalten kann, ohne Python einbeziehen zu müssen. Weitere Informationen finden Sie im tf.data-Handbuch .

Akkumulieren von Werten in einer Schleife

Ein übliches Muster besteht darin, Zwischenwerte aus einer Schleife zu akkumulieren. Normalerweise wird dies durch Anhängen an eine Python-Liste oder Hinzufügen von Einträgen zu einem Python-Wörterbuch erreicht. Da es sich jedoch um Python-Nebenwirkungen handelt, funktionieren sie in einer dynamisch abgewickelten Schleife nicht wie erwartet. Verwenden Sie tf.TensorArray , um Ergebnisse aus einer dynamisch abgewickelten Schleife zu sammeln.

 batch_size = 2
seq_len = 3
feature_size = 4

def rnn_step(inp, state):
  return inp + state

@tf.function
def dynamic_rnn(rnn_step, input_data, initial_state):
  # [batch, time, features] -> [time, batch, features]
  input_data = tf.transpose(input_data, [1, 0, 2])
  max_seq_len = input_data.shape[0]

  states = tf.TensorArray(tf.float32, size=max_seq_len)
  state = initial_state
  for i in tf.range(max_seq_len):
    state = rnn_step(input_data[i], state)
    states = states.write(i, state)
  return tf.transpose(states.stack(), [1, 0, 2])
  
dynamic_rnn(rnn_step,
            tf.random.uniform([batch_size, seq_len, feature_size]),
            tf.zeros([batch_size, feature_size]))
 
<tf.Tensor: shape=(2, 3, 4), dtype=float32, numpy=
array([[[0.2486304 , 0.0612042 , 0.69624186, 0.28587592],
        [1.2193475 , 0.2389338 , 1.5216837 , 0.38649392],
        [1.7640524 , 1.1970762 , 2.3265643 , 0.81419575]],

       [[0.36599267, 0.41830885, 0.73540664, 0.63987565],
        [0.48354673, 1.1808103 , 1.7210082 , 0.8333106 ],
        [0.7138835 , 1.2030114 , 1.8544207 , 1.1647347 ]]], dtype=float32)>

Weiterführende Literatur

Informationen zum Exportieren und Laden einer Function finden Sie im SavedModel-Handbuch . Weitere Informationen zu Diagrammoptimierungen, die nach der Ablaufverfolgung durchgeführt werden, finden Sie im Grappler-Handbuch . Informationen zum Optimieren Ihrer Datenpipeline und zum Profilieren Ihres Modells finden Sie im Profiler-Handbuch .