Soll ich @ tf.function für alle Funktionen verwenden?

8

Ein offizielles Tutorial zu @tf.functionsagt:

Verwenden Sie tf.function, um Diagramme aus Ihren Programmen zu erstellen, um Spitzenleistungen zu erzielen und Ihr Modell überall einsetzbar zu machen. Dank AutoGraph funktioniert eine überraschende Menge an Python-Code nur mit tf.function, aber es gibt immer noch Fallstricke, vor denen man sich hüten muss.

Die wichtigsten Erkenntnisse und Empfehlungen sind:

  • Verlassen Sie sich nicht auf Python-Nebenwirkungen wie Objektmutation oder Listenanhänge.
  • tf.function funktioniert am besten mit TensorFlow-Ops und nicht mit NumPy-Ops oder Python-Primitiven.
  • Verwenden Sie im Zweifelsfall das for x in y-Idiom.

Es wird nur erwähnt, wie@tf.function kommentierte Funktionen implementiert werden, nicht jedoch, wann sie verwendet werden sollen.

Gibt es eine Heuristik, wie man entscheidet, ob ich zumindest versuchen soll, eine Funktion mit Anmerkungen zu versehen tf.function? Es scheint, dass es keine Gründe gibt, es nicht zu tun, es sei denn, ich bin zu faul, um Nebenwirkungen zu entfernen oder einige Dinge wie range()-> zu ändern tf.range(). Aber wenn ich dazu bereit bin ...

Gibt es einen Grund, nicht @tf.functionfür alle Funktionen zu verwenden?

Problemoffizier
quelle
1
Warum diese Tags hinzufügen? Wir könnten auch hinzufügen tensorflow0.1, tensorflow0.2, tensorflow0.3, tensorflow0.4, tensorflow0.5und so weiter, sowie ein Tag für jede dieser tfModule und Klassen dann. Fügen Sie außerdem ein Tag für jedes Standardmodul von Python sowie für seine Funktionen und Klassen hinzu.
ForceBru
Aus diesem Grund habe ich das Tag tensorflow2.x eingeführt, da es Fragen gibt, die sich nicht nur auf tensorflow2.0, sondern auch auf das Tag tensorflow2.x beziehen. Es wäre jedoch ungeeignet und nicht durchführbar, für jede Version einer Bibliothek ein Tag hinzuzufügen. Nehmen Sie das Beispiel von Python. Sie haben nicht python3.4.6 ..... python.3.8.2, sondern python3.x
Timbus Calin
Einerseits heißt es in der tf.functionAnleitung "Dekorieren Sie Funktionen auf Modulebene und Methoden von Klassen auf Modulebene und vermeiden Sie das Dekorieren lokaler Funktionen oder Methoden". Ich scheine mich an explizitere Formulierungen zu erinnern, wie "nicht jede Funktion dekorieren, tf.functionin übergeordneten Funktionen verwenden, wie eine Trainingsschleife", aber ich kann mich falsch erinnern (oder vielleicht wurde sie entfernt). OTOH, diese Diskussion hat interessante Beiträge von Entwicklern, am Ende scheint es in Ordnung zu sein, sie in etwa jeder Funktion für Tensoren / Vars zu verwenden.
Jdehesa
Mit @jdehesa AFAIK @tf.functionkommentierte Funktionen kompilieren auch die Funktionen, die sie selbst für Diagramme aufrufen. Sie müssten also nur den Einstiegspunkt in das Modul mit Anmerkungen versehen, der mit Ihrer Beschreibung übereinstimmt. Es würde aber auch nicht schaden, Funktionen weiter unten im Aufrufstapel manuell zu kommentieren.
Problemoffizier
@problemofficer Ja, in der von mir verlinkten GitHub-Ausgabe gibt es einige Diskussionen darüber, ob das Erstellen mehrerer Zwischenfunktionen einen leichten Einfluss auf die Leistung haben könnte, aber es scheint, dass der Graph Optimizer (Grappler) bei Bedarf Funktionen "inline" kann, aber auf der anderen Seite Wenn ein anderer Nicht- tf.functionBenutzer mehrmals aufgerufen wird, kann dies die "Codeduplizierung" in der Grafik nicht verhindern, weshalb eine weit verbreitete Verwendung empfehlenswert erscheint.
Jdehesa

Antworten:

4

TLDR: Es hängt von Ihrer Funktion ab und davon, ob Sie in Produktion oder Entwicklung sind. Nicht verwenden, tf.functionwenn Sie Ihre Funktion einfach debuggen möchten oder wenn sie unter die Einschränkungen der AutoGraph- oder tf.v1-Codekompatibilität fällt. Ich würde wärmstens empfehlen, die Inside TensorFlow-Vorträge über AutoGraph und Funktionen zu sehen, nicht über Sitzungen .

Im Folgenden werde ich die Gründe aufschlüsseln, die alle aus Informationen stammen, die Google online zur Verfügung gestellt hat.

Im Allgemeinen tf.functionbewirkt der Dekorator, dass eine Funktion als aufrufbare Funktion kompiliert wird, die ein TensorFlow-Diagramm ausführt. Dies beinhaltet:

  • Konvertierung des Codes über AutoGraph, falls erforderlich (einschließlich aller Funktionen, die von einer mit Anmerkungen versehenen Funktion aufgerufen werden)
  • Verfolgen und Ausführen des generierten Diagrammcodes

Zu den dahinter stehenden Gestaltungsideen stehen detaillierte Informationen zur Verfügung.

Vorteile der Dekoration einer Funktion mit tf.function

Allgemeine Vorteile

  • Schnellere Ausführung , insbesondere wenn die Funktion aus vielen kleinen Operationen besteht (Quelle)

Für Funktionen mit Python-Code / Verwenden von AutoGraph über tf.functionDekoration

Wenn Sie AutoGraph verwenden möchten, wird die Verwendung tf.functiondringend empfohlen, wenn Sie AutoGraph direkt aufrufen. Gründe hierfür sind: Automatische Steuerungsabhängigkeiten, die für einige APIs, mehr Caching und Ausnahmehilfen (Quelle) erforderlich sind .

Nachteile der Dekoration einer Funktion mit tf.function

Allgemeine Nachteile

  • Wenn die Funktion nur aus wenigen teuren Operationen besteht, wird es nicht viel Beschleunigung geben (Quelle)

Für Funktionen mit Python-Code / Verwenden von AutoGraph über tf.functionDekoration

  • Keine Ausnahme fangen (sollte im eifrigen Modus durchgeführt werden; außerhalb der dekorierten Funktion) (Quelle)
  • Das Debuggen ist viel schwieriger
  • Einschränkungen aufgrund versteckter Nebenwirkungen und TF-Kontrollfluss

Detaillierte Informationen zu AutoGraph-Einschränkungen sind verfügbar.

Für Funktionen mit tf.v1-Code

  • Es ist nicht erlaubt, Variablen mehr als einmal zu erstellen tf.function, dies kann sich jedoch ändern, da der tf.v1-Code ausläuft (Quelle).

Für Funktionen mit tf.v2-Code

  • Keine besonderen Nachteile

Beispiele für Einschränkungen

Variablen mehrmals erstellen

Es ist nicht zulässig, Variablen mehrmals zu erstellen, wie vim folgenden Beispiel:

@tf.function
def f(x):
    v = tf.Variable(1)
    return tf.add(x, v)

f(tf.constant(2))

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

Im folgenden Code wird dies gemindert, indem sichergestellt self.vwird , dass dieser nur einmal erstellt wird:

class C(object):
    def __init__(self):
        self.v = None
    @tf.function
    def f(self, x):
        if self.v is None:
            self.v = tf.Variable(1)
        return tf.add(x, self.v)

c = C()
print(c.f(tf.constant(2)))

# => tf.Tensor(3, shape=(), dtype=int32)

Versteckte Nebenwirkungen, die nicht von AutoGraph erfasst wurden

Änderungen wie self.ain diesem Beispiel können nicht ausgeblendet werden, was zu einem Fehler führt, da (noch) keine funktionsübergreifende Analyse durchgeführt wird (Quelle) :

class C(object):
    def change_state(self):
        self.a += 1

    @tf.function
    def f(self):
        self.a = tf.constant(0)
        if tf.constant(True):
            self.change_state() # Mutation of self.a is hidden
        tf.print(self.a)

x = C()
x.f()

# => InaccessibleTensorError: The tensor 'Tensor("add:0", shape=(), dtype=int32)' cannot be accessed here: it is defined in another function or code block. Use return values, explicit Python locals or TensorFlow collections to access it. Defined in: FuncGraph(name=cond_true_5, id=5477800528); accessed from: FuncGraph(name=f, id=5476093776).

Änderungen in der Sicht sind kein Problem:

class C(object):
    @tf.function
    def f(self):
        self.a = tf.constant(0)
        if tf.constant(True):
            self.a += 1 # Mutation of self.a is in plain sight
        tf.print(self.a)

x = C()
x.f()

# => 1

Beispiel für eine Einschränkung aufgrund des TF-Kontrollflusses

Diese if-Anweisung führt zu einem Fehler, da der Wert für else für den TF-Steuerungsfluss definiert werden muss:

@tf.function
def f(a, b):
    if tf.greater(a, b):
        return tf.constant(1)

# If a <= b would return None
x = f(tf.constant(3), tf.constant(2))   

# => ValueError: A value must also be returned from the else branch. If a value is returned from one branch of a conditional a value must be returned from all branches.
prouast
quelle
1
Dies ist eine gute Zusammenfassung. Es ist auch erwähnenswert, dass tf.function beim Aufruf aus dem eifrigen Modus nach dem ersten Anruf einen Overhead von etwa 200 us (Geben oder Nehmen) hat. Das Aufrufen einer tf.-Funktion von einer anderen tf.-Funktion ist jedoch in Ordnung. Sie möchten also so viel Berechnung wie möglich abschließen. Wenn es nicht die Einschränkungen gäbe, sollten Sie das gesamte Programm einschließen.
Dan Moldovan
Diese Antwort lautet tl; dr IMHO und beantwortet meine Frage nicht wirklich, sondern gibt nur die gleichen fragmentierten Informationen, die ich selbst gefunden habe. Auch zu sagen, dass ich nicht @tf.functionfür die Produktion, sondern nur für die Entwicklung verwenden sollte, ist keine praktikable Lösung. Erstens schafft beim maschinellen Lernen (zumindest in der Forschung) das Training während der Entwicklungsphase auch das Endprodukt (das trainierte Modell). Zweitens sind Dekorateure eine bedeutende Veränderung. Ich kann sie nicht einfach "nach der Entwicklung" einfügen und sicherstellen, dass sich der Code gleich verhält. Das bedeutet, dass ich sie schon dort entwickeln und testen muss.
problemofficer
@problemofficer Entschuldigung für die Verwirrung. Als ich in meiner Antwort über die Produktion sprach, dachte ich darüber nach, ein Training (für große Datenmengen) durchzuführen. In meiner eigenen Forschung entwickle / debugge ich meine Funktionen mit einem Spielzeug-Dataset im eifrigen Modus und füge sie tf.functiongegebenenfalls hinzu.
Prouast
2

tf.function ist nützlich beim Erstellen und Verwenden von Berechnungsgraphen. Sie sollten im Training und in der Bereitstellung verwendet werden, werden jedoch für die meisten Ihrer Funktionen nicht benötigt.

Nehmen wir an, wir bauen eine spezielle Schicht, die von einem größeren Modell getrennt sein wird. Wir möchten nicht, dass der tf.function-Dekorator über der Funktion steht, die diese Ebene erstellt, da er lediglich definiert, wie die Ebene aussehen wird.

Nehmen wir andererseits an, wir werden entweder eine Vorhersage treffen oder unser Training mit einer Funktion fortsetzen. Wir möchten, dass der Dekorator tf.funktion ist, da wir tatsächlich das Berechnungsdiagramm verwenden, um einen Wert zu erhalten.

Ein gutes Beispiel wäre die Konstruktion eines Encoder-Decoder-Modells. Setzen Sie den Dekorateur NICHT um die Funktion, um den Encoder oder Decoder oder eine beliebige Ebene zu erstellen. Dies ist nur eine Definition dessen, was er tun wird. Stellen Sie den Dekorateur auf die Methode "Zug" oder "Vorhersage", da diese tatsächlich den Berechnungsgraphen für die Berechnung verwenden.

Drew
quelle
1
Aber was ist mit Nebenwirkungen oder z tf.range(). AFAIK diese können nicht automatisch konvertiert werden. Daher müsste ich meine benutzerdefinierten Ebenen von Anfang an unter Berücksichtigung des automatischen Diagramms schreiben. Daher kann ich die aufrufende (Vorhersage-) Funktion nicht einfach dekorieren.
Problemoffizier