Aide à protéger la Grande barrière de corail avec tensorflow sur Kaggle Rejoignez Défi

Introduction aux modules, couches et modèles

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, de façon abstraite :

  • Une fonction qui calcule quelque chose sur tenseurs (une passe en avant)
  • Quelques variables pouvant être mises à jour en réponse à la formation

Dans ce guide, vous irez 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 couches 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 pouvant être entraînées. Dans tensorflow, la plupart des implémentations de haut niveau de couches et modèles, tels que Keras ou Sonnet , sont construits sur la même classe fondamentale: tf.Module .

Voici un exemple d'un très simple tf.Module 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 est spécial à propos de rien __call__ , sauf pour agir comme un appelable Python ; vous pouvez invoquer vos modèles avec les fonctions que vous souhaitez.

Vous pouvez activer et désactiver la capacité d'apprentissage des variables pour n'importe quelle raison, y compris le gel des couches et des variables pendant le réglage fin.

Par sous - classement tf.Module , tout tf.Variable ou tf.Module instances attribuées aux propriétés de cet objet sont automatiquement collectées. Cela vous permet d'enregistrer et de variables de charge, et 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 couche 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)

tf.Module cas recueillerons automatiquement, de manière récursive, tout tf.Variable ou tf.Module cas qui lui est attribué. Cela vous permet de gérer les collections de tf.Module s avec une seule instance de modèle et enregistrer et 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)>

En attente de création de variables

Vous avez peut-être remarqué ici que vous devez définir à la fois les tailles d'entrée et de sortie de la couche. Il en est ainsi le w variable a une forme connue et peut être alloué.

En reportant la création de 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 pourquoi les couches tensorflow souvent besoin de préciser la forme de leurs sorties, comme dans tf.keras.layers.Dense , plutôt que l'entrée et la taille de sortie.

Économiser des poids

Vous pouvez enregistrer un tf.Module à la fois comme un point de contrôle et un SavedModel .

Les points de contrôle ne sont que les poids (c'est-à-dire les valeurs de l'ensemble de 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'attributs.

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])]

Au cours de la formation distribuée (multi-machines), ils peuvent être fragmenté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 chargez à nouveau 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 l'a démontré par tensorflow service et tensorflow Lite , même lorsque vous téléchargez un modèle formé de tensorflow Hub .

Tensorflow a besoin de savoir comment faire les calculs décrits en Python, mais sans le code d' origine. Pour ce faire, vous pouvez faire un graphique, qui est décrit dans l' Introduction au guide des graphiques et des fonctions .

Ce graphique contient les opérations ou les opérations, qui mettent en œuvre la fonction.

Vous pouvez définir un graphique dans le modèle ci - dessus en ajoutant le @tf.function décorateur pour indiquer que ce code devrait fonctionner comme un graphique.

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 de la même manière qu'avant. Chaque signature unique transmise à la fonction crée un graphique distinct. Vérifiez l' Introduction aux graphiques et guide des 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 :

%tensorboard --logdir logs/func

Une capture d'écran du graphique dans TensorBoard

Création d' un SavedModel

La méthode recommandée de partager des modèles complètement formés est d'utiliser SavedModel . SavedModel contient à la fois un ensemble de fonctions et un ensemble de poids.

Vous pouvez enregistrer le modèle que vous venez d'entraîner 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 saved_model.pb fichier est un protocole tampon 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éée. Ceci est souhaité dans les situations où vous n'avez pas (ou ne voulez pas) d'interpréteur Python, comme servir à grande échelle ou sur un périphérique périphérique, ou dans des 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 tensorflow interne sans aucune des connaissances de classe. Il 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 êtes en mesure d'enregistrer des poids tensorflow et des graphiques en utilisant tf.Module , puis les charger à nouveau.

Modèles et couches Keras

Notez que jusqu'à présent, il n'y a aucune mention de Keras. Vous pouvez construire votre propre API de haut niveau sur le dessus de tf.Module , et les gens ont.

Dans cette section, vous examinerez comment Keras utilise tf.Module . Un guide d'utilisation complet aux modèles KERAS se trouve dans le guide de Keras .

Couches Keras

tf.keras.layers.Layer est la classe de base de toutes les couches KERAS, et il hérite de tf.Module .

Vous pouvez convertir un module dans une couche Keras simplement en échangeant le parent puis changer __call__ à l' 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)

Couches KERAS ont leur propre __call__ qui fait un peu de comptabilité décrit dans la section suivante et appelle ensuite 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)>

La build étape

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. Il est défini dans la build fonction.

la build est appelée exactement une fois, et il est appelé à la forme de l'entrée. Il est généralement utilisé pour créer des variables (poids).

Vous pouvez réécrire MyDense couche ci - dessus pour être flexible à 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)>]

Depuis la build est appelée 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
  • Support intégré pour une option de training argument pour différencier entre la formation et l' utilisation d'inférence
  • get_config et from_config méthodes qui vous permettent de stocker de façon précise les configurations pour permettre le clonage de modèle en Python

En savoir plus sur eux dans le guide complet de couches personnalisées et des modèles.

modèles Keras

Vous pouvez définir votre modèle en tant que couches Keras imbriquées.

Cependant, Keras fournit également une classe de modèle multifonction appelé tf.keras.Model . Elle 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 couches KERAS. Les modèles Keras sont livrés avec des 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 d' en haut avec le code presque identique, encore une fois la conversion __call__ d' call() et changer 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>)

Redéfinition tf.keras.Model est une approche très Pythonic pour construire des modèles de 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 des assemblages simples de couches et entrées existantes, vous pouvez gagner du temps et de l' espace en utilisant l' API fonctionnelle , qui vient avec des fonctionnalités supplémentaires autour de la reconstruction du modèle et l' architecture.

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. Le input_shape argument en ce cas ne doit pas être complètement spécifié; vous pouvez laisser certaines dimensions comme None .

Enregistrement des modèles Keras

Modèles KERAS peuvent être un point de reprise, et qui seront les mêmes que tf.Module .

Modèles KERAS peuvent également être enregistrés avec tf.saved_model.save() , car ils 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 également enregistrer métrique, perte, et les états 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 pour l' épargne et sérialisation .

Et après

Si vous voulez en savoir plus de détails sur Keras, vous pouvez suivre les guides existants KERAS ici .

Un autre exemple d'une API de haut niveau construit sur tf.module est Sonnet de DeepMind, qui est couvert sur leur site .