image semantic segmentation keras tensorflow Cours pratiques - Deep learning

Segmentation sémantique d’images

Introduction & code source Github

Je vous met ici l’ensemble du code source sur mon dépôt Github, avec un exemple concret de segmentation sémantique d’images sur la reconnaissance de carries sur des radiographies dentaire.

 

Que-ce que la segmentation sémantique ?

Ce type de segmentation consiste à classifier chaque pixel d’une image en un label. Plus d’informations ici.

 

Types de vos données

En entrée, nous allons envoyer à un réseau de neurones des images RGB. Plus d’informations sur la composition d’une image ici. Mais de façon concise, notre image RGB à la forme d’un tenseur d’ordre 3 et de dimensions ( hauteur x largeur x 3 ). Le 3 est le nombre de canal. 1 pour une image noir/blanc, 3 pour RGB. Un canal pour le rouge, un pour le vert, et un dernier pour le bleu. Chaque valeur de la matrice représente donc un pixel. Ce pixel qui à une valeur entre 0 et 255, selon l’intensité de la couleur d’un canal spécifique.

En sortie, on souhaite avoir une matrice de dimensions (hauteur x largeur  x 1), avec chaque pixel ayant n’ont pas une intensité entre 0 et 255, mais un nombre correspondant à nos classes. La valeur du pixel est donc compris entre 0 (indice du background) à n (nombre de classe dans nos images).

Une image sera plus parlante :

semantic segmentation image keras tensorflow

 

Dataset X et Y

Pour réaliser ce genre de classification on utilise de l’entrainement supervisé. Cela consiste à entrainer notre réseau de neurones sur des couples (X, Y).

  • X est votre image dans laquelle vous souhaitez analyser la présence ou non d’une classe
  • Y que l’on peut appeller son ‘étiquette’, est ce que l’on cherche à avoir. Cela correspond donc à nos masques contenant nos classes à détecter. Nous allons détailler juste après la notion de masque.

 

Labeliser les données

Nous avons précédemment parlé de masque. Un masque est une partie de votre image que l’on souhaite mettre en évidence. Dans notre cas, le masque sera un polygone représentant une carrie. Selon le type d’objet que vous souhaitez détecter, cela pourra avoir une forme de carré, cercle, etc.

Pour chacune de vos images, vous aller devoir donc générer les masques, et par la suite les labéliser, les annoter. Via des logiciels spécialisés (VoTT, SuperAnnotate, LabelMe, etc), vous allez pouvoir segmenter vos classes au sein de vos images. Voici un Example de segmentation de Carrie sur des radiographies :

image segmentation tools vott super annotate

 

A la suite de cette phase d’annotation, selon le logiciel que vous utilisez, il va vous générer un fichier (JSON dans mon cas, mais cela peut être dans le format que vous voulez). Celui-ci contiendra pour chaque photos un ensemble de coordonnées, de points (X,Y) correspondant aux formes géométriques que vous aurez dessiné sur le logiciel.

segmentation image labeling

Dans l’exemple précédent, on peut voir que pour une photo, j’ai le type de forme géométrique utilisé pour mon masque de carie, un ID de classe, une proba, et un tableau de coordonnées de points.

 

Génération des masques

Vous devez accorder la configuration de vos masques selon ce que vous souhaitez avoir en sortie du réseau. Vous allez utiliser une fonction d’activation spécifique afin de faire de la prédiction d’une valeur binaire, ou utiliser une autre fonction d’activation pour de la prédiction multi classes. Mais vous pouvez très bien utiliser l’une ou l’autre selon comment vous agencez vos données. On va voir cela dans les prochaines lignes.

Ces différences de configuration de masque concerne la fonction d’activation de la couche final de votre réseau. Et selon elle, vous devez accorder vos métriques et fonction de perte.

 

Génération du masque

Pour générer vos masques, vous allez devoir parcourir l’ensemble de vos radiographies une à une avec le fichier JSON de coordonnées qui lui est associé. Vous allez pouvoir tracer des masques  via plusieurs procédés. Le but est de remplir des matrices Numpy, avec la classe souhaités contenu dans chaque pixel.

Pour vous donnez des idées de librairies pouvant le faire facilement :

  • Skimage via Polygon2Mask
  • Pillow (PIL) via ImageDraw
  • Scipy
  • OpenCV
  • Matplotlib via points_inside_poly

 

Exemple d’un masque crée via Skimage :

image segmentation mask polygon 2d numpy

 

⚠️ Un point extrêmement important qui m’a bieeen fait galérer dans mes prédictions concernant la génération de mes masques sont le format de sauvegarde.

En effet pour les sauvegarder, privilégiez les formats PNG ou TIFF. Car JPG est un format comprimé et vous allez perdre de l’information voir pire avoir des changements de classe. TIFF est un format sans perte, un peu lourd. PNG est un format compressé mais sans perte, donc vous pouvez le choisir sans soucis.

 

Integer ou One hot encoding ?

Comment définir nos classes pour que notre réseau apprennent à les reconnaîtrais ? Vous pouvez avoir le choix selon votre type de segmentation, qu’elle soit binaire ou multi-classes. Comment donc définir nos classes dans nos matrices ?

 

Integer encoding consiste à donner un entier, un nombre unique qui sert d’identifiant unique pour chaque classe. Pour un exemple avec 3 classes différentes, on peut définir comme ci :

  • Chat, 0
  • Chien, 1
  • Lapin, 2

Dans notre exemple, chaque pixel aura donc une valeur entre 0 et n, correspondant au nombre de classe total présent au sein de notre dataset.

 

One hot encoding consiste à définir un ensemble de colonne selon le nombre de classe possible. Chaque colonne représente une seule classe. Seul la colonne représentant la classe aura comme valeur 1. Les autres colonnes auront comme valeur 0. Si on prends pour trois classe (chat, chien, lapin) on aura un vecteur de la forme suivante :

[probabilité chat, probabilité chien, probabilité lapin], soit [0, 0, 1] par exemple, si on a un lapin à prédire.

Dans notre exemple, chaque pixel aura donc comme valeur un tenseur d’ordre 1, un vecteur. Celui ci sera de taille n, correspondant au nombre de classe total présent au sein de notre dataset.

 

integer labeling vs one hot encoding

 

Pour résumer, si vos données ont des relations entre elles, privilégiez l’integer encoding. Dans le cas contraire ou vos données n’ont pas de relation, privilégiez le one hot encoding.

 

Préparation des données pour du binaire ( 1 classe en plus du background )

Votre fonction d’activation final sera sigmoid.

sigmoid activation function

 

Masque et fonction d’activation finale du réseau pour du binary class

Vous aurez donc des masques de dimensions [Hauteur, Largeur, 1]. Chaque pixel aura une valeur comprise entre 0 et 1.

De façon concrète vous aurez ceci en sortie de prédictions :

En fonction de la sortie, vous aurez un hyperparamètre de sensibilité à définir, un ‘threshold’ permettant de définir la frontière entre votre classe 0 et 1. Pour le cas de l’exemple si dessus, j’ai mis ‘0.5’. Vous êtes obligé de choisir une fonction d’activation finale sigmoid.

 

Préparation des données pour du multi-classes ( > 2 classes )

softmax activation function

Pensez à ajouter une classe supplémentaire étant le background. En effet elle servira de classe ‘poubelle’ si on ne rencontre aucun de nos classes.

Pour les sauvegarder étant donnée que l’on aura N canal, on ne peut utiliser le TIFF ou PNG car ils ont (je fais un raccourcie et une généralité car c’est plus complexe que cela) que 4 channels (rouge, vert, bleu alpha pour la transparence). Donc vous pourrez les sauvegarder directement sous leur format matricielle Numpy (.NPY).

 

Masque et fonction d’activation finale du réseau pour du multiclasses

Selon le type de fonction d’activation finale pour votre dernière couche de votre dernier neurone de votre réseau, vous aurez un agencement différent de vos données en sortie. Vous devrez donc prendre en considération pour en faire autant avec vos masques que vous générez et envoyer en entrée du réseau lors de la constitution de votre dataset.

 

Output d’un réseau basé sur sigmoid :

Tenseur de taille : [hauteurImage x largeurImage x nombreClasses]

 

Output d’un réseau basé sur softmax :

Tenseur de taille : [hauteurImage x largeurImage x 1]

semantic segmentation image keras tensorflow

Réaliser une prédiction

Masque pour classe unique

Etant donné que l’on souhaite prédire une seule classe, notre masque sera binaire et ne contiendra que des 0 (classe background ou poubelle) ou 1 (notre classe à détecter).

prediction classe binary sigmoid

En sortie de notre réseau, nous devrons appliquer une fonction à l’ensemble de nos valeurs de notre tenseur. Pour les valeurs inférieur à notre threshold, on applique la valeur 0. Pour celle au dessus de notre threshold, on applique la classe 1.

Masque pour multi-classes

Si l’on souhaite prédire plusieurs classes, nous aurons donc soit plusieurs cannal (pour du sigmoid ) ou un seul (pour du softmax). Dans un cas ou l’autre, on va avoir pour un même pixel autant de valeurs que de classes à prédire. La classe prédite par notre réseau et la classe qui aura la valeur la plus haute. On utilisera la fonction argMax par exemple de Numpy pour nous récupérer la classe prédite.

prediction multi classes numpy argmax softmax

 

Fonction de perte plus adapté

Les fonctions de perte de type CrossEntropy sont les fonctions basique pour tout problème de classification.

Alors pourquoi ne pas garder nos fonctions de pertes cités plus haut si elles font le boulot ? Car celles-ci évaluent pour chaque pixel de façon individuelle, pour en faire une moyenne sur l’ensemble des pixels. Etant donnée que l’on va travailler avec des dataset déséquilibrés, nous pouvoir avoir des fonctions qui sont d’avantages étudiés pour ces problématique ci. En effet en gardant ces fonctions de base, on pourrait alors avoirs des chances d’avoir des prédictions penchant vers les classes les plus présente au sein de nos images, car basé sur leurs distributions.

Si vous souhaitez néanmoins rester sur une fonction de type CrossEntropy, sachez qu’il en existe des variantes. En effet, vous pouvez attribuer des poids différents pour chacune de vos classes, selon leur plus ou moins grande présence aux seins de vos images.

Mais nous allons passer en revu certaines autres fonctions de pertes qui peuvent s’avérer bien plus efficaces pour de la segmentation. Préférez la Focal Tversky ou la Lovasz.

 

Métrique de suivi la plus adapté

Evitez les traditionnelles accurary ou d’un style similaire pour ce domaine ci. Préférez l’IoU ou la Dice.

 

 

classification localisation segmentation detection Cours théoriques - Deep learning

Différences entre classification d’image, localisation/détection & segmentation d’objets

Image classification

image segmentation object detection localization deep learning cnn keras tensorflow

Dans la tâche la plus courante lorsque l’on parle de traitement d’image, on parle de classification d’image. Cela résulte à identifier une classe au sein de l’image. 

 

 

Détection d’objet

image segmentation object detection localization deep learning cnn keras tensorflow

La détection d’objet se superpose à la simple classification d’image, en ajoutant de la localisation d’objet. Cela fonctionne encore avec une classe au sein de l’image, en plus d’avoir sa position. Celle-ci est souvent définie par des rectangles, appelés bounding box.

 

Segmentation d’objets

image segmentation object detection localization deep learning cnn keras tensorflow

Segmentation sémantique

C’est le processus permettant de classifier chaque pixel d’une image en un label particulier. On ne peut faire de différence entre deux instances d’un même objet. Par exemple, si on a deux voitures sur une image, ce type de segmentation donnera le même label sur l’ensemble des pixels des deux voitures.

Segmentation d’instance

La segmentation d’instance va attribuer un unique label sur chaque instance d’un même objet au sein de l’image. En reprenant le précédent exemple, si nous avons à nouveau deux voitures sur une image, chacune d’entre elles se verront attribuer une couleur différente. À l’inverse de la segmentation sémantique, qui aurait défini la même couleur pour les deux voitures.

 

recommandeur hashtag image Cours pratiques - Deep learning

Classification d’images multi labels/classes

Pour ce nouveau cours, je vous propose de revenir sur de la classification d’image. Contrairement à mon cours sur la classification d’image simple, celui-ci sera légèrement différent en utilisant de la classification d’une multitude de label pour une même image donnée.

Le but est de créer un système permettant de proposer des hashtags en fonction d’une image donnée en entrée. Nous resterons sur nos outils habituels, à savoir Tensforflow en backend et Keras pour l’API de haut niveau, nous facilitant la mise en place d’un réseau de neurones, qui utilisera de la convolution pour cette fois-ci.

Comme d’habitude, le code source entièrement documenté est sur mon Github, libre à vous de venir pour me faire part d’éventuels correctifs et optimisation.

C’est parti ! 😉

ATTENTION : On va commencer à travailler sur des dataset assez conséquent en termes de taille, comparé aux autres tutoriels. Ce cours a pour principal but d’expliquer des méthodes, un cheminement, ainsi que des astuces pour constituer un projet en data science, et non pas d’avoir des modèles ultra performants, sinon je serais sur kaggle et non pas sur mon site perso. Travaillant sur un ordinateur portable dépourvue de carte graphique, je ne peux malheureusement pas entraîner de modèle performant.

resultat prediction
Résultat d’une prédiction de hashtag, pour une image donnée

Pré-requis

Nullement besoin d’être top 1 europe Kaggle pour comprendre mon code. Mais si vous souhaitez aller plus loin dans ce cours et pousser les performances de votre modèle, je peux vous conseiller deux autres articles qui vous seront utile :

 

Concernant le code, on va avoir besoin de quelques librairies externe pour ce projet.  Je vous laisse gérer leur installation via Conda ou Pip selon vos préférences et environnement à chacun :

  • Tensorflow
  • Keras
  • Pandas ( gestion de tableaux performants pour la lecture et écriture de certaines de nos données )
  • TQDM ( outils permettant de créer des barres de chargement au sein d’un shell, utile pour savoir où on en est du traitement de données en temps réel )
  • PIL ( gestion d’image pour leur lecture et écriture )
  • HARRISON dataset (torrent)
  • Fix image corrompue + liste méta data

 

Constitution du dataset

Pour créer un dataset, vous devrez crawler des réseaux sociaux proposant des images ainsi que des hastag, facilement récupérable via leur API. Je pense sans trop me tromper que les deux plus grands sont Instagram ainsi que Flickr.

 

Crawler FlickR

Directement via leur API public : https://www.flickr.com/services/api/

 

Malheureusement pour nous, Instagram a depuis quelques années, restreint l’accès à leur API, nous empêchant de récupérer nos données. Il existe néanmoins certaines astuces pour contourner tout ça.

 

Crawler instagram, Astuce 1

Vous pouvez récupérer des informations sur des profiles avec pas mal d’informations sous format JSON. Avec un simple parseur, vous pourrez faire votre propre outil de crawl via de simple liens :

Avoir des informations au format JSON pour un profil spécifique :

  • https://www.instagram.com/{pseudo_du_profil}/?__a=1

Vous pouvez donc avoir accès aux profils que vous souhaitez, si et seulement si celui-ci est en public.

Avoir des informations au format JSON concernant un hashtag spécifique :

  • https://www.instagram.com/explore/tags/{hashtag_a_tester}/?__a=1

Ne vous en faîte pas, vous pouvez récupérer les images via les liens disposés dans le fichier JSON.

 

Crawler instagram, Astuce 2

Certains malins ont réussi à faire des outils bien plus pratique comme celui-ci (instagram-scrapper). Pour l’avoir utilisé, vous pouvez effectuer des recherches selon des hashtag, et donc créer son propre dataset avec ses propres classes souhaitées.

 

Utiliser des dataset pré-existant

Comparatif de 3 dataset comportant des images associés à des hashtag

Je n’ai pas voulu perdre trop de temps à constituer mon propre dataset, et me concentrer sur mon algorithme. J’ai trouvé deux dataset pouvant être intéressant pour notre projet :

  • HARRISON : Celui qui me semble le plus adapté pour notre projet. En effet, il est plus récent, et crawler depuis instagram même. Bon compromis pour la taille. Chaque image comporte de 1 à 10 hashtag
  • MIRFLICKR : Peut donner éventuellement de meilleurs résultats car composé de bien plus d’images. Cependant, il est plus orienté photo que réseau social ; je m’explique. Ses tags sont principalement orientés pour la photographie, à en suivre le schéma suivant, qui montre le top des 10 hashtag les plus cités sur nos deux dataset. Je ne pense pas dans le cadre de notre projet, le fait de mentionner un constructeur (nikon,canon…)  apportent une plus valus pour notre recommandeur. En effet, lors de la constitution de ce dataset, les chercheurs ont utilisé le fichier EXIF ( Exchangable image file format ) qui sont associés à chaque photo , lorsque on est sur la plateforme Flickr.  Ce fichier contient des méta données concernant une multitude de paramètres sur les appareils photos ( constructeur, exposition, ouverture, taille des focales et objectifs, iso, résolution, compression, etc…).
comparatif top 10 word dataset
Top 10 des hashtag les plus présents

 

Pré traitement de nos données

Le dataset HARRISON est constitué de la façon suivante :

structure harrison dataset
Structure du dataset HARRISON

La façon dont est organisé le rangement des classes par dossier est parfait pour notre réseau, on ne touchera pas leur agencement. Cependant, on ne peut en dire autant pour la partie des fichiers texte qui contient l’ensemble de nos métas données qui caractérise nos classes d’images. On va devoir faire quelques arrangements.

La première étape va être de rassembler les liens des images avec leur hashtag respectif (data_list.txt & tag_list.txt) dans un seul et même fichier :


 

La seconde étape va être de changer le format du fichier vocab_index ; on supprime les espaces inutiles, et on forme des couples => « nomTag, idTag » :

 

Constitution du modèle & entrainement

On va pouvoir passer à la partie la plus fun des réseaux de neurones, entraîner notre réseaux (forcement, on a rien à faire 😎 ).

Petite astuce en cas de présence d’images qui seraient corrompues. Sois-vous téléchargez les deux images qui nous remontent des erreurs qui sont disponible sur le Github du dataset et vous aller les remplacer dans leurs dossier respectifs, soit vous pouvez forcer PIL à traiter ces images ci :

 

On va charger nos fichiers contenant nos métas donnés :

La 3ème ligne va être extrêmement importante, puisque on souhaite faire comprendre à notre réseau que l’image 2 contient à la fois le label « SNOW » et « MOUNTAINS » séparé par notre virgule, et non un seul label « SNOW,MOUNTAINS ». Pour cela on va convertir chacune de nos entrées en liste.

 

Nous pouvons définir l’ensemble de nos variables globales :

  • NB_CLASSES : va permettre de définir le nombre de neurone dans notre dernière couche du réseau. Nous souhaitons un neurone par classe, qui va permettre de déterminer la probabilité de la présence ou non de cette classe, qui sera entre 0 et 1.
  • NB_EPOCH : nombre d’époque durant l’entrainement. L’entrainement étant extrêmement long, je le laisse à 1 par obligation.
  • BATCH_SIZE : nombre de donnée envoyé dans le réseau par itération.
  • SHUFFLE : permet de mélanger les données. Important puisque dans nos fichiers, ils sont listés classe par classe et donc à la suite. On souhaite que le réseau apprenne de façon équilibré et arbitraire.
  • IMG_SIZE : permet de resize nos images. Correspond à la taille de tenseur en input du réseau.
  • TRAINSIZE_RATIO : définit le ratio entre jeu de donnée d’entrainement et de validation
  • TRAINSIZE : nombre d’image pour le jeu d’entrainement
  • LIST_CLASS : liste de nos labels
  • DIRECTORY_DATA : répertoire parent contenant le dataset harrison
  • DIRECTORY_TRAINED_MODEL : répertoire ou on va aller sauvegarder notre modèle, une fois qu’il sera entraîné.
  • COLOR_MODE : permet de choisir entre des images en couleurs, ou en grayscale.

 

On va ensuite définir nos appels de retours ( callback ) appelé à la fin de chaque itération :

  • modelCheckPoint : va permettre de définir comment on souhaite enregistrer notre modèle : répertoire, avec ou sans poids, etc.
  • earlyStopping : va permettre de juger l’évolution d’une métrique (validation_accuracy) sur le jeu de donnée. Si celle-ci n’évolue plus selon un certain paramètre défini, un certain gap (patiente), on stop l’entrainement

 

Nous allons ensuite charger nos images en mémoire. Contrairement au cours sur la classification d’image, on va utiliser un outils concu dans Keras qui est le ImageDataGenerator. Il va nous permettre des choses bien plus poussé contrairement à la méthode que j’avais pu utiliser il y a de cela quelques mois pour le chargement des images. J’avais à l’époque utilisé Numpy pour lire mes images, les transformers en tenseur, et les enregistrer sur le disque. Pour enfin dans un second temps les relire pour les charger en mémoire. L’avantage des imagesDataGenerator sont multiples

  1. Une seule lecture, pas d’écriture.
  2. C’est un générateur ; il envoie les données au fur et à mesure et permet donc de traiter de datasets bien plus volumineux car ne charge pas TOUT le dataset en RAM.

On a donc un gain de mémoire et de temps.

On définit une normalisation des données. Cela permet de traiter des données des images compris entre 0 et 1, et non plus sur l’ensemble de leurs échelles de couleurs RGB, qui s’étend de 0 à 255, ce qui permet une meilleure compréhension de la part de notre réseau ; il n’aime pas vraiment les valeurs extrêmes.

 

La classe ImageDataGenerator nous fournit 3 principales méthodes :

  1. flow()
  2. flow_from_directory()
  3. flow_from_datadrame()

C’est cette dernière que nous allons utiliser pour traiter nos images.

Nous allons créer un générateur pour nos images du jeu de donnée d’entrainement, ainsi qu’un second pour celle de validation. On va pouvoir lui fournir en entrée notre dataframe chargé précédemment avec l’ensemble des chemins vers nos images, avec leurs labels associés. Les principaux attributs que l’on va utiliser sont :

  • dataframe : dataframe contenant les méta données. Bien faire attention a dissocier les images du jeu d’entrainement et de validation à donner à nos deux générateur.
  • directory : répertoire contenant nos images.
  • x_col : nom de colonne contenant les chemins.
  • y_col : nom de colonne contenant les labels.
  • shuffle : permet de mélanger les images.
  • class_mode : choix entre le mode binaire ( deux classe à prédire ) ou categorical ( plusieurs ).
  • target_size : choix de la taille des images en entrée.
  • color_mode : choix entre images en couleurs, ou grayscale.
  • classes : listes des labels

 

Petit point à ne pas oublier pour la suite du projet, lorsque vous voudrez tester votre modèle pour effectuer de nouvelles prédictions sur de nouvelles images. Chose que j’ai bien entendu oublier lors du lancement du fichier pour la première fois 😅.

A savoir le fait d’enregistrer les labels avec leurs indices, sous format JSON pour faciliter le parsage par la suite du fichier. C’est grâce à lui que on pourra retrouver les labels en sortie dans la dernière couche de neurone du réseau.

 

Pour gagner du temps, on va effectuer du Transfer learning. On va récupérer un réseau qui sera pré entraîné sur un jeu de donne ( IMAGENET ). Dans ce cas-là, on va utiliser MobileNetV2, en précisant la taille d’entrée de nos images avec le nombre de canaux souhaité (3 pour couleurs, 1 pour grayscale). Ce qui nous donne la dimension d’entrée de (96,96,3). On lui indique que on ne souhaite pas avoir ses couches de décisions finales, et que on souhaite du max_pooling sur les couches de pooling.

Etant donnée que les images de IMAGENET et de notre dataset sont différentes, on aura donc des sorties différentes. C’est pour cela que précédemment je ne souhaitais pas d’inclure leurs couches supérieures. On va ajouter nos propres couches de décisions pour que le réseaux MobileNet soit utilisable sur nos images à nous. Cependant, on va geler les couches profondes pour qu’elle ne puisse pas être modifié lors de notre entrainement. C’est ça le but du transfer learning, utilisé des réseaux pré entraîné, mais en modifier la surcouche pour qu’il soit adapté à nos problèmes, tout en gardant les couches profondes intactes pour gagner du temps lors de l’entrainement.

La dernière ligne va nous permettre d’indiquer les entrées et sorties de nos deux modèles pour permettre de les fusionner afin d’en avoir un seul et unique. On n’oublie pas de compiler notre modèle en choisissant un optimizer de son choix. Vous pouvez trouver d’avantages d’optimizer ici.

Concernant la fonction d’objectif, il y a un article scientifique qui aurait démontré qu’avec un environnement ou les poids sont choisi de façon aléatoire à l’initialisation du réseau, que la cross-entropy serait plus performante que la mean-squared-error pour trouver un minimum local. L’article est disponible ici si vous souhaitez d’avantages d’informations, et vous pouvez trouver d’avantages de fonction de perte ici.

 

Dernière étape de ce chapitre, l’entrainement du réseau.

  • generator : définit le générateur du jeu de donnée d’entrainement.
  • validation_data : définit le générateur du jeu de donnée de validation.
  • callback : définit les appels de retours effectué à chaque fin d’époque.

 

Prédiction sur de nouvelles images

Le but est de créer un nouveau fichier ou on va devoir redéfinir le même environnement de pré-traitement des images que lors de l’entrainement, pour lui donner à notre modèle, des formats d’images identiques à celui durant lequel il a appris. La première étape va donc être de créer un fichier texte pour indiquer les chemins des images que l’on souhaite prédire leurs hashtags, en remplissant notre fichier imgTest.Txt.

 

On va donc pouvoir ensuite charger le modèle, charger notre dataframe contenant nos chemins vers nos images de test, accompagné de leurs labels respectifs.

Ensuite, on va recréer notre ImageDataGenerator avec les mêmes paramètres de normalization d’image que on avait lors de l’entrainement.

 

On va pouvoir définir notre générateur de prédiction basé sur notre modèle entraîné :

 

Pour une image en entrée, nous auront une sortie composée de 1000 prédiction entre 0 et 1, correspondant à nos 1000 classe.

On va devoir définir un interrupteur booleen, pour lequel on lèvera si un label est présent ou non dans une image si la prédiction de celui-ci dépasse la valeur de l’interrupteur. Pour notre modèle, on lève un flag de présence si une prédiction pour une classe donnée dépasse 10% de probabilité. On se chargera par la suite à charger notre fichier JSON crée précédemment.

 

On va ensuite itérer sur l’ensemble de nos images séparément :

On va pouvoir comparer le tableau de probabilité à la suite d’une prédiction d’une image, avec notre fichier JSON. Cela va nous permettre de savoir à quel label appartient tel ou tel flag qui aura été levé, vu que on a seulement sa position dans le tableau à la suite d’une prédiction.

 

Dernière étape du projet :

On va récupérer l’ensemble des chemins des images que l’on a insérer dans notre générateur de prédiction, et les fusionner à notre tableau des sorties de labels effectué précédemment. A votre souhait de vouloir les afficher dans la console, ou les enregistrer dans un fichier csv.

 

 

Axe d’amélioration

  • Vers une reconnaissance de lieux ou de monuments ?

En effet, on a peu parlé du fichier EXIF que compose les images venant de Flickr. Mais on pourrait penser à l’exploiter davantage pour éventuellement avoir de nouveaux types d’informations pertinentes qui pourrait nous renseigner, afin d’avoir un système permettant une reconnaissance de monuments connus, ou encore de lieux grâce aux données géographique présent, via la latitude et longitude.

 

Conclusion

Nous venons de voir à travers de ce projet comment réaliser un recommandeur de hashtag selon une image en entrée. Mais celle-ci est en réalité une simple classification d’image à multi label. On peut le comparer à une classification simple d’image comme sur mon cours sur comment classifier des fleurs. Les principaux changements entre ces deux types de classifieurs seront les suivant :

comparatif classification sortie multiple et sortie unique
Comparatif des différences entre un classifieur à sortie unique et un classifieur à sortie multiple
  • Fonction d’activation : Pour la dernière couche d’un classifieur à sortie unique, on souhaite une seule classe qui corresponde à notre donnée d’entrée. Par exemple un classifieur chien/chat ne nous donnera qu’une de ces deux classes en sortie. On a donc un impact entre les deux classes, elles sont dépendantes. En effet quand le modèle pense détecter une forte proba pour une classe ( chien 95% ), l’autre classe sera donc faible ( chat 5% ), car c’est les probas de l’ensemble des classes qui sont égale à 1 ( soit :  proba(chat) + proba(chien) = 1). On choisira alors la fonction d’activation softmax.  Alors que pour une classification à sortie multiple, on souhaite que le calcul de la proba des différentes classes soit indépendante, puisque plusieurs classes peuvent être présent dans notre donnée d’entrée. On aurait donc : 0 < proba(chat) < 1 et 0 < proba(chien) < 1. Pour cela on doit donc utiliser la fonction d’activation sigmoid. Pour comprendre les fonctions d’activations en détails, j’ai écrit auparavant un article disponible ici.
  • Fonction de perte : Celle-ci est choisi selon le problème à résoudre. Comme expliqué au point précédent, on n’a pas le même problème à résoudre. On aura donc pas la même fonction d’objectif.

 

Je vous joins ici l’ensemble de mon code source documenté et commenté sur mon profil Github, avec les informations nécessaire pour sa compilation et lancement. Vous aurez l’ensemble des informations nécessaires pour pouvoir en recréer un vous-même. Je compte d’ailleurs sur vous pour me proposer d’éventuelles corrections et optimisations pour le mien. 🙂

 

Remerciements

HARRISON16, Minseok Park and Hanxiang Li and Junmo Kim, HARRISON: A Benchmark on HAshtag Recommendation for Real-world Images in Social Networks, 2016