Les GAN sont-ils à la mesure pour la musique ?
Peut-on faire apprendre à un algorithme de type GAN à synthétiser de la musique sans partitions ? Un travail de recherche qui a animé un bon nombre de mes soirées !
Petit rappel algorithmique
La notion de GAN (generative adversarial network) est de nos jours bien intégrée mais un petit rappel ne fera pas de mal pour ceux qui n’y sont pas initiés. C’est un problème d’apprentissage supervisé. Jusque là pas grand chose de compliqué : on optimise les paramètres d’un algorithme pour que celui-ci fasse le moins d’erreurs vis-à-vis d’une fonction objectif, qui elle prend en compte la réponse de l’algorithme et le label de la donnée (la vérité si vous préférez). Les GANs s’inscrivent dans cette lignée avec une petite subtilité : il y a deux algorithmes, un générateur dont le but est de générer de la donnée à partir d’un bruit aléatoire, et un discriminateur qui doit distinguer la vraie donnée (issue de notre jeu d’apprentissage) de la fausse (issue du générateur). Tous deux ont des fonctions objectifs qui s’opposent :
De par cette formule nous observons que le discriminateur optimise l’opposé du générateur. En d’autres termes le discriminateur essaye de discerner la vraie donnée de la fausse à la différence du générateur qui lui tente de produire de la vraie donnée aux yeux du discriminateur.
Dessiner des images pour de l’audio
Maintenant que le rappel est fait, intéressons-nous plus en détail à notre objectif : créer de la musique via de la synthèse d’audio brute. Par synthèse brute j’entends que nous n’allons pas générer une partition mais bel et bien des sons.
C’est en parcourant les articles liés à cette problématique que celui de Google m’est apparu : GANSynth. Les auteurs génèrent de la musique en utilisant un réseau à convolutions en 2 dimensions, donc en générant des images. Pourquoi partir sur des convolutions à 2 dimensions ? Les CNN ont montré des résultats impressionnants sur de la synthèse d’image et c’est un bon point de départ pour ce projet car la littérature scientifique regorge d’architectures à expérimenter.
Mais quel est le rapport entre une image et un audio ? Une image est en 2 dimensions alors q’un audio monophonique n’en a qu’une ! Toute l’astuce est dans la représentation de l’audio :
Pour représenter un audio en deux dimensions, l’une des possibilités qui s’offre à nous est d’utiliser les transformées de Fourier, plus précisément les transformées de Fourier discrètes courtes (STFT) dont voici une illustration :
A la suite de cette transformation, chaque petit bout d’audio est maintenant représenté par un vecteur de complexes correspondant à plusieurs samples de notre audio brut. Chaque vecteur étant la décomposition en fréquences du signal (le petit bout d’audio / les plusieurs samples de l’audio). En empilant ces vecteurs nous pouvons obtenir une image où chaque pixel est un nombre complexe, image dont la hauteur correspond à quel paquet de fréquences le pixel (plus précisément le complexe) fait référence (de la note la plus grave à la plus aiguë) et dont la largeur correspond au temps.
Cela ne ressemble pas à grand chose et c’est sur ce point que les auteurs de GANSynth ont proposé plusieurs améliorations :
- Utiliser la magnitude et la phase de nos nombres complexes
- Transformer la phase en “phase instantanée”
- Amplifier les magnitudes selon une échelle type Bark
Représenter les nombres complexes avec leur magnitude et phase colle mieux avec ce que nous percevons de l’audio, du moins pour la magnitude qui permet de visualiser des motifs musicaux comme des harmoniques :
Pour la phase ceci est moins évident car les motifs ne sont pas discernables pour un humain (et donc de ce fait moins évident pour notre GAN) : cela part du principe que la phase sera toujours “déphasée” vis-à-vis du début de la fenêtre de la STFT. Ainsi il est plus judicieux de représenter le décalage de la phase par rapport au début de la fenêtre STFT, comme une sorte de taux d’accroissement. C’est ce que décrivent les auteurs de GANSynth en proposant une méthode de représentation de la phase appelée phase instantanée :
Ce qui donne en code (PyTorch) :
L’application d’une échelle type Bark aux magnitudes retranscrit le fonctionnement de l’oreille humaine : pour un même volume sonore en décibels, le volume perçu pour une note dans le registre des médiums, un La 440 Hz par exemple, ne sera pas le même que pour le registre des aiguës, un La à 14 080 Hz par exemple. C’est pourquoi un modèle psycho-acoustique type Bark permet de mieux faire ressortir les fréquences aiguës et donc de respecter la perception humaine du volume sonore.
Nos audios sont maintenant convertis en images de deux couleurs : la magnitude et la phase instantanée, il ne reste plus qu’à définir notre architecture de GAN pour commencer l’entrainement !
L’architecture de base
La première architecture expérimentée lors de mes travaux ressemblait fortement à celle de l’article DCGAN : à savoir un réseau à convolutions avec comme particularité d’utiliser des convolutions à pas de 2 pour réduire les dimensions du spectrogramme dans le discriminateur (et l’augmenter avec des convolutions transposées pour le générateur).
Le mode collapse fut au rendez-vous (mode d’échec dans lequel le générateur ne produit que la même donnée malgré différentes entrées aléatoires). Pour y remédier, l’utilisation du GAN de Wasserstein avec pénalité de gradient me permit de solutionner le problème et m’amena à des premiers résultats concluants :
Voici un petit bout de code illustrant les deux architectures :
Et la fonction objectif pour le GAN de Wasserstein :
Cependant le générateur semblait être dans un mode collapse un peu plus “évolué” que celui ne produisant qu’une seule donnée. Je m’explique : les musiques se ressemblaient toutes les unes les autres malgré des variations notoires dans la production des sons.
Malgré des résultats concluants, le modèle restait très sensible aux hyper-paramètres, allant d’un mode d’échec total à un mode collapse. Il était de ce fait nécessaire de stabiliser l’entrainement en se plongeant dans l’état de l’art des GANs sur l’image afin de voir si d’éventuelles techniques ont été mises au point.
Essais de Progressive GAN
L’article “Progressive Growing of GAN” offre une belle perspective de stabilisation de l’entrainement. Le principe est assez simple : il faut entraîner les couches successivement en partant de celle de plus faible résolution et en ajoutant les couches suivantes au fil de l’entrainement pour arriver à la résolution finale de l’image. Chaque couche doublant la taille de l’image sauf pour la dernière qui transforme les canaux (couleurs latentes) d’un pixel vers l’espace de la magnitude et phase (vers nos 2 couleurs/canaux).
Une nouvelle couche est introduite en utilisant l’ancienne. Pour ceci il faut :
- produire une image via notre générateur avec l’ancienne couche (celle qui convertit en magnitude et phase l’image)
- doubler la taille de cette image (par plus proche pixel voisin par exemple) pour correspondre à la taille de sortie de la nouvelle couche
- produire une image via notre générateur avec la nouvelle couche
- additionner les deux images en les pondérant chacune par des facteurs distincts
Le facteur pour ces deux images, l’image de l’ancienne couche et celle de la nouvelle couche, doit varier de manière linéaire de 100% pour l’ancienne à 100% pour la nouvelle pendant un certain nombre d’itérations. Ce mécanisme s’apparente à une phase de transition qui doit permettre à l’algorithme de continuer sa convergence de manière optimale. Une fois la phase de transition finie, l’entrainement peut se poursuivre normalement.
Ce mécanisme sera plus parlant avec l’illustration des auteurs :
Vous pouvez retrouver l’implémentation sur ce lien : https://github.com/Ipsedo/MusicGAN/tree/progan
Cependant, cette architecture n’est pas plus facile à “dompter” sur les dernières résolutions : la phase de transition vers une nouvelle couche est délicate et résulte souvent en un mode d’échec pour l’algorithme. Je peux noter un point important : l’utilisation de convolutions à pas de 2 est à éviter pour ces phases de transitions. En effet, à basse résolution l’algorithme semble tenir le coup mais ce n’est pas le cas à haute résolution : les images résultantes montrent que l’algorithme échoue car ne reproduit plus les motifs jusqu’à maintenant appris lors des précédentes couches :
Ce constat est en accord avec ce que les auteurs de l’article préconisent : à la place des convolutions à pas de 2, utiliser des interpolations de type linéaire. Ceci correspond mieux à l’introduction d’une nouvelle couche à partir de l’ancienne (en doublant la résolution de l’image de l’ancienne couche).
Cependant, en utilisant des interpolations linéaires pour doubler la taille de l’image et ainsi éviter l’utilisation de convolutions transposées, le mode collapse survient plus facilement voire systématiquement…
Une autre instabilité de l’utilisation de convolution transposée à pas de 2 est la production d’artefacts : si le pas n’est pas un sous multiple de la taille de la fenêtre de convolution transposée, il y a de grandes chances que des artefacts apparaissent (et d’autant plus pendant les phases de transition comme sur les illustrations plus haut !).
Conclusion
A ce jour les transitions à haute résolution restent un problème majeur lors de l’entrainement et je suis toujours en recherche de moyens pour y remédier.
La solution présentée par les auteurs de GANSynth a montré son efficacité sur de simples notes (créer un La de trompette par exemple). Couplez ceci à un autre algorithme générant la partition et le résultat sera impressionnant comme avec Magenta.
Créer une musique de toute pièce est nettement plus compliqué et à ce jour je n’ai pas réussi à faire mieux que l’extrait sur YouTube présenté plus haut. Plusieurs explications sont à prendre en compte :
Premièrement le tempo varie d’une musique à l’autre et ne sera jamais “synchronisé” avec le début et la fin d’un spectrogramme rendant la tache beaucoup plus rude pour notre GAN.
Deuxièmement, à la différence d’une solution comme Magenta qui distingue les instruments, cette approche tente de synthétiser tous les instruments ensemble et notre GAN n’a aucun moyen de désambiguïser à quel instrument une note appartient.
Ces deux points sont les principales difficultés que je vois pour la synthèse de musiques dans l’espace des fréquences.
Mais il me reste encore quelques pistes :
- Tester la variante unroll GAN (sans les convolutions transposées) permettant ainsi d’éviter le mode collapse survenant fréquemment lors des entraînements, mais la consommation de mémoire devient vite impraticable.
- Ajouter une notion temporelle plus forte qu’avec des convolutions 2D, avec par exemple un réseau récurrent couplé au CNN.
Je ne perds pas espoir de faire le premier concert de musiques synthétiques ! Retrouvez moi sur GitHub car ce projet risque de me suivre encore longtemps :)
Références
[1] GANSynth: Adversarial Neural Audio Synthesis — Jesse Engel, Kumar Krishna Agrawal, Shuo Chen, Ishaan Gulrajani, Chris Donahue, Adam Roberts — 23 Feb 2019
[2] Progressive Growing of GANs for Improved Quality, Stability, and Variation — Tero Karras, Timo Aila, Samuli Laine, Jaakko Lehtinen — 27 Oct 2017
[3] Improved Training of Wasserstein GANs — Ishaan Gulrajani, Faruk Ahmed, Martin Arjovsky, Vincent Dumoulin, Aaron Courville — 31 Mar 2017