Vous êtes sur la page 1sur 29

Module09 Deep

Learning 02

Module09 Deep Learning 02


Tensorflow 2.0 pour Deep Learning - Partie 02
Introduction de MLPs
Neurones biologiques
Le Perceptron
Le perceptron multicouche
Rappel de la rétropropagation
MLP de classification
Implémentation de MLP avec Keras
Construire un classificateur d'images à l'aide de l'API séquentielle de Keras
Utilisation de Keras pour charger le jeu de données
Création du modèle à l'aide de l'API séquentielle
Compilation du modèle
Entraînement et évaluation du modèle
Utiliser le modèle pour faire des prédictions
Enregistrement et restauration d'un modèle
Entraînement Réseaux de Neurones profonds
Les problèmes de gradients qui disparaissent/explosent
Glorot and He Initialization
Fonctions d'activation non saturantes
Réutiliser les couches pré-entraînées/ Transfer Learning
Apprentissage par transfert avec Keras
Éviter le surajustement(Overfitting) grâce à la régularisation
l1 et l2 régularisation
Dropout
Vision par ordinateur - Réseaux de neurones convolutifs - Computer vision - Convolutional Neural Networks
Introduction
Détection des bords - I
Détection des bords - II
Padding (Remplissage)
Valid convolutions
Same Convolutions
Convolutions foulées(Strided Convolutions)
Convolutions sur volume
Une couche d'un réseau convolutif
Rappel: Fonctions d'activation ReLU & GeLU(Rectified Linear Unit)
Construction d'une couche d'un réseau convolutif
Processus de calcul dans la couche
Résumé avec la notation
Exemple de CNN simple
Couche de Pooling
Max Pooling
Average Pooling
Exemple CNN
Implémentation des couches CNN
Packages
Zero-Padding
Exercice
Une seule étape de convolution
Exercice
Réseaux de neurones convolutifs - Passe avant
Exercice
Couche de Max-Pooling & Average-Pooling
Exercice
Différentes Architectures CNN
LeNet-5
AlexNet
Exercice: Implémenter AlexNet avec API Keras
VGGNet
Exercice: Implémenter VGG19 en utilisant les APIs par défaut (Dense, Conv2D, MaxPooling etc.)
ResNet
L'architecture de ResNet
Mise en œuvre du bloc d'identité
Mise en œuvre du bloc convolutif
Mise en œuvre de ResNet-50
1 Tensorflow 2.0 pour Deep Learning - Partie 02

1.1 Introduction de MLPs

Dans le dernière module, nous avons fait une première comparaison entre un modèle de réseaux de
neurones et un modèle de régression logistique, nous avons aussi vu une première introduction des APIs
Tensorflow qui sont largement utilisées dans le domaine de Deep-Learning. Dans ce module, nous allons
reparcourir l'historie de développement de réseaux de neurones artificiels ainsi d'étudier plus en détail une
autre API avancée de Tensorflow: Keras.

Les oiseaux nous ont inspirés à voler et la nature a inspiré d'innombrables autres inventions. Logiquement,
lorsqu'on regarde l'architecture du cerveau pour trouver l'inspiration sur la façon de construire une machine
intelligente. C'est le logique qui a déclenché les réseaux de neurones artificiels (ANN - Artificial Neural
Network).

Les ANN sont au cœur même du Deep Learning. Ils sont polyvalents, puissants et évolutives, ce qui les rend
idéales pour s'attaquer à des machines volumineuses et très complexes. Des tâches d'apprentissage telles
que la classification de milliards d'images (par exemple, Google Images), alimentant les services de
reconnaissance vocale (par exemple, Siri d'Apple), recommandant les meilleures vidéos à regarder chaque
jour pour des centaines de millions d'utilisateurs (par exemple, YouTube), ou apprendre à battre le
champion du monde au jeu de Go (DeepMind’s Alpha Go).

La première partie de ce chapitre présente les réseaux de neurones artificiels, en commençant par un tour
rapide des toutes premières architectures ANN et menant au multicouche Perceptrons (MLPs), qui sont
fortement utilisés aujourd'hui. Dans la deuxième partie, nous verrons comment mettre en œuvre des
réseaux de neurones à l'aide de l'API Keras et les autres détails d'un reseau de neurones.

1.1.1 Neurones biologiques

Avant de discuter des neurones artificiels, jetons un coup d'œil rapide sur un neurone biologique
Nous n'allons pas rentrer dans le détail de toutes les parties d'un neurone, mais ce qu'il faut savoir c'est que
neurones biologiques individuels semblent se comporter d'une manière assez simple, mais ils sont
organisés en un vaste réseau de milliards, chaque neurone étant typiquement connecté à des milliers
d'autres neurones. Des calculs très complexes peuvent être réalisé par un réseau de neurones assez
simples, un peu comme une fourmilière complexe peuvent émerger des efforts combinés de simples
fourmis. Il semble que les neurones soient souvent organisé en couches consécutives, en particulier dans le
cortex cérébral:

1.1.2 Le Perceptron

Le Perceptron est l'une des architectures ANN les plus simples, inventée en 1957 par Franck Rosenblatt. Il
est basé sur un neurone artificiel légèrement différent appelé unité logique à seuil (TLU - Threshold Logic
Unit) ou parfois un linéaire unité de seuil (LTU - Linear Threshold Unit)
Les entrées et sorties sont des nombres ou les caractéristiques de chaque échantillons, et chaque
connexion d'entrée est associée à un poids. Le TLU calcule une somme pondérée de ses entrées (
), applique ensuite une fonction en escalier à cette somme et
affiche le résultat : , où .

La fonction d'étape la plus couramment utilisée dans Perceptrons est l'étape Heaviside fonction. Parfois, la
fonction de signe est utilisée à la place.

Une seule TLU peut être utilisée pour une classification binaire linéaire simple. Il calcule une combinaison
linéaire des entrées, et si le résultat dépasse un seuil, il sort la classe positive. Sinon, il sort la classe
négative (tout comme un Logistic Régression ou classificateur SVM linéaire)

Un Perceptron est simplement composé d'une seule couche de TLU, chaque TLU connecté à toutes les
entrées. Lorsque tous les neurones d'une couche sont connectés à chaque neurone de la couche précédente
(c'est-à-dire ses neurones d'entrée), la couche est appelée une couche entièrement connectée, ou une
couche dense. Les entrées du Perceptron sont alimentées vers neurones de passage spéciaux appelés
neurones d'entrée : ils produisent n'importe quelle entrée ils sont nourris. Tous les neurones d'entrée
forment la couche d'entrée. De plus, un biais supplémentaire caractéristique est généralement ajoutée (x =
1) : elle est généralement représentée à l'aide d'un type spécial de neurone appelé neurone de polarisation,
qui produit 1 tout le temps. Un Perceptron avec deux entrées et trois sorties est représenté comme ceci:
Grâce à l'algèbre linéaire, l'équation suivante permet de calculer efficacement les sorties d'une couche de
neurones artificiels pour plusieurs instances à la fois.

Dans cette équation:

X représente la matrice des entités en entrée. Il a une ligne par instance/échantillon et une colonne par
caractéristique.

La matrice de poids W contient tous les poids de connexion à l'exception des ceux du neurone de bias.
Le vecteur de biais b contient tous les poids de connexion entre le biais neurone et les neurones

artificiels. Il a un terme de biais par artificiel neurone.


La fonction s'appelle la fonction d'activation.

Notez aussi que contrairement aux classificateurs de régression logistique, les Perceptrons ne produisent
pas de probabilité de classe, ils font plutôt des prédictions basées sur un seuil strict. C'est une raison de
préférer la régression logistique aux perceptrons.

1.1.3 Le perceptron multicouche

Un MLP est composé d'une couche d'entrée, d'une ou plusieurs couches de TLU, appelées couches cachées,
et une dernière couche de TLU appelée couche de sortie(similaire que ce qu'on a vu dans le dernière
module). Lorsqu'un ANN contient une pile profonde de couches cachées, on l'appelle un réseau de
neurones profond (DNN - Deep Neural Network).

Note importante:

Le signal ne circule que dans un seul sens (des entrées vers les sorties), donc cela est un exemple de
réseau de neurones à anticipation (FNN - Feedforward Neural Network).

1.1.4 Rappel de la rétropropagation

Pendant de nombreuses années, les chercheurs ont eu du mal à trouver un moyen de former des MLP, sans
Succès. Mais en 1986, David Rumelhart, Geoffrey Hinton et Ronald Williams a publié un article
révolutionnaire qui a introduit la rétropropagation algorithme d'entraînement , qui est encore utilisé
aujourd'hui. En bref, c'est Gradient Descent. En utilisant une technique efficace pour calculer la
gradients automatiquement : en seulement deux passages dans le réseau (un vers l'avant, un vers l'arrière),
l'algorithme de rétropropagation est capable de calculer le gradient de l'erreur du réseau en ce qui concerne
chaque paramètre du modèle. Autrement dit, il peut découvrir comment chaque poids de connexion et
chaque terme de biais doivent être modifié afin de réduire l'erreur. Une fois qu'il a ces dégradés, il exécute
simplement une étape régulière de descente de gradient, et tout le processus est répété jusqu'à ce que le
réseau converge vers la solution.

Processus plus en détail:

Il traite un mini-batch(mini-lot) à la fois (par exemple, contenant 32 instances chacune), et il passe par
l'ensemble d'entraînement complet plusieurs fois. Chaque passage est appelé une époque.

Chaque mini-lot est transmis à la couche d'entrée du réseau, qui l'envoie à la première couche cachée.
L'algorithme calcule ensuite la sortie de tous les neurones de cette couche (pour chaque instance du

mini-lot). Le résultat est transmis à la couche suivante, sa sortie est calculée et transmise à la couche
suivante, et ainsi de suite jusqu'à ce que nous obtenions la sortie de la dernière couche, la couche de
sortie. C'est la passe en avant : c'est exactement comme faire des pronostics, sauf que tous les
résultats intermédiaires sont conservés puisqu'ils sont nécessaires pour la passe

arrière(rétropropagation).
Ensuite, l'algorithme mesure l'erreur de sortie du réseau (c'est-à-dire qu'il utilise une fonction de perte

qui compare la sortie souhaitée et la sortie réelle du réseau et renvoie une certaine mesure de
l'erreur).

Ensuite, il calcule la contribution de chaque connexion de sortie à l'erreur. Cela se fait analytiquement
en appliquant la règle de la chaîne (peut-être la règle la plus fondamentale en calcul), ce qui rend cette

étape rapide et précis.


L'algorithme mesure alors combien de ces contributions d'erreur est venu de chaque connexion dans

la couche ci-dessous, à nouveau en utilisant la règle de chaîne, en travaillant en arrière jusqu'à ce que

l'algorithme atteigne la couche d'entrée. Comme


expliqué précédemment, cette passe inverse mesure efficacement l'erreur gradient sur tous les poids

de connexion dans le réseau par propager le gradient d'erreur vers l'arrière à travers le réseau (d'où la
nom de l'algorithme).

Enfin, l'algorithme effectue une étape Gradient Descent pour ajuster tous les poids de connexion dans
le réseau, en utilisant les gradients d'erreur calculé.

Cet algorithme de rétropropagation est si important qu'il vaut la peine de le résumer à nouveau : pour
chaque instance d'entraînement, l'algorithme de rétropropagation fait d'abord une prédiction (passe avant)
et mesure l'erreur, puis parcourt chaque couche en sens inverse pour mesurer la contribution d'erreur de
chaque connexion (passe inverse), et enfin ajuste les poids de connexion pour réduire l'erreur (étape
Gradient Descent).


Note importante

Il est important d'initialiser de manière aléatoire les poids de connexion de toutes les couches
cachées, ou sinon la formation/l'entraînement échouera. Par exemple, si vous initialisez tous les poids
et biais à zéro, alors tous les neurones d'une couche donnée seront parfaitement identiques, et donc
la rétropropagation les affectera exactement de la même manière, ils resteront donc identiques. En
d'autres termes, malgré des centaines de neurones par couche, votre modèle agira comme s'il n'avait
qu'un seul neurone par couche : il ne sera pas trop intelligent. Si à la place vous initialisez
aléatoirement les poids, vous cassez la symétrie et autorisez rétropropagation pour former une
équipe diversifiée de neurones.

Pour que cet algorithme fonctionne correctement, ses auteurs ont apporté une modification clé à
l'architecture de MLP : ils ont remplacé la fonction d'étape par la fonction sigmoïde
C'était essentiel parce que la fonction échelon(Heaviside ou signe) ne contient que des segments plats, il n'y
a donc pas de gradient à calculer, tandis que la fonction logistique a une dérivée non nulle bien définie
partout, permettant à Gradient Descent de faire
des progrès à chaque étape. En fait, l'algorithme de rétropropagation fonctionne bien avec de nombreuses
autres fonctions d'activation, pas seulement la fonction logistique. Voici deux autres choix populaires :

La fonction tangente hyperbolique :

La fonction Unité Linéaire Rectifiée :

1.2 MLP de classification

Les MLP peuvent être utilisés pour des tâches de classification. Pour une classification binaire problème,
vous avez juste besoin d'un seul neurone de sortie utilisant l'activation logistique fonction : la sortie sera un
nombre compris entre 0 et 1, que vous pouvez interpréter comme la probabilité estimée de la classe
positive. La probabilité estimée de la classe négative est égale à un moins ce nombre. Plus généralement, Si
chaque instance ne peut appartenir qu'à une seule classe, sur trois ou plus possibles classes (par exemple,
les classes 0 à 9 pour la classification des images numériques), vous devez alors avoir un neurone de sortie
par classe, et vous devez utiliser l'activation softmax fonction pour toute la couche de sortie. En ce qui
concerne la fonction de perte, puisque nous prévoyons des distributions de probabilité, la perte d'entropie
croisée est souvent un bon choix.
1.2.1 Implémentation de MLP avec Keras

Keras est une API d'apprentissage en profondeur de haut niveau qui vous permet de créer,
former, évaluer et exécuter toutes sortes de réseaux de neurones. L'implémentation de référence, également
appelé Keras, a été développé par François Chollet dans le cadre d'une recherche projet et a été publié en
tant que projet open source en mars 2015. Il a rapidement a gagné en popularité en raison de sa facilité
d'utilisation, de sa flexibilité et de son beau design. À effectuer les calculs lourds requis par les réseaux de
neurones, cette référence l'implémentation repose sur un backend de calcul. À l'heure actuelle, vous pouvez
choisir à partir des bibliothèques d'apprentissage en profondeur open source populaires : TensorFlow,
Microsoft Cognitive Toolkit (CNTK), PyTorch et Theano. Ainsi, pour éviter toute confusion, nous ferons
référence à cette implémentation de référence sous le nom de Keras multibackend.

De plus, TensorFlow lui-même est désormais fourni avec sa propre implémentation Keras, tf.keras. Il ne
prend en charge que TensorFlow en tant que backend, mais il a l'avantage d'offrir quelques fonctionnalités
supplémentaires très utiles: par exemple, il prend en charge l'API de données de TensorFlow, ce qui le rend
facile à charger et à prétraiter efficacement les données. Pour cette raison, nous utiliserons tf.keras dans ce
programme.
1.2.2 Construire un classificateur d'images à l'aide de l'API séquentielle de Keras

Dans le chapitre précédent, nous avons installé Tensorflow pour notre première implémentation du réseau
de neurones. Pour tester votre installation, ouvrez un shell Python ou un notebook Jupyter, puis importez
TensorFlow et tf.keras et imprimez leurs versions :

import tensorflow as tf
tf.__version__
from tensorflow import keras
keras.__version__

Dans ce chapitre nous allons continuer à aborder la base de données Fashion MNIST que nous avons
étudiés pendant le séminaire du module Deep Learning 01.

1.2.3 Utilisation de Keras pour charger le jeu de données

Keras fournit des fonctions utilitaires pour récupérer et charger des ensembles de données communs, y
compris MNIST, Mode MNIST(Fashion MNIST)

Chargement du Fashion MNIST:

fashion_mnist = keras.datasets.fashion_mnist
(x_train_full, y_train_full), (x_test, y_test) = fashion_mnist.load_data()

Lors du chargement de MNIST ou Fashion MNIST en utilisant Keras plutôt qu'un fichier csv, une différence
importante est que chaque image est représentée sous la forme d'un tableau 28 × 28 plutôt qu'un tableau
1D de taille 784. De plus, les intensités de pixels sont représentées sous forme d'entiers (de 0 à 255) plutôt
que de flottants (de 0,0 à 255,0).

print(x_train_full.shape)
print(x_train_full.dtype)

/***********Expected Output****************/
(60000, 28, 28)
dtype('uint8')
*******************************************/

Notez que l'ensemble de données est déjà divisé en un ensemble d'apprentissage et un ensemble de test,
mais il y a aucun ensemble de validation, nous allons donc en créer un maintenant. De plus, puisque nous
allons entraîner le réseau de neurones à l'aide de Gradient Descent, nous devons mettre à l'échelle l'entrée
Caractéristiques. Pour plus de simplicité, nous réduirons les intensités des pixels dans la plage 0-1 en les
divisant par 255,0 (cela les convertit également en flottants):
x_valid, x_train = x_train_full[:5000] / 255.0, x_train_full[5000:] / 255.0
y_valid, y_train = y_train_full[:5000], y_train_full[5000:]

Pour Fashion MNIST, comment nous avons vu, la liste des classes est représentée ci dessous:

class_names = ["T-shirt/top", "Trouser", "Pullover", "Dress", "Coat",


"Sandal", "Shirt", "Sneaker", "Bag", "Ankle boot"]

print(class_names[y_train[5]])

/***********Expected Output****************/
'Ankle boot'
*******************************************/

1.2.4 Création du modèle à l'aide de l'API séquentielle

Voici une classification MLP avec deux couches cachés(Nous l'avons brièvement vue pendant le séminaire
du module Deep Learning 01) :

model = keras.models.Sequential()
model.add(keras.layers.Flatten(input_shape=[28, 28]))
model.add(keras.layers.Dense(300, activation="relu"))
model.add(keras.layers.Dense(100, activation="relu"))
model.add(keras.layers.Dense(10, activation="softmax"))

Détails de ce code:

La première ligne crée un modèle séquentiel. C'est le type le plus simple de Modèle Keras pour les
réseaux de neurones qui ne sont composés que d'un seul pile de couches connectées

séquentiellement. C'est ce qu'on appelle le séquentiel API.


Ensuite, nous construisons la première couche et l'ajoutons au modèle. C'est un Aplatir couche dont le

rôle est de convertir chaque image d'entrée en un tableau 1D : s'il reçoit les données d'entrée X, il
calcule X.reshape(-1, 1). Cette couche fait

ne pas avoir de paramètres. Puisqu'il s'agit de la première couche du modèle, vous devez spécifier le

input_shape, qui n'inclut pas la taille du lot, seulement la forme des instances. Alternativement, vous

pouvez ajouter un keras.layers.InputLayer comme première couche, en définissant input_shape=


[28,28].

Ensuite, nous ajoutons une couche cachée dense avec 300 neurones. Il utilisera le Fonction

d'activation ReLU. Chaque couche Dense gère son propre poids matrice, contenant tous les poids de

connexion entre les neurones et leurs entrées. Il gère également un vecteur de termes de biais (un par
neurone).

Ensuite, nous ajoutons une deuxième couche cachée dense avec 100 neurones, en utilisant également

la fonction d'activation ReLU.

Enfin, nous ajoutons une couche de sortie Dense avec 10 neurones (un par classe), en utilisant la

fonction d'activation softmax (car les classes sont exclusives).

Des fois, au lieu d'ajouter les couches un par un comme nous venons de le faire, vous pouvez passer une
liste de couches lors de la création du modèle séquentiel:

model = keras.models.Sequential([
keras.layers.Flatten(input_shape=[28, 28]),
keras.layers.Dense(300, activation="relu"),
keras.layers.Dense(100, activation="relu"),
keras.layers.Dense(10, activation="softmax")
])

La méthode summary() du modèle affiche toutes les couches du modèle, y compris le nom de chaque
calque (qui est généré automatiquement à moins que vous ne le définissiez création de la couche), sa forme
de sortie (Aucun signifie que la taille du lot peut être n'importe quoi), et son nombre de paramètres. Le
résumé se termine par le nombre total de de paramètres, y compris les paramètres entraînables et non
entraînables. Ici nous n'avons que paramètres entraînables.

model.summary()

/***********Expected Output****************/
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
flatten (Flatten) (None, 784) 0

dense (Dense) (None, 300) 235500


dense_1 (Dense) (None, 100) 30100

dense_2 (Dense) (None, 10) 1010

=================================================================
Total params: 266,610
Trainable params: 266,610
Non-trainable params: 0
*******************************************/


Exercice: Pourquoi le nombre de paramètres est à 235500, 30100, 1010?

Vous pouvez également obtenir facilement la liste des couches d'un modèle, pour récupérer une couche par
son indice, ou vous pouvez les récupérer par nom :

print(model.layers)
hidden1 = model.layers[1]
print(hidden1.name)
print(model.get_layer('dense') is hidden1)

/***********Expected Output****************/
[<keras.layers.core.flatten.Flatten at 0x2014c79bdf0>,
<keras.layers.core.dense.Dense at 0x2014c79bd60>,
<keras.layers.core.dense.Dense at 0x201549b96a0>,
<keras.layers.core.dense.Dense at 0x20154b3e850>]

dense

True
*******************************************/

Tous les paramètres d'une couche sont accessibles à l'aide de ses get_weights() et set_weights() .
Pour une couche Dense, cela inclut à la fois la connexion poids et les termes de biais :

weights, biases = hidden1.get_weights()


print(weights)
print(weights.shape)
print(biases)
print(biases.shape)

/***********Expected Output****************/
[[ 0.01218569 0.03236897 0.00019528 ... -0.05867157 0.07149352
-0.04674136]
[ 0.05008923 -0.06608111 -0.05280486 ... -0.01209959 0.03334276
-0.05666441]
[ 0.06146993 -0.01885458 0.03362052 ... 0.0414452 -0.02292581
0.03818423]
...
[-0.06381042 0.06287131 -0.03124221 ... -0.06031035 0.05847181
-0.01478235]
[-0.07018333 -0.04096211 -0.04573346 ... 0.03283916 0.06464979
-0.01625229]
[-0.05720002 -0.02775222 -0.03766729 ... -0.04195247 -0.05619773
0.01919395]]

(784, 300)

[0. 0. 0. 0. 0. ... 0. 0. 0. 0. 0.]


(300,)
*******************************************/


Notez que la couche Dense a initialisé les poids de connexion de manière aléatoire (ce qui est
nécessaire pour briser la symétrie, comme nous en avons discuté précédemment), et les biais étaient
initialisé à zéros.

1.2.5 Compilation du modèle

Après la création d'un modèle, vous devez appeler sa méthode compile() pour spécifier la fonction de
perte et l'optimiseur à utiliser. Facultativement, vous pouvez spécifier une liste d'éléments supplémentaires
métriques à calculer pendant l'entraînement et l'évaluation :

model.compile(loss="sparse_categorical_crossentropy", optimizer="sgd", metrics=


["accuracy"])

Nous utilisons la fonction de perte "sparse_categorical_crossentropy" car nous avons des étiquettes

clairsemées/sparse (c'est-à-dire que pour chaque instance, il n'y a qu'un index de classe cible, de 0 à 9
dans ce cas), et les classes sont exclusifs.

En ce qui concerne l'optimiseur, "sgd" signifie que nous allons entraîner le modèle à l'aide de simples

Descente de gradient stochastique. (Nous préciserons les différents optimiseurs plustard)

Enfin, puisqu'il s'agit d'un classifieur, il est utile de mesurer sa "précision" lors entraînement et
évaluation.
1.2.6 Entraînement et évaluation du modèle

Maintenant, le modèle est prêt à être formé. Pour cela, nous devons simplement appeler sa méthode fit
() :

history = model.fit(x_train, y_train, epochs=30, validation_data=(x_valid,


y_valid))

/***********Expected Output****************/
Epoch 1/30
1719/1719 [==============================] - 3s 2ms/step - loss: 0.7215 -
accuracy: 0.7660 - val_loss: 0.5278 - val_accuracy: 0.8178
Epoch 2/30
1719/1719 [==============================] - 3s 1ms/step - loss: 0.4914 -
accuracy: 0.8292 - val_loss: 0.4592 - val_accuracy: 0.8442
...
...
...
Epoch 29/30
1719/1719 [==============================] - 3s 2ms/step - loss: 0.2330 -
accuracy: 0.9164 - val_loss: 0.2921 - val_accuracy: 0.8916
Epoch 30/30
1719/1719 [==============================] - 3s 2ms/step - loss: 0.2293 -
accuracy: 0.9171 - val_loss: 0.2967 - val_accuracy: 0.8950
*******************************************/

Nous lui transmettons les entités d'entrée (x_train) et les classes cibles (y_train), ainsi comme le nombre
d'époques à former (ou bien il serait par défaut à seulement 1, ce qui certainement pas suffisant pour
converger vers une bonne solution). Nous passons également un ensemble de validation (c'est facultatif).
Keras mesurera la perte et les mesures supplémentaires sur cet ensemble à la fin de chaque époque, ce qui
est très utile pour voir à quel point le le modèle est vraiment performant. Si la performance sur l'ensemble
d'entraînement est bien meilleure que sur l'ensemble de validation, votre modèle est probablement un
modèle overfitted.

A chaque époque de l'entraînement, Keras affiche le nombre d'instances traitées jusqu'à présent (ainsi
qu'une progression
barre), le temps d'apprentissage moyen par échantillon, ainsi que la perte et la précision (ou tout autre
métriques supplémentaires que vous avez demandées) à la fois sur l'ensemble d'entraînement et sur
l'ensemble de validation. Vous pouvez voir que la perte d'entraînement a diminué, ce qui est bon signe, et
la validation la précision atteint 89,50 % après 30 époques. Ce n'est pas trop loin de la précision
d'entraînement, il ne semble donc pas y avoir beaucoup de overfitting.

La méthode fit() renvoie un objet History contenant les paramètres d'apprentissage (history.params),
la liste des époques traversées (history.epoch) et surtout un dictionnaire (history.history) contenant la perte
et l'extra mesures qu'il a mesurées à la fin de chaque époque sur l'ensemble d'entraînement et sur le
ensemble de validation (le cas échéant). Si vous utilisez ce dictionnaire pour créer un pandas DataFrame et
appelez sa méthode plot(), vous obtenez les courbes d'apprentissage:
Vous pouvez voir que la précision de l'entraînement et la précision de la validation stablement augmentent
pendant l'entraînement, tandis que la perte d'entraînement et la perte de validation diminuent. De plus, les
courbes de validation sont proches des courbes d'apprentissage, ce qui signifie qu'il n'y a pas trop de
surajustement(overfitting)

Une fois que vous êtes satisfait de la précision de la validation de votre modèle, vous devez évaluez-le sur
l'ensemble de test pour estimer l'erreur de généralisation avant de déployer le modèle à la production. Vous
pouvez facilement le faire en utilisant la méthode évalue ()

model.evaluate(x_test, y_test)

/***********Expected Output****************/
313/313 [==============================] - 1s 3ms/step - loss: 60.1254 - accuracy:
0.8518
[60.1253547668457, 0.8518000245094299]
*******************************************/

1.2.7 Utiliser le modèle pour faire des prédictions

Ensuite, nous pouvons utiliser la méthode predict() du modèle pour faire des prédictions sur de
nouvelles instances. Comme nous n'avons pas de nouvelles instances, nous utiliserons simplement la
première trois instances de l'ensemble de test :

x_new = x_test[:3]
y_proba = model.predict(x_new)
y_proba.round(2)
/***********Expected Output****************/
array([[0., 0., 0., 0., 0., 0., 0., 0., 0., 1.],
[0., 0., 1., 0., 0., 0., 0., 0., 0., 0.],
[0., 1., 0., 0., 0., 0., 0., 0., 0., 0.]], dtype=float32)
*******************************************/

Pour avoir les vraies classes de chaque prédiction, il s'agit de prendre l'indice de la plus grande probabilité:

import numpy as np
y_pred = classes_x=np.argmax(y_proba,axis=1)
print(y_pred)
np.array(class_names)[y_pred]

/***********Expected Output****************/
array(['Ankle boot', 'Pullover', 'Trouser'], dtype='<U11')
*******************************************/

1.2.8 Enregistrement et restauration d'un modèle

Lors de l'utilisation de l'API séquentielle ou de l'API fonctionnelle, l'enregistrement d'un Keras formé modèle
est aussi simple que possible :

model = keras.layers.Sequential([...]) # or keras.Model([...])


model.compile([...])
model.fit([...])
model.save("my_keras_model.h5")

Cela enregistre le modèle que vous venez d'entraîner. Pour le re-utiliser:

model = keras.models.load_model("my_keras_model.h5")

Format Keras H5

Keras prend en charge la sauvegarde d'un seul fichier contenant HDF5 l'architecture, les valeurs de poids et
du modèle de compile() informations.

1.3 Entraînement Réseaux de Neurones profonds

Dans la section précédente, nous avons introduit les réseaux de neurones artificiels et formé notre premier
les réseaux de neurones. Mais c'étaient des réseaux peu profonds, avec juste quelques couches cachées.
Quoi si vous devez résoudre un problème complexe, comme détecter des centaines de types de objets dans
des images haute résolution ? Vous devrez peut-être former un DNN(Deep Neural Network) beaucoup plus
profond, peut-être avec 10 couches ou bien plus, chacune contenant des centaines de neurones,
reliés par des centaines de milliers de connexions. L'entraînement d'un DNN profond n'est pas un
promenade dans le parc. Voici quelques-uns des problèmes que vous pourriez rencontrer :

Vous pouvez être confronté au délicat problème des Vanishing Gradients(gradients de fuite) ou au

problème lié aux Exploding Gradients(gradients explosifs). C'est à ce moment que les gradients
grandissent de plus en plus petit, ou de plus en plus grand, lorsqu'il s'écoule vers l'arrière via le DNN

pendant l'entraînement . Ces deux problèmes rendent les couches très difficiles à entraîner

Vous ne disposez peut-être pas de suffisamment de données d'entraînement pour un si grand réseau,

ou il pourrait être trop coûteux à étiqueter.


L'entraînement peut être extrêmement lent.

Un modèle avec des millions de paramètres risquerait fortement de sur-ajuster ensemble

d'entraînement, surtout s'il n'y a pas assez d'instances d'entraînement ou si ils sont trop bruyants(les

bruts d'entraînement).

Dans cette section, nous allons passer en revue chacun de ces problèmes et présenter des techniques pour
les résoudre.

1.3.1 Les problèmes de gradients qui disparaissent/explosent

Comme nous l'avons vu, l'algorithme de rétropropagation fonctionne en allant de la couche de sortie à la
couche d'entrée, propageant le gradient d'erreur le long du réseau. Une fois que l'algorithme a calculé le
gradient de la fonction de coût avec chaque paramètre du réseau, il utilise ces gradients pour mettre à jour
chaque paramètre avec une étape Gradient Descent. Malheureusement, les gradients deviennent souvent de
plus en plus petits à mesure que l'algorithme progresse vers les couches inférieures. En conséquence, la
mise à jour Gradient Descent laisse les poids de connexion des couches inférieures pratiquement inchangés,
et l'entraînement ne converge jamais vers une bonne solution. Nous appelons cela les gradients de
fuite(Vanishing Gradients) problème. Dans le cas contraire, l'inverse peut se produire : les gradients peuvent
grossir et plus gros jusqu'à ce que les couches obtiennent des mises à jour de poids incroyablement
importantes et l'algorithme diverge. C'est le problème des gradients explosifs, qui est plus souvent
rencontré dans les réseaux de neurones récurrents(RNN).

Ce comportement malheureux a été observé empiriquement il y a longtemps, et c'était l'un des les raisons
pour lesquelles les réseaux de neurones profonds ont été quasiment abandonnés au début des années
2000. Ce n'était pas clair ce qui rendait les gradients si instables lors de l'entraînement d'un DNN, mais un
peu de lumière a été apportée dans un article de 2010 par Xavier Glorot et Yoshua Bengio .

En bref, ils ont montré qu'avec la fonction d'activation(Sigmoïde) et un certain schéma d'initialisation, la
variance des sorties de chaque couche est beaucoup supérieure à la variance de ses entrées. A l'avenir dans
le réseau, la variance continue d'augmenter après chaque couche jusqu'à ce que la fonction d'activation
sature aux couches supérieures. Cette saturation est en fait aggravée par le fait que la fonction logistique a
une moyenne de 0,5 et non de 0.
En regardant la fonction d'activation logistique, vous pouvez voir que lorsque les entrées deviennent
importantes (négatives ou positives), la fonction sature à 0 ou 1, avec une dérivée extrêmement proche de
0. Ainsi, lorsque la rétropropagation se déclenche
n'a pratiquement aucun gradient à se propager à travers le réseau. Et il y aura peu de gradient existant
continue de se diluer au fur et à mesure que la rétropropagation progresse vers le bas à travers les couches
supérieures, il n'y a donc vraiment plus rien pour les couches inférieures.

1.3.1.1 Glorot and He Initialization

Dans leur article, Glorot et Bengio proposent un moyen d'atténuer considérablement problème de gradients
instables. Ils soulignent que nous avons besoin que le signal(flux des données entrantes et sortantes de
chaque neurone) circule correctement dans les deux sens : dans le sens direct lors des propagrations, et
dans le sens inverse lors de la rétropropagation des gradients. Nous ne voulons pas le signal s'éteint, nous
ne voulons pas non plus qu'il explose et sature.

Pour que le signal coule correctement, les auteurs soutiennent que nous avons besoin

de la variance des sorties de chaque calque soit égal à la variance de ses entrées

et nous avons besoin que

les gradients aient variance égale avant et après avoir traversé une couche dans le sens

inverse(veuillez consulter l'article joint en cours si vous êtes intéressé par les détails mathématiques).

Il est en fait pas possible de garantir les deux à moins que la couche ait un nombre égal de entrées et
neurones. Ces nombres sont appelés fan-in et fan-out de chaque couche.

Glorot et Bengio ont proposé un bon compromis qui s'est avéré fonctionnent très bien en pratique : les
poids de connexion de chaque couche doivent être initialisé aléatoirement comme décrit:

Initialisation Glorot:
Une distribution normale avec un moyen à 0 et variance , où

Ou une distribution uniforme entre et avec

Si vous remplacez le par le dans l'équation, vous obtenez une stratégie d'initialisation
proposée par Yann LeCun dans les années 1990. Il l'a appelé LeCun initialisation . pour la distribution
uniforme, juste calculer

Le tableau de synthèse pour les différentes stratégie d'initialisation et ses fonctions d'activation associées:

Initialisation Fonction d'activation (Distribution normale)

Glorot None, tanh, logistic, softmax

He ReLU and variants

LeCun SELU

Par défaut, Keras utilise l'initialisation Glorot avec une distribution uniforme. Lorsque création d'une
couche, vous pouvez le changer en initialisation He en définissant kernel_initializer="he_uniform"
ou kernel_initializer="he_normal" comme ça:

keras.layers.Dense(10, activation="relu", kernel_initializer="he_normal")

Si vous voulez une initialisation He avec une distribution uniforme mais basée sur le plutôt que
, vous pouvez utiliser l'initialiseur VarianceScaling comme ceci :

he_avg_init = keras.initializers.VarianceScaling(scale=2., mode='fan_avg',


distribution='uniform')
keras.layers.Dense(10, activation="sigmoid", kernel_initializer=he_avg_init)

1.3.1.2 Fonctions d'activation non saturantes

L'une des idées de l'article de 2010 de Glorot et Bengio était que le les problèmes de gradients instables
étaient en partie dus à un mauvais choix d'activation fonction. Il s'avère que d'autres fonctions d'activation
se comportent beaucoup mieux dans les réseaux de neurones profonds - en particulier, la fonction
d'activation ReLU, principalement parce qu'il ne sature pas pour les valeurs positives (et parce qu'il est
rapide à calculer).
Malheureusement, la fonction d'activation ReLU n'est pas parfaite. Il souffre d'un problème connu sous le
nom de ReLU mourants : pendant l'entraînement, certains neurones fonctionnent efficacement "mourir", ce
qui signifie qu'ils arrêtent de produire autre chose que 0. Dans certains cas, vous pouvez constater que la
moitié des neurones de votre réseau sont morts, surtout si vous avez utilisé un
grand taux d'apprentissage. Un neurone meurt lorsque son poids est modifié de telle manière que la
somme pondérée de ses entrées est négative pour toutes les instances de l'ensemble de training set(des
données d'entraînement).

Lorsque cela se produit, il continue de produire des zéros, et Gradient Descent n'affecte plus car le gradient
de la fonction ReLU est nul lorsque son entrée est négative.

Pour résoudre ce problème, vous pouvez utiliser une variante de la fonction ReLU, telle que comme le

Leaky ReLU

ELU
Pour utiliser la fonction d'activation leaky ReLU, créez une couche LeakyReLU et ajoutez-la à votre modèle
juste après le calque sur lequel vous souhaitez l'appliquer :

model = keras.models.Sequential([
[...]
keras.layers.Dense(10, kernel_initializer="he_normal"),
keras.layers.LeakyReLU(alpha=0.2),
[...]
])

1.3.2 Réutiliser les couches pré-entraînées/ Transfer Learning

Ce n'est généralement pas une bonne idée de former un très grand DNN à partir de zéro : à la place, vous
devriez toujours essayer de trouver un réseau de neurones existant qui accomplit une tâche similaire à celle
que vous essayez d'aborder, puis réutiliser les couches basses de ce réseau. Cette technique est appelé
apprentissage par transfert( Transfer Learning ). Cela accélérera non seulement considérablement
l'entraînement, mais aussi nécessitent beaucoup moins de données d'entraînement.
1.3.2.1 Apprentissage par transfert avec Keras

Prenons un exemple. Supposons que l'ensemble de données Fashion MNIST ne contient que 8 classes, par
exemple, toutes les classes à l'exception de la sandale et de la chemise. Quelqu'un a construit et formé un
modèle Keras sur cet ensemble et a obtenu des performances raisonnablement bonnes (> 90 % de
précision).

Appelons ce modèle A. Vous voulez maintenant vous attaquer à un autre tâche : vous avez des images de
sandales et de chemises, et vous souhaitez former un classificateur binaire (positif=chemise,
négatif=sandale).

Votre ensemble de données est assez petit seulement avoir 200 images étiquetées. Lorsque vous entraînez
un nouveau modèle pour cette tâche (appelons-le modèle B) avec la même architecture que le modèle A, il
fonctionne raisonnablement bien(précision de 97,2 %) sans avoir passé encore l'étape Transfer Learning.
Mais comme c'est une tâche beaucoup plus facile (il n'y a que deux classes), vous espériez plus.

Tout d'abord, vous devez charger le modèle A et créer un nouveau modèle basé sur les couches. Réutilisons
toutes les couches à l'exception de la couche de sortie :

model_A = keras.models.load_model("my_model_A.h5")
model_B_on_A = keras.models.Sequential(model_A.layers[:-1]) #layers[:-1] nous
permet de supprimer la dernière couche de sortie du modèle A
model_B_on_A.add(keras.layers.Dense(1, activation="sigmoid"))
Notez que model_A et model_B_on_A partagent maintenant certaines couches. Quand tu t'entraînes
model_B_on_A, cela affectera également model_A. Si vous voulez éviter cela, vous devez pour cloner
model_A avant de réutiliser ses couches. Pour ce faire, vous clonez le modèle A architecture avec
clone.model() , puis copiez ses poids (depuis clone_model() ne clone pas les poids) :

model_A_clone = keras.models.clone_model(model_A)
model_A_clone.set_weights(model_A.get_weights())

Vous pouvez maintenant entraîner model_B_on_A pour la tâche B, mais depuis la nouvelle couche de sortie
a été initialisée de manière aléatoire, il fera de grosses erreurs (au moins pendant les premières époques), il
y aura donc de grands gradients d'erreur qui peuvent détruire les poids réutilisés. Pour éviter cela, une
approche consiste à geler les couches réutilisées pendant les premières époques, donnant à la nouvelle
couche un peu de temps pour apprendre des poids raisonnables. Pour faire ça, définissez l'attribut
entraînable de chaque couche sur False et compilez le modèle :

for layer in model_B_on_A.layers[:-1]:


layer.trainable = False
model_B_on_A.compile(loss="binary_crossentropy", optimizer="sgd", metrics=
["accuracy"])


Note importante:

Vous devez toujours compiler votre modèle après avoir gelé ou dégelé des couches

Vous pouvez maintenant entraîner le modèle pour quelques époques, puis dégeler les couches réutilisés (ce
qui nécessite de compiler à nouveau le modèle) et poursuivre l'entraînement pour affiner les couches
réutilisées pour la tâche B. Après avoir dégelé les couches réutilisées, il s'agit généralement d'un bonne idée
de réduire le taux d'apprentissage, encore une fois pour éviter d'endommager les poids réutilisés:

# Quelques époques
history = model_B_on_A.fit(x_train_B, y_train_B, epochs=4, validation_data=
(x_valid_B, y_valid_B))
# Dégéler les couches
for layer in model_B_on_A.layers[:-1]:
layer.trainable = True
optimizer = keras.optimizers.SGD(lr=1e-4) # the default lr is 1e-2
# Recompiler les modèles
model_B_on_A.compile(loss="binary_crossentropy", optimizer=optimizer, metrics=
["accuracy"])

history = model_B_on_A.fit(x_train_B, y_train_B, epochs=16,validation_data=


(x_valid_B, y_valid_B))
Alors, quel est le verdict final ? Eh bien, la précision du test de ce modèle est de 99,25 %, ce qui signifie que
l'apprentissage par transfert a réduit le taux d'erreur de 2,8 % à presque 0,7 % !

model_B_on_A.evaluate(X_test_B, y_test_B)
[0.06887910133600235, 0.9925]


Êtes-vous convaincu? Vous ne devriez pas l'être : j'ai triché ! Si vous essayez de changer les classes,
vous verrez que l'amélioration généralement chute, voire disparaît ou s'inverse.

Il s'avère que l'apprentissage par transfert ne fonctionne pas très bien avec petits réseaux denses,
probablement parce que les petits réseaux apprennent peu de modèles, et les réseaux denses
apprennent des modèles très spécifiques, qui ne seront probablement pas utiles dans d'autres
tâches. L'apprentissage par transfert fonctionne mieux avec les réseaux neurones convolutifs
profonds, qui ont tendance à apprendre des détecteurs de caractéristiques beaucoup plus généraux
(surtout dans les couches inférieures). Nous reviendrons sur l'apprentissage par transfert dans le
prochain module en utilisant les techniques dont nous venons de présenter

Et qu'est ce que c'est un réseau de neurones convolutif? Vous le verrez dans le prochain chapitre Vision
par ordinateur - Réseaux de neurones convolutifs - Computer vision - Convolutional
Neural Networks .

1.3.3 Éviter le surajustement(Overfitting) grâce à la régularisation

Réseaux de neurones profonds comportent généralement des dizaines de milliers de paramètres, parfois
même des millions. Cela leur donne une quantité incroyable de liberté et signifie qu'ils peuvent s'adapter à
un énorme variété d'ensembles de données complexes. Mais cette grande flexibilité rend également le
réseau susceptibles de sur-adapter l'ensemble d'entraînement. Nous avons besoin de régularisation.

1.3.3.1 l1 et l2 régularisation

Comme nous avons vu dans les modules précédents de machine learning, pour les modèles linéaires
simples, vous pouvez utiliser régularisation pour contraindre les poids de connexion d'un réseau de
neurones, et/ou régularisation si vous voulez un modèle creux (Sparse: avec de nombreux poids égaux à
0).

Ici est de comment appliquer la régularisation aux poids de connexion d'une couche Keras, en utilisant
un facteur de régularisation de 0,01 :

layer = keras.layers.Dense(100, activation="elu", kernel_initializer="he_normal",


kernel_regularizer=keras.regularizers.l2(0.01))
Étant donné que vous souhaiterez généralement appliquer le même régularisateur à toutes les couches de
votre réseau, ainsi qu'en utilisant la même fonction d'activation et la même stratégie d'initialisation dans
toutes les couches cachées, vous pouvez vous retrouver à répéter la même chose arguments. Cela rend le
code moche et sujet aux erreurs. Pour éviter cela, vous pouvez essayer de refactoriser votre code pour
utiliser des boucles. Une autre option consiste à utiliser Python fonction functools.partial() , qui
vous permet de créer un mince wrapper pour tout appelable, avec quelques valeurs d'argument par défaut :

from functools import partial

RegularizedDense = partial(keras.layers.Dense, activation="elu",


kernel_initializer="he_normal", kernel_regularizer=keras.regularizers.l2(0.01))

model = keras.models.Sequential([
keras.layers.Flatten(input_shape=[28, 28]),
RegularizedDense(300),
RegularizedDense(100),
RegularizedDense(10, activation="softmax",
kernel_initializer="glorot_uniform")
])

1.3.3.2 Dropout

L'abandon(Dropout) est l'une des techniques de régularisation les plus populaires pour les réseaux de
neurones profonds. Il a été proposé dans un article de Geoffrey Hinton en 2012 et plus loin détaillé dans un
article de 2014 de Nitish Srivastava et al., et il s'est avéré très réussi.

C'est un algorithme assez simple : à chaque pas d'entraînement, chaque neurone (y compris le neurones
d'entrée, mais toujours en excluant les neurones de sortie) a une probabilité d'être temporairement
"abandonné", ce qui signifie qu'il sera entièrement ignoré pendant cette étape d'entraînement, mais il peut
être actif lors de l'étape suivante.
Une autre façon de comprendre le pouvoir du dropout est de se rendre compte qu'un réseau de neurones
est généré à chaque étape d'entraînement. Étant donné que chaque neurone peut être présent ou absent, il
y a au total 2 réseaux possibles (où N est le nombre total de neurones largables). C'est un nombre si énorme
qu'il est pratiquement impossible que le même réseau de neurones soit échantillonné deux fois. Une fois
que vous avez exécuté 10 000 étapes d'entraînement, vous avez essentiellement formé 10 000 étapes
différentes réseaux de neurones (chacun avec une seule instance d'entraînement). Ces réseaux de neurones
sont évidemment pas indépendants car ils partagent beaucoup de leurs poids, mais ils sont pourtant tous
différents. Le réseau de neurones qui en résulte peut être considéré comme un ensemble moyen de tous ces
réseaux de neurones plus petits.


En pratique, vous pouvez généralement appliquer le décrochage uniquement aux neurones du haut
pour trois couches (à l'exclusion de la couche de sortie).

Pour implémenter le dropout à l'aide de Keras, vous pouvez utiliser la couche keras.layers.Dropout .
Pendant l'entraînement, il abandonne au hasard certaines entrées (en les mettant à 0) et divise les entrées
restantes par la probabilité de conservation. Après l'entraînement, il le fait rien du tout, il passe simplement
les entrées à la couche suivante. Le code suivant applique la régularisation d'abandon avant chaque couche
Dense, en utilisant un taux d'abandon(Dropout) de 0.2 :
model = keras.models.Sequential([
keras.layers.Flatten(input_shape=[28, 28]),
keras.layers.Dropout(rate=0.2),
keras.layers.Dense(300, activation="elu",
kernel_initializer="he_normal"),
keras.layers.Dropout(rate=0.2),
keras.layers.Dense(100, activation="elu",
kernel_initializer="he_normal"),
keras.layers.Dropout(rate=0.2),
keras.layers.Dense(10, activation="softmax")
])

Si vous observez que le modèle est surajusté, vous pouvez augmenter le taux d'abandon. Inversement, vous
devriez essayer de diminuer le taux d'abandon si le modèle est sous-ajusté sur ensemble d'entraînement.
Cela peut également aider à augmenter le taux d'abandon pour les grandes couches, et réduisez-le pour les
petits. De plus, de nombreuses architectures de pointe n'utilisent que abandon après la dernière couche
cachée, vous pouvez donc essayer ceci si l'abandon complet est trop fort. L'abandon a tendance à ralentir
considérablement la convergence, mais il en résulte généralement dans un bien meilleur modèle lorsqu'il
est réglé correctement. Donc, ça vaut généralement le coup du temps et des efforts supplémentaires.

Vous aimerez peut-être aussi