![]() | ![]() | ![]() | ![]() |
Installer
import numpy as np
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
introduction
L'API fonctionnelle Keras est un moyen de créer des modèles plus souples que l' tf.keras.Sequential
API. L'API fonctionnelle peut gérer des modèles avec une topologie non linéaire, des couches partagées et même plusieurs entrées ou sorties.
L'idée principale est qu'un modèle d'apprentissage en profondeur est généralement un graphe acyclique dirigé (DAG) de couches. Ainsi , l'API fonctionnelle est un moyen de construire des graphiques de couches.
Considérez le modèle suivant :
(input: 784-dimensional vectors)
↧
[Dense (64 units, relu activation)]
↧
[Dense (64 units, relu activation)]
↧
[Dense (10 units, softmax activation)]
↧
(output: logits of a probability distribution over 10 classes)
Il s'agit d'un graphique de base avec trois couches. Pour construire ce modèle à l'aide de l'API fonctionnelle, commencez par créer un nœud d'entrée :
inputs = keras.Input(shape=(784,))
La forme des données est définie comme un vecteur de 784 dimensions. La taille du lot est toujours omise car seule la forme de chaque échantillon est spécifiée.
Si, par exemple, vous avez une entrée d'image avec une forme de (32, 32, 3)
, vous devez utiliser:
# Just for demonstration purposes.
img_inputs = keras.Input(shape=(32, 32, 3))
Les inputs
qui est retourné contient des informations sur la forme et dtype
des données d'entrée que vous nourrissez à votre modèle. Voici la forme :
inputs.shape
TensorShape([None, 784])
Voici le type :
inputs.dtype
tf.float32
Vous créez un nouveau nœud dans le graphe des couches en appelant une couche sur cette inputs
objet:
dense = layers.Dense(64, activation="relu")
x = dense(inputs)
L'action « appel de couche » revient à tracer une flèche à partir des « entrées » vers cette couche que vous avez créée. Vous êtes « passer » les entrées à la dense
couche, et vous obtenez x
comme la sortie.
Ajoutons quelques couches supplémentaires au graphique des couches :
x = layers.Dense(64, activation="relu")(x)
outputs = layers.Dense(10)(x)
À ce stade, vous pouvez créer un Model
en spécifiant ses entrées et sorties dans le graphique des couches:
model = keras.Model(inputs=inputs, outputs=outputs, name="mnist_model")
Voyons à quoi ressemble le résumé du modèle :
model.summary()
Model: "mnist_model" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= input_1 (InputLayer) [(None, 784)] 0 _________________________________________________________________ dense (Dense) (None, 64) 50240 _________________________________________________________________ dense_1 (Dense) (None, 64) 4160 _________________________________________________________________ dense_2 (Dense) (None, 10) 650 ================================================================= Total params: 55,050 Trainable params: 55,050 Non-trainable params: 0 _________________________________________________________________
Vous pouvez également tracer le modèle sous forme de graphique :
keras.utils.plot_model(model, "my_first_model.png")
Et, éventuellement, afficher les formes d'entrée et de sortie de chaque couche dans le graphique tracé :
keras.utils.plot_model(model, "my_first_model_with_shape_info.png", show_shapes=True)
Ce chiffre et le code sont presque identiques. Dans la version code, les flèches de connexion sont remplacées par l'opération d'appel.
Un « graphique de couches » est une image mentale intuitive pour un modèle d'apprentissage en profondeur, et l'API fonctionnelle est un moyen de créer des modèles qui reflètent étroitement cela.
Formation, évaluation et inférence
La formation, l' évaluation et le travail d'inférence exactement de la même manière pour les modèles construits en utilisant l'API fonctionnelle pour Sequential
modèles.
Le Model
offre de classe une boucle de formation intégrée (l' fit()
méthode) et une boucle d'évaluation intégrée (l' evaluate()
méthode). Notez que vous pouvez facilement personnaliser ces boucles pour mettre en œuvre des routines de formation au - delà de l' apprentissage supervisé (par exemple GAN ).
Ici, chargez les données d'image MNIST, reformulez-les en vecteurs, adaptez le modèle aux données (tout en surveillant les performances sur une division de validation), puis évaluez le modèle sur les données de test :
(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()
x_train = x_train.reshape(60000, 784).astype("float32") / 255
x_test = x_test.reshape(10000, 784).astype("float32") / 255
model.compile(
loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
optimizer=keras.optimizers.RMSprop(),
metrics=["accuracy"],
)
history = model.fit(x_train, y_train, batch_size=64, epochs=2, validation_split=0.2)
test_scores = model.evaluate(x_test, y_test, verbose=2)
print("Test loss:", test_scores[0])
print("Test accuracy:", test_scores[1])
Epoch 1/2 750/750 [==============================] - 3s 3ms/step - loss: 0.3430 - accuracy: 0.9035 - val_loss: 0.1851 - val_accuracy: 0.9463 Epoch 2/2 750/750 [==============================] - 2s 3ms/step - loss: 0.1585 - accuracy: 0.9527 - val_loss: 0.1366 - val_accuracy: 0.9597 313/313 - 0s - loss: 0.1341 - accuracy: 0.9592 Test loss: 0.13414572179317474 Test accuracy: 0.9592000246047974
Pour en savoir plus, voir la formation et l' évaluation guide.
Enregistrer et sérialiser
Enregistrement des travaux de modèle et sérialisation de la même façon pour les modèles construits en utilisant l'API fonctionnelle comme ils le font pour Sequential
modèles. La méthode standard pour enregistrer un modèle fonctionnel est d'appeler model.save()
pour enregistrer l'ensemble du modèle en un seul fichier. Vous pouvez recréer ultérieurement le même modèle à partir de ce fichier, même si le code qui a construit le modèle n'est plus disponible.
Ce fichier enregistré comprend :
- architecture de modèle
- valeurs de poids du modèle (qui ont été apprises pendant la formation)
- config modèle de formation, le cas échéant (comme transmis à la
compile
) - optimiseur et son état, le cas échéant (pour reprendre l'entraînement là où vous l'avez laissé)
model.save("path_to_my_model")
del model
# Recreate the exact same model purely from the file:
model = keras.models.load_model("path_to_my_model")
2021-08-25 17:50:55.989736: 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. INFO:tensorflow:Assets written to: path_to_my_model/assets
Pour plus de détails, lisez le modèle sérialisation et sauver guide.
Utiliser le même graphique de couches pour définir plusieurs modèles
Dans l'API fonctionnelle, les modèles sont créés en spécifiant leurs entrées et sorties dans un graphique de couches. Cela signifie qu'un seul graphique de couches peut être utilisé pour générer plusieurs modèles.
Dans l'exemple ci - dessous, vous utilisez la même pile de couches pour instancier deux modèles: un encoder
modèle tours entrées d'image dans des vecteurs 16 dimensions, et un bout en bout autoencoder
modèle de formation.
encoder_input = keras.Input(shape=(28, 28, 1), name="img")
x = layers.Conv2D(16, 3, activation="relu")(encoder_input)
x = layers.Conv2D(32, 3, activation="relu")(x)
x = layers.MaxPooling2D(3)(x)
x = layers.Conv2D(32, 3, activation="relu")(x)
x = layers.Conv2D(16, 3, activation="relu")(x)
encoder_output = layers.GlobalMaxPooling2D()(x)
encoder = keras.Model(encoder_input, encoder_output, name="encoder")
encoder.summary()
x = layers.Reshape((4, 4, 1))(encoder_output)
x = layers.Conv2DTranspose(16, 3, activation="relu")(x)
x = layers.Conv2DTranspose(32, 3, activation="relu")(x)
x = layers.UpSampling2D(3)(x)
x = layers.Conv2DTranspose(16, 3, activation="relu")(x)
decoder_output = layers.Conv2DTranspose(1, 3, activation="relu")(x)
autoencoder = keras.Model(encoder_input, decoder_output, name="autoencoder")
autoencoder.summary()
Model: "encoder" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= img (InputLayer) [(None, 28, 28, 1)] 0 _________________________________________________________________ conv2d (Conv2D) (None, 26, 26, 16) 160 _________________________________________________________________ conv2d_1 (Conv2D) (None, 24, 24, 32) 4640 _________________________________________________________________ max_pooling2d (MaxPooling2D) (None, 8, 8, 32) 0 _________________________________________________________________ conv2d_2 (Conv2D) (None, 6, 6, 32) 9248 _________________________________________________________________ conv2d_3 (Conv2D) (None, 4, 4, 16) 4624 _________________________________________________________________ global_max_pooling2d (Global (None, 16) 0 ================================================================= Total params: 18,672 Trainable params: 18,672 Non-trainable params: 0 _________________________________________________________________ Model: "autoencoder" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= img (InputLayer) [(None, 28, 28, 1)] 0 _________________________________________________________________ conv2d (Conv2D) (None, 26, 26, 16) 160 _________________________________________________________________ conv2d_1 (Conv2D) (None, 24, 24, 32) 4640 _________________________________________________________________ max_pooling2d (MaxPooling2D) (None, 8, 8, 32) 0 _________________________________________________________________ conv2d_2 (Conv2D) (None, 6, 6, 32) 9248 _________________________________________________________________ conv2d_3 (Conv2D) (None, 4, 4, 16) 4624 _________________________________________________________________ global_max_pooling2d (Global (None, 16) 0 _________________________________________________________________ reshape (Reshape) (None, 4, 4, 1) 0 _________________________________________________________________ conv2d_transpose (Conv2DTran (None, 6, 6, 16) 160 _________________________________________________________________ conv2d_transpose_1 (Conv2DTr (None, 8, 8, 32) 4640 _________________________________________________________________ up_sampling2d (UpSampling2D) (None, 24, 24, 32) 0 _________________________________________________________________ conv2d_transpose_2 (Conv2DTr (None, 26, 26, 16) 4624 _________________________________________________________________ conv2d_transpose_3 (Conv2DTr (None, 28, 28, 1) 145 ================================================================= Total params: 28,241 Trainable params: 28,241 Non-trainable params: 0 _________________________________________________________________
Ici, l'architecture de décodage est strictement symétrique par rapport à l'architecture de codage, de sorte que la forme de sortie est la même que la forme d'entrée (28, 28, 1)
.
L'inverse d'un Conv2D
couche est une Conv2DTranspose
couche, et l'inverse d'un MaxPooling2D
couche est une UpSampling2D
couche.
Tous les modèles sont appelables, tout comme les calques
Vous pouvez traiter tout modèle comme si elle était une couche en invoquant sur une Input
ou à la sortie d' une autre couche. En appelant un modèle, vous ne réutilisez pas seulement l'architecture du modèle, vous réutilisez également ses poids.
Pour voir cela en action, voici une version différente de l'exemple d'encodeur automatique qui crée un modèle d'encodeur, un modèle de décodeur et les enchaîne en deux appels pour obtenir le modèle d'encodeur automatique :
encoder_input = keras.Input(shape=(28, 28, 1), name="original_img")
x = layers.Conv2D(16, 3, activation="relu")(encoder_input)
x = layers.Conv2D(32, 3, activation="relu")(x)
x = layers.MaxPooling2D(3)(x)
x = layers.Conv2D(32, 3, activation="relu")(x)
x = layers.Conv2D(16, 3, activation="relu")(x)
encoder_output = layers.GlobalMaxPooling2D()(x)
encoder = keras.Model(encoder_input, encoder_output, name="encoder")
encoder.summary()
decoder_input = keras.Input(shape=(16,), name="encoded_img")
x = layers.Reshape((4, 4, 1))(decoder_input)
x = layers.Conv2DTranspose(16, 3, activation="relu")(x)
x = layers.Conv2DTranspose(32, 3, activation="relu")(x)
x = layers.UpSampling2D(3)(x)
x = layers.Conv2DTranspose(16, 3, activation="relu")(x)
decoder_output = layers.Conv2DTranspose(1, 3, activation="relu")(x)
decoder = keras.Model(decoder_input, decoder_output, name="decoder")
decoder.summary()
autoencoder_input = keras.Input(shape=(28, 28, 1), name="img")
encoded_img = encoder(autoencoder_input)
decoded_img = decoder(encoded_img)
autoencoder = keras.Model(autoencoder_input, decoded_img, name="autoencoder")
autoencoder.summary()
Model: "encoder" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= original_img (InputLayer) [(None, 28, 28, 1)] 0 _________________________________________________________________ conv2d_4 (Conv2D) (None, 26, 26, 16) 160 _________________________________________________________________ conv2d_5 (Conv2D) (None, 24, 24, 32) 4640 _________________________________________________________________ max_pooling2d_1 (MaxPooling2 (None, 8, 8, 32) 0 _________________________________________________________________ conv2d_6 (Conv2D) (None, 6, 6, 32) 9248 _________________________________________________________________ conv2d_7 (Conv2D) (None, 4, 4, 16) 4624 _________________________________________________________________ global_max_pooling2d_1 (Glob (None, 16) 0 ================================================================= Total params: 18,672 Trainable params: 18,672 Non-trainable params: 0 _________________________________________________________________ Model: "decoder" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= encoded_img (InputLayer) [(None, 16)] 0 _________________________________________________________________ reshape_1 (Reshape) (None, 4, 4, 1) 0 _________________________________________________________________ conv2d_transpose_4 (Conv2DTr (None, 6, 6, 16) 160 _________________________________________________________________ conv2d_transpose_5 (Conv2DTr (None, 8, 8, 32) 4640 _________________________________________________________________ up_sampling2d_1 (UpSampling2 (None, 24, 24, 32) 0 _________________________________________________________________ conv2d_transpose_6 (Conv2DTr (None, 26, 26, 16) 4624 _________________________________________________________________ conv2d_transpose_7 (Conv2DTr (None, 28, 28, 1) 145 ================================================================= Total params: 9,569 Trainable params: 9,569 Non-trainable params: 0 _________________________________________________________________ Model: "autoencoder" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= img (InputLayer) [(None, 28, 28, 1)] 0 _________________________________________________________________ encoder (Functional) (None, 16) 18672 _________________________________________________________________ decoder (Functional) (None, 28, 28, 1) 9569 ================================================================= Total params: 28,241 Trainable params: 28,241 Non-trainable params: 0 _________________________________________________________________
Comme vous pouvez le voir, le modèle peut être imbriqué : un modèle peut contenir des sous-modèles (puisqu'un modèle est comme un calque). Un cas d'utilisation commune pour la nidification du modèle est ensembling. Par exemple, voici comment regrouper un ensemble de modèles en un seul modèle qui fait la moyenne de leurs prédictions :
def get_model():
inputs = keras.Input(shape=(128,))
outputs = layers.Dense(1)(inputs)
return keras.Model(inputs, outputs)
model1 = get_model()
model2 = get_model()
model3 = get_model()
inputs = keras.Input(shape=(128,))
y1 = model1(inputs)
y2 = model2(inputs)
y3 = model3(inputs)
outputs = layers.average([y1, y2, y3])
ensemble_model = keras.Model(inputs=inputs, outputs=outputs)
Manipuler des topologies de graphes complexes
Modèles avec plusieurs entrées et sorties
L'API fonctionnelle facilite la manipulation de plusieurs entrées et sorties. Cela ne peut pas être manipulé avec l' Sequential
API.
Par exemple, si vous créez un système pour classer les tickets de problème client par priorité et les acheminer vers le service approprié, le modèle aura trois entrées :
- le titre du ticket (saisie de texte),
- le corps du texte du ticket (saisie de texte), et
- toutes les balises ajoutées par l'utilisateur (entrée catégorielle)
Ce modèle aura deux sorties :
- le score de priorité entre 0 et 1 (sortie sigmoïde scalaire), et
- le service qui doit gérer le ticket (sortie softmax sur l'ensemble des services).
Vous pouvez construire ce modèle en quelques lignes avec l'API fonctionnelle :
num_tags = 12 # Number of unique issue tags
num_words = 10000 # Size of vocabulary obtained when preprocessing text data
num_departments = 4 # Number of departments for predictions
title_input = keras.Input(
shape=(None,), name="title"
) # Variable-length sequence of ints
body_input = keras.Input(shape=(None,), name="body") # Variable-length sequence of ints
tags_input = keras.Input(
shape=(num_tags,), name="tags"
) # Binary vectors of size `num_tags`
# Embed each word in the title into a 64-dimensional vector
title_features = layers.Embedding(num_words, 64)(title_input)
# Embed each word in the text into a 64-dimensional vector
body_features = layers.Embedding(num_words, 64)(body_input)
# Reduce sequence of embedded words in the title into a single 128-dimensional vector
title_features = layers.LSTM(128)(title_features)
# Reduce sequence of embedded words in the body into a single 32-dimensional vector
body_features = layers.LSTM(32)(body_features)
# Merge all available features into a single large vector via concatenation
x = layers.concatenate([title_features, body_features, tags_input])
# Stick a logistic regression for priority prediction on top of the features
priority_pred = layers.Dense(1, name="priority")(x)
# Stick a department classifier on top of the features
department_pred = layers.Dense(num_departments, name="department")(x)
# Instantiate an end-to-end model predicting both priority and department
model = keras.Model(
inputs=[title_input, body_input, tags_input],
outputs=[priority_pred, department_pred],
)
Tracez maintenant le modèle :
keras.utils.plot_model(model, "multi_input_and_output_model.png", show_shapes=True)
Lors de la compilation de ce modèle, vous pouvez affecter différentes pertes à chaque sortie. Vous pouvez même attribuer différents poids à chaque perte - pour moduler leur contribution à la perte totale d'entraînement.
model.compile(
optimizer=keras.optimizers.RMSprop(1e-3),
loss=[
keras.losses.BinaryCrossentropy(from_logits=True),
keras.losses.CategoricalCrossentropy(from_logits=True),
],
loss_weights=[1.0, 0.2],
)
Étant donné que les couches de sortie ont des noms différents, vous pouvez également spécifier les pertes et les poids de perte avec les noms de couche correspondants :
model.compile(
optimizer=keras.optimizers.RMSprop(1e-3),
loss={
"priority": keras.losses.BinaryCrossentropy(from_logits=True),
"department": keras.losses.CategoricalCrossentropy(from_logits=True),
},
loss_weights={"priority": 1.0, "department": 0.2},
)
Entraînez le modèle en passant des listes de tableaux NumPy d'entrées et de cibles :
# Dummy input data
title_data = np.random.randint(num_words, size=(1280, 10))
body_data = np.random.randint(num_words, size=(1280, 100))
tags_data = np.random.randint(2, size=(1280, num_tags)).astype("float32")
# Dummy target data
priority_targets = np.random.random(size=(1280, 1))
dept_targets = np.random.randint(2, size=(1280, num_departments))
model.fit(
{"title": title_data, "body": body_data, "tags": tags_data},
{"priority": priority_targets, "department": dept_targets},
epochs=2,
batch_size=32,
)
Epoch 1/2 40/40 [==============================] - 5s 9ms/step - loss: 1.2899 - priority_loss: 0.7186 - department_loss: 2.8564 Epoch 2/2 40/40 [==============================] - 0s 9ms/step - loss: 1.2668 - priority_loss: 0.6991 - department_loss: 2.8389 <keras.callbacks.History at 0x7fc1a66dc790>
Lors de l' appel en forme avec un Dataset
objet, il doit donner soit un tuple de listes comme ([title_data, body_data, tags_data], [priority_targets, dept_targets])
ou un tuple de dictionnaires comme ({'title': title_data, 'body': body_data, 'tags': tags_data}, {'priority': priority_targets, 'department': dept_targets})
.
Pour une explication plus détaillée, reportez - vous à la formation et l' évaluation guide.
Un modèle ResNet jouet
En plus des modèles avec plusieurs entrées et sorties, l'API fonctionnelle, il est facile de manipuler des topologies de connectivité non-linéaires - ce sont des modèles avec des couches qui ne sont pas connectées de manière séquentielle, que le Sequential
API ne peut pas gérer.
Un cas d'utilisation courant est celui des connexions résiduelles. Construisons un modèle ResNet jouet pour CIFAR10 pour démontrer ceci :
inputs = keras.Input(shape=(32, 32, 3), name="img")
x = layers.Conv2D(32, 3, activation="relu")(inputs)
x = layers.Conv2D(64, 3, activation="relu")(x)
block_1_output = layers.MaxPooling2D(3)(x)
x = layers.Conv2D(64, 3, activation="relu", padding="same")(block_1_output)
x = layers.Conv2D(64, 3, activation="relu", padding="same")(x)
block_2_output = layers.add([x, block_1_output])
x = layers.Conv2D(64, 3, activation="relu", padding="same")(block_2_output)
x = layers.Conv2D(64, 3, activation="relu", padding="same")(x)
block_3_output = layers.add([x, block_2_output])
x = layers.Conv2D(64, 3, activation="relu")(block_3_output)
x = layers.GlobalAveragePooling2D()(x)
x = layers.Dense(256, activation="relu")(x)
x = layers.Dropout(0.5)(x)
outputs = layers.Dense(10)(x)
model = keras.Model(inputs, outputs, name="toy_resnet")
model.summary()
Model: "toy_resnet" __________________________________________________________________________________________________ Layer (type) Output Shape Param # Connected to ================================================================================================== img (InputLayer) [(None, 32, 32, 3)] 0 __________________________________________________________________________________________________ conv2d_8 (Conv2D) (None, 30, 30, 32) 896 img[0][0] __________________________________________________________________________________________________ conv2d_9 (Conv2D) (None, 28, 28, 64) 18496 conv2d_8[0][0] __________________________________________________________________________________________________ max_pooling2d_2 (MaxPooling2D) (None, 9, 9, 64) 0 conv2d_9[0][0] __________________________________________________________________________________________________ conv2d_10 (Conv2D) (None, 9, 9, 64) 36928 max_pooling2d_2[0][0] __________________________________________________________________________________________________ conv2d_11 (Conv2D) (None, 9, 9, 64) 36928 conv2d_10[0][0] __________________________________________________________________________________________________ add (Add) (None, 9, 9, 64) 0 conv2d_11[0][0] max_pooling2d_2[0][0] __________________________________________________________________________________________________ conv2d_12 (Conv2D) (None, 9, 9, 64) 36928 add[0][0] __________________________________________________________________________________________________ conv2d_13 (Conv2D) (None, 9, 9, 64) 36928 conv2d_12[0][0] __________________________________________________________________________________________________ add_1 (Add) (None, 9, 9, 64) 0 conv2d_13[0][0] add[0][0] __________________________________________________________________________________________________ conv2d_14 (Conv2D) (None, 7, 7, 64) 36928 add_1[0][0] __________________________________________________________________________________________________ global_average_pooling2d (Globa (None, 64) 0 conv2d_14[0][0] __________________________________________________________________________________________________ dense_6 (Dense) (None, 256) 16640 global_average_pooling2d[0][0] __________________________________________________________________________________________________ dropout (Dropout) (None, 256) 0 dense_6[0][0] __________________________________________________________________________________________________ dense_7 (Dense) (None, 10) 2570 dropout[0][0] ================================================================================================== Total params: 223,242 Trainable params: 223,242 Non-trainable params: 0 __________________________________________________________________________________________________
Tracez le modèle :
keras.utils.plot_model(model, "mini_resnet.png", show_shapes=True)
Maintenant, entraînez le modèle :
(x_train, y_train), (x_test, y_test) = keras.datasets.cifar10.load_data()
x_train = x_train.astype("float32") / 255.0
x_test = x_test.astype("float32") / 255.0
y_train = keras.utils.to_categorical(y_train, 10)
y_test = keras.utils.to_categorical(y_test, 10)
model.compile(
optimizer=keras.optimizers.RMSprop(1e-3),
loss=keras.losses.CategoricalCrossentropy(from_logits=True),
metrics=["acc"],
)
# We restrict the data to the first 1000 samples so as to limit execution time
# on Colab. Try to train on the entire dataset until convergence!
model.fit(x_train[:1000], y_train[:1000], batch_size=64, epochs=1, validation_split=0.2)
Downloading data from https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz 170500096/170498071 [==============================] - 11s 0us/step 170508288/170498071 [==============================] - 11s 0us/step 13/13 [==============================] - 2s 29ms/step - loss: 2.3364 - acc: 0.1063 - val_loss: 2.2986 - val_acc: 0.0850 <keras.callbacks.History at 0x7fc19df22610>
Calques partagés
Une autre bonne utilisation de l'API fonctionnelle sont des modèles qui utilisent des couches partagées. Les couches partagées sont des instances de couche qui sont réutilisées plusieurs fois dans le même modèle - elles apprennent des caractéristiques qui correspondent à plusieurs chemins dans le graphe des couches.
Les couches partagées sont souvent utilisées pour coder les entrées d'espaces similaires (par exemple, deux morceaux de texte différents qui présentent un vocabulaire similaire). Ils permettent le partage d'informations entre ces différentes entrées, et ils permettent d'entraîner un tel modèle sur moins de données. Si un mot donné est vu dans l'une des entrées, cela profitera au traitement de toutes les entrées qui passent par la couche partagée.
Pour partager une couche dans l'API fonctionnelle, appelez plusieurs fois la même instance de couche. Par exemple, est ici une Embedding
couche partagée entre deux entrées de texte différents:
# Embedding for 1000 unique words mapped to 128-dimensional vectors
shared_embedding = layers.Embedding(1000, 128)
# Variable-length sequence of integers
text_input_a = keras.Input(shape=(None,), dtype="int32")
# Variable-length sequence of integers
text_input_b = keras.Input(shape=(None,), dtype="int32")
# Reuse the same layer to encode both inputs
encoded_input_a = shared_embedding(text_input_a)
encoded_input_b = shared_embedding(text_input_b)
Extraire et réutiliser des nœuds dans le graphe des couches
Étant donné que le graphique des couches que vous manipulez est une structure de données statique, il est possible d'y accéder et de l'inspecter. Et c'est ainsi que vous pouvez tracer des modèles fonctionnels sous forme d'images.
Cela signifie également que vous pouvez accéder aux activations des couches intermédiaires ("nœuds" dans le graphique) et les réutiliser ailleurs - ce qui est très utile pour quelque chose comme l'extraction de caractéristiques.
Regardons un exemple. Il s'agit d'un modèle VGG19 avec des poids pré-entraînés sur ImageNet :
vgg19 = tf.keras.applications.VGG19()
Downloading data from https://storage.googleapis.com/tensorflow/keras-applications/vgg19/vgg19_weights_tf_dim_ordering_tf_kernels.h5 574717952/574710816 [==============================] - 15s 0us/step 574726144/574710816 [==============================] - 15s 0us/step
Et voici les activations intermédiaires du modèle, obtenues en interrogeant la structure de données du graphe :
features_list = [layer.output for layer in vgg19.layers]
Utilisez ces fonctionnalités pour créer un nouveau modèle d'extraction de fonctionnalités qui renvoie les valeurs des activations de couche intermédiaire :
feat_extraction_model = keras.Model(inputs=vgg19.input, outputs=features_list)
img = np.random.random((1, 224, 224, 3)).astype("float32")
extracted_features = feat_extraction_model(img)
Cela est très pratique pour des tâches telles que le transfert de style nerveux , entre autres.
Étendre l'API à l'aide de couches personnalisées
tf.keras
comprend un large éventail de haut-couches, par exemple:
- Couches: convolutifs
Conv1D
,Conv2D
,Conv3D
,Conv2DTranspose
- Couches Pooling:
MaxPooling1D
,MaxPooling2D
,MaxPooling3D
,AveragePooling1D
- Couches RNN:
GRU
,LSTM
,ConvLSTM2D
-
BatchNormalization
,Dropout
,Embedding
, etc.
Mais si vous ne trouvez pas ce dont vous avez besoin, il est facile d'étendre l'API en créant vos propres couches. Toutes les couches sous - classe la Layer
classe et mettre en œuvre:
-
call
méthode, qui spécifie le calcul effectué par la couche. -
build
méthode, qui crée les poids de la couche (ce qui est juste une convention de style puisque vous pouvez créer des poids dans__init__
, ainsi).
Pour en savoir plus sur la création de couches à partir de zéro, lisez des couches personnalisées et des modèles guide.
Ce qui suit est une implémentation de base de tf.keras.layers.Dense
:
class CustomDense(layers.Layer):
def __init__(self, units=32):
super(CustomDense, self).__init__()
self.units = units
def build(self, input_shape):
self.w = self.add_weight(
shape=(input_shape[-1], self.units),
initializer="random_normal",
trainable=True,
)
self.b = self.add_weight(
shape=(self.units,), initializer="random_normal", trainable=True
)
def call(self, inputs):
return tf.matmul(inputs, self.w) + self.b
inputs = keras.Input((4,))
outputs = CustomDense(10)(inputs)
model = keras.Model(inputs, outputs)
Pour le support sérialisation dans votre couche personnalisée, définissez une get_config
méthode qui renvoie les arguments du constructeur de l'instance de la couche:
class CustomDense(layers.Layer):
def __init__(self, units=32):
super(CustomDense, self).__init__()
self.units = units
def build(self, input_shape):
self.w = self.add_weight(
shape=(input_shape[-1], self.units),
initializer="random_normal",
trainable=True,
)
self.b = self.add_weight(
shape=(self.units,), initializer="random_normal", trainable=True
)
def call(self, inputs):
return tf.matmul(inputs, self.w) + self.b
def get_config(self):
return {"units": self.units}
inputs = keras.Input((4,))
outputs = CustomDense(10)(inputs)
model = keras.Model(inputs, outputs)
config = model.get_config()
new_model = keras.Model.from_config(config, custom_objects={"CustomDense": CustomDense})
Éventuellement, mettre en oeuvre la méthode de classe from_config(cls, config)
qui est utilisé lors de la recréation d' une instance de couche donné son dictionnaire de configuration. L'implémentation par défaut de from_config
est:
def from_config(cls, config):
return cls(**config)
Quand utiliser l'API fonctionnelle
Si vous utilisez l'API fonctionnelle Keras pour créer un nouveau modèle, ou tout simplement sous - classe du Model
classe directement? En général, l'API fonctionnelle est de niveau supérieur, plus simple et plus sûre, et possède un certain nombre de fonctionnalités que les modèles sous-classés ne prennent pas en charge.
Cependant, le sous-classement de modèles offre une plus grande flexibilité lors de la création de modèles qui ne sont pas facilement exprimables sous forme de graphes acycliques dirigés de couches. Par exemple, on ne pouvait pas mettre en œuvre un arbre RNN avec l'API fonctionnelle et devrait sous - classe Model
directement.
Pour un regard en profondeur les différences entre l'API fonctionnelle et subclassing modèle, lisez Quelles sont les API symboliques et IMPERIEUSE tensorflow 2.0? .
Points forts de l'API fonctionnelle :
Les propriétés suivantes sont également vraies pour les modèles séquentiels (qui sont également des structures de données), mais ne le sont pas pour les modèles sous-classés (qui sont du bytecode Python, pas des structures de données).
Moins bavard
Il n'y a pas de super(MyClass, self).__init__(...)
, pas d' def call(self, ...):
, etc.
Comparer:
inputs = keras.Input(shape=(32,))
x = layers.Dense(64, activation='relu')(inputs)
outputs = layers.Dense(10)(x)
mlp = keras.Model(inputs, outputs)
Avec la version sous-classée :
class MLP(keras.Model):
def __init__(self, **kwargs):
super(MLP, self).__init__(**kwargs)
self.dense_1 = layers.Dense(64, activation='relu')
self.dense_2 = layers.Dense(10)
def call(self, inputs):
x = self.dense_1(inputs)
return self.dense_2(x)
# Instantiate the model.
mlp = MLP()
# Necessary to create the model's state.
# The model doesn't have a state until it's called at least once.
_ = mlp(tf.zeros((1, 32)))
Validation du modèle lors de la définition de son graphe de connectivité
Dans l'API fonctionnelle, la spécification d'entrée (forme et DTYPE) est créée à l' avance ( à l' aide d' Input
). Chaque fois que vous appelez une couche, la couche vérifie que la spécification qui lui est transmise correspond à ses hypothèses, et sinon, elle générera un message d'erreur utile.
Cela garantit que tout modèle que vous pouvez créer avec l'API fonctionnelle s'exécutera. Tout le débogage - autre que le débogage lié à la convergence - se produit de manière statique pendant la construction du modèle et non au moment de l'exécution. Ceci est similaire à la vérification de type dans un compilateur.
Un modèle fonctionnel est traçable et inspectable
Vous pouvez tracer le modèle sous forme de graphique et accéder facilement aux nœuds intermédiaires de ce graphique. Par exemple, pour extraire et réutiliser les activations de couches intermédiaires (comme vu dans un exemple précédent) :
features_list = [layer.output for layer in vgg19.layers]
feat_extraction_model = keras.Model(inputs=vgg19.input, outputs=features_list)
Un modèle fonctionnel peut être sérialisé ou cloné
Parce qu'un modèle fonctionnel est une structure de données plutôt qu'un morceau de code, il est sérialisable en toute sécurité et peut être enregistré en tant que fichier unique qui vous permet de recréer exactement le même modèle sans avoir accès au code d'origine. Voir le guide de sérialisation et d' économie .
Pour sérialiser un modèle sous - classé, il est nécessaire de préciser l'implémenteur un get_config()
et from_config()
méthode au niveau du modèle.
Faiblesse fonctionnelle de l'API :
Il ne prend pas en charge les architectures dynamiques
L'API fonctionnelle traite les modèles comme des DAG de couches. Cela est vrai pour la plupart des architectures d'apprentissage en profondeur, mais pas pour toutes - par exemple, les réseaux récursifs ou les Tree RNN ne suivent pas cette hypothèse et ne peuvent pas être implémentés dans l'API fonctionnelle.
Mélanger les styles d'API
Choisir entre l'API fonctionnelle ou le sous-classement de modèle n'est pas une décision binaire qui vous limite à une catégorie de modèles. Tous les modèles de la tf.keras
API peuvent interagir les uns avec les autres, si elles sont Sequential
des modèles, des modèles fonctionnels ou modèles qui sont écrits sous - classées à partir de zéro.
Vous pouvez toujours utiliser un modèle fonctionnel ou Sequential
modèle dans le cadre d'un modèle ou une couche sous - classé:
units = 32
timesteps = 10
input_dim = 5
# Define a Functional model
inputs = keras.Input((None, units))
x = layers.GlobalAveragePooling1D()(inputs)
outputs = layers.Dense(1)(x)
model = keras.Model(inputs, outputs)
class CustomRNN(layers.Layer):
def __init__(self):
super(CustomRNN, self).__init__()
self.units = units
self.projection_1 = layers.Dense(units=units, activation="tanh")
self.projection_2 = layers.Dense(units=units, activation="tanh")
# Our previously-defined Functional model
self.classifier = model
def call(self, inputs):
outputs = []
state = tf.zeros(shape=(inputs.shape[0], self.units))
for t in range(inputs.shape[1]):
x = inputs[:, t, :]
h = self.projection_1(x)
y = h + self.projection_2(state)
state = y
outputs.append(y)
features = tf.stack(outputs, axis=1)
print(features.shape)
return self.classifier(features)
rnn_model = CustomRNN()
_ = rnn_model(tf.zeros((1, timesteps, input_dim)))
(1, 10, 32)
Vous pouvez utiliser une couche ou modèle sous - classé l'API fonctionnelle tant qu'elle met en oeuvre un call
méthode qui suit l' un des motifs suivants:
-
call(self, inputs, **kwargs)
- Lorsque desinputs
est un tenseur ou une structure imbriquée de tenseurs (par exemple , une liste des tenseurs), et où**kwargs
sont des arguments non-tenseur (non-entrées). -
call(self, inputs, training=None, **kwargs)
- Lorsque latraining
est un booléen indiquant si la couche doit se comporter en mode de formation et le mode d'inférence. -
call(self, inputs, mask=None, **kwargs)
- Si lemask
est un tenseur de masque booléen (utile pour RNNs, par exemple). -
call(self, inputs, training=None, mask=None, **kwargs)
- Bien sûr, vous pouvez avoir à la fois le masquage et le comportement spécifique à la formation en même temps.
De plus, si vous implémentez la get_config
méthode sur votre couche personnalisée ou modèle, les modèles fonctionnels que vous créez toujours sérialisable et cloneable.
Voici un exemple rapide d'un RNN personnalisé, écrit à partir de zéro, utilisé dans un modèle fonctionnel :
units = 32
timesteps = 10
input_dim = 5
batch_size = 16
class CustomRNN(layers.Layer):
def __init__(self):
super(CustomRNN, self).__init__()
self.units = units
self.projection_1 = layers.Dense(units=units, activation="tanh")
self.projection_2 = layers.Dense(units=units, activation="tanh")
self.classifier = layers.Dense(1)
def call(self, inputs):
outputs = []
state = tf.zeros(shape=(inputs.shape[0], self.units))
for t in range(inputs.shape[1]):
x = inputs[:, t, :]
h = self.projection_1(x)
y = h + self.projection_2(state)
state = y
outputs.append(y)
features = tf.stack(outputs, axis=1)
return self.classifier(features)
# Note that you specify a static batch size for the inputs with the `batch_shape`
# arg, because the inner computation of `CustomRNN` requires a static batch size
# (when you create the `state` zeros tensor).
inputs = keras.Input(batch_shape=(batch_size, timesteps, input_dim))
x = layers.Conv1D(32, 3)(inputs)
outputs = CustomRNN()(x)
model = keras.Model(inputs, outputs)
rnn_model = CustomRNN()
_ = rnn_model(tf.zeros((1, 10, 5)))