Vous êtes sur la page 1sur 16

4 Différentes Architectures CNN

Les architectures CNN typiques empilent quelques couches convolutives (chacune généralement suivi d'une
couche ReLU), puis une couche de regroupement(Pooling), puis encore quelques convolutions couches
(+ReLU), puis une autre couche de pooling, et ainsi de suite. L'image devient plus en plus petite à mesure
qu'elle progresse dans le réseau, mais elle devient aussi généralement plus en plus profonde, grâce aux
couches convolutives. Au départ de l'architecture CNN, un réseau de neurones feedforward régulier est
ajouté, composé de quelques couches entièrement connectées (+ ReLU), et la couche finale génère la
prédiction (par exemple, une couche softmax qui génère une classe estimée probabilités).

Voici comment mettre en place un CNN simple pour s'attaquer au Fashion MNIST base de données(Introduit
dans le premier chapitre):

model = keras.models.Sequential([
keras.layers.Conv2D(64, 7, activation="relu", padding="same", input_shape=[28,
28, 1]),
keras.layers.MaxPooling2D(2),
keras.layers.Conv2D(128, 3, activation="relu", padding="same"),
keras.layers.Conv2D(128, 3, activation="relu", padding="same"),
keras.layers.MaxPooling2D(2),
keras.layers.Conv2D(256, 3, activation="relu", padding="same"),
keras.layers.Conv2D(256, 3, activation="relu", padding="same"),
keras.layers.MaxPooling2D(2),
keras.layers.Flatten(),
keras.layers.Dense(128, activation="relu"),
keras.layers.Dropout(0.5),
keras.layers.Dense(64, activation="relu"),
keras.layers.Dropout(0.5),
keras.layers.Dense(10, activation="softmax")
])

Détail du modèle:

La première couche utilise 64 filtres assez gros (7×7) mais pas de foulée car les images d'entrée ne sont
pas très grandes. Il définit également input_shape=[28, 28, 1], car les images sont de 28 × 28 pixels,

avec un seul canal de couleur.


Ensuite, nous avons une couche de pooling maximale(max pooling) qui utilise une taille de pool de 2,
donc elle divise chaque dimension spatiale par un facteur de 2.

Puis on répète deux fois la même structure : deux couches convolutives suivi d'une couche de pooling
maximale. Pour des images plus grandes, nous pourrions répéter cette structure plusieurs fois (le

nombre de répétitions est un hyperparamètre que vous pouvez régler).


Notez que le nombre de filtres augmente à mesure que nous montons le CNN vers la couche de sortie

(c'est initialement 64, puis 128, puis 256) : cela a du sens, car le nombre de caractéristiques de bas
niveau est souvent assez faible (par exemple, petits cercles, lignes horizontales), mais il existe de

nombreuses façons différentes pour les combiner en caractéristiques de niveau supérieur. C'est une
pratique courante de doubler le nombre de filtres après chaque couche de pooling : puisqu'un pooling
couche divise chaque dimension spatiale par un facteur de 2, nous pouvons nous permettre de

doubler le nombre de cartes d'entités dans la couche suivante sans craindre exploser le nombre de
paramètres, l'utilisation de la mémoire ou le calcul.

Vient ensuite le réseau entièrement connecté, composé de deux denses cachés couches et une couche
de sortie dense. A noter qu'il faut aplatir ses entrées, puisqu'un réseau dense attend un tableau 1D

d'entités pour chaque instance. Nous ajoutons également deux couches d'abandon(dropout), avec un
taux d'abandon de 50 % chacune pour réduire l'effet overfitting.

Au fil des années, des variantes de cette architecture fondamentale ont été développées, conduisant à des
avancées étonnantes dans le domaine. Dans ce chapitre, nous allons découvrir quelques modèles CNN le
plus connus.

4.1 LeNet-5

L'architecture LeNet-5 est peut-être l'architecture CNN la plus connue. Il a été créé par Yann LeCun en 1998
et a été largement utilisé pour la reconnaissance des chiffres manuscrits (MNIST). Il est composé des
couches affichées dans le tableau suivant:

Layer Type Maps(Nb. of Kernel) Size Kernel Stride Activation

In Input 1 32 × 32 - - -

C1 Convolution 6 28 × 28 5×5 1 tanh

S2 Avg pooling 6 14 × 14 2×2 2 tanh

C3 Convolution 16 10 × 10 5×5 1 tanh

S4 Avg pooling 16 5×5 2×2 2 tanh

C5 Convolution 120 1×1 5×5 1 tanh

F6 Fully connected - 84 - - tanh

Out Fully connected - 10 - - RBF


Il y a quelques détails supplémentaires à noter :

Les images MNIST font 28 × 28 pixels, mais elles sont complétées par des zéros à 32 × 32 pixels et

normalisé avant d'être transmis au réseau. Le reste du réseau n'utilise aucun rembourrage(padding),
c'est pourquoi la taille continue à diminuer au fur et à mesure que l'image progresse sur le réseau.
Les couches de regroupement moyennes(Average Pooling) sont légèrement plus complexes que

d'habitude : chaque neurone calcule la moyenne de ses entrées, puis multiplie le résultat par un
coefficient apprenable (un par filtre) et ajoute un terme de biais apprenable (encore une fois, un par

filtre), puis applique enfin la fonction d'activation


La couche de sortie est un peu spéciale : au lieu de calculer la matrice multiplication des entrées et du

vecteur poids, chaque neurone sort le carré de la distance euclidienne entre son vecteur d'entrée et
son vecteur de poids. Chaque sortie mesure à quel point l'image appartient à une classe de chiffres

particulière. La fonction de coût d'entropie croisée est maintenant préférée, car il pénalise beaucoup
plus les mauvaises prédictions, produisant des gradients plus importants et convergeant plus
rapidement. (Radial Basis Function)

Son implémentation Keras est donc la suivante:

lenet_5_model = keras.models.Sequential([
keras.layers.Conv2D(6, kernel_size=5, strides=1, activation='tanh',
input_shape=[32, 32, 1], padding='same'), #C1
keras.layers.AveragePooling2D(), #S2
keras.layers.Conv2D(16, kernel_size=5, strides=1, activation='tanh',
padding='valid'), #C3
keras.layers.AveragePooling2D(), #S4
keras.layers.Conv2D(120, kernel_size=5, strides=1, activation='tanh',
padding='valid'), #C5
keras.layers.Flatten(), #Flatten
keras.layers.Dense(84, activation='tanh'), #F6
keras.layers.Dense(10, activation='softmax') #Output layer
])

Testons LeNet-5 sur les données des chiffres manuscrits:


import tensorflow as tf
from tensorflow import keras
import numpy as np

(train_x, train_y), (test_x, test_y) = keras.datasets.mnist.load_data()


train_x = train_x / 255.0
test_x = test_x / 255.0

# Avec tf.expand_dims, nous pouvons compléter les données à une taille de 32 * 32


train_x = tf.expand_dims(train_x, 3)
test_x = tf.expand_dims(test_x, 3)
val_x = train_x[:5000]
val_y = train_y[:5000]

# Compiler le modèle avec un optimiseur et une fonction de perte


lenet_5_model.compile(optimizer='adam',
loss=keras.losses.sparse_categorical_crossentropy, metrics=['accuracy'])

lenet_5_model.fit(train_x, train_y, epochs=5, validation_data=(val_x, val_y))

lenet_5_model.evaluate(test_x, test_y)

4.2 AlexNet

L'architecture AlexNet CNN a remporté le défi ImageNet ILSVRC 2012 en une grande marge : il a atteint un
taux d'erreur de 17 % parmi les cinq premiers, tandis que le deuxième meilleur atteint seulement 26 % ! Il a
été développé par Alex Krizhevsky (d'où le nom), Ilya Sutskever et Geoffrey Hinton. Il est similaire à LeNet-5,
mais beaucoup plus grand et plus profond, et c'était le premier à empiler des couches convolutives
directement au-dessus de les uns les autres, au lieu d'empiler une couche de pooling au-dessus de chaque
couche convolution.
Maps(Nb. of
Layer Type Size Kernel Stride Padding Activation
Kernel)

227 ×
In Input 3 (RGB) - - - -
227

55 × 11 ×
C1 Convolution 96 4 valid ReLU
55 11

27 ×
S2 Max pooling 96 3×3 2 valid -
27

27 ×
C3 Convolution 256 5×5 1 same ReLU
27

13 ×
S4 Max pooling 256 3×3 2 valid -
13

13 ×
C5 Convolution 384 3×3 1 same ReLU
13

13 ×
C6 Convolution 384 3×3 1 same ReLU
13

13 ×
C7 Convolution 256 3×3 1 same ReLU
13

Fully
F8 - 4,096 - - - ReLU
connected

Fully
F9 - 4,096 - - - ReLU
connected

Fully
Out - 1,000 - - - Softmax
connected
Pour réduire le overfitting, les auteurs ont utilisé deux techniques de régularisation. D'abord, ils ont
appliqué la méthode de dropout avec un taux d'abandon de 50 % aux sorties des couches F8 et F9.
Deuxièmement, ils ont effectué une méthode d'augmentation des données en décalant aléatoirement les
images d'apprentissage de divers décalages, en les retournant horizontalement et en modifiant les
conditions d'éclairage.


Note importante - Augmentation des données:

L'augmentation des données augmente artificiellement la taille de l'ensemble d'apprentissage en


générer de nombreuses variantes réalistes de chaque instance d'entraînement. Cela réduit le
overfitting, ce qui en fait une technique de régularisation. Les instances générées doit être aussi
réaliste que possible : idéalement, étant donné une image d'un ensemble d'entraînement augmenté,
un humain ne devrait pas être en mesure de dire s'il a été augmentée ou non. Le simple fait d'ajouter
du bruit blanc n'aidera pas. Les modifications doivent pouvoir être apprises.

Par exemple, vous pouvez légèrement décaler, faire pivoter et redimensionner chaque image de
l'entraînement définie par différentes quantités et ajouter les images résultantes à l'entraînement.
Cela oblige le modèle à être plus tolérant
variations dans la position, l'orientation et la taille des objets dans les images. Pour un modèle plus
tolérant aux différentes conditions d'éclairage. En général, vous pouvez retournez également les
images horizontalement (à l'exception du texte et d'autres images asymétriques objets). En
combinant ces transformations, vous pouvez augmenter considérablement la taille de votre ensemble
d'entraînement.
4.2.1 Exercice: Implémenter AlexNet avec API Keras

AlexNet_model = keras.models.Sequential([
### Codes à Remplir ###
])

4.3 VGGNet

Le gagnant du défi ILSVRC 2014 était VGGNet, développé par Karen Simonyan et Andrew Zisserman du
Visual Geometry Group (VGG) laboratoire de recherche à l'Université d'Oxford. Il a une très simple et
classique architecture, avec 2 ou 3 couches convolutives et une couche de pooling, puis à nouveau 2 ou 3
couches convolutives et une couche de pooling, et ainsi de suite (atteignant un total de seulement 16 ou 19
couches convolutives, selon la variante VGG), plus sous réseaux final dense de 2 couches cachées et la
couche de sortie. Il n'utilisait que des filtres 3 × 3, mais de nombreux filtres.

La colonne E dans le tableau suivant est pour VGG19 (les autres colonnes sont pour d'autres variantes de
modèles VGG) :
tf.keras.applications.vgg19.VGG19(
include_top=False,
weights='imagenet',
input_tensor=None,
input_shape=None,
pooling=None,
classes=1000,
classifier_activation='softmax'
)
Paramètres Explication

s'il faut inclure les 3 couches entièrement connectées en haut du


include_top
réseau.

None (initialization random), 'imagenet' (pre-entaîné sur


weights
ImageNet), ou le chemin vers le fichier de poids à charger.

tenseur Keras facultatif (c'est-à-dire la sortie de


input_tensor layers.Input() ) à utiliser comme entrée d'image pour le
modèle.

tuple de forme facultatif, à spécifier uniquement si include_top


est False (sinon la forme d'entrée doit être (224, 224, 3) (avec
le format de données channels_last ) ou (3, 224, 224) (
input_shape avec le format de données channels_first ). Il doit avoir
exactement 3 canaux d'entrée, et la largeur et la hauteur ne doivent
pas être inférieures à 32. Par exemple, (200, 200, 3) serait une
valeur valide.

Mode de pooling facultatif pour l'extraction de caractéristiques


lorsque include_top est False . None signifie que la sortie
du modèle sera la sortie du tenseur 4D du dernier bloc convolutif.
pooling
avg signifie que le avg_pooing sera appliqué au sortie du dernier
bloc convolutif, et donc la sortie du modèle sera un tenseur 2D.
max signifie que maxpooling sera appliquée globalement.

nombre facultatif de classes dans lesquelles classer les images, à


classes spécifier uniquement si include_top vaut True et si aucun
argument weights n'est spécifié.

Un str ou appelable. La fonction d'activation à utiliser sur le


couche "top". Ignoré sauf si include_top=True . Définissez
classifier_activation
classifier_activation=None pour renvoyer les logis/les
sorties de la couche "top".

4.3.1 Exercice: Implémenter VGG19 en utilisant les APIs par défaut (Dense, Conv2D,
MaxPooling etc.)

def VGG19(input_shape):
model = Sequential()
### Codes à Remplir ###
return model
4.4 ResNet

Kaiming He et al. a remporté le défi ILSVRC 2015 en utilisant un réseau résiduel (ou ResNet), qui a fourni un
taux d'erreur stupéfiant parmi les cinq premiers inférieur à 3,6 %. La variante gagnante utilisait un CNN
extrêmement profond composé de 152 couches (autres
les variantes avaient 34, 50 et 101 couches). Elle confirme la tendance générale : les modèles sont de plus
en plus profond, avec de moins en moins de paramètres. La clé pour être capable de former un réseau aussi
profond est d'utiliser des Skip Connections (connexions de saut, également appelées raccourcis
connexions) : le signal entrant dans une couche est également ajouté à la sortie d'une couche située un peu
plus haut dans le réseaux. Voyons pourquoi cela est utile.

Lors de l'entraînement d'un réseau de neurones, le but est de lui faire modéliser une fonction cible . Si
vous ajoutez l'entrée à la sortie du réseau (c'est-à-dire que vous ajoutez un saut connexion), alors le
réseau sera obligé de modéliser à plutôt que . C'est ce qu'on appelle l'apprentissage
résiduel(Residual Learning - ResNet).

Lorsque vous initialisez un réseau de neurones régulier, ses poids sont proches de zéro, donc le le réseau ne
produit que des valeurs proches de zéro. Si vous ajoutez un saut de connexion, en sortant, le réseau ne
produit qu'une copie de ses entrées. En d'autres termes, il a d'abord modélise la fonction identité. Si la
fonction cible est assez proche de l'identité fonction (ce qui est souvent le cas), cela accélérera
considérablement l'entraînement.

Bloc d'identité
De plus, si vous ajoutez de nombreuses saut connexions(Skip Connections), le réseau peut commencer à
effectuer des progrès même si plusieurs couches n'ont pas encore commencer à s'entraîner. Le réseau
résiduel profond peut être vu comme un empilement d'unités résiduelles (RUs), où chaque unité résiduelle
est un petit réseau de neurones avec un lien de saut.

Bloc Convolutif

Nous pouvons utiliser ce type de bloc lorsque les dimensions d'entrée et de sortie ne correspondent pas. La
différence avec le bloc d'identité est qu'il y a une couche CONV2D dans le chemin de raccourci.

4.4.1 L'architecture de ResNet

Chaque l'unité résiduelle est composée de deux couches convolutives sans couche pooling. avec la
normalisation par lots (Batch Normalization) et l'activation ReLU, en utilisant des noyaux/filtres de 3 × 3 et
en préservant les dimensions spatiales (foulée 1, remplissage "même" / same padding).
4.4.2 Mise en œuvre du bloc d'identité

Pour construire une connexion de saut, on stocke d'abord la valeur initiale de X dans une variable et puis on
ajoute les couches normales et au final, on utilise Add()() pour construire le skip . C'est à dire ajouter le
shortcut(chemin de raccourci) et le chemin normal de votre réseau CNN.

X_shortcut = X # Store the initial value of X in a variable


## Perform convolution + batch norm operations on X
X = Add()([X, X_shortcut]) # SKIP Connection

Ci dessous une implémentation de bloc d'identité

def identity_block(X, f, filters, stage, block):

conv_name_base = 'res' + str(stage) + block + '_branch'


bn_name_base = 'bn' + str(stage) + block + '_branch'
F1, F2, F3 = filters

X_shortcut = X

X = Conv2D(filters=F1, kernel_size=(1, 1), strides=(1, 1), padding='valid',


name=conv_name_base + '2a', kernel_initializer=glorot_uniform(seed=0))(X)
X = BatchNormalization(axis=3, name=bn_name_base + '2a')(X)
X = Activation('relu')(X)

X = Conv2D(filters=F2, kernel_size=(f, f), strides=(1, 1), padding='same',


name=conv_name_base + '2b', kernel_initializer=glorot_uniform(seed=0))(X)
X = BatchNormalization(axis=3, name=bn_name_base + '2b')(X)
X = Activation('relu')(X)
X = Conv2D(filters=F3, kernel_size=(1, 1), strides=(1, 1), padding='valid',
name=conv_name_base + '2c', kernel_initializer=glorot_uniform(seed=0))(X)
X = BatchNormalization(axis=3, name=bn_name_base + '2c')(X)

X = Add()([X, X_shortcut])# SKIP Connection


X = Activation('relu')(X)

return X

Le code est assez simple mais il y a une chose importante à considérer - puisque X , X_shortcut ci-
dessus sont deux matrices, vous ne pouvez les ajouter que si elles ont la même forme. Donc, si les
opérations de convolution + norme de lot(batch normalisation) sont effectuées de manière à ce que la
forme de sortie soit la même, nous pouvons simplement les ajouter comme indiqué ci-dessus.

4.4.3 Mise en œuvre du bloc convolutif

Dans le cas où x_shortcut passe par une couche de convolution choisie de telle sorte que la sortie de
celle-ci soit de la même dimension que la sortie du bloc de convolution, comme indiqué ci-dessus dans la
graphe Bloc Convolutif.

Son implémentation devient donc:

def convolutional_block(X, f, filters, stage, block, s=2):

conv_name_base = 'res' + str(stage) + block + '_branch'


bn_name_base = 'bn' + str(stage) + block + '_branch'

F1, F2, F3 = filters

X_shortcut = X

X = Conv2D(filters=F1, kernel_size=(1, 1), strides=(s, s), padding='valid',


name=conv_name_base + '2a', kernel_initializer=glorot_uniform(seed=0))(X)
X = BatchNormalization(axis=3, name=bn_name_base + '2a')(X)
X = Activation('relu')(X)

X = Conv2D(filters=F2, kernel_size=(f, f), strides=(1, 1), padding='same',


name=conv_name_base + '2b', kernel_initializer=glorot_uniform(seed=0))(X)
X = BatchNormalization(axis=3, name=bn_name_base + '2b')(X)
X = Activation('relu')(X)

X = Conv2D(filters=F3, kernel_size=(1, 1), strides=(1, 1), padding='valid',


name=conv_name_base + '2c', kernel_initializer=glorot_uniform(seed=0))(X)
X = BatchNormalization(axis=3, name=bn_name_base + '2c')(X)

# On ajoute une couche Conv2D dans le shortcut pour que la sortie soit de même
forme que chemin principal
X_shortcut = Conv2D(filters=F3, kernel_size=(1, 1), strides=(s, s),
padding='valid', name=conv_name_base + '1',
kernel_initializer=glorot_uniform(seed=0))(X_shortcut)
# On effectue également une étape de BatchNormalization
X_shortcut = BatchNormalization(axis=3, name=bn_name_base + '1')(X_shortcut)

X = Add()([X, X_shortcut])
X = Activation('relu')(X)

return X

Une chose importante à noter ici est que la connexion de saut(skip connection) est appliquée avant
l'activation RELU, comme indiqué dans le schéma ci-dessus. La recherche a montré que cela donne les
meilleurs résultats.

4.4.4 Mise en œuvre de ResNet-50

ResNet a de nombreuses variantes qui fonctionnent sur le même concept mais ont des nombres de couches
différents. Resnet50 est utilisé pour désigner la variante qui peut fonctionner avec 50 couches de réseau de
neurones. Dans ce programme, nous utiliserons cette variante pour illustrer l'implémentation d'un ResNet.

En utilisant les deux fonctions au-dessus identity_block et convolutional_block , nous allons


pouvoir implémenter ResNet-50 sans les couches fully connected(Dense/ Entièrement connectée). Nous
préciserons pourquoi.

def ResNet50(input_shape=(224, 224, 3)):

X_input = Input(input_shape)

X = ZeroPadding2D((3, 3))(X_input)

X = Conv2D(64, (7, 7), strides=(2, 2), name='conv1',


kernel_initializer=glorot_uniform(seed=0))(X)
X = BatchNormalization(axis=3, name='bn_conv1')(X)
X = Activation('relu')(X)
X = MaxPooling2D((3, 3), strides=(2, 2))(X)

X = convolutional_block(X, f=3, filters=[64, 64, 256], stage=2, block='a',


s=1)
X = identity_block(X, 3, [64, 64, 256], stage=2, block='b')
X = identity_block(X, 3, [64, 64, 256], stage=2, block='c')

X = convolutional_block(X, f=3, filters=[128, 128, 512], stage=3, block='a',


s=2)
X = identity_block(X, 3, [128, 128, 512], stage=3, block='b')
X = identity_block(X, 3, [128, 128, 512], stage=3, block='c')
X = identity_block(X, 3, [128, 128, 512], stage=3, block='d')

X = convolutional_block(X, f=3, filters=[256, 256, 1024], stage=4, block='a',


s=2)
X = identity_block(X, 3, [256, 256, 1024], stage=4, block='b')
X = identity_block(X, 3, [256, 256, 1024], stage=4, block='c')
X = identity_block(X, 3, [256, 256, 1024], stage=4, block='d')
X = identity_block(X, 3, [256, 256, 1024], stage=4, block='e')
X = identity_block(X, 3, [256, 256, 1024], stage=4, block='f')

X = X = convolutional_block(X, f=3, filters=[512, 512, 2048], stage=5,


block='a', s=2)
X = identity_block(X, 3, [512, 512, 2048], stage=5, block='b')
X = identity_block(X, 3, [512, 512, 2048], stage=5, block='c')

X = AveragePooling2D(pool_size=(2, 2), padding='same')(X)

model = Model(inputs=X_input, outputs=X, name='ResNet50')

return model

base_model = ResNet50(input_shape=(224, 224, 3))

Revenons maintenant sur le sujet que nous avons parlé dans la section 1.3.2: Réutiliser les couches pré-
entraînées/ Transfer Learning. Le réseau de neurones profond convolutif prend des jours à s'entraîner et son
entraînement nécessite de nombreuses ressources de calcul. Donc, pour surmonter cela, nous utilisons
l'apprentissage par transfert dans cette implémentation Keras de ResNet 50.

L'apprentissage par transfert est une technique par laquelle un modèle de réseau de neurone profond est
d'abord formé sur un problème similaire au problème à résoudre. Une ou plusieurs couches du modèle
formé sont ensuite utilisées dans un nouveau modèle formé sur le problème d'intérêt. En termes simples,
l'apprentissage par transfert fait référence à un processus dans lequel un modèle formé sur un problème est
utilisé d'une manière ou d'une autre sur un deuxième problème connexe.

Ici, nous définissons manuellement la couche entièrement connectée(Fully connected) de manière à


pouvoir générer les classes requises et à partir du modèle pré-entraîné. C'est aussi pour ça dans
l'implémentation initiale de ResNet-50, nous n'avons pas ajouté les couches fully connected.

headModel = base_model.output
headModel = Flatten()(headModel)
headModel=Dense(256, activation='relu',
name='fc1',kernel_initializer=glorot_uniform(seed=0))(headModel)
headModel=Dense(128, activation='relu',
name='fc2',kernel_initializer=glorot_uniform(seed=0))(headModel)
headModel = Dense( 1,activation='sigmoid',
name='fc3',kernel_initializer=glorot_uniform(seed=0))(headModel)

Enfin, créons le modèle qui prend l'entrée de la dernière couche de la couche d'entrée et les sorties de la
dernière couche du modèle de tête:
model = Model(inputs=base_model.input, outputs=headModel)

Vous aimerez peut-être aussi