Bienvenue dans la Partie 3 de la série d’apprentissage profond appliqué. La partie 1 était une introduction pratique aux réseaux de neurones artificiels, couvrant à la fois la théorie et l’application avec de nombreux exemples de code et de visualisation. Dans la partie 2, nous avons appliqué l’apprentissage en profondeur à des ensembles de données du monde réel, couvrant les 3 problèmes les plus couramment rencontrés comme études de cas: classification binaire, classification multiclasse et régression.
Maintenant, nous allons commencer à plonger dans des architectures d’apprentissage profond spécifiques, en commençant par les plus simples: les Autoencodeurs.
- Introduction
- Architecture
- Implémentation
- Autoencodeurs Débruitants
- Autoencodeurs clairsemés
- Cas d’utilisation
- Conclusion
Le code de cet article est disponible ici sous forme de cahier Jupyter, n’hésitez pas à le télécharger et à l’essayer vous-même.
Introduction
Les autoencodeurs sont un type spécifique de réseaux de neurones à réaction où l’entrée est la même que la sortie. Ils compressent l’entrée dans un code de dimension inférieure, puis reconstruisent la sortie de cette représentation. Le code est un « résumé » compact ou une « compression » de l’entrée, également appelée représentation d’espace latent.
Un autoencodeur se compose de 3 composants: codeur, code et décodeur. Le codeur comprime l’entrée et produit le code, le décodeur reconstruit ensuite l’entrée uniquement à l’aide de ce code.
Pour construire un autoencodeur, nous avons besoin de 3 choses: une méthode d’encodage, une méthode de décodage et une fonction de perte pour comparer la sortie avec la cible. Nous les explorerons dans la section suivante.
Les autoencodeurs sont principalement un algorithme de réduction de dimensionnalité (ou de compression) avec quelques propriétés importantes:
- Spécifique aux données: Les autoencodeurs ne peuvent compresser de manière significative que des données similaires à celles sur lesquelles ils ont été formés. Comme ils apprennent des fonctionnalités spécifiques aux données d’entraînement données, ils sont différents d’un algorithme de compression de données standard comme gzip. Nous ne pouvons donc pas nous attendre à un autoencodeur formé sur des chiffres manuscrits pour compresser des photos de paysage.
- Avec perte: La sortie de l’autoencodeur ne sera pas exactement la même que l’entrée, ce sera une représentation proche mais dégradée. Si vous voulez une compression sans perte, ce n’est pas la solution.
- Sans surveillance: Pour entraîner un autoencodeur, nous n’avons rien à faire, il suffit de lui lancer les données d’entrée brutes. Les autoencodeurs sont considérés comme une technique d’apprentissage non supervisée car ils n’ont pas besoin d’étiquettes explicites pour s’entraîner. Mais pour être plus précis, ils sont auto-supervisés car ils génèrent leurs propres étiquettes à partir des données de formation.
Architecture
Explorons les détails de l’encodeur, du code et du décodeur. Le codeur et le décodeur sont tous deux des réseaux de neurones à rétroaction entièrement connectés, essentiellement les ANN que nous avons couverts dans la partie 1. Le code est une couche unique d’un ANN avec la dimensionnalité de notre choix. Le nombre de nœuds dans la couche de code (taille du code) est un hyperparamètre que nous définissons avant d’entraîner l’autoencodeur.
Il s’agit d’une visualisation plus détaillée d’un autoencodeur. Tout d’abord, l’entrée passe par le codeur, qui est un ANN entièrement connecté, pour produire le code. Le décodeur, qui a la structure ANN similaire, produit alors la sortie uniquement en utilisant le code. Le but est d’obtenir une sortie identique à l’entrée. Notez que l’architecture du décodeur est l’image miroir de l’encodeur. Ce n’est pas une exigence, mais c’est généralement le cas. La seule exigence est que la dimensionnalité de l’entrée et de la sortie doit être la même. Tout ce qui se trouve au milieu peut être joué avec.
Il y a 4 hyperparamètres que nous devons définir avant d’entraîner un autoencodeur:
- Taille du code : nombre de nœuds dans la couche intermédiaire. Une taille plus petite entraîne plus de compression.
- Nombre de couches: l’autoencodeur peut être aussi profond que nous le souhaitons. Dans la figure ci-dessus, nous avons 2 couches dans l’encodeur et le décodeur, sans tenir compte de l’entrée et de la sortie.
- Nombre de nœuds par couche : l’architecture d’autoencodeur sur laquelle nous travaillons est appelée autoencodeur empilé car les couches sont empilées les unes après les autres. Les autoencodeurs empilés ressemblent généralement à un « sandwich ». Le nombre de nœuds par couche diminue à chaque couche suivante du codeur et augmente de nouveau dans le décodeur. De plus, le décodeur est symétrique au codeur en termes de structure de couche. Comme indiqué ci-dessus, cela n’est pas nécessaire et nous avons un contrôle total sur ces paramètres.
- Fonction de perte: nous utilisons soit l’erreur quadratique moyenne (mse), soit la crossentropie binaire. Si les valeurs d’entrée sont dans la plage, nous utilisons généralement la crossentropie, sinon nous utilisons l’erreur quadratique moyenne. Pour plus de détails, regardez cette vidéo.
Les autoencodeurs sont entraînés de la même manière que les ANN via une rétropropagation. Consultez l’introduction de la partie 1 pour plus de détails sur la façon dont les réseaux de neurones sont formés, elle s’applique directement aux autoencodeurs.
Implémentation
Implémentons maintenant un autoencodeur pour l’architecture suivante, 1 couche cachée dans l’encodeur et le décodeur.
Nous utiliserons l’ensemble de données MNIST extrêmement populaire comme entrée. Il contient des images en noir et blanc de chiffres manuscrits.
Ils sont de taille 28×28 et nous les utilisons comme vecteur de 784 nombres entre. Vérifiez le bloc-notes jupyter pour les détails.
Nous allons maintenant implémenter l’autoencodeur avec Keras. Les hyperparamètres sont: 128 nœuds dans la couche cachée, la taille du code est de 32 et la crossentropie binaire est la fonction de perte.
Ceci est très similaire aux ANN sur lesquelles nous avons travaillé, mais nous utilisons maintenant l’API fonctionnelle Keras. Reportez-vous à ce guide pour plus de détails, mais voici une comparaison rapide. Avant, nous avions l’habitude d’ajouter des couches en utilisant l’API séquentielle comme suit:
model.add(Dense(16, activation='relu'))
model.add(Dense(8, activation='relu'))
Avec l’API fonctionnelle, nous faisons cela:
layer_1 = Dense(16, activation='relu')(input)
layer_2 = Dense(8, activation='relu')(layer_1)
C’est un moyen plus verbeux mais plus flexible de définir des modèles complexes. Nous pouvons facilement saisir des parties de notre modèle, par exemple uniquement le décodeur, et travailler avec cela. La sortie de la méthode Dense est une couche appelable, en utilisant l’API fonctionnelle, nous lui fournissons l’entrée et stockons la sortie. La sortie d’un calque devient l’entrée du calque suivant. Avec l’API séquentielle, la méthode add a implicitement géré cela pour nous.
Notez que toutes les couches utilisent la fonction d’activation relu, car c’est la norme avec les réseaux neuronaux profonds. La dernière couche utilise l’activation sigmoïde car nous avons besoin que les sorties soient entre. L’entrée est également dans la même plage.
Notez également la fonction call to fit, avant avec ANNs, nous faisions:
model.fit(x_train, y_train)
Mais maintenant nous le faisons:
model.fit(x_train, x_train)
N’oubliez pas que les cibles de l’autoencodeur sont les mêmes que l’entrée. C’est pourquoi nous fournissons les données de formation comme cible.
Visualisation
Maintenant, visualisons à quel point notre autoencodeur reconstruit son entrée.
Nous exécutons l’autoencodeur sur le jeu de tests simplement en utilisant la fonction predict de Keras. Pour chaque image du jeu de tests, nous obtenons la sortie de l’autoencodeur. Nous nous attendons à ce que la sortie soit très similaire à l’entrée.
Ils sont en effet assez similaires, mais pas exactement les mêmes. On le remarque plus clairement dans le dernier chiffre « 4 ». Comme c’était une tâche simple, notre autoencodeur s’est plutôt bien comporté.
Conseil
Nous avons un contrôle total sur l’architecture de l’autoencodeur. Nous pouvons le rendre très puissant en augmentant le nombre de couches, de nœuds par couche et, surtout, la taille du code. L’augmentation de ces hyperparamètres permettra à l’autoencodeur d’apprendre des codages plus complexes. Mais nous devons faire attention à ne pas le rendre trop puissant. Sinon, l’autoencodeur apprendra simplement à copier ses entrées vers la sortie, sans apprendre de représentation significative. Il imitera simplement la fonction d’identité. L’autoencodeur reconstruira parfaitement les données d’entraînement, mais il sera surajustant sans pouvoir généraliser à de nouvelles instances, ce qui n’est pas ce que nous voulons.
C’est pourquoi nous préférons une architecture « sandwitch », et gardons délibérément la taille du code petite. La couche de codage ayant une dimensionnalité inférieure à celle des données d’entrée, l’autoencodeur est dit sous-complet. Il ne pourra pas copier directement ses entrées sur la sortie et sera obligé d’apprendre des fonctionnalités intelligentes. Si les données d’entrée ont un motif, par exemple le chiffre « 1 » contient généralement une ligne un peu droite et le chiffre « 0 » est circulaire, il apprendra ce fait et le codera sous une forme plus compacte. Si les données d’entrée étaient complètement aléatoires sans aucune corrélation ou dépendance interne, un autoencodeur sous-complet ne pourra pas les récupérer parfaitement. Mais heureusement, dans le monde réel, il y a beaucoup de dépendance.
Autoencodeurs débruitants
Garder la couche de code petite a forcé notre autoencodeur à apprendre une représentation intelligente des données. Il existe un autre moyen de forcer l’autoencodeur à apprendre des fonctionnalités utiles, ce qui consiste à ajouter du bruit aléatoire à ses entrées et à lui faire récupérer les données originales sans bruit. De cette façon, l’autoencodeur ne peut pas simplement copier l’entrée sur sa sortie car l’entrée contient également du bruit aléatoire. Nous lui demandons de soustraire le bruit et de produire les données significatives sous-jacentes. C’est ce qu’on appelle un autoencodeur débruitant.
La rangée du haut contient les images originales. Nous leur ajoutons un bruit gaussien aléatoire et les données bruitées deviennent l’entrée de l’autoencodeur. L’autoencodeur ne voit pas du tout l’image d’origine. Mais nous nous attendons à ce que l’autoencodeur régénère l’image originale sans bruit.
Il n’y a qu’une seule petite différence entre l’implémentation de l’autoencodeur débruitant et celle régulière. L’architecture ne change pas du tout, seule la fonction fit. Nous avons formé l’autoencodeur régulier comme suit:
autoencoder.fit(x_train, x_train)
L’autoencodeur de débruitage est formé comme:
autoencoder.fit(x_train_noisy, x_train)
Aussi simple que cela, tout le reste est exactement le même. L’entrée de l’autoencodeur est l’image bruyante et la cible attendue est celle sans bruit d’origine.
Visualisation
Visualisons maintenant si nous sommes capables de récupérer les images sans bruit.
Ça a l’air plutôt bien. La rangée du bas est la sortie de l’autoencodeur. Nous pouvons faire mieux en utilisant une architecture d’autoencodeur plus complexe, telle que les autoencodeurs convolutifs. Nous aborderons les circonvolutions dans le prochain article.
Autoencodeurs clairsemés
Nous avons introduit deux façons de forcer l’autoencodeur à apprendre des fonctionnalités utiles: garder la taille du code petite et débruiter les autoencodeurs. La troisième méthode utilise la régularisation. Nous pouvons régulariser l’autoencodeur en utilisant une contrainte de rareté telle que seule une fraction des nœuds aurait des valeurs non nulles, appelées nœuds actifs.
En particulier, nous ajoutons un terme de pénalité à la fonction de perte de sorte que seule une fraction des nœuds deviennent actifs. Cela oblige l’autoencodeur à représenter chaque entrée comme une combinaison d’un petit nombre de nœuds et lui demande de découvrir une structure intéressante dans les données. Cette méthode fonctionne même si la taille du code est grande, car seul un petit sous-ensemble des nœuds sera actif à tout moment.
C’est assez facile de le faire dans Keras avec un seul paramètre. Pour rappel, nous avons précédemment créé la couche de code comme suit:
code = Dense(code_size, activation='relu')(input_img)
Nous ajoutons maintenant un autre paramètre appelé activity_regularizer en spécifiant la force de régularisation. Il s’agit généralement d’une valeur dans la plage. Ici, nous avons choisi 10e-6.
code = Dense(code_size, activation='relu', activity_regularizer=l1(10e-6))(input_img)
La perte finale du modèle clairsemé est supérieure de 0,01 à celle du modèle standard, en raison du terme de régularisation ajouté.
Démontrons que les encodages générés par le modèle régularisé sont en effet rares. Si nous examinons l’histogramme des valeurs de code pour les images de l’ensemble de tests, la distribution est la suivante:
La moyenne pour le modèle standard est de 6,6 mais pour le modèle régularisé, elle est de 0,8, une réduction assez importante. Nous pouvons voir qu’une grande partie des valeurs de code dans le modèle régularisé sont en effet 0, ce que nous voulions. La variance du modèle régularisé est également assez faible.
Cas d’utilisation
Maintenant, nous pourrions poser les questions suivantes. Quelle est la qualité des autoencodeurs pour compresser l’entrée? Et s’agit-il d’une technique d’apprentissage profond couramment utilisée?
Malheureusement, les autoencodeurs ne sont pas largement utilisés dans les applications réelles. En tant que méthode de compression, ils ne fonctionnent pas mieux que ses alternatives, par exemple jpeg fait mieux la compression de photos qu’un autoencodeur. Et le fait que les auto-encodeurs soient spécifiques aux données les rend peu pratiques en tant que technique générale. Ils ont cependant 3 cas d’utilisation courants:
- Débruitage des données: nous en avons vu un exemple sur les images.
- Réduction de la dimensionnalité : la visualisation de données à haute dimension est un défi. le t-SNE est la méthode la plus couramment utilisée, mais se heurte à un grand nombre de dimensions (généralement supérieures à 32). Les autoencodeurs sont donc utilisés comme étape de prétraitement pour réduire la dimensionnalité, et cette représentation compressée est utilisée par t-SNE pour visualiser les données dans l’espace 2D. Pour d’excellents articles sur t-SNE, reportez-vous ici et ici.
- Autoencodeurs variationnels (VAE): il s’agit d’un cas d’utilisation plus moderne et complexe des autoencodeurs et nous les couvrirons dans un autre article. Mais en résumé, VAE apprend les paramètres de la distribution de probabilité modélisant les données d’entrée, au lieu d’apprendre une fonction arbitraire dans le cas des autoencodeurs vanilla. En échantillonnant des points de cette distribution, nous pouvons également utiliser le VAE comme modèle génératif. Voici une bonne référence.
Conclusion
Les autoencodeurs sont une technique de réduction de dimensionnalité très utile. Ils sont très populaires comme matériel pédagogique dans les cours d’introduction à l’apprentissage profond, probablement en raison de leur simplicité. Dans cet article, nous les avons traités en détail et j’espère que cela vous a plu.
Le code complet de cet article est disponible ici si vous souhaitez le pirater vous-même. Si vous avez des commentaires, n’hésitez pas à me contacter sur twitter.