Einführung in Graphen und tf.function

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

Überblick

Dieser Leitfaden geht unter die Oberfläche von TensorFlow und Keras, um zu demonstrieren, wie TensorFlow funktioniert. Wenn Sie stattdessen sofort wollen mit Keras beginnen, überprüfen Sie die aus Sammlung von Keras Führer .

In diesem Handbuch erfahren Sie, wie Sie mit TensorFlow einfache Änderungen an Ihrem Code vornehmen können, um Diagramme zu erhalten, wie Diagramme gespeichert und dargestellt werden und wie Sie damit Ihre Modelle beschleunigen können.

Dies ist eine große Bildübersicht , dass Abdeckungen wie tf.function Sie von eifriger Ausführung Graph Ausführung umschalten kann. Für eine vollständigere Beschreibung der tf.function , gehen Sie auf die tf.function Führung .

Was sind Grafiken?

In den letzten drei Führer, lief Sie TensorFlow eifrig. Dies bedeutet, dass TensorFlow-Operationen von Python Operation für Operation ausgeführt werden und die Ergebnisse an Python zurückgeben.

Während Eifer Execution mehrere einzigartige Vorteile bietet, ermöglicht die Graph-Ausführung die Portabilität außerhalb von Python und bietet tendenziell eine bessere Leistung. Graph - Ausführungseinrichtung , daß tensor Berechnungen werden als TensorFlow Graph ausgeführt wird , manchmal als ein bezeichneten tf.Graph oder einfach ein „Diagramm“ .

Graphs sind Datenstrukturen , die einen Satz enthalten tf.Operation Objekte, die Einheiten der Berechnung darstellen; und tf.Tensor Objekte, die die Dateneinheiten darstellen , die zwischen den Operationen fließen. Sie werden in einem definierten tf.Graph Kontext. Da es sich bei diesen Diagrammen um Datenstrukturen handelt, können sie ohne den ursprünglichen Python-Code gespeichert, ausgeführt und wiederhergestellt werden.

So sieht ein TensorFlow-Diagramm aus, das ein zweischichtiges neuronales Netzwerk darstellt, wenn es in TensorBoard visualisiert wird.

Ein einfaches TensorFlow-Diagramm

Die Vorteile von Grafiken

Mit einer Grafik haben Sie eine große Flexibilität. Sie können Ihr TensorFlow-Diagramm in Umgebungen verwenden, die keinen Python-Interpreter haben, wie z. B. mobile Anwendungen, eingebettete Geräte und Back-End-Server. TensorFlow verwendet Graphen als Format für gespeicherte Modelle , wenn es sie von Python exportiert.

Diagramme lassen sich auch leicht optimieren, sodass der Compiler Transformationen wie folgt durchführen kann:

  • Statisch schließen , um den Wert von Tensoren durch ständigen Knoten in Ihrer Berechnung Faltung ( „konstante Faltung“).
  • Trennen Sie Unterteile einer Berechnung, die unabhängig sind, und teilen Sie sie auf Threads oder Geräte auf.
  • Vereinfachen Sie arithmetische Operationen, indem Sie gängige Unterausdrücke eliminieren.

Es gibt eine ganze Optimierungssystem, Grappler , diese und andere speedups auszuführen.

Kurz gesagt, Grafiken sind äußerst nützlich und lassen Sie Ihren TensorFlow schnell laufen, parallel laufen und effizient auf mehreren Geräten ausgeführt werden .

Sie möchten jedoch Ihre Machine-Learning-Modelle (oder andere Berechnungen) der Einfachheit halber immer noch in Python definieren und dann bei Bedarf automatisch Diagramme erstellen.

Aufstellen

import tensorflow as tf
import timeit
from datetime import datetime

Nutzen von Grafiken

Sie erstellen und eine Grafik in TensorFlow unter Verwendung laufen tf.function , entweder als direkter Anruf oder als Dekorateur. tf.function nimmt eine reguläre Funktion als Eingabe und gibt eine Function . Eine Function ist ein Python aufrufbar , die TensorFlow Graphen aus der Python - Funktion baut. Sie verwenden eine Function in der gleichen Weise wie seine Python - Äquivalent.

# Define a Python function.
def a_regular_function(x, y, b):
  x = tf.matmul(x, y)
  x = x + b
  return x

# `a_function_that_uses_a_graph` is a TensorFlow `Function`.
a_function_that_uses_a_graph = tf.function(a_regular_function)

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

orig_value = a_regular_function(x1, y1, b1).numpy()
# Call a `Function` like a Python function.
tf_function_value = a_function_that_uses_a_graph(x1, y1, b1).numpy()
assert(orig_value == tf_function_value)
2021-08-24 01:23:14.241796: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-24 01:23:14.249686: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-24 01:23:14.250560: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-24 01:23:14.252417: I tensorflow/core/platform/cpu_feature_guard.cc:142] This TensorFlow binary is optimized with oneAPI Deep Neural Network Library (oneDNN) to use the following CPU instructions in performance-critical operations:  AVX2 AVX512F FMA
To enable them in other operations, rebuild TensorFlow with the appropriate compiler flags.
2021-08-24 01:23:14.253003: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-24 01:23:14.254038: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-24 01:23:14.255008: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-24 01:23:14.832899: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-24 01:23:14.833850: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-24 01:23:14.834806: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-24 01:23:14.835645: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1510] Created device /job:localhost/replica:0/task:0/device:GPU:0 with 14648 MB memory:  -> device: 0, name: Tesla V100-SXM2-16GB, pci bus id: 0000:00:05.0, compute capability: 7.0
2021-08-24 01:23:15.557316: I tensorflow/compiler/mlir/mlir_graph_optimization_pass.cc:185] None of the MLIR Optimization Passes are enabled (registered 2)

Auf der Außenseite eine Function schreiben mit sieht aus wie eine normale Funktion , die Sie TensorFlow Operationen. Darunter , es ist jedoch sehr unterschiedlich. Eine Function kapselt mehr tf.Graph s hinter einer API . Das ist , wie Function der Lage ist , Ihnen die geben , Vorteile der Graph Ausführung , wie Geschwindigkeit und Einsetzbarkeit.

tf.function gilt für eine Funktion und alle anderen Funktionen , die es nennt:

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

# Use the decorator to make `outer_function` a `Function`.
@tf.function
def outer_function(x):
  y = tf.constant([[2.0], [3.0]])
  b = tf.constant(4.0)

  return inner_function(x, y, b)

# Note that the callable will create a graph that
# includes `inner_function` as well as `outer_function`.
outer_function(tf.constant([[1.0, 2.0]])).numpy()
array([[12.]], dtype=float32)

Wenn Sie TensorFlow 1.x verwendet haben, werden Sie feststellen , dass zu keinem Zeitpunkt haben Sie einen definieren müssen Placeholder oder tf.Session .

Konvertieren von Python-Funktionen in Graphen

Jede Funktion , die Sie mit TensorFlow schreiben enthält eine Mischung aus eingebaut TF - Operationen und Python - Logik, wie if-then - Klauseln, Schleifen, break , return , continue und mehr. Während TensorFlow Operationen durch eine leicht eingefangen werden tf.Graph , Python spezifischen Logik benötigt einen zusätzlichen Schritt zu werden , um einen Teil des Diagramms zu unterziehen. tf.function verwendet eine Bibliothek namens Autographen ( tf.autograph ) Python Code in graphen Generieren von Code zu konvertieren.

def simple_relu(x):
  if tf.greater(x, 0):
    return x
  else:
    return 0

# `tf_simple_relu` is a TensorFlow `Function` that wraps `simple_relu`.
tf_simple_relu = tf.function(simple_relu)

print("First branch, with graph:", tf_simple_relu(tf.constant(1)).numpy())
print("Second branch, with graph:", tf_simple_relu(tf.constant(-1)).numpy())
First branch, with graph: 1
Second branch, with graph: 0

Obwohl es unwahrscheinlich ist, dass Sie Grafiken direkt anzeigen müssen, können Sie die Ausgaben überprüfen, um die genauen Ergebnisse zu überprüfen. Diese sind nicht leicht zu lesen, also müssen Sie nicht zu genau hinschauen!

# This is the graph-generating output of AutoGraph.
print(tf.autograph.to_code(simple_relu))
def tf__simple_relu(x):
    with ag__.FunctionScope('simple_relu', 'fscope', ag__.ConversionOptions(recursive=True, user_requested=True, optional_features=(), internal_convert_user_code=True)) as fscope:
        do_return = False
        retval_ = ag__.UndefinedReturnValue()

        def get_state():
            return (do_return, retval_)

        def set_state(vars_):
            nonlocal retval_, do_return
            (do_return, retval_) = vars_

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

        def else_body():
            nonlocal retval_, do_return
            try:
                do_return = True
                retval_ = 0
            except:
                do_return = False
                raise
        ag__.if_stmt(ag__.converted_call(ag__.ld(tf).greater, (ag__.ld(x), 0), None, fscope), if_body, else_body, get_state, set_state, ('do_return', 'retval_'), 2)
        return fscope.ret(retval_, do_return)
# This is the graph itself.
print(tf_simple_relu.get_concrete_function(tf.constant(1)).graph.as_graph_def())
node {
  name: "x"
  op: "Placeholder"
  attr {
    key: "_user_specified_name"
    value {
      s: "x"
    }
  }
  attr {
    key: "dtype"
    value {
      type: DT_INT32
    }
  }
  attr {
    key: "shape"
    value {
      shape {
      }
    }
  }
}
node {
  name: "Greater/y"
  op: "Const"
  attr {
    key: "dtype"
    value {
      type: DT_INT32
    }
  }
  attr {
    key: "value"
    value {
      tensor {
        dtype: DT_INT32
        tensor_shape {
        }
        int_val: 0
      }
    }
  }
}
node {
  name: "Greater"
  op: "Greater"
  input: "x"
  input: "Greater/y"
  attr {
    key: "T"
    value {
      type: DT_INT32
    }
  }
}
node {
  name: "cond"
  op: "StatelessIf"
  input: "Greater"
  input: "x"
  attr {
    key: "Tcond"
    value {
      type: DT_BOOL
    }
  }
  attr {
    key: "Tin"
    value {
      list {
        type: DT_INT32
      }
    }
  }
  attr {
    key: "Tout"
    value {
      list {
        type: DT_BOOL
        type: DT_INT32
      }
    }
  }
  attr {
    key: "_lower_using_switch_merge"
    value {
      b: true
    }
  }
  attr {
    key: "_read_only_resource_inputs"
    value {
      list {
      }
    }
  }
  attr {
    key: "else_branch"
    value {
      func {
        name: "cond_false_34"
      }
    }
  }
  attr {
    key: "output_shapes"
    value {
      list {
        shape {
        }
        shape {
        }
      }
    }
  }
  attr {
    key: "then_branch"
    value {
      func {
        name: "cond_true_33"
      }
    }
  }
}
node {
  name: "cond/Identity"
  op: "Identity"
  input: "cond"
  attr {
    key: "T"
    value {
      type: DT_BOOL
    }
  }
}
node {
  name: "cond/Identity_1"
  op: "Identity"
  input: "cond:1"
  attr {
    key: "T"
    value {
      type: DT_INT32
    }
  }
}
node {
  name: "Identity"
  op: "Identity"
  input: "cond/Identity_1"
  attr {
    key: "T"
    value {
      type: DT_INT32
    }
  }
}
library {
  function {
    signature {
      name: "cond_false_34"
      input_arg {
        name: "cond_placeholder"
        type: DT_INT32
      }
      output_arg {
        name: "cond_identity"
        type: DT_BOOL
      }
      output_arg {
        name: "cond_identity_1"
        type: DT_INT32
      }
    }
    node_def {
      name: "cond/Const"
      op: "Const"
      attr {
        key: "dtype"
        value {
          type: DT_BOOL
        }
      }
      attr {
        key: "value"
        value {
          tensor {
            dtype: DT_BOOL
            tensor_shape {
            }
            bool_val: true
          }
        }
      }
      experimental_debug_info {
        original_node_names: "cond/Const"
      }
    }
    node_def {
      name: "cond/Const_1"
      op: "Const"
      attr {
        key: "dtype"
        value {
          type: DT_BOOL
        }
      }
      attr {
        key: "value"
        value {
          tensor {
            dtype: DT_BOOL
            tensor_shape {
            }
            bool_val: true
          }
        }
      }
      experimental_debug_info {
        original_node_names: "cond/Const_1"
      }
    }
    node_def {
      name: "cond/Const_2"
      op: "Const"
      attr {
        key: "dtype"
        value {
          type: DT_INT32
        }
      }
      attr {
        key: "value"
        value {
          tensor {
            dtype: DT_INT32
            tensor_shape {
            }
            int_val: 0
          }
        }
      }
      experimental_debug_info {
        original_node_names: "cond/Const_2"
      }
    }
    node_def {
      name: "cond/Const_3"
      op: "Const"
      attr {
        key: "dtype"
        value {
          type: DT_BOOL
        }
      }
      attr {
        key: "value"
        value {
          tensor {
            dtype: DT_BOOL
            tensor_shape {
            }
            bool_val: true
          }
        }
      }
      experimental_debug_info {
        original_node_names: "cond/Const_3"
      }
    }
    node_def {
      name: "cond/Identity"
      op: "Identity"
      input: "cond/Const_3:output:0"
      attr {
        key: "T"
        value {
          type: DT_BOOL
        }
      }
      experimental_debug_info {
        original_node_names: "cond/Identity"
      }
    }
    node_def {
      name: "cond/Const_4"
      op: "Const"
      attr {
        key: "dtype"
        value {
          type: DT_INT32
        }
      }
      attr {
        key: "value"
        value {
          tensor {
            dtype: DT_INT32
            tensor_shape {
            }
            int_val: 0
          }
        }
      }
      experimental_debug_info {
        original_node_names: "cond/Const_4"
      }
    }
    node_def {
      name: "cond/Identity_1"
      op: "Identity"
      input: "cond/Const_4:output:0"
      attr {
        key: "T"
        value {
          type: DT_INT32
        }
      }
      experimental_debug_info {
        original_node_names: "cond/Identity_1"
      }
    }
    ret {
      key: "cond_identity"
      value: "cond/Identity:output:0"
    }
    ret {
      key: "cond_identity_1"
      value: "cond/Identity_1:output:0"
    }
    attr {
      key: "_construction_context"
      value {
        s: "kEagerRuntime"
      }
    }
    arg_attr {
      key: 0
      value {
        attr {
          key: "_output_shapes"
          value {
            list {
              shape {
              }
            }
          }
        }
      }
    }
  }
  function {
    signature {
      name: "cond_true_33"
      input_arg {
        name: "cond_identity_1_x"
        type: DT_INT32
      }
      output_arg {
        name: "cond_identity"
        type: DT_BOOL
      }
      output_arg {
        name: "cond_identity_1"
        type: DT_INT32
      }
    }
    node_def {
      name: "cond/Const"
      op: "Const"
      attr {
        key: "dtype"
        value {
          type: DT_BOOL
        }
      }
      attr {
        key: "value"
        value {
          tensor {
            dtype: DT_BOOL
            tensor_shape {
            }
            bool_val: true
          }
        }
      }
      experimental_debug_info {
        original_node_names: "cond/Const"
      }
    }
    node_def {
      name: "cond/Identity"
      op: "Identity"
      input: "cond/Const:output:0"
      attr {
        key: "T"
        value {
          type: DT_BOOL
        }
      }
      experimental_debug_info {
        original_node_names: "cond/Identity"
      }
    }
    node_def {
      name: "cond/Identity_1"
      op: "Identity"
      input: "cond_identity_1_x"
      attr {
        key: "T"
        value {
          type: DT_INT32
        }
      }
      experimental_debug_info {
        original_node_names: "cond/Identity_1"
      }
    }
    ret {
      key: "cond_identity"
      value: "cond/Identity:output:0"
    }
    ret {
      key: "cond_identity_1"
      value: "cond/Identity_1:output:0"
    }
    attr {
      key: "_construction_context"
      value {
        s: "kEagerRuntime"
      }
    }
    arg_attr {
      key: 0
      value {
        attr {
          key: "_output_shapes"
          value {
            list {
              shape {
              }
            }
          }
        }
      }
    }
  }
}
versions {
  producer: 808
  min_consumer: 12
}

Die meiste Zeit, tf.function wird ohne besondere Überlegungen arbeiten. Allerdings gibt es einige Einschränkungen, und die tf.function Führung kann hier helfen, sowie die vollständige Autoreferenz

Polymorphismus: eine Function , viele Diagramme

A tf.Graph ist spezialisiert auf eine bestimmte Art der Eingänge (beispielsweise mit einem bestimmten Tensoren dtype oder Objekten mit der gleichen id() ).

Jedes Mal , wenn Sie eine aufrufen Function mit neuen dtypes und Formen in ihren Argumenten, Function erstellt eine neue tf.Graph für die neuen Argumente. Die dtypes und Formen eines tf.Graph ‚s - Eingänge sind als Eingangssignatur bekannt oder nur eine Signatur.

Die Function speichert die tf.Graph entsprechend dieser Signatur in einem ConcreteFunction . Ein ConcreteFunction ist ein Wrapper um einen tf.Graph .

@tf.function
def my_relu(x):
  return tf.maximum(0., x)

# `my_relu` creates new graphs as it observes more signatures.
print(my_relu(tf.constant(5.5)))
print(my_relu([1, -1]))
print(my_relu(tf.constant([3., -3.])))
tf.Tensor(5.5, shape=(), dtype=float32)
tf.Tensor([1. 0.], shape=(2,), dtype=float32)
tf.Tensor([3. 0.], shape=(2,), dtype=float32)

Wenn die Function bereits mit dieser Signatur genannt, Function keinen neuen erstellen tf.Graph .

# These two calls do *not* create new graphs.
print(my_relu(tf.constant(-2.5))) # Signature matches `tf.constant(5.5)`.
print(my_relu(tf.constant([-1., 1.]))) # Signature matches `tf.constant([3., -3.])`.
tf.Tensor(0.0, shape=(), dtype=float32)
tf.Tensor([0. 1.], shape=(2,), dtype=float32)

Weil es durch mehrere Grafiken gesichert ist, eine Function ist polymorph. Das ermöglicht es mehr Eingabetypen zu unterstützen , als ein einzelner tf.Graph darstellen könnte, sowie jede optimieren tf.Graph für eine bessere Leistung.

# There are three `ConcreteFunction`s (one for each graph) in `my_relu`.
# The `ConcreteFunction` also knows the return type and shape!
print(my_relu.pretty_printed_concrete_signatures())
my_relu(x)
  Args:
    x: float32 Tensor, shape=()
  Returns:
    float32 Tensor, shape=()

my_relu(x=[1, -1])
  Returns:
    float32 Tensor, shape=(2,)

my_relu(x)
  Args:
    x: float32 Tensor, shape=(2,)
  Returns:
    float32 Tensor, shape=(2,)

Mit tf.function

Bisher haben Sie gelernt , wie eine Python - Funktion in ein Diagramm umwandeln einfach mit tf.function als Dekorateur oder Wrapper. Aber in der Praxis immer tf.function Arbeit kann richtig heikel sein! In den folgenden Abschnitten erfahren Sie , wie Sie Ihren Code Arbeit mit wie erwartet machen tf.function .

Diagrammausführung vs. eifrige Ausführung

Der Code in einer Function kann sowohl mit Spannung und als Graph ausgeführt werden. In der Standardeinstellung Function führt seinen Code als Graph:

@tf.function
def get_MSE(y_true, y_pred):
  sq_diff = tf.pow(y_true - y_pred, 2)
  return tf.reduce_mean(sq_diff)
y_true = tf.random.uniform([5], maxval=10, dtype=tf.int32)
y_pred = tf.random.uniform([5], maxval=10, dtype=tf.int32)
print(y_true)
print(y_pred)
tf.Tensor([2 6 1 5 4], shape=(5,), dtype=int32)
tf.Tensor([4 0 9 5 1], shape=(5,), dtype=int32)
get_MSE(y_true, y_pred)
<tf.Tensor: shape=(), dtype=int32, numpy=22>

Um sicherzustellen , dass Ihre Function ist Graph , der die gleiche Berechnung wie sein Äquivalent Python - Funktion tut, können Sie es eifrig machen ausführen mit tf.config.run_functions_eagerly(True) . Dies ist ein Schalter, schaltet sich aus , Function ‚s Fähigkeit , Diagramme zu erstellen und auszuführen, statt normalerweise den Code ausführt.

tf.config.run_functions_eagerly(True)
get_MSE(y_true, y_pred)
<tf.Tensor: shape=(), dtype=int32, numpy=22>
# Don't forget to set it back when you are done.
tf.config.run_functions_eagerly(False)

Allerdings Function kann sich anders verhalten unter Graph und eifrig Ausführung. Die Python print ist ein Beispiel , wie diese beiden Modi unterscheiden. Lassen Sie uns prüfen, was passiert , wenn man eine einfügen print Anweisung an Ihre Funktion und es immer wieder aufrufen.

@tf.function
def get_MSE(y_true, y_pred):
  print("Calculating MSE!")
  sq_diff = tf.pow(y_true - y_pred, 2)
  return tf.reduce_mean(sq_diff)

Beobachten Sie, was gedruckt wird:

error = get_MSE(y_true, y_pred)
error = get_MSE(y_true, y_pred)
error = get_MSE(y_true, y_pred)
Calculating MSE!

Ist die Ausgabe überraschend? get_MSE gedruckt nur einmal , obwohl es dreimal genannt wurde.

Um zu erklären, der print wird Anweisung ausgeführt , wenn Function des ursprünglichen Code läuft , um die Grafik in einem Prozess bekannt als erstellen „Tracing“ . Verfolgen der TensorFlow Operationen in einem Diagramm erfasst, und print wird nicht in der Grafik erfasst. Das Diagramm wird dann für alle drei Anrufe ausgeführt , ohne jemals wieder den Python - Code ausgeführt wird .

Deaktivieren Sie als Plausibilitätsprüfung die Diagrammausführung zum Vergleichen:

# Now, globally set everything to run eagerly to force eager execution.
tf.config.run_functions_eagerly(True)
# Observe what is printed below.
error = get_MSE(y_true, y_pred)
error = get_MSE(y_true, y_pred)
error = get_MSE(y_true, y_pred)
Calculating MSE!
Calculating MSE!
Calculating MSE!
tf.config.run_functions_eagerly(False)

print ist ein Python - Nebenwirkung, und es gibt andere Unterschiede , die Sie beachten sollten , wenn eine Funktion in eine Umwandlung Function .

Nicht strikte Ausführung

Die Graphausführung führt nur die Operationen aus, die notwendig sind, um die beobachtbaren Effekte zu erzeugen, einschließlich:

  • Der Rückgabewert der Funktion
  • Dokumentierte bekannte Nebenwirkungen wie:

Dieses Verhalten wird normalerweise als "nicht strikte Ausführung" bezeichnet und unterscheidet sich von der eifrigen Ausführung, die alle erforderlichen oder nicht erforderlichen Programmoperationen durchläuft.

Insbesondere zählt die Laufzeitfehlerprüfung nicht als beobachtbarer Effekt. Wenn eine Operation übersprungen wird, weil sie unnötig ist, kann sie keine Laufzeitfehler auslösen.

Im folgenden Beispiel wird die „unnötige“ Operation tf.gather weist bei Graph Ausführung ist, so dass die Laufzeitfehler InvalidArgumentError nicht erhöht wird, wie es in eifrig Ausführung sein würde. Verlassen Sie sich nicht darauf, dass beim Ausführen eines Diagramms ein Fehler auftritt.

def unused_return_eager(x):
  # Get index 1 will fail when `len(x) == 1`
  tf.gather(x, [1]) # unused 
  return x

try:
  print(unused_return_eager(tf.constant([0.0])))
except tf.errors.InvalidArgumentError as e:
  # All operations are run during eager execution so an error is raised.
  print(f'{type(e).__name__}: {e}')
tf.Tensor([0.], shape=(1,), dtype=float32)
@tf.function
def unused_return_graph(x):
  tf.gather(x, [1]) # unused
  return x

# Only needed operations are run during graph exection. The error is not raised.
print(unused_return_graph(tf.constant([0.0])))
tf.Tensor([0.], shape=(1,), dtype=float32)

tf.function Best Practices

Es kann einige Zeit dauern , bis auf das Verhalten zu gewöhnen Function . Kommen Sie schnell, Erstanwender gestartet sollte mit Dekoration Spielzeug Funktionen spielen , um @tf.function zu erleben bekommen mit dem Gehen von begierig auf Graph Ausführung.

Designing for tf.function kann die beste Wahl für das Schreiben von Graph-kompatible TensorFlow Programme sein. Hier sind einige Tipps:

  • Umschalten zwischen eifrig und Grafik Ausführung früh und häufig mit tf.config.run_functions_eagerly genau zu bestimmen , ob / wann die beiden Modi auseinandergehen.
  • Erstellen tf.Variable s außerhalb der Python - Funktion und ändern Sie sie auf der Innenseite. Das gleiche gilt für Objekte , die verwenden tf.Variable , wie keras.layers , keras.Model s und tf.optimizers .
  • Vermeiden Sie Funktionen schreiben , die auf äußere Python Variablen abhängen , ohne tf.Variable s und Keras Objekte.
  • Schreiben Sie lieber Funktionen, die Tensoren und andere TensorFlow-Typen als Eingabe verwenden. Sie können auch in anderen Objekttypen passieren , aber vorsichtig sein !
  • Fügen Sie so viel wie möglich Berechnung unter einem tf.function den Performance - Gewinn zu maximieren. Dekorieren Sie beispielsweise einen ganzen Trainingsschritt oder die gesamte Trainingsschleife.

Die Beschleunigung sehen

tf.function verbessert in der Regel die Leistung des Codes, aber die Höhe der Geschwindigkeitserhöhung hängt von der Art der Berechnung Sie laufen. Kleine Berechnungen können durch den Overhead des Aufrufens eines Graphen dominiert werden. Sie können den Leistungsunterschied wie folgt messen:

x = tf.random.uniform(shape=[10, 10], minval=-1, maxval=2, dtype=tf.dtypes.int32)

def power(x, y):
  result = tf.eye(10, dtype=tf.dtypes.int32)
  for _ in range(y):
    result = tf.matmul(x, result)
  return result
print("Eager execution:", timeit.timeit(lambda: power(x, 100), number=1000))
Eager execution: 1.977389062999464
power_as_graph = tf.function(power)
print("Graph execution:", timeit.timeit(lambda: power_as_graph(x, 100), number=1000))
Graph execution: 0.5930650999998761

tf.function wird häufig zu beschleunigen Trainingsschlaufen verwendet, und Sie können in mehr darüber erfahren Schreiben einer Trainingsschleife von Grund auf neu mit Keras.

Leistung und Kompromisse

Diagramme können Ihren Code beschleunigen, aber der Prozess ihrer Erstellung ist mit einigem Aufwand verbunden. Bei einigen Funktionen dauert die Erstellung des Graphen länger als die Ausführung des Graphen. Diese Investition amortisiert sich normalerweise schnell durch die Leistungssteigerung nachfolgender Ausführungen, aber es ist wichtig zu wissen, dass die ersten Schritte eines großen Modelltrainings aufgrund des Tracings langsamer sein können.

Egal wie groß Ihr Modell ist, Sie möchten häufiges Nachzeichnen vermeiden. Die tf.function Führung bespricht , wie Set - Eingang Spezifikationen und Verwendung Tensor Argumente zurückzuverfolgen zu vermeiden. Wenn Sie feststellen, dass die Leistung ungewöhnlich schlecht ist, sollten Sie überprüfen, ob Sie versehentlich eine Rückverfolgung durchführen.

Wenn eine Function verfolgt?

Um herauszufinden , wann Ihre Function wird Tracing, fügen Sie einen print auf seine Code - Anweisung. Als Faustregel gilt : Function wird die Execute - print Anweisung jedes Mal , es nachzeichnet.

@tf.function
def a_function_with_python_side_effect(x):
  print("Tracing!") # An eager-only side effect.
  return x * x + tf.constant(2)

# This is traced the first time.
print(a_function_with_python_side_effect(tf.constant(2)))
# The second time through, you won't see the side effect.
print(a_function_with_python_side_effect(tf.constant(3)))
Tracing!
tf.Tensor(6, shape=(), dtype=int32)
tf.Tensor(11, shape=(), dtype=int32)
# This retraces each time the Python argument changes,
# as a Python argument could be an epoch count or other
# hyperparameter.
print(a_function_with_python_side_effect(2))
print(a_function_with_python_side_effect(3))
Tracing!
tf.Tensor(6, shape=(), dtype=int32)
Tracing!
tf.Tensor(11, shape=(), dtype=int32)

Neue Python-Argumente lösen immer die Erstellung eines neuen Graphen aus, daher die zusätzliche Ablaufverfolgung.

Nächste Schritte

Sie können mehr darüber erfahren tf.function auf der API - Referenz Seite und durch Befolgen der Bessere Leistung mit tf.function Führung.