Les réseaux convolutionnels
Nous présentons dans ce chapitre les réseaux convolutionnels (Convolutional Neural Network - CNN). Pour parler de réseau convolutionnel, il faut qu’au moins une couche de type Conv soit présente dans le réseau. Un réseau à base de couches Linear ne permet pas d’obtenir un réseau convolutionnel ! Historiquement, l’apparition de cette technique a permis aux réseaux de neurones d’atteindre un tout autre niveau de performance.
La couche Conv2D
Principe
Pour calculer le résultat d’une couche conv2D utilisant un noyau de taille (u,v) sur une image représentée par un tableau de taille (n,m), on considère toutes les sous-images possibles de taille (u,v) présentes dans l’image et on calcule leurs convolutions avec le noyau. Pour rappel, la convolution consiste à multiplier membre à membre chaque valeur des entrées et à sommer les résultats pour obtenir une seule valeur. Ainsi, le résultat obtenu en sortie de la couche Conv2D est un tableau de taille (n-u+1, m-v+1).
Voici le code de principe correspondant :
def Conv2D(Tnoyau, TImage) :
sizeNoyau = Tnoyau.shape
sizeImage = TImage.shape
# tableau résultat :
size = (sizeImage[0]-sizeNoyau[0]+1, sizeImage[1]-sizeNoyau[1]+1)
R = NouveauTensor(size)
# Calcul
for x in range(0, size[0]):
for y in range(0, size[1]):
exImage = ExtractImage(TImage,x,y,sizeNoyau)
value = Conv(exImage,TImage) # multiplication membre à membre et sommation
R[x,y] = value
return R
Avec :
Voici un exemple utilisant une image 4x4 et un noyau 2x2 :
Dans une image, les zones de transition sont souvent « douces ». Il n’est pas toujours nécessaire de calculer la convolution en chaque point de l’image, mais seulement tous les k pixels. Ce saut est appelé pas ou stride. Il permet de réduire la taille du tableau de sortie d’un facteur k².
Si on veut obtenir une sortie dont la taille soit identique à l’image de départ, on peut élargir virtuellement l’image d’entrée avec des 0. Ce paramètre de marge ou zero padding permet ainsi d’ajuster la taille du tableau de sortie.
Les noyaux de taille complexe
Nous allons d’abord considérer une base d’images. Une image se caractérise par :
le nombre de lignes (Rows) aussi appelé sa hauteur (Height)
le nombre de colonnes (Columns) aussi appelé sa largeur (Width)
le nombre de canaux (Channels) aussi appelé profondeur (Depth), 3 pour une image RGB, 1 pour une image en niveaux de gris
Il existe deux conventions pour représenter les images dans un tableau :
Channels Last : la dernière dimension du tableau correspond au canal des couleurs : (Height,Width,Channels)
Channels First : la première dimension du tableau correspond au canal des couleurs : (Channels,Height,Width)
Dans tous les cas, l’ordre Heigth/Width est identique, seul change la position de l’indice des canaux. La fonction Conv2D() des librairies d’IA dispose d’un paramètre permettant de régler cette option. Dans la suite, nos exemples seront donnés en Channel First : (Channels,Height,Width).
Les couches Conv2D ont la capacité de traiter une série d’images. On ne transmet pas les images une à une, mais en lot, afin d’occuper toutes les ressources CPU/GPU disponibles. Ainsi, l’entrée d’une couche Conv2D correspond à un tableau 4 dimensions :
Taille des tableaux en entrée des couches Conv2D : (N,C,H,W), avec :
N : Nombre d’images
C : Nombre de canaux
H : Hauteur / Height
W : Width / Largeur
Commençons par le cas des images en niveaux de gris. Lorsque l’on effectue la convolution 2D par un noyau de taille (u,v), on obtient en sortie un tableau de taille (H',W')=(H-u+1,W-v+1). Un couche Conv2D peut traiter plusieurs convolutions en parallèle ceci en utilisant plusieurs noyaux, de 4 à 32 généralement. De cette façon la sortie associée à 1 image de taille (H,W) aura une taille (K,H',W') où K désigne le nombre de noyaux (kernels) présents dans la couche Conv2D. Pour terminer, si nous traitons un lot de N images en entrée, nous obtenons finalement en sortie un tableau de taille (N,K,H',L'). En conclusion :
Pour une entrée de taille (N,C,H,W) et K noyaux de taille (u,v) :
N : nombre d’images
C : nombres de canaux
(H,W) : Height/Width des images
La taille de la sortie de la couche Conv2D est : (N,K,H',W') avec H'=H-u+1 et W'=W-v+1.
Examinons maintenant le cas des images RVB. Elles sont représentées par un tableau de taille (C,H,L)=(3,H,L). Dans ce cas précis, le noyau N de la convolution aura une taille (3,u,v). Ainsi, le noyau s’étend à une troisième dimension. La formule de la convolution 2D devient :
Note
A ce niveau il existe une petite subtilité. La taille réelle des noyaux n’est pas (u,v) mais plus exactement (C,u,v) où C correspond aux nombres de canaux. En effet, cette extension est nécessaire pour que la formule de la convolution 2D fonctionne comme énoncé.
Terminologie
Une couche Conv gère plusieurs noyaux encore appelés caractéristiques (feature en anglais, ou encore kernel). Le nombre de features s’appelle la profondeur de la couche Conv.
Le pas, la marge et la profondeur de la couche CONV sont des hyperparamètres d’apprentissage du réseau : ils sont choisis par l’utilisateur en début d’apprentissage et n’évoluent pas durant l’apprentissage.
La couche Conv participe aux calculs Forward du réseau et son résultat contribue à l’erreur finale. Ainsi les valeurs contenues dans ses features correspondent à des poids qui vont ainsi être optimisés par la méthode du gradient.
Interprétation des features
Mais à quoi peuvent correspondre les features une fois appris ? Il s’agit ici d’une question difficile. Cependant, on a réussi à déterminer que pour la couche traitant l’image d’entrée, ces features correspondent à des détecteurs de contours. Voici un exemple de features appris pour le problème de la classification des chiffres manuscrits :
Ainsi les features cherchent à détecter des « caractéristiques » permettant de décrire l’information présente dans l’image. Si une couche Conv a seulement 5 features, elle va devoir en choisir 5 qui représentent au mieux l’information recherchée dans l’image.
Exercices
Exercice 1
Exercice 2
Donnez la taille de la sortie d’une couche Conv2D en fonction des informations suivantes :
Entrée
Nb Noyaux
Taille Noyaux
Sortie
(1,1,10,10)
1
(3,3)
(1,1,8,8)
(9,1,32,32)
1
(5,5)
(7,3,12,12)
1
(3,3)
(5,3,28,28)
9
(5,5)
La couche POOL
Objectif
L’objectif d’une couche Pool est de réduire la quantité d’informations qui transitent d’une couche à l’autre. Par exemple, pour un tableau 2D, on peut choisir de le diviser en blocs de taille 2x2 et d’effectuer pour chaque bloc une opération du type : moyennage, min ou max… Le tableau résultat aura donc une taille 4 fois moindre.
Nous présentons un exemple de MaxPool-2D avec une réduction 2x2 :
La couche DropOut
Cette couche permet de mettre à zéro aléatoirement certaines valeurs d’un tableau ceci avec une probabilité donnée. Il n’y a pas réduction de la taille des données, mais simplement une simulation d’une perte d’information. L’intérêt est d’améliorer l’indépendance entre les différents features. En effet, supposons que le réseau doive trouver deux features A et B. Sans la technique de DropOut, il pourrait aussi bien sélectionner les features A et A+B, car les traitements sur les couches suivantes pourraient reconstruire les features A et B idéaux. En désactivant de temps à autre le feature A ou B, cela force le réseau à converger sans tenir compte des valeurs des features voisins.
Les couches DropOut ne sont utiles que durant la phase d’apprentissage et non durant la phase de validation. Ainsi, elles seront activés durant la phase d’apprentissage et ignorées pendant la phase de validation.
On donne pour cette couche une probabilité p d’effacer une donnée. Cela sous-entend que la somme des valeurs en sortie est abaissée (en moyenne) d’un facteur 1-p par rapport à l’entrée. Pour compenser cette perte, un facteur 1/(1-p) est appliquée aux valeurs conservées pour préserver le niveau moyen des valeurs d’entrée. Voici un exemple de l’application d’une couche Dropout avec une probabilité de 0.5 sur un tableau rempli de valeurs 1. Vous remarquez que sur les 10 valeurs présentes 6 ont été mises aléatoirement à zéro. Les valeurs de sortie sont multipliées par 2 = 1/p pour compenser le niveau d’entrée. En relançant ce test, on obtient des résultats différents.
Input : tensor([1., 1., 1., 1., 1., 1., 1., 1., 1., 1.])
Output : tensor([0., 0., 0., 0., 2., 2., 0., 0., 2., 2.])
Lecture d’illustration
Etudions l’exemple suivant :
Nous analysons ce réseau de gauche à droite. Sur ce schéma, les tableaux en entrée/sortie des différentes couches sont représentés par des empilements de rectangle. Les opérations effectuées par chaque couche sont notées en bas des illustrations.
En entrée, on trouve une image 2D de résolution 32x32 en niveaux de gris.
La sortie de la première couche est le résultat d’une convolution 2D donnant un tableau de taille (32,32,32). Il y a donc 32 features dans cette couche Conv. Les features étant de taille 5x5, la résolution de sortie aurait dû être de 28x28 et non 32x32, un paramètre de zéro padding a dû être utilisé pour compenser la perte de résolution.
La couche de Max-Pooling réduit la résolution par 2, ce qui donne un tableau (32,16,16). Il y a toujours 32 canaux car l’opération de MaxPooling2D ne s’applique qu’aux deux premières dimensions.
Une nouvelle couche Conv de 64 features est appliquée. Le tableau d’entrée ayant une taille de (32,16,16), celui de sortie une taille de (64,16,16) et celui du noyau 3x3, le noyau de la couche Conv2D a été construit avec un tableau (64,32,3,3). Cette couche traite donc le tableau d’entrée comme une image 16x16 de 32 canaux.
La couche de Max-Pooling suivante réduit la résolution par 2, ce qui donne un tableau (64,8,8) en sortie. Le nombre de canaux reste inchangé.
Le tableau [64,8,8] subit un « flatten » pour être aplati sous la forme d’un tableau 1D de taille 64x8x8 = 4096.
On trouve ensuite une couche Linear (aussi appelée Fully Connected). La grandeur 256 indique sûrement le nombre de neurones présents dans cette couche. La fonction d’activation n’est pas précisée, on peut supposer qu’il s’agit d’une fonction ReLU. Chaque neurone de cette couche est connecté aux 4096 entrées de la couche précédente.
La dernière couche correspond à une deuxième couche Linear. Elle contient 10 neurones qui calculent 10 scores correspondant aux 10 catégories étudiées.
Pour finir, les valeurs obtenues étant quelconques, négatives comme positives, on applique la fonction Softmax qui transforment ces 10 valeurs en 10 probabilités de somme 100%.
Les deux parties d’un réseau
La partie gauche du réseau, proche de l’entrée, cherche à évaluer des features permettant à la partie droite du réseau de faciliter sa décision. Ainsi, les réseaux utilisés en classification d’images ont tendance à se séparer en deux parties :
La partie gauche, proche des données d’entrée, a pour objectif d’estimer et de trouver un ensemble de critères « feature extraction » ceci en empilant plusieurs couches Conv.
La partie droite doit construire sa réponse et apprendre à classifier l’image à partir des critères évalués par la partie gauche du réseau. Pour mettre en place cette partie, on utilise plusieurs couches Linear empilées.
On peut ainsi transférer des réseaux déjà entraînés vers d’autres problèmes. Par exemple pour un réseau entraîné sur la classification d’images, on peut reprendre ce réseau tel quel, garder sa partie gauche car on suppose qu’elle contient des détections de features dans une image et remettre à zéro la partie droite afin d’apprendre sur un nouveau set d’images spécialisées : bâtiments, bactéries, véhicules. Cela permet d’économiser un temps précieux d’apprentissage.