Aller au contenu principal

Module 2 — API séquentielle : premier modèle en quelques lignes

Le module 1 a montré les briques de bas niveau. Keras les enveloppe dans une interface où un réseau complet tient en dix lignes. L'API séquentielle est la plus simple des trois, et elle couvre une bonne moitié des besoins réels.

Une pile de couches, une entrée, une sortie

Sequential empile des couches dans l'ordre. La sortie de chacune devient l'entrée de la suivante, sans exception.

from tensorflow import keras
from tensorflow.keras import layers

modele = keras.Sequential([
keras.Input(shape=(28, 28)),
layers.Flatten(),
layers.Dense(128, activation="relu"),
layers.Dropout(0.2),
layers.Dense(10, activation="softmax"),
])

Quatre décisions sont déjà prises dans ce bloc. La forme d'entrée est déclarée explicitement, ce qui permet à Keras de construire les poids immédiatement au lieu d'attendre la première donnée. Flatten aplatit l'image en un vecteur de 784 valeurs. La couche cachée de 128 neurones en ReLU applique la leçon du module 2 du cours 07. La sortie en softmax sur dix neurones correspond à dix classes exclusives.

Déclarez toujours la forme d'entrée

Sans keras.Input, le modèle reste non construit : ses poids n'existent pas encore, model.summary() échoue et le nombre de paramètres est inconnu. Keras attend la première donnée pour déduire les dimensions. Cela fonctionne, mais on perd la vérification immédiate des formes, qui est le meilleur moment pour détecter une erreur d'architecture.

Compiler, c'est choisir trois choses

Un modèle construit ne sait pas encore apprendre. compile lui attache un optimiseur, une perte et des métriques.

modele.compile(
optimizer=keras.optimizers.Adam(learning_rate=1e-3),
loss="sparse_categorical_crossentropy",
metrics=["accuracy"],
)

Le choix de la perte n'est pas libre : il découle du format des étiquettes et de l'activation de sortie.

ÉtiquettesSortiePerte
entiers, une classe par observationsoftmaxsparse_categorical_crossentropy
vecteurs à un seul 1softmaxcategorical_crossentropy
0 ou 1sigmoïde, 1 neuronebinary_crossentropy
plusieurs étiquettes simultanéessigmoïde par neuronebinary_crossentropy
valeur continuelinéairemse ou huber

La confusion entre les deux premières lignes est l'erreur la plus fréquente des débutants. Elle ne provoque pas d'exception : elle produit une perte qui ne descend pas, ou qui descend de façon absurde. Le préfixe sparse_ signifie « les étiquettes sont des entiers », rien de plus.

La perte et la métrique ne jouent pas le même rôle

La perte est ce que l'optimiseur minimise ; elle doit être dérivable. La métrique ne sert qu'à vous informer et peut être non dérivable — l'exactitude, par exemple, ne l'est pas. Une métrique dans loss provoque une erreur ; une perte dans metrics fonctionne mais n'apprend rien.

Lire un résumé avant d'entraîner

model.summary() mérite un examen attentif avant chaque entraînement.

Layer (type)          Output Shape       Param #
=====================================================
flatten (Flatten) (None, 784) 0
dense (Dense) (None, 128) 100480
dropout (Dropout) (None, 128) 0
dense_1 (Dense) (None, 10) 1290
=====================================================
Total params: 101,770

Deux vérifications s'imposent. D'abord les formes : chaque Output Shape doit correspondre à votre intention, et le None de tête doit y figurer partout. Ensuite le compte de paramètres, qui se recalcule de tête : une couche Dense de 784 entrées vers 128 sorties a 784×128+128=100480784 \times 128 + 128 = 100\,480 paramètres, poids plus biais. Un écart avec votre calcul signale une couche mal dimensionnée.

Notez que Flatten et Dropout n'ont aucun paramètre : ce sont des transformations sans rien à apprendre.

Entraîner et surveiller

historique = modele.fit(
x_entrainement, y_entrainement,
epochs=20,
batch_size=32,
validation_split=0.2,
verbose=2,
)

validation_split=0.2 réserve les 20 derniers pourcents des données, sans les mélanger. Si votre jeu est trié par classe, cette validation ne contiendra qu'une partie des classes et les scores seront ininterprétables. Mélangez avant, ou fournissez un validation_data explicite.

L'objet historique conserve les valeurs par époque, et c'est lui qu'on trace pour lire les courbes d'apprentissage étudiées au module 9 du cours 07 :

import matplotlib.pyplot as plt

plt.plot(historique.history["loss"], label="entrainement")
plt.plot(historique.history["val_loss"], label="validation")
plt.legend()

Évaluer, prédire, et ne pas confondre les deux

perte, exactitude = modele.evaluate(x_test, y_test)
probabilites = modele.predict(x_test)
classes = probabilites.argmax(axis=1)

evaluate a besoin des étiquettes et retourne des scores. predict n'en a pas besoin et retourne les sorties brutes du réseau — ici des probabilités, pas des classes. Oublier le argmax est une source classique de résultats incompréhensibles.

Un détail invisible mais important : Dropout est actif pendant fit et inactif pendant evaluate et predict. Keras gère ce basculement automatiquement. C'est pourquoi la perte d'entraînement affichée peut être supérieure à celle de validation aux premières époques, sans que ce soit anormal.

Quand Sequential ne suffit plus

L'API séquentielle suppose une chaîne linéaire. Elle devient impuissante dès que l'architecture s'écarte de ce schéma :

  • deux entrées de natures différentes, une image et un texte par exemple ;
  • deux sorties, comme une classification et une régression simultanées ;
  • une branche qui contourne des couches, ce qui est la définition même d'une connexion résiduelle ;
  • une couche appliquée deux fois aux mêmes poids, dans une architecture siamoise.

Ces quatre cas ne sont pas exotiques : ils couvrent la majorité des architectures modernes. C'est ce qui motive le module suivant.

En résumé

  • Sequential empile des couches en chaîne linéaire ; déclarer keras.Input construit les poids tout de suite et rend les erreurs de forme visibles immédiatement.
  • compile fixe optimiseur, perte et métriques ; la perte se déduit du format des étiquettes, et le préfixe sparse_ signifie simplement que les étiquettes sont des entiers.
  • model.summary() se relit avant chaque entraînement : les formes doivent correspondre à l'intention, et le compte de paramètres doit se retrouver à la main.
  • predict retourne des probabilités, pas des classes ; Dropout est actif pendant l'entraînement et neutralisé en évaluation, ce qui explique des courbes en apparence inversées.

Module suivant : l'API fonctionnelle, qui lève les quatre limites énumérées ci-dessus.