Entre chien et chat (92% de précision)

Classifier des images de chats et de chiens avec une précision de 92%, sans transfer learning

Chien ou chat?

Introduction

Dans cet article, nous allons construire un réseau de neurones convolutionnel pour classer des photos de chats et de chiens, avec une précision de 92%

Nous n'utiliserons pas le transfer learning cette fois-ci (donc on ne triche pas!), et j'expliquerai en détail le chemin que j'ai suivi pour arriver à la solution de cet exercice classique.

Vous allez apprendre comment :

  • construire et régler un réseau de neurones convolutionel avec keras pour le classement d'images.
  • choisir le bon optimiseur pour que votre réseau soit capable d'apprendre
  • utiliser l'ImageDataGenerator de Keras pour augmenter votre dataset et limiter le surentraînement.

Et en bonus, vous essaierez le réseau pré-entraîné ResNet50, juste pour voir ce que ça donne.

Faire tourner ce tuto

Dans ce post, contrairement à la plupart de ceux que l'on peut trouver sur ce blog, je ne fournis pas de recette pour exécuter ce notebook sur Google Colab. J'ai essayé, mais il semble que:

  • les transferts avec les disques sur les machines virtuelles de Google Colab prennent trop de temps. Cela ralentit énormément l'entraînement des réseaux de neurones pour les datasets relativement gros et lus en continu depuis le disque, comme celui que nous allons utiliser.
  • les machines virtuelles n'ont pas assez de coeurs CPU pour supporter le pré-traitement des images que nous allons appliquer.

En conséquence, vous allez devoir tourner sur votre propre machine.

D'abord, installez TensorFlow pour votre PC Linux ou Windows . En suivant ces recettes, vous installerez aussi Anaconda, avec le package keras.

Ensuite, installez les packages dont nous aurons besoin avec anaconda:

conda install numpy matplotlib

Clonez mon repo github localement, et démarrez le serveur jupyter notebook:

git clone https://github.com/cbernet/maldives.git
cd maldives/dogs_vs_cats
jupyter notebook

Enfin, ouvrez le notebook dogs_vs_cats_local_fr.ipynb .

Je n'ai pas testé cette recette. Si elle ne marche pas, dîtes-le moi dans les commentaires, et je vous aiderai immédiatement.

Le dataset chiens et chats

Ce dataset a originellement été introduit pour une compétition Kaggle en 2013. Pour y accéder, vous devrez vous créer un compte Kaggle , et vous logguer sur ce compte. Pas de pression, on n'est pas là pour la compétition, mais pour apprendre!

Le dataset est disponible ici . Vous pouvez utiliser l'utilitaire Kaggle pour le récupérer, ou simplement télécharger le fichier train.zip (540 Mo). N'oubliez pas de vous logguer d'abord.

Les instructions ci-dessous pour préparer le dataset sont pour Linux ou macOS. Si vous travaillez sous Windows, je suis sûr que vous pourrez trouver un moyen de faire de même (par exemple, vous pouvez utiliser 7-zip pour décompresser l'archive, et l'explorateur Windows pour créer des répertoires et bouger les fichiers).

Une fois le téléchargement terminé, décompressez l'archive :

unzip train.zip

Listez le contenu du répertoire train :

ls train

Vous allez y trouver un grand nombre d'images de chiens et de chats.

Dans les sections suivantes, nous utiliserons Keras pour lire les images depuis le disque, avec la méthode flow_from_directory ) de la classe ImageDataGenerator .

Pour cela, il faut que les images des deux catégories soient dans des répertoires différents. Nous allons donc mettre toutes les images de chiens dans dogs , et toutes les images de chats dans cats :

mkdir cats 
mkdir dogs
find train -name 'dog.*' -exec mv {} dogs/ \;
find train -name 'cat.*' -exec mv {} cats/ \;

Vous vous demandez peut-être pourquoi j'ai utilisé find au lieu de mv pour déplacer ces fichiers. C'est dû au fait qu'avec mv , le shell doit passer un grand nombre d'arguments à la commande (tous les noms de fichier), et qu'il y a une limitation sur ce nombre sous macOS (avec Linux, tout va bien). Avec find , nous pouvons contourner cette limitation.

Initialisation

Maintenant, entrez dans la cellule ci-dessous le chemin vers le répertoire du dataset, celui qui contient les sous-répertoires dogs et cats . Puis exécutez cette cellule.

In [2]:
# définition du répertoire du dataset, 
# et déplacement dans ce répertoire
datasetdir = '/data2/cbernet/maldives/dogs_vs_cats'
import os
os.chdir(datasetdir)

# import des packages nécessaires
import matplotlib.pyplot as plt
import matplotlib.image as img
from tensorflow import keras
# raccourci vers la classe ImageDataGenerator 
ImageDataGenerator = keras.preprocessing.image.ImageDataGenerator

Un premier coup d'oeil au dataset chiens et chats

Commençons par afficher la premier image de chaque catégorie :

In [11]:
plt.subplot(1,2,1)
plt.imshow(img.imread('cats/cat.0.jpg'))
plt.subplot(1,2,2)
plt.imshow(img.imread('dogs/dog.0.jpg'))
Out[11]:
<matplotlib.image.AxesImage at 0x7f33ea1b7b38>

Ils sont bien mignons, mais allons plus loin et voyons quelle est la taille de nos images :

In [3]:
images = []
for i in range(10):
  im = img.imread('cats/cat.{}.jpg'.format(i))
  images.append(im)
  print('image shape', im.shape, 'maximum color level', im.max())
image shape (374, 500, 3) maximum color level 255
image shape (280, 300, 3) maximum color level 255
image shape (396, 312, 3) maximum color level 255
image shape (414, 500, 3) maximum color level 255
image shape (375, 499, 3) maximum color level 255
image shape (144, 175, 3) maximum color level 255
image shape (303, 400, 3) maximum color level 255
image shape (499, 495, 3) maximum color level 255
image shape (345, 461, 3) maximum color level 255
image shape (425, 320, 3) maximum color level 247

Dans la forme (shape) de l'image, les deux premières colonnes correspondent à la hauteur et la largeur de l'image en nombre de pixels, et la troisième aux trois canaux de couleur. Donc chaque pixel contient trois valeurs, pour rouge, vert, et bleu (RGB). Nous avons aussi imprimé le niveau de couleur maximum pour l'ensemble des troix canaux, et nous pouvons conclure que les niveaux RGB sont codés sur l'intervalle 0-255.

Toilettage : amélioration de la qualité du dataset

S'il y a une chose dont il faudrait se rappeler à la fin de ce tuto, la voici:

Ne faites jamais confiance à vos données

Les données sont toujours sales et bruitées.

Pour contrôler ce dataset, j'ai utilisé un outil permettant d'afficher un grand nombre d'images rapidement. En fait, je me suis contenté de l'application Aperçu de mac pour regarder tous les icônes d'aperçu dans les deux répertoires du dataset. Le cerveau peut rapidement identifier des problèmes évidents, même si l'on regarde globalement un grand nombre d'images à la fois. Ainsi, ce travail ne m'a pas pris plus de 20 minutes. Bien sûr, j'ai certainement manqué un certain nombre de problèmes moins évidents.

Quoiqu'il en soit, voici ce que j'ai trouvé.

D'abord, voici les indices des mauvaises images pour chaque catégorie :

In [4]:
bad_dog_ids = [5604, 6413, 8736, 8898, 9188, 9517, 10161, 
               10190, 10237, 10401, 10797, 11186]

bad_cat_ids = [2939, 3216, 4688, 4833, 5418, 6215, 7377, 
               8456, 8470, 11565, 12272]

Nous pouvons alors récupérer les images avec ces indices depuis les répertoires cats et dogs :

In [5]:
def load_images(ids, categ):
  '''retourne les images correspondant à une liste d'indices, 
  pour une catégorie donnée (cat ou dog)
  '''
  images = []
  dirname = categ+'s' # dog -> dogs
  for theid in ids: 
    fname = '{dirname}/{categ}.{theid}.jpg'.format(
        dirname=dirname,
        categ=categ, 
        theid=theid
    )
    im = img.imread(fname)
    images.append(im)
  return images
In [10]:
bad_dogs = load_images(bad_dog_ids, 'dog')
bad_cats = load_images(bad_cat_ids, 'cat')
In [11]:
def plot_images(images, ids):
    ncols, nrows = 4, 3
    fig = plt.figure( figsize=(ncols*3, nrows*3), dpi=90)
    for i, (img, theid) in enumerate(zip(images,ids)):
      plt.subplot(nrows, ncols, i+1)
      plt.imshow(img)
      plt.title(str(theid))
      plt.axis('off')
In [12]:
plot_images(bad_dogs, bad_dog_ids)

Certaines de ces images sont complètement inutiles, comme 5604 et 8736. Pour 10401 et 10797, nous voyons en fait un chat, alors que ces images sont supposées être des images de chiens! Garder les dessins de chien peut se discuter, mais mon impression est qu'il vaut mieux s'en débarasser. De même, nous pourrions garder 6413, mais je pense que le network se focalisera plus sur le dessin encadrant la photo que sur celle-ci.

Maintenant, regardons les mauvais chats :

In [13]:
plot_images(bad_cats, bad_cat_ids)