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.

 

 

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

constitution audio spectrogramme image Cours théoriques - Deep learning

Conversion d’un audio en spectre

Pour comprendre mon cours pratique quant à la réalisation d’une reconnaissance vocale de mots clés via un réseau de neurones utilisant de la convolution, vous devez comprendre les différentes étapes de transformations, entre notre fichier audio de base, vers un tenseur d’ordre 3 compréhensible et utilisable par notre modèle.

 

Mais, c’est quoi le son ?

Les sujets autour du son et des ondes étant plus proche du domaine de la physique que de l’informatique, je vais simplifier au mieux. Un son est une variation de pression qui va entrainer une vibration dans nos molécules d’air, se propageant ainsi sous forme d’ondes. On peut définir et analyser ce signal qui est en faîte une amplitude en fonction du temps. Cette amplitude sonore nous donne une indication concernant la puissance de celle-ci, puisqu’elle représente la valeur de la pression la plus haute attend par l’onde. Concrètement, plus on produit un son fort, plus son amplitude est forte, et inversement.

amplitude d'un son
Src : poitauc

Ces enchaînements de montées et de descentes que l’on peut observer sur la courbe, représente les surpression et dépression du milieu, engendré par le déplacement de l’onde.

Nous venons de voir comment les signaux sont défini dans l’espace temporel, on va maintenant s’attarder sur ces signaux dans un espace fréquentiel.

 

Fréquence et spectre

La fréquence va nous permettre de définir et de classer si un son est aigu ou grave, nous donnant des informations supplémentaires qui vont être importante pour la suite. Elle se définit en hertz, et représente le nombre d’oscillations (période) par seconde.

frequence d'un son
Src : illusions-sonores

 

Types d’images utilisable

Je vais parler des 3 plus connues pour vous donner quelques pistes d’utilisations pour alimenter vos réseaux de neurones. On va utiliser des spectrogramme, qui sont calculés selon le type, via une transformation discrète de Fourrier, une transformation rapide de Fourrier, ou encore une transformation discrète de cosinus. Ce sont des photographies représentent le spectre d’un son. Ces type de spectrogramme vont nous permettre d’analyser des sons et de nous renvoyer :

  • Un temps
  • Une fréquence
  • Une intensité

Vous devriez vous demander d’où provient ce besoin de passer sur des spectres… Et bien pour pouvoir reconnaître des phonèmes ! Un phonème est la plus petite entité phoniques, un élément sonore du langage parlé, pour faire simple : un son, qui peut correspondre à plusieurs sons. C’est cette association de phonème entre eux qui permettent de constituer les mots. Il en existe dans la langue Française 16 de voyelles, 17 de consommes, et 3 de semi consonnes/voyelles.

mfcc spectrogramme mel phoneme

En prenant l’exemple ci-dessus, je vous montre une comparaison entre notre 3 types de spectrogrammes différents, sur le mot ‘bonbon’. Il est constitué de deux phonèmes de voyelle et de deux phonèmes de consonne.

 

Références

Un article extrêmement intéressant que je vous recommande est celui-ci, ‘Comparison of time frequency representations for environmental sound classification using convolutional neural networks’. Il va bench jusqu’à 8 types d’images d’entrée pour réaliser de la classification de son en utilisant un CNN.

constitution image rgb Cours pratiques - Deep learning

Composition et conversion d’une image

Lorsque l’on souhaite analyser des images, ce sont les réseaux de neurones à convolution qui s’en sortent le mieux. Ils sont basés grâce à des expériences effectué sur le cortex et le système de vision des animaux, ces réseaux sont beaucoup plus légers que leurs confrères composé de couche de neurones entièrement connectés les uns aux autres. Attention cependant à ne pas faire trop le rapprochement entre CNN (Convolutional Neural Networks) et images, car ceux-ci peuvent tout aussi bien être utilisé sur du texte, pour réaliser des analyseur de sentiments pour ne citer qu’un exemple. Mais ce n’est pas l’objectif de cet article-ci, j’en reparlerais dans des articles dédiés à ce sujet. 😉

Comme je disais, ces réseaux qui analysent des images, vont pouvoir extraire des caractéristiques propre à celle-ci et à l’ensemble des objets, personnes etc. la constituant. Cependant, comme je l’ai expliqué dans ce post-ci, les réseaux de neurones effectuent des calculs matricielles sur des tenseurs. Ce serait donc outrageux d’envoyer directement nos images tel quel, dans leurs formats natif comme jpg ou encore png (et de toute façon, ce n’est pas possible).

Je vais donc vous montrer comment est constitué une image, afin que vous compreniez les processus que j’effectue sur mes tutoriels pratiques, lorsque je convertis les images de mon jeu de données en tableaux de valeurs (fichier Numpy).

 

Comment est constitué une image ?

Premièrement, notre image est composée de pixel. Je ne vais vous faire la description globale de ce qu’est un Pixel (wikipedia le fera bien mieux que moi), mais c’est l’unité de base, qui définit une image. Une image ayant une taille de 50 par 50 veut dire qu’elle sera composée de 50 pixel par 50 pixel.

cannaux image rgb
Les 3 canaux RGB constituant une image

 

Ensuite, il faut savoir qu’une image en couleurs est composé de 3 canaux, le célèbre RGB (pour Rouge Vert Bleu). Vous aurez donc deviné, que pour une image en noir et blanc, celle-ci est composé exclusivement d’un seul canal.

Ainsi, on peut représenter chaque canaux par une matrice de dimension correspondant à la largeur et la hauteur de l’image. Chaque pixel de l’image va donc représenter une variable de la matrice, qui correspond à l’intensité de la couleurs à ce pixel précis. Petit rappel concernant ce sujet, un pixel peut être définit via une variable comprise entre 0 et 255, correspondant à l’intensité de sa couleur. Cependant, pour homogénéiser nos matrices, nous allons diviser par 255 chacune de nos valeurs, pour avoir à l’entrée de notre réseau de neurones, des matrices ayant l’ensemble de ses valeurs entre 0 et 1.

 

image tenseur cannaux rgb matrice
Empilement de chaque matrice de nos canaux qui forment un tenseur d’ordre 3

Nous nous retrouvons au final avec 3 matrices correspondant à nos 3 canaux de couleurs, qui seront empilés pour former ce qu’on appelle un Tenseur d’ordre 3. Et c’est sur ces structures algébriques que la magie de notre réseau va opérer. 😉

reconnaissance d'image Cours pratiques - Deep learning

Classification d’images

Pour ce premier tutoriel , je vous proposer de réaliser très facilement avec Tensorflow en backend et Keras en API de haut niveau, un classificateur d’images, permettant de réaliser une reconnaissance d’images. Nous allons décortiquer comment réaliser l’ensemble du processus, allant du traitement des données, à l’entrainement de notre réseau de neurones, jusqu’au test de notre modèle dans de futures condition réelles pour pouvoir avoir une idée de comment se comporte notre algorithme avant même qu’il soit intégré dans une application.

N’ayant pas tellement la main verte ( en plus d’être daltonien ), on va créer un modèle permettant de reconnaître entre 5 fleurs différentes.

classificateur d image
Voici les différentes fleurs que l’on va apprendre à notre réseau de neurones à reconnaître

On va sur cet article se concentrer sur les différentes notions et étapes nécessaire pour pouvoir réaliser un tel classificateur d’image. Pour la partie technique et les plus impatients d’entre vous, je vous joint ici l’ensemble du code source du projet disponible sur mon Github.

C’est parti ! 😉

 

Pré-requis

Cours théorique sur la constitution et la conversion d’une image

 

Conversion de notre dataset en tableau numpy

Nous allons dans un premier temps, devoir transformer nos images d’entrées. En effet, on ne peut charger nos images en format png directement dans notre réseau de neurones. Celui-ci ne fonctionne qu’avec des tenseurs. On va donc convertir nos images vers des matrices de valeurs qui vont être empilés. Je vous ait écrit un article à propos de la constitution d’une image et quant à sa conversion,  vers un tenseur de valeurs, qui correspondent aux intensités de couleurs des 3 différents canaux ( Rouge, Vert, Bleu ) correspondant pour chaque pixel composant l’image. Nous avons ainsi un fichier numpy par classe. D’habitude, la plupart des gens inclus ce processus directement dans le même fichier d’entrainement du modèle. Ce qui n’est pas optimisé puisque l’on est obligé de re-créer ces tableaux à chaque entrainement, ce qui est purement une perte de temps. Ainsi en faisant de cette manière, nous allons les créer une seule et unique fois.

 

Pré traitement des données

On va devoir générer deux types différents de dataset à partir de nos fichiers Numpy :

  • Dataset d’entrainement
  • Dataset de validation

Le premier va permettre à notre réseau d’apprendre et d’extraire des caractéristiques distinctes de chacune de nos fleurs.

Le second quand à lui va servir à valider le modèle en fin de chaque itération au cours de l’entrainement. En effet, en montrant de nouvelles images à notre réseau, il va lui permettre de se recalibrer pour éviter de sur-apprendre les fleurs du jeu de données d’entrainement. Cette calibration va lui permettre de bien meilleurs généralisation de données.

Il faudra respecter un certain ratio entre ces deux jeux de données. A partir de notre dataset original, nous allons récupérer 80 à 90% des données pour le dataset d’entrainement, et donc de 10 à 20% pour le dataset de validation.
Notre réseau à convolution va avoir comme entrée un tenseur de la dimension suivante :

( n, w, h, c )

  • n : nombre total d’image de notre dataset
  • w : largeur en pixel de nos images
  • h : hauteur en pixel de nos images
  • c : nombre de canaux de nos images. Correspond donc à 1 pour du noir & blanc, et 3 pour des entrées en couleurs

Il faudra donc bien faire attention de reshape nos données en les récupérant depuis nos fichiers numpy.

 

Création du modèle

Je souhaitais reprendre le model d’alexNET. Mais étant donnée mon peu de donnée de 250Mo ( ce qui est ridicule en terme de donnée ), je suis parti sur un modèle extrêmement simple que j’ai pris au hasard. Du moins pas complètement au hasard, puisque on utilise un réseau à convolution, on doit respecter des templates concernant les empilement des différentes couches :

[ [Conv -> ReLU]*n -> Pool ] *q -> [FC -> ReLU]*k -> FC -> Softmax

  • Conv : couche de convolution
  • ReLU : fonction d’activation, Rectified Linear Unit
  • Pool : couche de convolution
  • FC : couche de neurones entièrement connecté
  • Softmax : fonction d’activation à sorties multiples

 

Entrainement du modèle

La partie rapide du projet. C’est simple, vous n’avez rien à faire, juste à attendre que votre réseau apprenne. Celui ci va se renforcer au fur et a mesure des itérations que va parcourir votre modèle sur votre jeu de donnée, devenant ainsi meilleur.

entrainement d'un réseau de neurone
Dernière itération de l’entrainement de mon réseau de neurones

 

Suivit de l’entrainement

graphique de suivi de metriques loss
Évolution de la perte au cours de l’entrainement
graphique de suivi de metriques precision
Évolution de la précision au cours de l’entrainement

Une fois le modèle entraîné, on va vouloir voir comment il s’est comporté durant l’entrainement. Que cela soit la fonction de perte ou de précision, on va pouvoir avoir de réels informations et indices sur le comportement de notre réseau, et ce sur le jeu de donnée d’entrainement et de validation.

On peut apercevoir que le modèle n’a pas finit d’apprendre, en effet la courbe concernant le jeu de donnée de validation connait une stagnation. Nous verrons plus loin dans l’article comment améliorer notre modèle.

 

Réaliser une prédiction

Enfin la partie intéressante ! Maintenant que notre modèle est entraîné, on va enfin pouvoir réaliser des prédictions sur de nouvelles images. Nous avons juste à le charger en mémoire, à transformer notre image au format jpg, vers un tableau numpy, puis de reshape sa dimension vu précédemment. Nous aurons en sortie un tableau de 5 valeurs, correspondant aux 5 neurones de la couche de sortie de notre modèle, et donc à nos 5 classes de fleurs. On aura pour chaque classe un pourcentage concernant sa prédiction. On prendra alors la valeur la plus élevée des 5, qui correspond donc à la prédiction effectué par notre modèle.

prediction d'un réseau de neurones
Résultat d’une prédiction d’une nouvelle donnée depuis mon réseau de neurones

 

Test de notre modèle sur un jeu de donnée entier

Maintenant que nous avons un modèle, on souhaite savoir comment il va se comporter sur de grandes quantités de nouvelles données. En effet, il serait dommage de perdre du temps de l’intégrer dans notre application pour se rendre compte bien plus tard que notre réseau n’est absolument pas fonctionnel. Perte de temps et d’argent garantie. 😉

On va donc recréer un dataset de nouvelles images, auxquelles notre réseau n’aura jamais vu auparavant, pour permettre de prédire au mieux comment notre réseau va se comporter en application réelle. Pour notre exemple, on va reprendre nos 5 types de fleurs différentes, avec des images que j’ai pu récupérer sur un moteur de recherche. Plus votre dataset sera important, et plus vous aurez une idée précise du comportement de votre réseau. Pour le cas du tutoriel ( et que je suis fenéant ), j’ai pris seulement 3 images différentes pour chacune des fleurs.

Le but de notre matrice ne va pas s’arrêter là. En effet, son application va aller bien plus loin. Il va nous permettre de mettre en évidence d’éventuel erreurs qui pourrait être critique ou acceptable, ce qui sont 2 choses réellement différentes, j’en écrirais un article d’ici peu pour de plus amples informations.

matrice de confusion

On obtient un score global de 93% de bonnes prédictions sur de nouvelles données. Nous pouvons nous rendre compte qu’il a donné de parfaite prédiction concernant 4 de nos classes. Cependant, notre modèle s’est trompé sur 1 fleur sur 3, concernant les tulipes. Le but de ce procédé va donc être de viser une diagonale pour avoir des prédictions proche de 1.

 

Axe d’amélioration

On voit sur les graphiques de suivi de métriques que notre courbe d’apprentissage laisse à désirer sur le jeu de données de validation, mais s’en sort plutôt bien sur notre jeu de données de test, de notre matrice de confusion. Pour le tutoriel, j’ai pris des photos relativement simple, ce qui peut justifier notre haut taux de reconnaissance. Il s’en sort beaucoup moins bien sur celui de validation. Je vais vous proposer plusieurs pistes pour corriger cela et vous permettre de développer un modèle bien plus robuste que le mien.

  • Augmenter notre jeu de données : en effet, on a entre 700 et 1000 fichiers pour chacune de nos classe, ce qui est extrêmement ridicule. Plus on va fournir un jeu de données important et diversifié, plus il pourra apprendre et donc réaliser de meilleurs prédictions. Vous pouvez soit en récupérer d’avantage vous même à la main. Ou si votre jeu de données est cependant limité ou impossible à étendre, vous pouvez toujours utiliser des techniques de data augmentation.
  • Augmenter la taille du réseau : n’ayant que très peu de données, mon choix d’un réseau aussi simple est justifié. Cependant si on augmente notre jeu de données, nous allons pouvoir augmenter la profondeur de notre réseau de neurones. Ajouter des couches va permettre au réseau d’extraire des caractéristiques plus complexes.
  • Augmenter la résolution de nos images d’entrées : n’ayant pas un GPU à disposition pour mes entraînements, je suis dans l’obligation d’utiliser seulement mon CPU, me limitant ainsi dans mes calculs de tenseurs. Cependant, augmenter la résolution des images va permettre au réseau de mieux s’en sortir. En effet, plus la qualité des images du dataset est haute, et plus les prédictions en seront bonne.

 

Conclusion

Je vous montre comment classifier des fleurs ( je vous l’accorde c’est absolument inutile ). Mais la principal chose est de comprendre la démarche du projet. Si vous comprenez comment fonctionne ce projet, vous pouvez l’appliquer ailleurs. Vous pouvez très bien faire votre propre réseau de neurones capable d’analyser des images médicales, telles que les radiographies et échographie, pour mettre en évidence d’éventuelles tumeurs qui aboutissent à des cancers pour ne donner qu’un simple exemple d’utilisation. Vous pouvez éventuellement installer des dizaines de caméras sur la voiture de votre mère, et créer votre propre voiture autonome si vous vous en sentez le courage. 😉

Je vous joint 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écessaire 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 miens. 🙂

data augmentation Cours théoriques - Intelligence artificielle

Data augmentation

Pour pouvoir entrainer nos modèles, il nous faut d’énormes quantités de données. En effet, la quantité et surtout la qualité de notre dataset va avoir un rôle majeur pour l’élaboration d’un modèle de bonne facture. En effet, il est logique d’avoir avoir des données qui soient comparable entre elle. Quand je dis comparable, c’est qu’elles aient le même format, la même taille et longueur, etc. Et c’est à partir de ces contraintes que commence les problèmes. En effet, avoir des data spécifique selon notre problème avec les points précèdent cité peut souvent relever de l’impossible. C’est là que la data augmentation va pouvoir nous être grandement utile.

Le principe de data augmentation repose sur le principe d’augmenter de façon artificielle nos donnée, en y appliquant des transformations. On va pouvoir augmenter la diversité et donc le champ d’apprentissage de notre modèle, qui va pouvoir mieux s’adapter pour prédire de nouvelles données. Le principe de cette méthode est relativement simple, celle-ci est montré par l’image suivante concernant de l’augmentation sur des images :

data augmentation sur image

En partant d’une simple image, nous pouvons la dupliquer autant de fois que nous avons des types de transformation différentes à lui appliquer. Et nous pouvons en augmenter davantage en croisant ces effets sur une même image, et en y appliquant différents valeurs de l’effet dans une fourchette donnée, pour avoir un résultat plus ou moins poussé.

 

Voici un exemple de mel-spectrogramme, dont on à appliquer des transformations à un extrait audio sain, sur le mot ‘Bonjour’. (Figure 1)

data augmentation sur audio

On peut aussi imaginer un grand nombre de transformation sur des données audios.
– Tempo : change la vitesse de parole de l’enregistrement sans en changer la longueur. (Figure 3)
– Pitch : change l’intonation de la voix (plus aigüe ou plus grave). (Figure 2)
Et la liste peut être plus longue : bandpass, equalizer, highpass, lowpass, chorus, delay, stretch, contrast, etc.