Codons le réseau neuronal convolutif en NumPy simple
Mystères des réseaux de neurones
nous vivre à une époque fascinante, où le Deep Learning [DL] est continuellement appliqué dans de nouveaux domaines de notre vie et révolutionne très souvent des industries autrement stagnantes. Parallèlement, les frameworks open source tels que Keras et PyTorch uniformiser les règles du jeu et donner à chacun un accès à des outils et algorithmes de pointe. Une communauté solide et une API simple de ces bibliothèques permettent d’avoir des modèles de pointe à portée de main, même sans connaissance approfondie des mathématiques qui le rend possible.
Cependant, la compréhension de ce qui se passe à l’intérieur du réseau neuronal [NN] aide beaucoup avec des tâches comme la sélection de l’architecture, le réglage des hyperparamètres ou l’optimisation des performances. Puisque je crois que rien ne vous apprend plus que de vous salir les mains, Je vais vous montrer comment créer un réseau neuronal convolutionnel [CNN] capable de classer MNIST images, avec une précision de 90%, en utilisant uniquement NumPy.
REMARQUE: Le réseau neuronal convolutif est un type de réseau neuronal profond, le plus couramment utilisé pour analyser les images.
Cet article s’adresse principalement aux personnes ayant une certaine expérience avec les frameworks DL. Cependant, si vous n’êtes qu’un débutant – entrant dans le monde des réseaux neuronaux – n’ayez pas peur! Cette fois, je ne prévois pas d’analyser d’équations mathématiques. Honnêtement, je ne vais même pas les écrire. Au lieu de cela, je ferai de mon mieux pour vous donner une intuition sur ce qui se passe sous le couvert de ces bibliothèques bien connues.
introduction
Comme déjà mentionné, notre objectif principal est de construire un CNN, basé sur l’architecture montrée dans l’illustration ci-dessus et de tester ses capacités sur l’ensemble de données d’image MNIST. Cette fois, cependant, nous n’utiliserons aucun des frameworks DL populaires. Au lieu de cela, nous profiterons de NumPy – une bibliothèque puissante mais de bas niveau pour l’algèbre linéaire en Python. Bien sûr, cette approche compliquera considérablement notre travail, mais en même temps, elle nous permettra de comprendre ce qui se passe à chaque étape de notre modèle. En cours de route, nous créerons une bibliothèque simple contenant toutes les couches nécessaires, afin que vous puissiez continuer à expérimenter et à résoudre d’autres problèmes de classification.
REMARQUE: MNIST est une grande base de données de chiffres manuscrits qui est couramment utilisée comme référence pour les algorithmes de reconnaissance d’image. Chaque photo en noir et blanc mesure 28 x 28 px.
Images numériques, tenseurs et formes
Let s’arrêtons une seconde pour analyser la structure des images numériques, car elle a un impact direct sur nos décisions de conception. En réalité, les photos numériques sont d’énormes matrices de nombres. Chacun de ces nombres représente la luminosité d’un seul pixel. Dans le modèle RVB, l’image couleur est composée de trois de ces matrices correspondant à trois canaux de couleur – rouge, vert et bleu. D’un autre côté, pour représenter des images en niveaux de gris – comme celles que nous pouvons trouver dans l’ensemble de données MNIST – nous n’avons besoin que d’une telle matrice.
En algèbre linéaire, ces matrices structurées et multidimensionnelles sont appelées tenseurs. Les dimensions du tenseur sont décrites par leur forme. Par exemple, la forme d’une seule image MNIST est [28, 28, 1]
, où les valeurs successives indiquent la hauteur, la largeur et le nombre de canaux de couleur.
Modèle séquentiel
UNE Le modèle séquentiel est un modèle dans lequel les couches successives forment un flux linéaire – le résultat de la première couche est utilisé comme entrée pour la seconde, et ainsi de suite. Le modèle agit comme chef d’orchestre dans cet orchestre et est responsable du contrôle du flux de données entre les couches.
Il existe deux types de flux – en avant et en arrière. Nous utilisons propagation vers l’avant pour faire des prédictions sur la base de connaissances déjà accumulées et de nouvelles données fournies en entrée X
. D’autre part, la rétropropagation consiste à comparer nos prévisions Y_hat
avec de vraies valeurs Y
et tirer des conclusions. Ainsi, chaque couche de notre réseau devra fournir deux méthodes: forward_pass
et backward_pass
, qui sera accessible par le modèle. Certaines des couches – dense et convolutionnelle – auront également la capacité de rassembler des connaissances et d’apprendre. Ils gardent leurs propres tenseurs appelés poids et les mettent à jour à la fin de chaque époque. En termes simples, un une seule période de formation sur modèle est composée de trois éléments: passe avant et arrière ainsi que mise à jour des poids.
Convolution
Convolution est une opération où nous prenons une petite matrice de nombres (appelée noyau ou filtre) et la passons sur notre image pour la transformer en fonction des valeurs de filtre. Après avoir placé notre noyau sur un pixel sélectionné, nous prenons chaque valeur du filtre et les multiplions par paires avec les valeurs correspondantes de l’image. Enfin, nous résumons tout et mettons le résultat au bon endroit dans la matrice de sortie.
C’est assez simple, non? Droite? Eh bien, souvent, les choses ont tendance à être un peu plus compliquées. Afin d’accélérer les calculs, une couche traite généralement plusieurs images à la fois. Par conséquent, nous passons un tenseur à quatre dimensions avec une forme [n, h_in, w_in, c]
comme entrée. Ici n
correspond au nombre d’images traitées en parallèle – soi-disant taille du lot. Le reste des dimensions est assez standard – largeur, hauteur et nombre de canaux.
De plus, généralement, le tenseur d’entrée peut avoir plus d’un canal. Ci-dessus, vous pouvez voir un exemple de calque qui effectue la convolution sur des images en couleur. Ce processus est appelé convolution sur volume. La règle la plus importante, dans ce cas, est que le filtre et l’image doivent avoir le même nombre de canaux. Nous procédons comme dans la convolution standard, mais cette fois nous multiplions les paires de nombres du tenseur tridimensionnel.
Enfin, pour rendre les calques aussi polyvalents que possible, chacun d’eux contient généralement plusieurs filtres. Nous effectuons la convolution pour chacun des noyaux séparément, empilons les résultats les uns sur les autres et les combinons en un tout. Le passage vers l’avant de la couche convolutionnelle produit un tenseur à quatre dimensions avec[n, h_out, w_out, n_f]
forme, où n_f
correspond au nombre de filtres appliqués dans une couche donnée. Jetons un œil à la visualisation ci-dessous pour acquérir un peu plus d’intuition sur ces dimensions.
Max-Pooling
Il On pense généralement qu’une résolution plus élevée améliore la qualité des photos. Après tout, les bords lisses des objets visibles sur l’image rendent la scène globale plus attrayante pour l’œil humain. Fait intéressant, très souvent, plus de pixels ne se traduisent pas par une compréhension plus détaillée de l’image. Il semble que les ordinateurs ne s’en soucient pas trop. Le stockage de ces pixels redondants est appelé sur-représentation. Très souvent, même une réduction significative du volume du tenseur n’affecte pas la qualité des prédictions obtenues.
REMARQUE: De nos jours, la caméra de téléphone intelligent standard est capable de produire des images 12Mpx. Une telle image est représentée par un tenseur colosal composé de 36 millions de nombres.
La tâche principale de la couche de mise en commun est de réduire la taille spatiale de notre tenseur. Nous faisons cela pour limiter le nombre de paramètres que nous devons former – raccourcir tout le processus de formation. Cet effet est obtenu en divisant le tenseur en sections puis en appliquant une fonction de notre choix sur chaque pièce séparément. La fonction doit être définie de telle sorte que pour chaque section, elle renvoie une seule valeur. Selon notre choix, nous pouvons par exemple traiter la mise en commun max ou moyenne.
La visualisation ci-dessus montre une opération de mise en commun maximale simple. Pendant la propagation vers l’avant, nous parcourons chaque section et trouvons sa valeur maximale. Nous copions ce numéro et l’enregistrons dans la sortie. Dans le même temps, nous mémorisons également l’emplacement du numéro que nous avons sélectionné. En conséquence, deux tenseurs sont créés – la sortie, qui est ensuite transmise au calque suivant, et le masque, qui sera utilisé pendant la rétropropagation. La couche de mise en commun transforme la forme originale du tenseur [n, h_in, w_in, c]
à [n, h_out, w_out, c]
. Ici, le rapport entre h_in
et h_out
est défini par foulée et pool_size
hyperparamètres.
Lors de la rétropropagation à travers la couche de regroupement, nous commençons avec le tenseur des différentiels et essayons d’élargir ses dimensions. Pour commencer, nous créons un tenseur vide avec une forme [n, h_in, w_in, c]
et remplissez-le de zéros. Ensuite, utilisez le tenseur de masque mis en cache pour déplacer les valeurs d’entrée aux endroits précédemment occupés par des nombres maximum.
Abandonner
jeC’est l’une des méthodes les plus populaires de régularisation et de prévention du sur-ajustement du réseau neuronal. L’idée est simple – chaque unité de la couche d’abandon a la probabilité d’être temporairement ignorée pendant la formation. Ensuite, à chaque itération, nous sélectionnons au hasard les neurones que nous lâchons en fonction de la probabilité assignée. La visualisation ci-dessous montre un exemple de couche soumise à un décrochage. Nous pouvons voir comment, à chaque itération, les neurones aléatoires sont désactivés. En conséquence, les valeurs dans la matrice de poids deviennent plus uniformément réparties. Le modèle équilibre le risque et évite de miser tous les jetons sur un seul numéro. Pendant l’inférence, la couche d’abandon est désactivée afin que nous ayons accès à tous les paramètres.
REMARQUE: Le sur-ajustement se produit lorsque notre modèle correspond trop étroitement à un ensemble limité de points de données. Un tel modèle généralisera mal et échouera très probablement compte tenu du nouvel ensemble de données.
Aplatir
jeC’est sûrement la couche la plus simple que nous mettons en œuvre au cours de notre voyage. Cependant, il joue un rôle vital de lien entre les couches convolutives et densément connectées. Comme son nom l’indique, lors de la passe avant, sa tâche est d’aplatir l’entrée et de la changer d’un tenseur multidimensionnel en un vecteur. Nous inverserons cette opération lors de la passe arrière.
Activation
UNEparmi toutes les fonctions que nous utiliserons, il y en a quelques-unes simples mais puissantes. Les fonctions d’activation peuvent être écrites sur une seule ligne de code, mais elles confèrent au réseau neuronal la non-linéarité et l’expressivité dont il a désespérément besoin. Sans activations, NN deviendrait une combinaison de fonctions linéaires de sorte qu’il ne s’agirait que d’une fonction linéaire. Notre modèle aurait une expressivité limitée, pas plus grande qu’une régression logistique. L’élément de non-linéarité permet une plus grande flexibilité et la création de fonctions complexes au cours du processus d’apprentissage.
Dense
Similar aux fonctions d’activation, les couches denses sont le pain et le beurre du Deep Learning. Vous pouvez créer des réseaux de neurones entièrement fonctionnels – comme celui que vous pouvez voir sur l’illustration ci-dessous – en utilisant uniquement ces deux composants. Malheureusement, malgré une polyvalence évidente, ils ont un inconvénient assez important – ils sont coûteux en calcul. Chaque neurone de couche dense est connecté à chaque unité de la couche précédente. Un réseau dense comme celui-ci nécessite un grand nombre de paramètres entraînables. Cela est particulièrement problématique lors du traitement des images.
Heureusement, la mise en place d’une telle couche est très simple. La passe directe se résume à multiplier la matrice d’entrée par les poids et à ajouter un biais – une seule ligne de code NumPy. Chaque valeur de la matrice de poids représente une flèche entre les neurones du réseau visible sur la figure 10. La rétropropagation est un peu plus compliquée, mais uniquement parce que nous devons calculer trois valeurs: dA
– dérivé d’activation, dW
– dérivé des poids, et db
– dérivée de biais. Comme promis, je ne publierai pas de formules mathématiques dans cet article. Ce qui est essentiel, le calcul de ces écarts est assez simple pour que cela ne nous pose aucun problème. Si vous souhaitez creuser un peu plus profondément et n’avez pas peur de faire face à l’algèbre linéaire, je vous encourage à lire mon autre article où j’explique en détail tous les rebondissements de couches denses en arrière.
Conclusion
je J’espère que mon article a élargi vos horizons et amélioré votre compréhension des opérations mathématiques qui se déroulent à l’intérieur du NN. J’avoue avoir beaucoup appris en préparant du code, des commentaires et des visualisations utilisés dans ce post. Si vous avez des questions, n’hésitez pas à laisser un commentaire sous l’article ou à me contacter via les réseaux sociaux.
Cet article est une autre partie de la série « Mystères des réseaux de neurones », si vous n’en avez pas encore eu l’occasion, veuillez envisager de lire d’autres articles. Aussi, si vous aimez mon travail jusqu’à présent, suivez-moi sur Twitter, Moyen, et Kaggle. Découvrez d’autres projets sur lesquels je travaille comme MakeSense – outil d’étiquetage en ligne pour les petits projets de vision par ordinateur. Surtout, restez curieux!