Visualizza su TensorFlow.org | Esegui in Google Colab | Visualizza su GitHub | Scarica quaderno |
Sotto il cofano, TensorFlow 2 segue un paradigma di programmazione fondamentalmente diverso da TF1.x.
Questa guida descrive le differenze fondamentali tra TF1.x e TF2 in termini di comportamenti e API e in che modo sono tutte correlate al tuo percorso di migrazione.
Riepilogo ad alto livello dei principali cambiamenti
Fondamentalmente, TF1.xe TF2 utilizzano un diverso insieme di comportamenti di runtime attorno all'esecuzione (desideroso in TF2), variabili, flusso di controllo, forme tensoriali e confronti di uguaglianza dei tensori. Per essere compatibile con TF2, il tuo codice deve essere compatibile con il set completo di comportamenti TF2. Durante la migrazione, puoi abilitare o disabilitare la maggior parte di questi comportamenti individualmente tramite le tf.compat.v1.enable_*
o tf.compat.v1.disable_*
. L'unica eccezione è la rimozione delle raccolte, che è un effetto collaterale dell'abilitazione/disabilitazione dell'esecuzione ansiosa.
Ad alto livello, TensorFlow 2:
- Rimuove le API ridondanti .
- Rende le API più coerenti, ad esempio, Unified RNN e Unified Optimizer .
- Preferisce le funzioni alle sessioni e si integra meglio con il runtime Python con l'esecuzione di Eager abilitata per impostazione predefinita insieme a
tf.function
che fornisce dipendenze di controllo automatico per grafici e compilazione. - Depreca le raccolte di grafici globali.
- Modifica la semantica della concorrenza delle variabili usando
ResourceVariables
suReferenceVariables
. - Supporta il flusso di controllo basato su funzioni e differenziabile (Control Flow v2).
- Semplifica l'API TensorShape per contenere
int
s invece di oggettitf.compat.v1.Dimension
. - Aggiorna la meccanica dell'uguaglianza dei tensori. In TF1.x l'operatore
==
su tensori e variabili verifica l'uguaglianza dei riferimenti agli oggetti. In TF2 verifica l'uguaglianza dei valori. Inoltre, tensori/variabili non sono più hashable, ma è possibile ottenere riferimenti a oggetti hashable tramitevar.ref()
se è necessario utilizzarli in insiemi o come chiavidict
.
Le sezioni seguenti forniscono un po' di contesto in più sulle differenze tra TF1.x e TF2. Per saperne di più sul processo di progettazione alla base di TF2, leggi le RFC e i documenti di progettazione .
Pulizia dell'API
Molte API sono sparite o spostate in TF2. Alcune delle modifiche principali includono la rimozione di tf.app
, tf.flags
e tf.logging
a favore di absl-py ora open source , il rehoming dei progetti che vivevano in tf.contrib
e la pulizia dello spazio dei nomi tf.*
principale tramite spostare le funzioni meno utilizzate in sottopacchetti come tf.math
. Alcune API sono state sostituite con i loro equivalenti TF2: tf.summary
, tf.keras.metrics
e tf.keras.optimizers
.
tf.compat.v1
: Endpoint API legacy e compatibilità
I simboli negli spazi dei nomi tf.compat
e tf.compat.v1
non sono considerati API TF2. Questi spazi dei nomi espongono un mix di simboli di compatibilità, nonché endpoint API legacy di TF 1.x. Questi hanno lo scopo di aiutare la migrazione da TF1.x a TF2. Tuttavia, poiché nessuna di queste API compat.v1
sono API TF2 idiomatiche, non usarle per scrivere codice TF2 nuovo di zecca.
I singoli simboli tf.compat.v1
possono essere compatibili con TF2 perché continuano a funzionare anche con i comportamenti TF2 abilitati (come tf.compat.v1.losses.mean_squared_error
), mentre altri sono incompatibili con TF2 (come tf.compat.v1.metrics.accuracy
). Molti simboli compat.v1
(anche se non tutti) contengono informazioni sulla migrazione dedicate nella documentazione che spiega il loro grado di compatibilità con i comportamenti di TF2, nonché come migrarli alle API di TF2.
Lo script di aggiornamento TF2 può mappare molti simboli API compat.v1
su API TF2 equivalenti nel caso in cui siano alias o abbiano gli stessi argomenti ma con un ordine diverso. Puoi anche utilizzare lo script di aggiornamento per rinominare automaticamente le API TF1.x.
API di falsi amici
Ci sono una serie di simboli "falsi amici" trovati nello spazio dei nomi TF2 tf
(non sotto compat.v1
) che in realtà ignorano i comportamenti TF2 nascosti e/o non sono completamente compatibili con l'insieme completo dei comportamenti TF2. Pertanto, è probabile che queste API si comportino in modo anomalo con il codice TF2, potenzialmente in modo silenzioso.
-
tf.estimator.*
: Gli estimatori creano e utilizzano grafici e sessioni sotto il cofano. In quanto tali, questi non dovrebbero essere considerati compatibili con TF2. Se il codice esegue estimatori, non utilizza comportamenti TF2. -
keras.Model.model_to_estimator(...)
: Questo crea uno stimatore sotto il cofano, che come menzionato sopra non è compatibile con TF2. -
tf.Graph().as_default()
: inserisce i comportamenti del grafico TF1.x e non segue i comportamentitf.function
standard compatibili con TF2. Il codice che inserisce grafici come questo li eseguirà generalmente tramite Sessions e non dovrebbe essere considerato compatibile con TF2. -
tf.feature_column.*
Le API della colonna delle funzioni generalmente si basano sulla creazione di variabilitf.compat.v1.get_variable
in stile TF1 e presuppongono che le variabili create saranno accessibili tramite raccolte globali. Poiché TF2 non supporta le raccolte, le API potrebbero non funzionare correttamente durante l'esecuzione con i comportamenti TF2 abilitati.
Altre modifiche alle API
TF2 presenta miglioramenti significativi agli algoritmi di posizionamento del dispositivo che rendono superfluo l'utilizzo di
tf.colocate_with
. Se la rimozione provoca un peggioramento delle prestazioni, segnalare un bug .Sostituisci tutto l'utilizzo di
tf.v1.ConfigProto
con funzioni equivalenti datf.config
.
Esecuzione impaziente
TF1.x richiedeva di unire manualmente un albero di sintassi astratto (il grafico) effettuando chiamate API tf.*
e quindi compilare manualmente l'albero di sintassi astratto passando un insieme di tensori di output e tensori di input a una chiamata session.run
. TF2 viene eseguito avidamente (come fa normalmente Python) e rende i grafici e le sessioni come dettagli di implementazione.
Un notevole sottoprodotto dell'esecuzione desiderosa è che tf.control_dependencies
non è più richiesto, poiché tutte le righe di codice vengono eseguite in ordine (all'interno di una tf.function
, il codice con effetti collaterali viene eseguito nell'ordine scritto).
Niente più globali
TF1.x faceva molto affidamento su spazi dei nomi e raccolte globali impliciti. Quando hai chiamato tf.Variable
, sarebbe stato inserito in una raccolta nel grafico predefinito e rimarrebbe lì, anche se hai perso traccia della variabile Python che punta ad essa. Potresti quindi recuperare quella tf.Variable
, ma solo se conoscessi il nome con cui era stata creata. Questo era difficile da fare se non avevi il controllo della creazione della variabile. Di conseguenza, sono proliferati tutti i tipi di meccanismi per cercare di aiutarti a ritrovare le variabili e per i framework di trovare variabili create dall'utente. Alcuni di questi includono: ambiti di variabili, raccolte globali, metodi di supporto come tf.get_global_step
e tf.global_variables_initializer
, ottimizzatori che calcolano implicitamente i gradienti su tutte le variabili addestrabili e così via. TF2 elimina tutti questi meccanismi ( Variabili 2.0 RFC ) a favore del meccanismo predefinito: tieni traccia delle tue variabili. Se perdi le tracce di una tf.Variable
, viene raccolta la spazzatura.
Il requisito di tenere traccia delle variabili crea un po' di lavoro extra, ma con strumenti come gli spessori di modellazione e comportamenti come raccolte di variabili orientate agli oggetti implicite in tf.Module
s e tf.keras.layers.Layer
s , il carico è ridotto al minimo.
Funzioni, non sessioni
Una chiamata session.run
è quasi come una chiamata di funzione: specifichi gli input e la funzione da chiamare e ottieni una serie di output. In TF2, puoi decorare una funzione Python usando tf.function
per contrassegnarla per la compilazione JIT in modo che TensorFlow la esegua come un singolo grafico ( Functions 2.0 RFC ). Questo meccanismo consente a TF2 di ottenere tutti i vantaggi della modalità grafico:
- Prestazioni: la funzione può essere ottimizzata (potatura dei nodi, fusione del kernel, ecc.)
- Portabilità: la funzione può essere esportata/reimportata ( SavedModel 2.0 RFC ), consentendo di riutilizzare e condividere le funzioni TensorFlow modulari.
# TF1.x
outputs = session.run(f(placeholder), feed_dict={placeholder: input})
# TF2
outputs = f(input)
Con il potere di alternare liberamente il codice Python e TensorFlow, puoi sfruttare l'espressività di Python. Tuttavia, TensorFlow portatile viene eseguito in contesti senza un interprete Python, come mobile, C++ e JavaScript. Per evitare di riscrivere il codice quando aggiungi tf.function
, usa AutoGraph per convertire un sottoinsieme di costrutti Python nei loro equivalenti TensorFlow:
-
for
/while
->tf.while_loop
(break
econtinue
sono supportati) -
if
->tf.cond
-
for _ in dataset
->dataset.reduce
AutoGraph supporta annidamenti arbitrari del flusso di controllo, il che rende possibile implementare in modo efficiente e conciso molti programmi ML complessi come modelli di sequenza, apprendimento per rinforzo, cicli di formazione personalizzati e altro ancora.
Adattamento alle modifiche al comportamento di TF 2.x
La migrazione a TF2 è completa solo dopo la migrazione al set completo di comportamenti TF2. Il set completo di comportamenti può essere abilitato o disabilitato tramite tf.compat.v1.enable_v2_behaviors
e tf.compat.v1.disable_v2_behaviors
. Le sezioni seguenti discutono in dettaglio ogni importante cambiamento di comportamento.
Utilizzando tf.function
s
È probabile che le modifiche più importanti ai programmi durante la migrazione provengano dal cambiamento fondamentale del paradigma del modello di programmazione da grafici e sessioni all'esecuzione ansiosa e tf.function
. Fare riferimento alle guide alla migrazione di TF2 per ulteriori informazioni sul passaggio da API incompatibili con l'esecuzione ansiosa e tf.function
ad API compatibili con esse.
Di seguito sono riportati alcuni modelli di programma comuni non legati a nessuna API che potrebbe causare problemi quando si passa da tf.Graph
se tf.compat.v1.Session
s all'esecuzione desiderosa con tf.function
s.
Pattern 1: la manipolazione di oggetti Python e la creazione di variabili destinate a essere eseguite solo una volta vengono eseguite più volte
Nei programmi TF1.x che si basano su grafici e sessioni, l'aspettativa è solitamente che tutta la logica Python nel tuo programma verrà eseguita solo una volta. Tuttavia, con l'esecuzione desiderosa e tf.function
è lecito aspettarsi che la logica Python venga eseguita almeno una volta, ma possibilmente più volte (più volte con entusiasmo o più volte su diverse tracce tf.function
). A volte, tf.function
traccia anche due volte sullo stesso input, causando comportamenti imprevisti (vedi Esempio 1 e 2). Fare riferimento alla guida alla tf.function
per maggiori dettagli.
Esempio 1: creazione di variabili
Considera l'esempio seguente, in cui la funzione crea una variabile quando viene chiamata:
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)
Tuttavia, non è consentito eseguire il wrapping ingenuamente della funzione precedente che contiene la creazione di variabili con tf.function
. tf.function
supporta solo la creazione di variabili singleton alla prima chiamata . Per imporre ciò, quando tf.function rileva la creazione di variabili nella prima chiamata, tenterà di tracciare nuovamente e genererà un errore se è presente la creazione di variabili nella seconda traccia.
@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)
Una soluzione è memorizzare nella cache e riutilizzare la variabile dopo che è stata creata nella prima chiamata.
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()
Esempio 2: Tensori fuori campo dovuti al ritracciamento di tf.function
Come dimostrato nell'Esempio 1, tf.function
quando rileva la creazione di variabili nella prima chiamata. Ciò può causare ulteriore confusione, poiché i due tracciati creeranno due grafici. Quando il secondo grafico del ritracciamento tenta di accedere a un tensore dal grafico generato durante il primo tracciamento, Tensorflow genererà un errore lamentando che il tensore è fuori dall'ambito. Per illustrare lo scenario, il codice seguente crea un set di dati sulla prima chiamata tf.function
. Questo funzionerebbe come previsto.
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()
Tuttavia, se proviamo anche a creare una variabile sulla prima chiamata tf.function
, il codice genererà un errore lamentando che il set di dati è fuori dall'ambito. Questo perché il set di dati si trova nel primo grafico, mentre anche il secondo grafico sta tentando di accedervi.
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.
La soluzione più semplice è garantire che la creazione della variabile e la creazione del set di dati siano entrambe al di fuori della chiamata tf.funciton
. Per esempio:
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()
Tuttavia, a volte non è evitabile creare variabili in tf.function
(come le variabili slot in alcuni ottimizzatori keras TF ). Tuttavia, possiamo semplicemente spostare la creazione del set di dati al di fuori della chiamata tf.function
. Il motivo per cui possiamo fare affidamento su questo è perché tf.function
riceverà il set di dati come input implicito ed entrambi i grafici possono accedervi correttamente.
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()
Esempio 3: ricreazioni impreviste di oggetti Tensorflow dovute all'utilizzo di dict
tf.function
ha un supporto molto scarso per gli effetti collaterali di Python come l'aggiunta a un elenco o il controllo/aggiunta a un dizionario. Maggiori dettagli sono in "Prestazioni migliori con tf.function" . Nell'esempio seguente, il codice usa dizionari per memorizzare nella cache set di dati e iteratori. Per la stessa chiave, ogni chiamata al modello restituirà lo stesso iteratore del set di dati.
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
Tuttavia, il modello sopra non funzionerà come previsto in tf.function
. Durante il tracciamento, tf.function
ignorerà l'effetto collaterale python dell'aggiunta ai dizionari. Invece, ricorda solo la creazione di un nuovo set di dati e iteratore. Di conseguenza, ogni chiamata al modello restituirà sempre un nuovo iteratore. Questo problema è difficile da notare a meno che i risultati numerici o le prestazioni non siano sufficientemente significativi. Pertanto, consigliamo agli utenti di pensare attentamente al codice prima di avvolgere ingenuamente tf.function
nel codice Python.
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
Possiamo usare tf.init_scope
per sollevare il set di dati e la creazione dell'iteratore al di fuori del grafico, per ottenere il comportamento previsto:
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
La regola generale è evitare di fare affidamento sugli effetti collaterali di Python nella logica e usarli solo per eseguire il debug delle tracce.
Esempio 4: manipolazione di un elenco Python globale
Il codice TF1.x seguente utilizza un elenco globale delle perdite che utilizza solo per mantenere l'elenco delle perdite generate dalla fase di addestramento corrente. Si noti che la logica Python che aggiunge le perdite all'elenco verrà chiamata solo una volta, indipendentemente dal numero di passaggi di addestramento per i quali viene eseguita la sessione.
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(...)
Tuttavia, se questa logica Python è mappata ingenuamente a TF2 con un'esecuzione ansiosa, l'elenco globale delle perdite avrà nuovi valori aggiunti in ogni fase di addestramento. Ciò significa che il codice della fase di addestramento che in precedenza prevedeva che l'elenco contenesse solo le perdite della fase di addestramento corrente ora vede effettivamente l'elenco delle perdite di tutte le fasi di addestramento eseguite fino a quel momento. Si tratta di una modifica del comportamento non intenzionale e l'elenco dovrà essere cancellato all'inizio di ogni passaggio o reso locale al passaggio di addestramento.
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)
...
Schema 2: un tensore simbolico destinato a essere ricalcolato ad ogni passaggio in TF1.x viene accidentalmente memorizzato nella cache con il valore iniziale quando si passa a desideroso.
Questo modello di solito fa sì che il codice si comporti in modo anomalo durante l'esecuzione ansiosa al di fuori di tf.functions, ma genera un InaccessibleTensorError
se la memorizzazione nella cache del valore iniziale si verifica all'interno di un tf.function
. Tuttavia, tieni presente che per evitare il modello 1 sopra, spesso strutturerai inavvertitamente il tuo codice in modo tale che questa memorizzazione nella cache del valore iniziale avvenga al di fuori di qualsiasi tf.function
che potrebbe generare un errore. Quindi, presta particolare attenzione se sai che il tuo programma potrebbe essere suscettibile a questo schema.
La soluzione generale a questo modello è ristrutturare il codice o utilizzare callable Python, se necessario, per assicurarsi che il valore venga ricalcolato ogni volta invece di essere accidentalmente memorizzato nella cache.
Esempio 1: tasso di apprendimento/iperparametro/ecc. programmi che dipendono dal passo globale
Nel frammento di codice seguente, l'aspettativa è che ogni volta che viene eseguita la sessione verrà letto il valore global_step
più recente e verrà calcolata una nuova velocità di apprendimento.
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(...)
Tuttavia, quando provi a passare a desideroso, fai attenzione a finire con il calcolo del tasso di apprendimento solo una volta e poi riutilizzato, piuttosto che seguire il programma previsto:
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)
...
Poiché questo esempio specifico è un modello comune e gli ottimizzatori devono essere inizializzati solo una volta anziché in ogni fase di addestramento, gli ottimizzatori TF2 supportano le pianificazioni tf.keras.optimizers.schedules.LearningRateSchedule
o i callable Python come argomenti per la velocità di apprendimento e altri iperparametri.
Esempio 2: le inizializzazioni simboliche di numeri casuali assegnate come attributi dell'oggetto e poi riutilizzate tramite il puntatore vengono accidentalmente memorizzate nella cache quando si passa a desideroso
Considera il seguente modulo NoiseAdder
:
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
Usarlo come segue in TF1.x calcolerà un nuovo tensore di rumore casuale ogni volta che viene eseguita la sessione:
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(...)
Tuttavia, in TF2 l'inizializzazione di noise_adder
all'inizio causerà il calcolo di noise_distribution
solo una volta e verrà bloccato per tutti i passaggi di addestramento:
...
# initialize all variable-containing objects
noise_adder = NoiseAdder(shape, mean) # Freezes `self.noise_distribution`!
...
# computation pass
x_with_noise = noise_adder.add_noise(x)
...
Per risolvere questo problema, rifattorizzare NoiseAdder
per chiamare tf.random.normal
ogni volta che è necessario un nuovo tensore casuale, invece di fare riferimento allo stesso oggetto tensore ogni volta.
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
Pattern 3: il codice TF1.x si basa direttamente e cerca i tensori per nome
È comune per i test del codice TF1.x fare affidamento sul controllo di quali tensori o operazioni sono presenti in un grafico. In alcuni rari casi, il codice di modellazione si baserà anche su queste ricerche per nome.
I nomi dei tensori non vengono generati quando si esegue avidamente al di fuori di tf.function
, quindi tutti gli usi di tf.Tensor.name
devono avvenire all'interno di una tf.function
. Tieni presente che è molto probabile che i nomi effettivamente generati differiscano tra TF1.x e TF2 anche all'interno della stessa tf.function
e le garanzie API non garantiscono la stabilità dei nomi generati tra le versioni TF.
Pattern 4: la sessione TF1.x esegue selettivamente solo una parte del grafico generato
In TF1.x, puoi costruire un grafico e quindi scegliere di eseguirne selettivamente solo un sottoinsieme con una sessione, scegliendo un insieme di input e output che non richiedono l'esecuzione di tutte le operazioni nel grafico.
Ad esempio, potresti avere sia un generatore che un discriminatore all'interno di un singolo grafico e utilizzare chiamate tf.compat.v1.Session.run
separate per alternare solo l'addestramento del discriminatore o solo l'addestramento del generatore.
In TF2, a causa delle dipendenze del controllo automatico in tf.function
e dell'esecuzione ansiosa, non vi è alcuna potatura selettiva delle tracce di tf.function
. Verrebbe eseguito un grafico completo contenente tutti gli aggiornamenti delle variabili anche se, ad esempio, viene emesso solo l'output del discriminatore o del generatore da tf.function
.
Quindi, dovresti usare più tf.function
s contenenti diverse parti del programma, o un argomento condizionale alla tf.function
su cui ti rami in modo da eseguire solo le cose che vuoi effettivamente eseguire.
Rimozione raccolte
Quando l'esecuzione desiderosa è abilitata, le API compat.v1
relative alla raccolta di grafici (incluse quelle che leggono o scrivono in raccolte nascoste come tf.compat.v1.trainable_variables
) non sono più disponibili. Alcuni possono generare ValueError
s, mentre altri possono restituire silenziosamente elenchi vuoti.
L'utilizzo più standard delle raccolte in TF1.x consiste nel mantenere gli inizializzatori, il passaggio globale, i pesi, le perdite di regolarizzazione, le perdite di output del modello e gli aggiornamenti delle variabili che devono essere eseguiti, ad esempio dai livelli di BatchNormalization
.
Per gestire ciascuno di questi usi standard:
- Inizializzatori - Ignora. L'inizializzazione manuale delle variabili non è richiesta con l'esecuzione desiderosa abilitata.
- Passaggio globale: consultare la documentazione di
tf.compat.v1.train.get_or_create_global_step
per le istruzioni di migrazione. - Pesi: mappa i tuoi modelli su
tf.Module
s/tf.keras.layers.Layer
s/tf.keras.Model
s seguendo le indicazioni nella guida alla mappatura del modello e quindi utilizza i rispettivi meccanismi di rilevamento del peso cometf.module.trainable_variables
. - Perdite di regolarizzazione: mappa i tuoi modelli su
tf.Module
s/tf.keras.layers.Layer
s/tf.keras.Model
s seguendo le indicazioni nella guida alla mappatura dei modelli e quindi usatf.keras.losses
. In alternativa, puoi anche monitorare manualmente le tue perdite di regolarizzazione. - Modella le perdite di output: utilizza i meccanismi di gestione delle perdite
tf.keras.Model
o tieni traccia separatamente delle perdite senza utilizzare le raccolte. - Aggiornamenti del peso: ignora questa raccolta. L'esecuzione desiderosa e
tf.function
. (con autografo e dipendenze di controllo automatico) significano che tutti gli aggiornamenti delle variabili verranno eseguiti automaticamente. Quindi, non dovrai eseguire esplicitamente tutti gli aggiornamenti di peso alla fine, ma tieni presente che ciò significa che gli aggiornamenti di peso potrebbero avvenire in un momento diverso rispetto a quello che hanno fatto nel tuo codice TF1.x, a seconda di come stavi usando le dipendenze di controllo. - Riepiloghi: fare riferimento alla guida all'API di riepilogo della migrazione .
L'utilizzo di raccolte più complesse (come l'utilizzo di raccolte personalizzate) potrebbe richiedere il refactoring del codice per mantenere i propri negozi globali o per non fare affidamento sugli archivi globali.
ResourceVariables
invece di ReferenceVariables
ResourceVariables
ha garanzie di coerenza in lettura/scrittura più solide rispetto a ReferenceVariables
. Ciò porta a una semantica più prevedibile e più facile da ragionare sull'osservazione o meno del risultato di una scrittura precedente quando si utilizzano le variabili. È estremamente improbabile che questa modifica provochi la generazione di errori o l'interruzione silenziosa del codice esistente.
Tuttavia, è possibile, anche se improbabile , che queste garanzie di coerenza più forti possano aumentare l'utilizzo della memoria del programma specifico. Si prega di presentare un problema se si ritiene che questo sia il caso. Inoltre, se disponi di unit test che si basano su confronti esatti di stringhe con i nomi degli operatori in un grafico corrispondente alle letture di variabili, tieni presente che l'abilitazione delle variabili di risorsa può modificare leggermente il nome di questi operatori.
Per isolare l'impatto di questa modifica del comportamento sul codice, se l'esecuzione ansiosa è disabilitata è possibile utilizzare tf.compat.v1.disable_resource_variables()
e tf.compat.v1.enable_resource_variables()
per disabilitare o abilitare globalmente questa modifica del comportamento. ResourceVariables
verrà sempre utilizzato se l'esecuzione desiderosa è abilitata.
Flusso di controllo v2
In TF1.x, operazioni di flusso di controllo come tf.cond
e tf.while_loop
di basso livello in linea come Switch
, Merge
ecc. TF2 fornisce operazioni di flusso di controllo funzionali migliorate implementate con tracce tf.function
separate per ogni ramo e supporto differenziazione di ordine superiore.
Per isolare l'impatto di questa modifica del comportamento sul codice, se l'esecuzione ansiosa è disabilitata è possibile utilizzare tf.compat.v1.disable_control_flow_v2()
e tf.compat.v1.enable_control_flow_v2()
per disabilitare o abilitare globalmente questa modifica del comportamento. Tuttavia, puoi disabilitare il flusso di controllo v2 solo se anche l'esecuzione desiderosa è disabilitata. Se è abilitato, verrà sempre utilizzato il flusso di controllo v2.
Questa modifica del comportamento può cambiare drasticamente la struttura dei programmi TF generati che utilizzano il flusso di controllo, poiché conterranno diverse tracce di funzioni nidificate anziché un grafico piatto. Pertanto, qualsiasi codice fortemente dipendente dalla semantica esatta delle tracce prodotte potrebbe richiedere alcune modifiche. Ciò comprende:
- Codice basato su nomi di operatori e tensori
- Codice che fa riferimento ai tensori creati all'interno di un ramo di flusso di controllo TensorFlow dall'esterno di quel ramo. È probabile che questo produca un
InaccessibleTensorError
Questa modifica del comportamento deve essere da neutra a positiva, ma se riscontri un problema in cui il flusso di controllo v2 ha prestazioni peggiori per te rispetto al flusso di controllo TF1.x, segnala un problema con i passaggi di riproduzione.
Il comportamento dell'API TensorShape cambia
La classe TensorShape
è stata semplificata per contenere oggetti int
s, anziché tf.compat.v1.Dimension
. Quindi non è necessario chiamare .value
per ottenere un int
.
I singoli oggetti tf.compat.v1.Dimension
sono ancora accessibili da tf.TensorShape.dims
.
Per isolare l'impatto di questa modifica del comportamento sul codice, puoi utilizzare tf.compat.v1.disable_v2_tensorshape()
e tf.compat.v1.enable_v2_tensorshape()
per disabilitare o abilitare globalmente questa modifica del comportamento.
Di seguito vengono illustrate le differenze tra TF1.x e 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])
Se avevi questo in TF1.x:
value = shape[i].value
Quindi fallo in TF2:
value = shape[i]
value
16
Se avevi questo in TF1.x:
for dim in shape:
value = dim.value
print(value)
Quindi, fallo in TF2:
for value in shape:
print(value)
16 None 256
Se lo avevi in TF1.x (o usavi qualsiasi altro metodo di dimensione):
dim = shape[i]
dim.assert_is_compatible_with(other_dim)
Quindi fallo in 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
Il valore booleano di un tf.TensorShape
è True
se il rango è noto, False
in caso contrario.
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
Potenziali errori dovuti a modifiche di TensorShape
È improbabile che le modifiche al comportamento di TensorShape interrompano silenziosamente il codice. Tuttavia, potresti vedere che il codice relativo alla forma inizia a generare AttributeError
s poiché int
s e None
s non hanno gli stessi attributi di tf.compat.v1.Dimension
s. Di seguito sono riportati alcuni esempi di questi AttributeError
:
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'
Uguaglianza tensoriale per valore
Gli operatori binari ==
e !=
su variabili e tensori sono stati modificati per confrontare in base al valore in TF2 anziché in base al riferimento all'oggetto come in TF1.x. Inoltre, tensori e variabili non sono più direttamente utilizzabili come hash o utilizzabili in set o chiavi dict, perché potrebbe non essere possibile eseguire l'hashing per valore. Invece, espongono un metodo .ref()
che puoi usare per ottenere un riferimento hashable al tensore o alla variabile.
Per isolare l'impatto di questa modifica del comportamento, puoi utilizzare tf.compat.v1.disable_tensor_equality()
e tf.compat.v1.enable_tensor_equality()
per disabilitare o abilitare globalmente questa modifica del comportamento.
Ad esempio, in TF1.x, due variabili con lo stesso valore restituiranno false quando si utilizza l'operatore ==
:
tf.compat.v1.disable_tensor_equality()
x = tf.Variable(0.0)
y = tf.Variable(0.0)
x == y
False
Mentre in TF2 con i controlli di uguaglianza del tensore abilitati, x == y
restituirà 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>
Quindi, in TF2, se hai bisogno di confrontare per riferimento a un oggetto assicurati di usare is
e is not
tf.compat.v1.enable_tensor_equality()
x = tf.Variable(0.0)
y = tf.Variable(0.0)
x is y
False
Tensori e variabili di hashing
Con i comportamenti di TF1.x potevi aggiungere direttamente variabili e tensori a strutture di dati che richiedono l'hashing, come le chiavi set
e dict
.
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>}
Tuttavia, in TF2 con l'uguaglianza del tensore abilitata, i tensori e le variabili sono resi non hash a causa della semantica dell'operatore ==
e !=
che cambia in controlli di uguaglianza dei valori.
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.
Quindi, in TF2 se è necessario utilizzare oggetti tensore o variabili come chiavi o set
contenuti, è possibile utilizzare tensor.ref()
per ottenere un riferimento hashable che può essere utilizzato come chiave:
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>>}
Se necessario, puoi anche ottenere il tensore o la variabile dal riferimento usando reference.deref()
:
referenced_var = x.ref().deref()
assert referenced_var is x
referenced_var
<tf.Variable 'Variable:0' shape=() dtype=float32, numpy=0.0>
Risorse e ulteriori letture
- Visita la sezione Migrazione a TF2 per ulteriori informazioni sulla migrazione a TF2 da TF1.x.
- Leggi la guida alla mappatura dei modelli per saperne di più sulla mappatura dei tuoi modelli TF1.x per lavorare direttamente in TF2.