Version 21, last updated by fsavard at April 19, 2010 09:05 UTC
Transformation des images
Pour qu'on puisse progresser et remplir individuellement les différents tickets pour la transformation d'images, il faut s'entendre sur différentes choses. Notamment, il y a le format d'image que les différents éléments du "pipeline de transformation" vont s'échanger. Pour le langage, on s'est entendu pour Python.
Format des images
C'est le format des images en entrée/sortie des, modules de transformation. On va travailler avec les choix suivants:
- Les images sont 32x32 du début à la fin du pipeline.
- Les images sont en niveau de gris.
- Au chargement, elles sont converties en valeurs sur la plage 0.0 à 1.0.
- Motivation: 0.0-1.0 c'est moins arbitraire que, exemple, 0-255.
- Au chargement, elles sont converties en valeurs sur la plage 0.0 à 1.0.
- Le fond, par défaut, est de couleur "0". Les lettres sont en couleur "1".
- Pour une image sauvée en .PNG, disons, ça donne des lettres blanches sur fond noir.
- Motivation: c'est pratique d'avoir un fond par défaut de 0 pour faire du remplissage de trous laissés en bordure dans certains algorithmes.
- Pour une image sauvée en .PNG, disons, ça donne des lettres blanches sur fond noir.
- On utilise des "arrays" Numpy. On travaille avec des floats sur 4 bytes (float32) dans ces arrays.
- Cependant, à la fin du pipeline, le résultat est converti en valeurs de 0 à 255, sur un seul byte, pour prendre moins d'espace disque.
- Motivation: certains algos peuvent bénéficier de la précision supplémentaire dans les calculs intermédiaires, par exemple si on divise par un grand nombre pour ensuite renormaliser.
- Cependant, à la fin du pipeline, le résultat est converti en valeurs de 0 à 255, sur un seul byte, pour prendre moins d'espace disque.
Interface et fonctionnement des modules de transformation
Chaque module doit être contenu dans un module Python, simplement un fichier dans le répertoire "transformations" du repository. Voici les points principaux à connaître:
- Toute génération aléatoire est contrôlée par un paramètre complexity, allant de 0.0 (min) à 1.0 (max). C'est intuitivement ce qui contrôle l'amplitude de la distribution de la déformation (pas l'amplitude directement).
- À titre d'exemple pour une rotation d'angle A, si cet angle est généré aléatoirement selon une normale de variance sigma^2, alors on pourrait avoir "sigma = (complexity + epsilon) x 5.0" (le epsilon étant une très petite valeur servant à éviter une division par 0).
- Motivation: on veut peut-être utiliser la complexité pour contrôler l'ordre de présentation des exemples.
- Chaque module doit avoir une fonction regenerate_parameters() qui change les paramètres de la transformation, en fonction de la complexité. Autant que possible, entre les appels à regenerate_parameters(), la transformation appliquée reste la même (on voudra donc l'appliquer à des exemples différents, une batch d'exemples possiblement traités comme un seul bloc).
- Par exemple, pour une rotation, l'angle sera choisi aléatoirement (fonction de la complexité) et restera le même jusqu'à un nouvel appel à regenerate_parameters().
- Cette fonction retourne les nouveaux paramètres, et ils sont sauvegardés par le gestionnaire de pipeline, pour être rechargé au temps voulu, notamment à l'entraînement, quand on rechargera les images correspondantes.
- Motivation: on utilisera peut-être la possibilité de sauver de l'espace en ne sauvegardant pas les paramètres entre chaque appel à regenerate_parameters().
- Pour que cela fonctionne (réutilisation de paramètres plusieurs fois), il faudra faire une permutation aléatoire des exemples générés. Vu le nombre d'exemples considérés (qui ne rentrent pas tous en mémoire), une permutation vraiment aléatoire est très coûteuse en temps de calcul, mais on pourrait en faire une par blocs (chacun suffisamment petit pour entrer en mémoire, donc pour faire la permutation facilement).
- Ce dernier point est juste une précision, vous n'avez pas à vous en occuper pour vos algos si vous respectez l'idée de ne pas changer les paramètres entre les appels à regenerate_parameters.
- L'ordre des paramètres retournés par regenerate_parameters() doit respecter l'ordre des noms de paramètres retournés par la fonction get_settings_names(). Vous devez conserver toujours le même nombre de paramètres, et le même ordre.
- Motivation: en utilisant toujours le même ordre, on peut stocker les paramètres de façon plus efficace.
- Certains modules (notamment pour le bruit et les déformations élastiques) ont des paramètres qui sont complètement déterminés par la complexité. Par exemple, on pourrait avoir un niveau de bruit donné par "2 * complexity" (mettons), bien que le bruit lui-même soit aléatoire.
- Ceci par opposition à des paramètres comme un angle de rotation où la complexité influence la distribution de l'angle, pas l'angle lui-même.
- Plutôt que de stocker ces paramètres déterministes, on ne retourne rien (array vide) dans regenerate_parameters() et get_settings_names(). On retourne ces paramètres dans get_parameters_determined_by_complexity() et leurs noms dans get_settings_names_determined_by_complexity().
Voici un exemple de classe respectant cette convention:
# This is NOT a base class, just an interface to conform to.
def ExampleTransformation():
def __init__(self):
self.mysetting1 = 0.0
self.mysetting2 = 0.0
def get_settings_names(self):
return ['mysetting1','mysetting2']
def regenerate_parameters(self, complexity):
self.mysetting1 = numpy.random.rand() * complexity
self.mysetting2 = numpy.random.rand() * complexity * 2
return [self.mysetting1, self.mysetting2]
def transform_image(self, image):
return image
Ordre des transformations
Ici l'idée est que certaines transformations doivent être faites avant d'autres. Notamment, on va utiliser l'intensité des pixels comme un masque (comme dans Photoshop/GIMP) pour identifier les pixels où le fond doit être appliqué (les pixels "non lettre").
- Avant de prendre le masque
- épaississement
- shear / slant / skew
- déformations élastiques locales / warping
- autres filtres GIMP
- transformations affines
- rotation
- scale
- translation
- Allant derrière le masque de la lettre
- image de fond
- Ce qui embarque par dessus le résultat combiné "fond + lettre"
- bruit gaussien
- bruit poivre-et-sel
- polarité / lighting
Fonctionnement du pipeline
Batches, sauvegardes, etc. À faire. Pour l'instant, points importants:
- Nicolas a cherché pas mal, et on peut utiliser GIMP sans X (sur maggie46, ie. sur le cluster) et à partir de données en mémoire. Ça roule assez vite en mode batch (~7 ms par image).
- Le plan est donc de charger le script de génération dans une commande GIMP.
- On compte diviser en environ une centaine de batches de complexité constante, pour diviser le travail sur les différents CPUs du cluster.
- On compte sauvegarder les images avec structures "filetensor" (pylearn.io.filetensor, ".ft", comme NIST).
- C'est pas encore clair comment on va sauvegarder: en batch (de quelle taille?), dans un seul gros fichier par job sur le cluster... à voir.
- (Dumitru) pylearn.io.filetensor contient une jolie structure qui s'appelle arraylike, qui pourrait etre utile si on decide d'avoir un gros fichier pour les donnees (car ca permet de simplement faire un "seek" dar un fichier filetensor et les seeks sont assez rapide s'ils accedent aux exemples successives.
- On compte sauvegarder les paramètres dans des grandes matrices avec numpy.save()
- On doit avoir un ordre fixe de paramètres pour chaque module.
Pendant qu'on y est, quelques conventions Python
- Utilisez 4 espaces, pas des tabs, pour l'indentation.
- Sinon référez-vous à ce guide de conventions.
Vieilles discussions (log, pas besoin de lire ça)
Il faut s'entendre sur le langage utilisé. Vu les outils qu'on utilise déjà (Theano), la discussion en classe, et après en avoir parlé un peu avec Yoshua, ces choix semblent assez naturels:
* Pour le langage: Python
* Pour le format des images: des arrays Numpy ("array", pas "matrix", disons)
o C'est d'ailleurs assez facile de passer du format utilisé pour les arrays Numpy vers le format Python Imaging Library (PIL) et vice versa (numpy.array(my_pic), Image.fromarray(my_array)).
Questions:
- Va-t-on frapper un problème de performance avec ces choix?
- Est-ce qu'on veut vraiment que l'image soit 32x32 dès le début du pipeline, ou on la veut plus grosse et on la réduit à un certain point?
- Penser à l'antialias: si on fait toutes ces transformations sur 32x32, le résultat final va être moins beau (mon intuition, peut-être erronée)
- Yoshua: j'aurais tendance à rester en 32x32 tout le long.