Reconnaissance d’image avec Apprentissage Automatique sur Python, Réseau neuronal convolutif

source: the irish times, Pól Ó Muirí

Après avoir prétraité les données, il est temps de construire notre modèle pour effectuer la tâche de reconnaissance d’image. L’une des techniques consiste à utiliser un réseau de neurones à convolution.

Cet article fait suite à l’article que j’ai écrit sur le traitement d’images. Après avoir rendu les données disponibles pour la tâche de reconnaissance d’image, il est temps de créer un algorithme qui exécutera la tâche. Parmi les nombreuses techniques utilisées pour reconnaître les images en tant que modèle perceptron multicouche, le réseau neuronal à convolution (CNN) apparaît comme très efficace. Dans cet article, nous verrons comment créer un CNN et comment l’appliquer à un ensemble de données d’images.

Lorsque nous commençons à construire un modèle de reconnaissance d’image pour la première fois, il est généralement judicieux de l’entraîner et de l’évaluer sur un ensemble de données relativement simple.

L’une des tâches les plus simples que nous puissions effectuer est la reconnaissance manuscrite des chiffres. Étant donné une image d’un chiffre manuscrit (i.e., 0, 1, …, 9), nous voulons que notre modèle puisse classer correctement sa valeur numérique. Bien que cette tâche semble relativement simple, elle est en fait utilisée assez souvent dans la vie réelle, comme l’extraction automatique des numéros de carte de crédit d’une image. L’ensemble de données que nous utiliserons pour la reconnaissance des chiffres est l’ensemble de données MNIST, qui est l’ensemble de données utilisé pour la reconnaissance des chiffres basée sur l’apprentissage automatique.

La base de données MNIST (Modified National Institute of Standards and Technology) contient 60 000 exemples de formation et 10 000 exemples de tests. La base de données contient des chiffres manuscrits en niveaux de gris qui ont été redimensionnés pour tenir dans une boîte de pixels 20×20, qui a ensuite été centrée dans une image 28×28 (avec des espaces). La base de données MNIST est accessible via Python.

Dans cet article, je vais vous montrer comment coder votre réseau de neurones convolutifs à l’aide de keras, l’API de haut niveau de TensorFlow. J’utilise tensorflow 2.0 dans cet article.

1 – Initialisation

Chaque image en niveaux de gris ayant des dimensions de 28×28, il y a 784 pixels par image. Par conséquent, chaque image d’entrée correspond à un tenseur de 784 valeurs à virgule flottante normalisées comprises entre 0,0 et 1,0. L’étiquette d’une image est un tenseur à un chaud avec 10 classes (chaque classe représente un chiffre). En termes de code, nous avons img_rows= 28, img_cols=28 et num_classes=10. Ainsi, l’entrée a une forme (number_examples, img_rows, img_cols) d’où 60000x28x28.
Un autre élément important à mettre en place est la graine aléatoire car nous voulons garder le point de départ lorsqu’un ordinateur génère une séquence de nombres aléatoires.

Nous importons également le jeu de données MNIST.

import tensorflow as tf # tensorflow 2.0
from keras.datasets import mnist
import numpy as npseed=0
np.random.seed(seed) # fix random seed
tf.random.set_seed(seed)# input image dimensions
num_classes = 10 # 10 digits
img_rows, img_cols = 28, 28 # number of pixels
# the data, shuffled and split between train and test sets
(X_train, Y_train), (X_test, Y_test) = mnist.load_data()

2- Remodelage et redimensionnement

Comme mentionné dans la section précédente, les entrées ont une forme (number_examples, img_rows, img_cols). Cependant, pour utiliser les données avec notre réseau de neurones convolutifs, nous devons les mettre au format NHWC.

Le format NHWC a une forme avec quatre dimensions:

  1. Nombre d’échantillons de données d’image (taille du lot)
  2. Hauteur de chaque image
  3. Largeur de chaque image
  4. Canaux par image

La hauteur et la largeur de chaque image de l’ensemble de données sont img_rows et img_cols, tandis que le nombre de canaux est de 1 (car les images sont en niveaux de gris).

De plus, chaque pixel contient une valeur en niveaux de gris quantifiée par un entier compris entre 0 et 255. Ainsi, la base de données est normalisée pour avoir des valeurs à virgule flottante comprises entre 0,0 et 1,0. Dans ce cas, 0,0 correspond à une valeur de pixel en niveaux de gris de 255 (blanc pur), tandis que 1.0 correspond à une valeur de pixel en niveaux de gris de 0 (noir pur).

X_train = X_train.reshape(X_train.shape, img_rows, img_cols, 1) 
X_test = X_test.reshape(X_test.shape, img_rows, img_cols, 1)input_shape = (img_rows, img_cols, 1)# cast floats to single precision
X_train = X_train.astype('float32')
X_test = X_test.astype('float32')# rescale data in interval
X_train /= 255
X_test /= 255

3- Casting de vecteurs d’étiquettes Y

Nous devons transformer nos classes en vecteurs. Pour ce faire, appuyez sur la ligne suivante :

Y_train = keras.utils.to_categorical(Y_train, num_classes)
Y_test = keras.utils.to_categorical(Y_test, num_classes)

Pour avoir une meilleure explication de cette étape, vous devriez voir cet article.

Maintenant que nous avons traité nos données, nous pouvons commencer à construire un modèle.

Modèle de réseau neuronal à convolution

Comme mentionné à la fin de l’article que j’ai écrit sur le traitement d’image, les filtres jouent un rôle énorme dans la reconnaissance d’image. Nous utilisons des filtres pour transformer les entrées et extraire des fonctionnalités qui permettent à notre modèle de reconnaître certaines images. Un exemple de très haut niveau de ceci serait un filtre de détection de courbe, qui permet à notre modèle de distinguer les chiffres avec des courbes et les chiffres sans courbes.

1- Filtres

Comme tous les poids du réseau de neurones, les poids du filtre sont des variables entraînables. Nous entraînons notre réseau de neurones (via les poids de la matrice du noyau) à produire des filtres capables d’extraire les fonctionnalités cachées les plus utiles.

Lorsque les données d’entrée ont plusieurs canaux, un filtre aura une matrice de noyau distincte par canal. L’ensemble de données MNIST n’a qu’un seul canal, mais pour d’autres types de données d’image (par exemple RVB), nous entraînerions le modèle pour obtenir des poids optimaux pour la matrice noyau de chaque canal.

2 – Convolution

Nous avons maintenant atteint le point focal des réseaux de neurones convolutifs: la convolution. La convolution représente la façon dont nous appliquons nos poids de filtre aux données d’entrée. L’opération principale utilisée par une convolution est le produit matriciel, c’est-à-dire une sommation sur le produit élément par élément de deux matrices.

Le nombre de produits matriciels dans une convolution dépend des dimensions des données d’entrée et de la matrice du noyau, ainsi que de la taille de la foulée. La taille de foulée est le décalage vertical/horizontal de la matrice du noyau lorsqu’elle se déplace le long des données d’entrée.

3- Padding

Parfois, lorsque nous effectuons l’opération de produit scalaire comme vu précédemment, nous n’utilisons pas de ligne ou de colonne. Pour éviter ce phénomène, nous pouvons utiliser un rembourrage.

Ainsi, si nous voulons utiliser toutes les données d’entrée dans notre convolution, nous pouvons tamponner la matrice de données d’entrée avec des 0. Cela signifie que nous ajoutons des lignes / colonnes entièrement composées de 0 aux bords de la matrice de données d’entrée. Puisque 0 multiplié par un nombre quelconque donne 0, le remplissage n’affecte pas les produits matriciels. C’est important parce que nous ne voulons pas ajouter de distorsions à notre convolution.

4 – Couche de convolution

Une couche de convolution dans un CNN applique plusieurs filtres au tenseur d’entrée. Alors que chaque filtre a une matrice de noyau distincte pour chacun des canaux d’entrée, le résultat global de la convolution d’un filtre est la somme des circonvolutions sur tous les canaux d’entrée.

Ajouter plus de filtres à une couche de convolution permet à la couche de mieux extraire les entités cachées. Cependant, cela se fait au prix d’un temps de formation supplémentaire et d’une complexité de calcul, car les filtres ajoutent des poids supplémentaires au modèle. Le nombre de canaux pour les données de sortie est égal au nombre de filtres utilisés par la couche de convolution.

Pooling

Bien que la couche de convolution extrait des fonctionnalités cachées importantes, le nombre de fonctionnalités peut encore être assez important. Nous pouvons utiliser la mise en commun pour réduire la taille des données dans les dimensions de hauteur et de largeur. Cela permet au modèle d’effectuer moins de calculs et, finalement, de s’entraîner plus rapidement. Il empêche également le surajustement, en n’extrayant que les caractéristiques les plus saillantes et en ignorant les distorsions potentielles ou les caractéristiques inhabituelles que l’on trouve dans quelques exemples seulement.

Comment fonctionne la mise en commun?

Similaire à une convolution, nous utilisons des matrices de filtres dans la mise en commun. Cependant, le filtre de mise en commun n’a pas de poids et n’exécute pas de produits matriciels. Au lieu de cela, il applique une opération de réduction aux sous-sections des données d’entrée.

Le type de mise en commun généralement utilisé dans les CNN est appelé mise en commun maximale. Les filtres de max pooling utilisent l’opération max pour obtenir le nombre maximum dans chaque sous-matrice des données d’entrée.

Couches multiples

1 – Ajout de couches supplémentaires

Comme tous les réseaux de neurones, les CN peuvent bénéficier de couches supplémentaires. Les couches supplémentaires permettent à un CNN d’empiler essentiellement plusieurs filtres pour une utilisation sur les données d’image. Cependant, comme pour construire un réseau de neurones, nous devons faire attention au nombre de couches supplémentaires que nous ajoutons. Si nous ajoutons trop de couches à un modèle, nous courons le risque de le surajuster aux données d’entraînement et donc de se généraliser très mal. De plus, chaque couche supplémentaire ajoute de la complexité de calcul et augmente le temps d’entraînement de notre modèle.

2 – Augmenter les filtres

Nous augmentons généralement le nombre de filtres dans une couche de convolution plus elle est profonde dans notre modèle. Dans ce cas, notre deuxième couche de convolution comporte 64 filtres, contre les 32 filtres de la première couche de convolution. Plus la couche de convolution est profonde, plus les caractéristiques extraites deviennent détaillées. Par exemple, la première couche de convolution peut avoir des filtres qui extraient des entités telles que des lignes, des arêtes et des courbes. Lorsque nous arrivons au deuxième niveau, les filtres de la couche de convolution pourraient maintenant extraire des caractéristiques plus distinctives, telles que l’angle vif d’un 77 ou les courbes d’intersection d’un 88.

Couche entièrement connectée

1 – Couche entièrement connectée

Nous appliquons une couche entièrement connectée de taille 1024 (c’est-à-dire le nombre de neurones dans la couche) aux données de sortie de la deuxième couche de mise en commun. Le nombre d’unités est quelque peu arbitraire. Assez pour être puissant, mais pas au point d’être trop gourmand en ressources. Le but de la couche entièrement connectée est d’agréger les fonctionnalités de données avant de les convertir en classes. Cela permet au modèle de faire de meilleures prédictions que si nous venions de convertir directement la sortie de mise en commun en classes.

2- Aplatissement

Les données que nous utilisons dans notre modèle sont au format NHWC. Cependant, pour utiliser une couche entièrement connectée, nous avons besoin que les données soient une matrice, où le nombre de lignes représente la taille du lot et les colonnes représentent les entités de données. Cette fois, nous devons remodeler dans la direction opposée et convertir le NHWC en une matrice 2D.

Décrochage

1 – Co-adaptation

La co-adaptation fait référence au fait que plusieurs neurones d’une couche extraient les mêmes caractéristiques cachées, ou très similaires, des données d’entrée. Cela peut se produire lorsque les poids de connexion pour deux neurones différents sont presque identiques.

Lorsqu’une couche entièrement connectée comporte un grand nombre de neurones, une co-adaptation est plus susceptible de se produire. Cela peut poser problème pour deux raisons. Premièrement, c’est un gaspillage de calcul lorsque nous avons des neurones redondants calculant la même sortie. Deuxièmement, si de nombreux neurones extraient les mêmes caractéristiques, cela ajoute plus de signification à ces caractéristiques pour notre modèle. Cela conduit à un surajustement si les fonctionnalités extraites en double sont spécifiques uniquement à l’ensemble d’entraînement.

2 – Décrochage

La façon dont nous minimisons la co-adaptation pour des couches entièrement connectées avec de nombreux neurones consiste à appliquer le décrochage pendant l’entraînement. Dans dropout, nous fermons au hasard une fraction des neurones d’une couche à chaque étape de l’entraînement en réduisant à zéro les valeurs des neurones.

Couche Soft-max

Comme il y a 10 chiffres possibles qu’une image MNIST peut avoir, nous utilisons une couche entièrement connectée à 10 neurones pour obtenir les classes pour chaque classe de chiffres. La fonction Softmax est appliquée aux classes pour les convertir en probabilités par classe.

Construction du modèle

Maintenant, nous sommes prêts à construire notre modèle. Voici le code:

from keras.models import Sequential
from keras.layers import Dense, Conv2D, Flatten
from keras.layers import MaxPooling2D, Dropoutmodel = Sequential()#add model layers
model.add(Conv2D(32, kernel_size=(5, 5),
activation='relu',
input_shape=input_shape))
model.add(MaxPooling2D(pool_size=(2, 2)))# add second convolutional layer with 20 filters
model.add(Conv2D(64, (5, 5), activation='relu'))
# add 2D pooling layer
model.add(MaxPooling2D(pool_size=(2, 2)))
# flatten data
model.add(Flatten())
# add a dense all-to-all relu layer
model.add(Dense(1024, activation='relu'))
# apply dropout with rate 0.5
model.add(Dropout(0.5))
# soft-max layer
model.add(Dense(num_classes, activation='softmax'))

Le type de modèle que nous utiliserons est séquentiel. Séquentiel est le moyen le plus simple de construire un modèle dans Keras. Il vous permet de construire un modèle couche par couche.

Nous utilisons la méthode add() pour attacher des calques à notre modèle. Pour les besoins de notre exemple introductif, il suffit de se concentrer sur des couches denses pour plus de simplicité. Chaque couche Dense() accepte comme premier argument requis un entier qui spécifie le nombre de neurones. Le type de fonction d’activation de la couche est défini à l’aide de l’argument optionnel d’activation, dont l’entrée est le nom de la fonction d’activation au format chaîne. Les exemples incluent relu, tanh, elu, sigmoïde, softmax.

Dans ce réseau de neurones, nous avons 2 couches de convolution suivies à chaque fois d’une couche de mise en commun. Ensuite, nous aplatissons les données pour ajouter une couche dense sur laquelle nous appliquons l’abandon avec un taux de 0,5. Enfin, nous ajoutons un calque dense pour allouer chaque image avec la classe correcte.

Compilation du modèle

Ensuite, nous devons compiler notre modèle. La compilation du modèle prend trois paramètres : optimiseur, perte et métriques.

L’optimiseur contrôle le taux d’apprentissage. Nous utiliserons ‘adam’ comme optimiseur. Adam est généralement un bon optimiseur à utiliser dans de nombreux cas. L’optimiseur adam ajuste le taux d’apprentissage tout au long de l’entraînement.

Le taux d’apprentissage détermine la vitesse à laquelle les poids optimaux pour le modèle sont calculés. Un taux d’apprentissage plus faible peut conduire à des poids plus précis (jusqu’à un certain point), mais la réduction de la taille est le temps de calcul.

Nous utiliserons ‘categorical_crossentropy’ pour notre fonction de perte. C’est le choix le plus courant pour la classification. Un score inférieur indique que le modèle est plus performant.

Pour rendre les choses encore plus faciles à interpréter, nous utiliserons la métrique “précision” pour voir le score de précision sur l’ensemble de validation lorsque nous entraînerons le modèle.

#compile model using accuracy to measure model performance
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=)

Formation du modèle

Maintenant, nous allons entraîner notre modèle. Pour nous entraîner, nous utiliserons la fonction ‘fit()’ sur notre modèle avec les paramètres suivants : données d’entraînement (X_train), données cibles (Y_train), données de validation et nombre d’époques.

Pour nos données de validation, nous utiliserons le jeu de tests qui nous est fourni dans notre jeu de données, que nous avons divisé en X_test et Y_test.

Le nombre d’époques est le nombre de fois où le modèle parcourra les données. Plus nous courons d’époques, plus le modèle s’améliorera, jusqu’à un certain point. Après ce point, le modèle cessera de s’améliorer à chaque époque. Pour notre modèle, nous fixerons le nombre d’époques à 3.

#train the model
model.fit(X_train, Y_train, validation_data=(X_test, Y_test), epochs=3)

Évaluez le modèle

Maintenant que nous avons formé notre modèle, nous pouvons évaluer ses performances:

# evaluate the model
score = model.evaluate(X_test, Y_test, verbose=1)
# print performance
print()
print('Test loss:', score)
print('Test accuracy:', score)

Ainsi, nous avons une précision de 99,3% et une perte de 0,025 sur le jeu de test ce qui est très bon. Nous pouvons encore améliorer le modèle en augmentant le nombre d’époques et en introduisant une taille de lot.

Faites des prédictions

Si vous voulez voir les prédictions réelles que notre modèle a faites pour les données de test, nous pouvons utiliser la fonction predict_classes. Nous pouvons également à cela en utilisant la fonction predict donnera un tableau avec 10 nombres. Ces nombres sont les probabilités que l’image d’entrée représente chaque chiffre (0-9). L’indice de tableau avec le nombre le plus élevé représente la prédiction du modèle. La somme de chaque tableau est égale à 1 (puisque chaque nombre est une probabilité).

Pour montrer cela, nous montrerons les prédictions pour les 4 premières images de l’ensemble de tests.

Remarque: Si nous avons de nouvelles données, nous pouvons entrer nos nouvelles données dans la fonction predict pour voir les prédictions que notre modèle fait sur les nouvelles données. Comme nous n’avons pas de nouvelles données invisibles, nous montrerons les prédictions à l’aide de l’ensemble de tests pour l’instant.

#predict first 4 images in the test set
model.predict_classes(X_test)

#predict first 4 images in the test set
model.predict(X_test)

We can see that our model predicted 7, 2, 1 and 0 for the first four images.

Let’s compare this with the actual results.

#actual results for first 4 images in test set
y_test

Les résultats réels montrent que les quatre premières images sont également 7, 2,1 et 0. Notre modèle a prédit correctement!

Modèle entier

import tensorflow as tf # tensorflow 2.0
from keras.datasets import mnist
import numpy as np
seed=0
np.random.seed(seed) # fix random seed
tf.random.set_seed(seed)
# input image dimensions
num_classes = 10 # 10 digitsimg_rows, img_cols = 28, 28 # number of pixels# the data, shuffled and split between train and test sets
(X_train, Y_train), (X_test, Y_test) = mnist.load_data()X_train = X_train.reshape(X_train.shape, img_rows, img_cols, 1)
X_test = X_test.reshape(X_test.shape, img_rows, img_cols, 1)
input_shape = (img_rows, img_cols, 1)# cast floats to single precision
X_train = X_train.astype('float32')
X_test = X_test.astype('float32')# rescale data in interval
X_train /= 255
X_test /= 255Y_train = keras.utils.to_categorical(Y_train, num_classes)
Y_test = keras.utils.to_categorical(Y_test, num_classes)from keras.models import Sequential
from keras.layers import Dense, Conv2D, Flatten
from keras.layers import MaxPooling2D, Dropout
model = Sequential()#add model layers
model.add(Conv2D(32, kernel_size=(5, 5),
activation='relu',
input_shape=input_shape))
model.add(MaxPooling2D(pool_size=(2, 2)))
# add second convolutional layer with 20 filters
model.add(Conv2D(64, (5, 5), activation='relu'))
# add 2D pooling layer
model.add(MaxPooling2D(pool_size=(2, 2)))
# flatten data
model.add(Flatten())
# add a dense all-to-all relu layer
model.add(Dense(1024, activation='relu'))
# apply dropout with rate 0.5
model.add(Dropout(0.5))
# soft-max layer
model.add(Dense(num_classes, activation='softmax'))#compile model using accuracy to measure model performance
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=)#train the model
model.fit(X_train, Y_train, validation_data=(X_test, Y_test), epochs=3)# evaluate the model
score = model.evaluate(X_test, Y_test, verbose=1)# print performance
print()
print('Test loss:', score)
print('Test accuracy:', score)#predict first 4 images in the test set
model.predict(X_test)model.predict_classes(X_test)#actual results for first 4 images in test set
Y_test

À suivre

Dans cet article, j’ai abordé la deuxième partie de la reconnaissance d’image qui consiste à construire un Réseau de neurones à Convolution.

J’espère que vous avez trouvé ce pour quoi vous êtes venu ici dans cet article et que vous restez avec moi pour les prochains épisodes de ce voyage de reconnaissance d’images!

PS: Je suis actuellement étudiante en Master d’ingénierie à Berkeley, et si vous souhaitez discuter du sujet, n’hésitez pas à me joindre. Voici mon email.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.