Module 3 : Preprocessing des données avec l'API tf.data
Pourquoi s'interesser aux pipelines ?
- Les modèles de DL ont besoins de beaucoup de données
- Avant que la donnée ne soit envoyée au modèle, idéalement elle devrait :
- être mélangée
- mise en minibatch
- les minibatchs devraient être disponibles avant la fin de l'époque précédente.
Import des librairies
La version classique, ImageDataGenerator
Lors du dernière, on a vu que les couches convolutives étaient invariantes par translation, en d'autres termes que la position de l'objet que le CNN doit détecter dans l'image n'est pas importante. Au contraire des couches de neurones denses.
Cependant, les couches convolutives ne sont pas invariantes par les autres transformations géométriques : rotation, dilatation, symétrie, modification du contraste, etc.
Pour rendre un CNN robuste à ces modifications, on applique alors ce que l'on appelle de l'augmentation.
ImageDataGenerator
est la méthode de base lorsque que l'on souhaite créer un dataset d'images avec une série d'augmentations.
Ici, avec la commande suivante
on définit un générateur pour le jeu d'entraînement, qui sera appliqué à chaque minibatch.
Le principe est le suivant :
- On sélectionne un minibatch
- Ce minibatch est alors est alors envoyé dans
train_datagen
, où les opérations listées seront appliquées, soit de façon certaine comme lerescale
, soit avec une certaine probabilité. - Ce minibatch augmenté est alors donné pour entraînement au modèle.
Il faut comprendre la chose suivante :
L'augmentation des données se fait à la volée, l'intégrité des données stockées sur votre DD n'est jamais remise en cause.
Appliquons maintenant la librairie ImageDataGenerator
Exemple
Prenons le dataset CIFAR10, et voyons un peu ce que font les différentes transformations.
Augmentation sur des données stockées en local
Souvent, les données ne sont pas stockées sous la forme de fichiers plats, mais déjà répertorié dans des dossiers. Dans ce cas là c'est la méthode flow_from_directory
qui nous permettra de mettre en place le générateur d'augmentations.
Pour voir les modifications apportées, on crée des minibatchs de taille 1, et on en sélectionne par exemple 9.
Dans le cas où l'on a un dossier d'entraînement, un de validation et un de test. Il faut 3 flows différents.
Dans le cas où les données sont sotckées sous la forme de fichiers plats, Tensorflow sait le nombre d'étapes par époques à effectuer. Avec un générateur possiblement infini, il faut lui préciser.
On est capable de faire de l'augmentation, seul problème, on a besoin de définir le nombre d'étapes à chaque époque pour l'entraînement et la validation.
Combinaison avec tf.data du dataset augmenté
Comparons
Description de l'API tf.data
Lorsque l'on entraîne un réseau de neurones, on utilise un algorithme d'optimisation tel que la descente du gradient afin de minimiser la fonction de perte.
En utilisant l'API keras, on utilise la méthode .fit()
pour entraîner le modèle. Si le dataset est assez petit, il peut alors êêtre chargé complètement en mémoire pour l'entraînement. Cependant si le dataset est trop volumineux pour être chargé complètement en mémoire, il devra être chargé en morceaux, mini-batch par mini-batch, depuis le système de stockage sur lequel il est.
De plus, il est peut être nécéssaire de construire une méthode de préprocessing pour retravailler les données entrantes.
Tensorfow permet de faire cela grââce à son API tf.data
L'API Data
Toute l'API se concentre autour du concept de dataset. Dans cette version du dataset, chaque élément est directement un tenseur, ce qui permet une meilleure interaction avec Tensorflow.
Le but de cette séance sera de voir les différentes méthodes de construction d'un dataset avec cette API.
Créer un dataset Tensorflow depuis des tenseurs pré-existants
Si les données existent déjà sous la forme d'un tenseur, d'une liste Python, d'un tableau Numpy, ou d'une DataFrame Pandas, il est alors simple de construire un dataset via la commande suivante.
tf.data.Dataset.from_tensor_slices()
Une fois un dataset obtenu, on peut en sortir des batchs via la commande .batch(BATCH_SIZE)
.
On remarque que le dernier batch n'est pas de taille 3, en effet ls
comporte 8 éléments, qui n'est évidemment pas divisible par 3. Si la taille du batch est importante pour le modèle, on peut alors rajouter l'argument drop_remainder=True
.
Combiner 2 tenseurs en un dataset
Souvent, il est possible que l'on est plusieurs datasets, on peut avoir un dataset de features et un dataset de labels. Dans ce cas, on a besoin de les combiner pour pouvoir faire l'entraînement.
On souhaite combiner ces deux tenseurs. Remarquons que pour combiner ces deux tenseurs on doit avoir une correspondance bijective entre les éléments de ces tenseurs.
Remarquez qu'il est aussi possible de le faire via tf.data.Dataset.from_tensor_slices
.
Shuffle, Batch, repeat
L'intêret de l'API Data de Tensorflow est qu'une fois que le dataset est construit, il est possible de lui appliquer plusieurs transformations et opérations. Nous avons déjà vu en partie la commande .batch()
, nous allons maintenant voir l'interactions avec les autres commandes classiques.
Shuffle
.shuffle()
a deux arguments,
-
buffer_size
qui détermine, combien d'éléments sont tirés du dataset avant d'étre mélangés. Ainsi sibuffer_size
est plus petit que la taille du dataset, il n'est pas entièrement mélangé. Pour s'assurer que le dataset est parfaitement mélangé, il suffit de poserbuffer_size=len(t_x)
. -
seed
est le générateur pour le mélange, assurant la reproducibilité des résultats.
Repeat, batch
La commande .repeat()
elle est utilisée afin de répéter le dataset, pour permettre un meilleur comportement aléatoire lors du batch et du shuffle.
Remarquez la différence entre les deux opérrations. ds.batch(4).repeat(2)
sépare d'abord le dataset en batch de taille 4, puis les batchs se répètent deux fois. Si lors de la première fois le dernier batch n'était pas de la bonne taille, alors ce problème se répètera.
Dans le second cas, ds.repeat(2).batch(4)
, ce problème ne peut arriver qu'une seule fois. Ici le dataset est d'abord répété 2 fois, avant d'être divisé en batchs.
Notez que ce problème n'apparait pas si BATCH_SIZE est un multiple de la taille du dataset. Dans le doute, il est préférable de faire les batchs après le repeat.
Comme il semble que faire les batchs est plus efficace après le repeat. Voyons comment le shuffle se comporte uniquement avec le repeat.
Repeat, shuffle
La différence entre les deux solutions est la suivante :
-
avec
repeat(2).shuffle(buffer_size=len(ls), seed = RANDOM_SEED)
, le dataset est répété 2 fois, puis après seulement il est mélangé. -
avec
shuffle(buffer_size=len(ls), seed = RANDOM_SEED).repeat(2)
, le dataset est d'abord mélangé. Par défaut, unrepeat()
mis après leshuffle()
produit un nouvel ordre après chaque itération.
Si la première solution peut sembler mieux, car le mélange sera plus général, elle brouille la notion d'époque. Pour rappel, on définit une époque par le fait que tous les éléments du dataset sont passés une fois dans le réseau de neurones. Dans la première solution, il peut se passer beaucoup de temps avant que certains éléments du dataset apparaissent, par exemple, l'élément 3 apparait 2 fois, mais à la fin du nouveau dataset.
Dans le deuxième cas, comme on a d'abord mélangé, on est sûr que chaque élément du dataset apparaisse assez souvent, une fois par itération du dataset.
Pour résumer, les bonnes pratiques dans la construction pour l'instant sont
- Construction du dataset,
shuffle()
,repeat()
,batch()
.
Les autres opérations possibles
https://www.tensorflow.org/versions/r2.0/api_docs/python/tf/io
https://www.tensorflow.org/versions/r2.0/api_docs/python/tf/image
tf.io
et tf.image
sont deux modeuls complémentaires très utilisés dans la construction des datasets via l'API, tf.io
permet de faire l'interface input/output afin de lire les fichiers, et tf.image
est spécialisé dans le traitement des images.
Application avec CIFAR 10
L'intêret de l'API tf.data
est qu'elle permet de tout combiner dans une seule fonction pour des datasets prêts pour la production.
Annexe : Connection au drive, import des données, création des dossiers
Pour ce TP nous utiliserons le dataset d'entraînement fourni par Kaggle pour la compétition Dogs vs. Cats.
https://www.kaggle.com/c/dogs-vs-cats/data
Root dir
Train, Test, Validation dir
Adresse
mkdir
Train, Test, Validation subdirs
Adresse
mkdir
Partition des données
NUKE : suppression des dossiers
Créer un dataset depuis des images dans un dossier tf.data.Dataset.from_tensor_slices
Cette fois, ci on crée un dataset directement depuis le repertoire raw_data
, créer des dossiers séparés à chaque fois peut êêtre contraignant, surtout si l'on a beaucoup de données et beaucoup de classes.