Voir sur TensorFlow.org | Exécuter dans Google Colab | Voir la source sur GitHub | Télécharger le cahier |
Pour faire du machine learning dans TensorFlow, vous devrez probablement définir, enregistrer et restaurer un modèle.
Un modèle est, abstraitement :
- Une fonction qui calcule quelque chose sur les tenseurs (une passe avant )
- Quelques variables pouvant être mises à jour en réponse à la formation
Dans ce guide, vous allez sous la surface de Keras pour voir comment les modèles TensorFlow sont définis. Cela examine comment TensorFlow collecte les variables et les modèles, ainsi que la façon dont ils sont enregistrés et restaurés.
Installer
import tensorflow as tf
from datetime import datetime
%load_ext tensorboard
Définir des modèles et des calques dans TensorFlow
La plupart des modèles sont constitués de couches. Les couches sont des fonctions avec une structure mathématique connue qui peuvent être réutilisées et ont des variables entraînables. Dans TensorFlow, la plupart des implémentations de haut niveau de couches et de modèles, telles que Keras ou Sonnet , sont construites sur la même classe fondamentale : tf.Module
.
Voici un exemple d'un tf.Module
très simple qui fonctionne sur un tenseur scalaire :
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>
Les modules et, par extension, les couches sont une terminologie d'apprentissage en profondeur pour les "objets": ils ont un état interne et des méthodes qui utilisent cet état.
Il n'y a rien de spécial à propos de __call__
sauf qu'il agit comme un callable Python ; vous pouvez invoquer vos modèles avec toutes les fonctions que vous souhaitez.
Vous pouvez activer et désactiver la possibilité d'apprentissage des variables pour n'importe quelle raison, y compris le gel des couches et des variables lors du réglage fin.
En sous- tf.Module
, toutes les instances de tf.Variable
ou tf.Module
assignées aux propriétés de cet objet sont automatiquement collectées. Cela vous permet d'enregistrer et de charger des variables, ainsi que de créer des collections de 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.
Il s'agit d'un exemple de modèle de couche linéaire à deux couches composé de modules.
D'abord une couche dense (linéaire):
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)
Et puis le modèle complet, qui crée deux instances de calque et les applique :
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)
Les instances de tf.Module
collecteront automatiquement, de manière récursive, toutes les instances de tf.Variable
ou tf.Module
qui lui sont affectées. Cela vous permet de gérer des collections de tf.Module
s avec une seule instance de modèle, et d'enregistrer et de charger des modèles entiers.
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)>
Attente pour créer des variables
Vous avez peut-être remarqué ici que vous devez définir à la fois les tailles d'entrée et de sortie du calque. C'est ainsi que la variable w
a une forme connue et peut être allouée.
En reportant la création de la variable à la première fois que le module est appelé avec une forme d'entrée spécifique, vous n'avez pas besoin de spécifier la taille d'entrée à l'avance.
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)
Cette flexibilité est la raison pour laquelle les couches TensorFlow n'ont souvent besoin que de spécifier la forme de leurs sorties, comme dans tf.keras.layers.Dense
, plutôt que la taille d'entrée et de sortie.
Gain de poids
Vous pouvez enregistrer un tf.Module
à la fois comme point de contrôle et comme SavedModel .
Les points de contrôle ne sont que les poids (c'est-à-dire les valeurs de l'ensemble des variables à l'intérieur du module et de ses sous-modules) :
chkp_path = "my_checkpoint"
checkpoint = tf.train.Checkpoint(model=my_model)
checkpoint.write(chkp_path)
'my_checkpoint'
Les points de contrôle se composent de deux types de fichiers : les données elles-mêmes et un fichier d'index pour les métadonnées. Le fichier d'index garde une trace de ce qui est réellement enregistré et de la numérotation des points de contrôle, tandis que les données des points de contrôle contiennent les valeurs des variables et leurs chemins de recherche d'attribut.
ls my_checkpoint*
my_checkpoint.data-00000-of-00001 my_checkpoint.index
Vous pouvez regarder à l'intérieur d'un point de contrôle pour vous assurer que toute la collection de variables est enregistrée, triée par l'objet Python qui les contient.
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])]
Pendant la formation distribuée (multi-machine), ils peuvent être partitionnés, c'est pourquoi ils sont numérotés (par exemple, '00000-of-00001'). Dans ce cas, cependant, il n'y a qu'un seul fragment.
Lorsque vous rechargez des modèles, vous écrasez les valeurs de votre objet 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)>
Fonctions de sauvegarde
TensorFlow peut exécuter des modèles sans les objets Python d'origine, comme le montrent TensorFlow Serving et TensorFlow Lite , même lorsque vous téléchargez un modèle entraîné à partir de TensorFlow Hub .
TensorFlow doit savoir comment effectuer les calculs décrits en Python, mais sans le code d'origine . Pour cela, vous pouvez réaliser un graphe , décrit dans le guide Présentation des graphes et des fonctions .
Ce graphique contient des opérations, ou ops , qui implémentent la fonction.
Vous pouvez définir un graphe dans le modèle ci-dessus en ajoutant le décorateur @tf.function
pour indiquer que ce code doit s'exécuter sous forme de graphe.
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")
Le module que vous avez créé fonctionne exactement comme avant. Chaque signature unique transmise à la fonction crée un graphique distinct. Consultez le guide Introduction aux graphiques et aux fonctions pour plus de détails.
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)
Vous pouvez visualiser le graphique en le traçant dans un résumé 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)
Lancez TensorBoard pour afficher la trace résultante :
#docs_infra: no_execute
%tensorboard --logdir logs/func
Création d'un SavedModel
La méthode recommandée pour partager des modèles entièrement formés consiste à utiliser SavedModel
. SavedModel
contient à la fois une collection de fonctions et une collection de poids.
Vous pouvez enregistrer le modèle que vous venez de former comme suit :
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
Le fichier saved_model.pb
est un tampon de protocole décrivant la fonction tf.Graph
.
Les modèles et les couches peuvent être chargés à partir de cette représentation sans réellement créer une instance de la classe qui l'a créé. Ceci est souhaitable dans les situations où vous n'avez pas (ou ne voulez pas) d'interpréteur Python, comme le service à grande échelle ou sur un périphérique périphérique, ou dans les situations où le code Python d'origine n'est pas disponible ou pratique à utiliser.
Vous pouvez charger le modèle en tant que nouvel objet :
new_model = tf.saved_model.load("the_saved_model")
new_model
, créé à partir du chargement d'un modèle enregistré, est un objet utilisateur interne TensorFlow sans aucune connaissance de la classe. Il n'est pas de type SequentialModule
.
isinstance(new_model, SequentialModule)
False
Ce nouveau modèle fonctionne sur les signatures d'entrée déjà définies. Vous ne pouvez pas ajouter plus de signatures à un modèle restauré comme celui-ci.
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)
Ainsi, en utilisant SavedModel
, vous pouvez enregistrer les poids et les graphiques TensorFlow à l'aide tf.Module
, puis les recharger.
Modèles et calques Keras
Notez que jusqu'à présent, il n'y a aucune mention de Keras. Vous pouvez créer votre propre API de haut niveau au-dessus de tf.Module
, et les gens l'ont fait.
Dans cette section, vous examinerez comment Keras utilise tf.Module
. Un guide d'utilisation complet des modèles Keras se trouve dans le guide Keras .
Couches Keras
tf.keras.layers.Layer
est la classe de base de toutes les couches Keras et hérite de tf.Module
.
Vous pouvez convertir un module en une couche Keras simplement en échangeant le parent, puis en changeant __call__
pour 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)
Les couches Keras ont leur propre __call__
qui effectue une comptabilité décrite dans la section suivante, puis appelle call()
. Vous ne devriez remarquer aucun changement dans la fonctionnalité.
simple_layer([[2.0, 2.0, 2.0]])
<tf.Tensor: shape=(1, 3), dtype=float32, numpy=array([[0. , 0.179402, 0. ]], dtype=float32)>
L'étape de build
Comme indiqué, il est pratique dans de nombreux cas d'attendre pour créer des variables jusqu'à ce que vous soyez sûr de la forme d'entrée.
Les couches Keras sont livrées avec une étape de cycle de vie supplémentaire qui vous permet plus de flexibilité dans la façon dont vous définissez vos couches. Ceci est défini dans la fonction build
.
build
est appelé exactement une fois, et il est appelé avec la forme de l'entrée. Il est généralement utilisé pour créer des variables (pondérations).
Vous pouvez réécrire la couche MyDense
ci-dessus pour être flexible sur la taille de ses entrées :
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)
À ce stade, le modèle n'a pas été construit, il n'y a donc pas de variables :
flexible_dense.variables
[]
L'appel de la fonction alloue des variables de taille appropriée :
# 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)>]
Étant donné que build
n'est appelé qu'une seule fois, les entrées seront rejetées si la forme d'entrée n'est pas compatible avec les variables de la couche :
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]
Les couches Keras ont beaucoup plus de fonctionnalités supplémentaires, notamment :
- Pertes facultatives
- Prise en charge des métriques
- Prise en charge intégrée d'un argument de
training
facultatif pour différencier l'utilisation de la formation et de l'inférence -
get_config
etfrom_config
qui vous permettent de stocker avec précision les configurations pour permettre le clonage de modèle en Python
Découvrez-les dans le guide complet des calques et modèles personnalisés.
Modèles Kera
Vous pouvez définir votre modèle sous forme de couches Keras imbriquées.
Cependant, Keras fournit également une classe de modèle complète appelée tf.keras.Model
. Il hérite de tf.keras.layers.Layer
, donc un modèle Keras peut être utilisé, imbriqué et enregistré de la même manière que les calques Keras. Les modèles Keras sont dotés de fonctionnalités supplémentaires qui les rendent faciles à former, évaluer, charger, enregistrer et même former sur plusieurs machines.
Vous pouvez définir le SequentialModule
ci-dessus avec un code presque identique, en convertissant à nouveau __call__
en call()
et en changeant le parent :
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)
Toutes les mêmes fonctionnalités sont disponibles, y compris les variables de suivi et les sous-modules.
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>)
tf.keras.Model
est une approche très Pythonique pour créer des modèles TensorFlow. Si vous migrez des modèles à partir d'autres frameworks, cela peut être très simple.
Si vous construisez des modèles qui sont de simples assemblages de couches et d'entrées existantes, vous pouvez gagner du temps et de l'espace en utilisant l' API fonctionnelle , qui comprend des fonctionnalités supplémentaires autour de la reconstruction et de l'architecture du modèle.
Voici le même modèle avec l'API fonctionnelle :
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 principale différence ici est que la forme d'entrée est spécifiée à l'avance dans le cadre du processus de construction fonctionnelle. L'argument input_shape
dans ce cas n'a pas besoin d'être complètement spécifié ; vous pouvez laisser certaines dimensions sur None
.
Enregistrement des modèles Keras
Les modèles Keras peuvent être contrôlés, et cela ressemblera à tf.Module
.
Les modèles Keras peuvent également être enregistrés avec tf.saved_model.save()
, car ce sont des modules. Cependant, les modèles Keras ont des méthodes pratiques et d'autres fonctionnalités :
my_sequential_model.save("exname_of_file")
INFO:tensorflow:Assets written to: exname_of_file/assets
Tout aussi facilement, ils peuvent être rechargés dans :
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
enregistre également les états des métriques, des pertes et de l'optimiseur.
Ce modèle reconstruit peut être utilisé et produira le même résultat lorsqu'il sera appelé sur les mêmes données :
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)>
Il y a plus à savoir sur l'enregistrement et la sérialisation des modèles Keras, y compris la fourniture de méthodes de configuration pour les couches personnalisées pour la prise en charge des fonctionnalités. Consultez le guide d'enregistrement et de sérialisation .
Et après
Si vous souhaitez en savoir plus sur Keras, vous pouvez suivre les guides Keras existants ici .
Un autre exemple d'API de haut niveau construite sur tf.module
est Sonnet de DeepMind, qui est couvert sur leur site .