Visualizza su TensorFlow.org | Esegui in Google Colab | Visualizza l'origine su GitHub | Scarica quaderno |
Per eseguire l'apprendimento automatico in TensorFlow, è probabile che tu debba definire, salvare e ripristinare un modello.
Un modello è, astrattamente:
- Una funzione che calcola qualcosa sui tensori (un passaggio in avanti )
- Alcune variabili che possono essere aggiornate in risposta alla formazione
In questa guida, andrai sotto la superficie di Keras per vedere come vengono definiti i modelli TensorFlow. Questo esamina come TensorFlow raccoglie variabili e modelli, nonché come vengono salvati e ripristinati.
Impostare
import tensorflow as tf
from datetime import datetime
%load_ext tensorboard
Definizione di modelli e livelli in TensorFlow
La maggior parte dei modelli è composta da strati. I livelli sono funzioni con una struttura matematica nota che possono essere riutilizzate e hanno variabili addestrabili. In TensorFlow, la maggior parte delle implementazioni di alto livello di livelli e modelli, come Keras o Sonnet , sono basate sulla stessa classe fondamentale: tf.Module
.
Ecco un esempio di un tf.Module
molto semplice che opera su un tensore scalare:
class SimpleModule(tf.Module):
def __init__(self, name=None):
super().__init__(name=name)
self.a_variable = tf.Variable(5.0, name="train_me")
self.non_trainable_variable = tf.Variable(5.0, trainable=False, name="do_not_train_me")
def __call__(self, x):
return self.a_variable * x + self.non_trainable_variable
simple_module = SimpleModule(name="simple")
simple_module(tf.constant(5.0))
<tf.Tensor: shape=(), dtype=float32, numpy=30.0>
I moduli e, per estensione, i livelli sono una terminologia di deep learning per "oggetti": hanno uno stato interno e metodi che utilizzano quello stato.
Non c'è niente di speciale in __call__
tranne che per agire come un richiamabile Python ; puoi invocare i tuoi modelli con qualsiasi funzione desideri.
È possibile attivare e disattivare l'addestrabilità delle variabili per qualsiasi motivo, incluso il congelamento dei livelli e delle variabili durante la messa a punto.
Sottoclasse tf.Module
, tutte le istanze tf.Variable
o tf.Module
assegnate alle proprietà di questo oggetto vengono raccolte automaticamente. Ciò consente di salvare e caricare variabili e anche di creare raccolte di tf.Module
s.
# All trainable variables
print("trainable variables:", simple_module.trainable_variables)
# Every variable
print("all variables:", simple_module.variables)
trainable variables: (<tf.Variable 'train_me:0' shape=() dtype=float32, numpy=5.0>,) all variables: (<tf.Variable 'train_me:0' shape=() dtype=float32, numpy=5.0>, <tf.Variable 'do_not_train_me:0' shape=() dtype=float32, numpy=5.0>) 2021-10-26 01:29:45.284549: W tensorflow/python/util/util.cc:348] Sets are not currently considered sequences, but this may change in the future, so consider avoiding using them.
Questo è un esempio di un modello di livello lineare a due strati composto da moduli.
Prima uno strato denso (lineare):
class Dense(tf.Module):
def __init__(self, in_features, out_features, name=None):
super().__init__(name=name)
self.w = tf.Variable(
tf.random.normal([in_features, out_features]), name='w')
self.b = tf.Variable(tf.zeros([out_features]), name='b')
def __call__(self, x):
y = tf.matmul(x, self.w) + self.b
return tf.nn.relu(y)
E poi il modello completo, che crea due istanze di livello e le applica:
class SequentialModule(tf.Module):
def __init__(self, name=None):
super().__init__(name=name)
self.dense_1 = Dense(in_features=3, out_features=3)
self.dense_2 = Dense(in_features=3, out_features=2)
def __call__(self, x):
x = self.dense_1(x)
return self.dense_2(x)
# You have made a model!
my_model = SequentialModule(name="the_model")
# Call it, with random results
print("Model results:", my_model(tf.constant([[2.0, 2.0, 2.0]])))
Model results: tf.Tensor([[7.706234 3.0919805]], shape=(1, 2), dtype=float32)
Le istanze tf.Module
raccoglieranno automaticamente, in modo ricorsivo, tutte le istanze tf.Variable
o tf.Module
ad esse assegnate. Ciò consente di gestire raccolte di tf.Module
con una singola istanza di modello e di salvare e caricare interi modelli.
print("Submodules:", my_model.submodules)
Submodules: (<__main__.Dense object at 0x7f7ab2391290>, <__main__.Dense object at 0x7f7b6869ea10>)
for var in my_model.variables:
print(var, "\n")
<tf.Variable 'b:0' shape=(3,) dtype=float32, numpy=array([0., 0., 0.], dtype=float32)> <tf.Variable 'w:0' shape=(3, 3) dtype=float32, numpy= array([[ 0.05711935, 0.22440144, 0.6370985 ], [ 0.3136791 , -1.7006774 , 0.7256515 ], [ 0.16120772, -0.8412193 , 0.5250952 ]], dtype=float32)> <tf.Variable 'b:0' shape=(2,) dtype=float32, numpy=array([0., 0.], dtype=float32)> <tf.Variable 'w:0' shape=(3, 2) dtype=float32, numpy= array([[-0.5353216 , 1.2815404 ], [ 0.62764466, 0.47087234], [ 2.19187 , 0.45777202]], dtype=float32)>
In attesa di creare variabili
Potresti aver notato qui che devi definire le dimensioni di input e output per il livello. In questo modo la variabile w
ha una forma nota e può essere allocata.
Rinviando la creazione della variabile alla prima volta che il modulo viene chiamato con una forma di input specifica, non è necessario specificare la dimensione dell'input in anticipo.
class FlexibleDenseModule(tf.Module):
# Note: No need for `in_features`
def __init__(self, out_features, name=None):
super().__init__(name=name)
self.is_built = False
self.out_features = out_features
def __call__(self, x):
# Create variables on first call.
if not self.is_built:
self.w = tf.Variable(
tf.random.normal([x.shape[-1], self.out_features]), name='w')
self.b = tf.Variable(tf.zeros([self.out_features]), name='b')
self.is_built = True
y = tf.matmul(x, self.w) + self.b
return tf.nn.relu(y)
# Used in a module
class MySequentialModule(tf.Module):
def __init__(self, name=None):
super().__init__(name=name)
self.dense_1 = FlexibleDenseModule(out_features=3)
self.dense_2 = FlexibleDenseModule(out_features=2)
def __call__(self, x):
x = self.dense_1(x)
return self.dense_2(x)
my_model = MySequentialModule(name="the_model")
print("Model results:", my_model(tf.constant([[2.0, 2.0, 2.0]])))
Model results: tf.Tensor([[4.0598335 0. ]], shape=(1, 2), dtype=float32)
Questa flessibilità è il motivo per cui i livelli di TensorFlow spesso devono solo specificare la forma dei loro output, ad esempio in tf.keras.layers.Dense
, piuttosto che la dimensione sia dell'input che dell'output.
Risparmio di pesi
Puoi salvare un tf.Module
sia come checkpoint che come SavedModel .
I checkpoint sono solo i pesi (ovvero i valori dell'insieme di variabili all'interno del modulo e dei suoi sottomoduli):
chkp_path = "my_checkpoint"
checkpoint = tf.train.Checkpoint(model=my_model)
checkpoint.write(chkp_path)
'my_checkpoint'
I checkpoint sono costituiti da due tipi di file: i dati stessi e un file di indice per i metadati. Il file di indice tiene traccia di ciò che viene effettivamente salvato e la numerazione dei checkpoint, mentre i dati del checkpoint contengono i valori delle variabili e i relativi percorsi di ricerca degli attributi.
ls my_checkpoint*
my_checkpoint.data-00000-of-00001 my_checkpoint.index
Puoi guardare all'interno di un checkpoint per assicurarti che l'intera raccolta di variabili sia salvata, ordinata in base all'oggetto Python che le contiene.
tf.train.list_variables(chkp_path)
[('_CHECKPOINTABLE_OBJECT_GRAPH', []), ('model/dense_1/b/.ATTRIBUTES/VARIABLE_VALUE', [3]), ('model/dense_1/w/.ATTRIBUTES/VARIABLE_VALUE', [3, 3]), ('model/dense_2/b/.ATTRIBUTES/VARIABLE_VALUE', [2]), ('model/dense_2/w/.ATTRIBUTES/VARIABLE_VALUE', [3, 2])]
Durante l'addestramento distribuito (multi-macchina) possono essere partizionati, motivo per cui sono numerati (ad esempio, '00000-of-00001'). In questo caso, però, c'è solo uno shard.
Quando carichi nuovamente i modelli, sovrascrivi i valori nel tuo oggetto Python.
new_model = MySequentialModule()
new_checkpoint = tf.train.Checkpoint(model=new_model)
new_checkpoint.restore("my_checkpoint")
# Should be the same result as above
new_model(tf.constant([[2.0, 2.0, 2.0]]))
<tf.Tensor: shape=(1, 2), dtype=float32, numpy=array([[4.0598335, 0. ]], dtype=float32)>
Funzioni di salvataggio
TensorFlow può eseguire modelli senza gli oggetti Python originali, come dimostrato da TensorFlow Serving e TensorFlow Lite , anche quando scarichi un modello addestrato da TensorFlow Hub .
TensorFlow deve sapere come eseguire i calcoli descritti in Python, ma senza il codice originale . Per fare ciò, puoi creare un grafico , che è descritto nella Guida introduttiva ai grafici e alle funzioni .
Questo grafico contiene operazioni, o ops , che implementano la funzione.
Puoi definire un grafico nel modello sopra aggiungendo il decoratore @tf.function
per indicare che questo codice deve essere eseguito come grafico.
class MySequentialModule(tf.Module):
def __init__(self, name=None):
super().__init__(name=name)
self.dense_1 = Dense(in_features=3, out_features=3)
self.dense_2 = Dense(in_features=3, out_features=2)
@tf.function
def __call__(self, x):
x = self.dense_1(x)
return self.dense_2(x)
# You have made a model with a graph!
my_model = MySequentialModule(name="the_model")
Il modulo che hai creato funziona esattamente come prima. Ogni firma univoca passata alla funzione crea un grafico separato. Per i dettagli, consultare la Guida introduttiva ai grafici e alle funzioni .
print(my_model([[2.0, 2.0, 2.0]]))
print(my_model([[[2.0, 2.0, 2.0], [2.0, 2.0, 2.0]]]))
tf.Tensor([[0.62891716 0. ]], shape=(1, 2), dtype=float32) tf.Tensor( [[[0.62891716 0. ] [0.62891716 0. ]]], shape=(1, 2, 2), dtype=float32)
Puoi visualizzare il grafico tracciandolo all'interno di un riepilogo TensorBoard.
# Set up logging.
stamp = datetime.now().strftime("%Y%m%d-%H%M%S")
logdir = "logs/func/%s" % stamp
writer = tf.summary.create_file_writer(logdir)
# Create a new model to get a fresh trace
# Otherwise the summary will not see the graph.
new_model = MySequentialModule()
# Bracket the function call with
# tf.summary.trace_on() and tf.summary.trace_export().
tf.summary.trace_on(graph=True)
tf.profiler.experimental.start(logdir)
# Call only one tf.function when tracing.
z = print(new_model(tf.constant([[2.0, 2.0, 2.0]])))
with writer.as_default():
tf.summary.trace_export(
name="my_func_trace",
step=0,
profiler_outdir=logdir)
tf.Tensor([[0. 0.01750386]], shape=(1, 2), dtype=float32)
Avvia TensorBoard per visualizzare la traccia risultante:
#docs_infra: no_execute
%tensorboard --logdir logs/func
Creazione di un SavedModel
Il modo consigliato per condividere modelli completamente addestrati consiste nell'usare SavedModel
. SavedModel
contiene sia una raccolta di funzioni che una raccolta di pesi.
Puoi salvare il modello che hai appena addestrato come segue:
tf.saved_model.save(my_model, "the_saved_model")
INFO:tensorflow:Assets written to: the_saved_model/assets
# Inspect the SavedModel in the directory
ls -l the_saved_model
total 24 drwxr-sr-x 2 kbuilder kokoro 4096 Oct 26 01:29 assets -rw-rw-r-- 1 kbuilder kokoro 14702 Oct 26 01:29 saved_model.pb drwxr-sr-x 2 kbuilder kokoro 4096 Oct 26 01:29 variables
# The variables/ directory contains a checkpoint of the variables
ls -l the_saved_model/variables
total 8 -rw-rw-r-- 1 kbuilder kokoro 408 Oct 26 01:29 variables.data-00000-of-00001 -rw-rw-r-- 1 kbuilder kokoro 356 Oct 26 01:29 variables.index
Il file saved_model.pb
è un buffer di protocollo che descrive il funzionale tf.Graph
.
Modelli e livelli possono essere caricati da questa rappresentazione senza creare effettivamente un'istanza della classe che l'ha creata. Ciò è auspicabile in situazioni in cui non si dispone (o si desidera) un interprete Python, come servire su larga scala o su un dispositivo perimetrale, o in situazioni in cui il codice Python originale non è disponibile o pratico da usare.
Puoi caricare il modello come nuovo oggetto:
new_model = tf.saved_model.load("the_saved_model")
new_model
, creato dal caricamento di un modello salvato, è un oggetto utente TensorFlow interno senza alcuna conoscenza della classe. Non è di tipo SequentialModule
.
isinstance(new_model, SequentialModule)
False
Questo nuovo modello funziona sulle firme di input già definite. Non puoi aggiungere più firme a un modello ripristinato in questo modo.
print(my_model([[2.0, 2.0, 2.0]]))
print(my_model([[[2.0, 2.0, 2.0], [2.0, 2.0, 2.0]]]))
tf.Tensor([[0.62891716 0. ]], shape=(1, 2), dtype=float32) tf.Tensor( [[[0.62891716 0. ] [0.62891716 0. ]]], shape=(1, 2, 2), dtype=float32)
Pertanto, utilizzando SavedModel
, è possibile salvare pesi e grafici TensorFlow utilizzando tf.Module
, quindi caricarli di nuovo.
Modelli e strati Keras
Nota che fino a questo punto non si fa menzione di Keras. Puoi creare la tua API di alto livello su tf.Module
e le persone lo hanno fatto.
In questa sezione, esaminerai come Keras utilizza tf.Module
. Una guida utente completa ai modelli Keras è disponibile nella guida Keras .
Strati di Keras
tf.keras.layers.Layer
è la classe base di tutti i livelli Keras ed eredita da tf.Module
.
Puoi convertire un modulo in un livello Keras semplicemente sostituendo il genitore e quindi cambiando __call__
in call
:
class MyDense(tf.keras.layers.Layer):
# Adding **kwargs to support base Keras layer arguments
def __init__(self, in_features, out_features, **kwargs):
super().__init__(**kwargs)
# This will soon move to the build step; see below
self.w = tf.Variable(
tf.random.normal([in_features, out_features]), name='w')
self.b = tf.Variable(tf.zeros([out_features]), name='b')
def call(self, x):
y = tf.matmul(x, self.w) + self.b
return tf.nn.relu(y)
simple_layer = MyDense(name="simple", in_features=3, out_features=3)
I livelli Keras hanno il loro __call__
che fa alcune operazioni di contabilità descritte nella sezione successiva e quindi chiama call()
. Non dovresti notare alcun cambiamento nella funzionalità.
simple_layer([[2.0, 2.0, 2.0]])
<tf.Tensor: shape=(1, 3), dtype=float32, numpy=array([[0. , 0.179402, 0. ]], dtype=float32)>
La fase build
Come notato, in molti casi è conveniente attendere per creare variabili finché non si è sicuri della forma di input.
I livelli Keras sono dotati di un passaggio aggiuntivo del ciclo di vita che ti consente una maggiore flessibilità nel modo in cui definisci i tuoi livelli. Questo è definito nella funzione build
.
build
viene chiamato esattamente una volta e viene chiamato con la forma dell'input. Di solito viene utilizzato per creare variabili (pesi).
Puoi riscrivere il livello MyDense
sopra per essere flessibile alle dimensioni dei suoi input:
class FlexibleDense(tf.keras.layers.Layer):
# Note the added `**kwargs`, as Keras supports many arguments
def __init__(self, out_features, **kwargs):
super().__init__(**kwargs)
self.out_features = out_features
def build(self, input_shape): # Create the state of the layer (weights)
self.w = tf.Variable(
tf.random.normal([input_shape[-1], self.out_features]), name='w')
self.b = tf.Variable(tf.zeros([self.out_features]), name='b')
def call(self, inputs): # Defines the computation from inputs to outputs
return tf.matmul(inputs, self.w) + self.b
# Create the instance of the layer
flexible_dense = FlexibleDense(out_features=3)
A questo punto il modello non è stato costruito, quindi non ci sono variabili:
flexible_dense.variables
[]
La chiamata alla funzione alloca variabili di dimensioni appropriate:
# Call it, with predictably random results
print("Model results:", flexible_dense(tf.constant([[2.0, 2.0, 2.0], [3.0, 3.0, 3.0]])))
Model results: tf.Tensor( [[-1.6998017 1.6444504 -1.3103955] [-2.5497022 2.4666753 -1.9655929]], shape=(2, 3), dtype=float32)
flexible_dense.variables
[<tf.Variable 'flexible_dense/w:0' shape=(3, 3) dtype=float32, numpy= array([[ 1.277462 , 0.5399406 , -0.301957 ], [-1.6277349 , 0.7374014 , -1.7651852 ], [-0.49962795, -0.45511687, 1.4119445 ]], dtype=float32)>, <tf.Variable 'flexible_dense/b:0' shape=(3,) dtype=float32, numpy=array([0., 0., 0.], dtype=float32)>]
Poiché build
viene chiamato solo una volta, gli input verranno rifiutati se la forma dell'input non è compatibile con le variabili del livello:
try:
print("Model results:", flexible_dense(tf.constant([[2.0, 2.0, 2.0, 2.0]])))
except tf.errors.InvalidArgumentError as e:
print("Failed:", e)
Failed: In[0] mismatch In[1] shape: 4 vs. 3: [1,4] [3,3] 0 0 [Op:MatMul]
I livelli Keras hanno molte più funzionalità extra tra cui:
- Perdite facoltative
- Supporto per le metriche
- Supporto integrato per un argomento di
training
facoltativo per distinguere tra utilizzo di addestramento e inferenza -
get_config
efrom_config
metodi che consentono di archiviare accuratamente le configurazioni per consentire la clonazione del modello in Python
Leggi di loro nella guida completa ai livelli e ai modelli personalizzati.
Modelli Keras
Puoi definire il tuo modello come livelli Keras nidificati.
Tuttavia, Keras fornisce anche una classe modello completa denominata tf.keras.Model
. Eredita da tf.keras.layers.Layer
, quindi un modello Keras può essere utilizzato, nidificato e salvato allo stesso modo dei livelli Keras. I modelli Keras sono dotati di funzionalità extra che li rendono facili da addestrare, valutare, caricare, salvare e persino addestrare su più macchine.
Puoi definire il SequentialModule
dall'alto con un codice quasi identico, convertendo ancora __call__
in call()
e cambiando il genitore:
class MySequentialModel(tf.keras.Model):
def __init__(self, name=None, **kwargs):
super().__init__(**kwargs)
self.dense_1 = FlexibleDense(out_features=3)
self.dense_2 = FlexibleDense(out_features=2)
def call(self, x):
x = self.dense_1(x)
return self.dense_2(x)
# You have made a Keras model!
my_sequential_model = MySequentialModel(name="the_model")
# Call it on a tensor, with random results
print("Model results:", my_sequential_model(tf.constant([[2.0, 2.0, 2.0]])))
Model results: tf.Tensor([[5.5604653 3.3511646]], shape=(1, 2), dtype=float32)
Sono disponibili tutte le stesse funzionalità, comprese le variabili di tracciamento e i sottomoduli.
my_sequential_model.variables
[<tf.Variable 'my_sequential_model/flexible_dense_1/w:0' shape=(3, 3) dtype=float32, numpy= array([[ 0.05627853, -0.9386015 , -0.77410126], [ 0.63149 , 1.0802224 , -0.37785745], [-0.24788402, -1.1076807 , -0.5956209 ]], dtype=float32)>, <tf.Variable 'my_sequential_model/flexible_dense_1/b:0' shape=(3,) dtype=float32, numpy=array([0., 0., 0.], dtype=float32)>, <tf.Variable 'my_sequential_model/flexible_dense_2/w:0' shape=(3, 2) dtype=float32, numpy= array([[-0.93912166, 0.77979285], [ 1.4049559 , -1.9380962 ], [-2.6039495 , 0.30885765]], dtype=float32)>, <tf.Variable 'my_sequential_model/flexible_dense_2/b:0' shape=(2,) dtype=float32, numpy=array([0., 0.], dtype=float32)>]
my_sequential_model.submodules
(<__main__.FlexibleDense at 0x7f7b48525550>, <__main__.FlexibleDense at 0x7f7b48508d10>)
L'override tf.keras.Model
è un approccio molto Pythonico alla creazione di modelli TensorFlow. Se stai migrando modelli da altri framework, questo può essere molto semplice.
Se stai costruendo modelli che sono semplici assemblaggi di livelli e input esistenti, puoi risparmiare tempo e spazio utilizzando l' API funzionale , che include funzionalità aggiuntive relative alla ricostruzione e all'architettura del modello.
Ecco lo stesso modello con l'API funzionale:
inputs = tf.keras.Input(shape=[3,])
x = FlexibleDense(3)(inputs)
x = FlexibleDense(2)(x)
my_functional_model = tf.keras.Model(inputs=inputs, outputs=x)
my_functional_model.summary()
Model: "model" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= input_1 (InputLayer) [(None, 3)] 0 _________________________________________________________________ flexible_dense_3 (FlexibleDe (None, 3) 12 _________________________________________________________________ flexible_dense_4 (FlexibleDe (None, 2) 8 ================================================================= Total params: 20 Trainable params: 20 Non-trainable params: 0 _________________________________________________________________
my_functional_model(tf.constant([[2.0, 2.0, 2.0]]))
<tf.Tensor: shape=(1, 2), dtype=float32, numpy=array([[8.219393, 4.511119]], dtype=float32)>
La differenza principale qui è che la forma di input è specificata in anticipo come parte del processo di costruzione funzionale. L'argomento input_shape
in questo caso non deve essere specificato completamente; puoi lasciare alcune dimensioni come None
.
Salvataggio dei modelli Keras
I modelli Keras possono essere sottoposti a checkpoint e sarà lo stesso di tf.Module
.
I modelli Keras possono anche essere salvati con tf.saved_model.save()
, poiché sono moduli. Tuttavia, i modelli Keras hanno metodi pratici e altre funzionalità:
my_sequential_model.save("exname_of_file")
INFO:tensorflow:Assets written to: exname_of_file/assets
Altrettanto facilmente, possono essere ricaricati in:
reconstructed_model = tf.keras.models.load_model("exname_of_file")
WARNING:tensorflow:No training configuration found in save file, so the model was *not* compiled. Compile it manually.
Keras SavedModels
salva anche gli stati di metrica, perdita e ottimizzatore.
Questo modello ricostruito può essere utilizzato e produrrà lo stesso risultato quando richiamato sugli stessi dati:
reconstructed_model(tf.constant([[2.0, 2.0, 2.0]]))
<tf.Tensor: shape=(1, 2), dtype=float32, numpy=array([[5.5604653, 3.3511646]], dtype=float32)>
C'è altro da sapere sul salvataggio e la serializzazione dei modelli Keras, inclusa la fornitura di metodi di configurazione per livelli personalizzati per il supporto delle funzionalità. Consulta la guida al salvataggio e alla serializzazione .
Qual è il prossimo
Se vuoi conoscere maggiori dettagli su Keras, puoi seguire le guide Keras esistenti qui .
Un altro esempio di API di alto livello costruita su tf.module
è Sonnet di DeepMind, che è trattato sul loro sito .