Synthèse de la parole du réseau neuronal à l'aide de l'architecture Tacotron 2, ou «Obtenez l'alignement ou essayez de mourir»



Notre équipe a été chargée de: répéter les résultats du réseau neuronal de synthèse vocale artificielle Tacotron2 par DeepMind Il s'agit d'une histoire sur le chemin épineux que nous avons parcouru lors de la mise en œuvre du projet.

La tâche de la synthèse vocale par ordinateur intéresse depuis longtemps les scientifiques et les experts techniques. Cependant, les méthodes classiques ne permettent pas la synthèse de la parole, impossible à distinguer de l'homme. Et ici, comme dans de nombreux autres domaines, l'apprentissage en profondeur est venu à la rescousse.

Regardons les méthodes de synthèse classiques.

Synthèse vocale concaténative


Cette méthode est basée sur le pré-enregistrement de courts fragments audio, qui sont ensuite combinés pour créer un discours cohérent. Il s'avère être très propre et clair, mais complètement dépourvu de composants émotionnels et intonatifs, c'est-à-dire qu'il semble anormal. Et tout cela parce qu'il est impossible d'obtenir un enregistrement audio de tous les mots possibles prononcés dans toutes les combinaisons possibles d'émotions et de prosodie. Les systèmes concaténatifs nécessitent d'énormes bases de données et des combinaisons codées en dur pour former des mots. Le développement d'un système fiable prend beaucoup de temps.

Synthèse vocale paramétrique


Les applications TTS concaténationnelles sont limitées en raison des exigences de données élevées et du temps de développement. Par conséquent, une méthode statistique a été développée qui explore la nature des données. Il génère de la parole en combinant des paramètres tels que la fréquence, le spectre d'amplitude, etc.

La synthèse paramétrique comprend deux étapes.

  1. Premièrement, les caractéristiques linguistiques, telles que les phonèmes, la durée, etc., sont extraites du texte.
  2. Ensuite, pour le vocodeur (le système générant la forme d'onde), des signes sont extraits qui représentent le signal de parole correspondant: cepstre, fréquence, spectrogramme linéaire, spectrogramme de craie.
  3. Ces paramètres configurés manuellement, ainsi que les caractéristiques linguistiques, sont transférés au modèle de vocodeur, et il effectue de nombreuses transformations complexes pour générer une onde sonore. Dans le même temps, le vocodeur évalue les paramètres de la parole, tels que la phase, la prosodie, l'intonation et autres.

Si nous pouvons approximer les paramètres qui définissent la parole sur chacune de ses unités, alors nous pouvons créer un modèle paramétrique. La synthèse paramétrique nécessite beaucoup moins de données et de travail acharné que les systèmes concaténatifs.

Théoriquement, tout est simple, mais dans la pratique, il existe de nombreux artefacts qui conduisent à un discours étouffé avec un son "bourdonnant", qui ne sonne pas du tout comme un son naturel.

Le fait est qu'à chaque étape de la synthèse, nous codons en dur certaines fonctionnalités et espérons obtenir un discours réaliste. Mais les données sélectionnées sont basées sur notre compréhension de la parole, et la connaissance humaine n'est pas absolue, par conséquent, les signes pris ne seront pas nécessairement la meilleure solution possible.

Et ici, le Deep Learning entre en scène dans toute sa splendeur.

Les réseaux de neurones profonds sont un outil puissant qui, théoriquement, peut approximer une fonction arbitrairement complexe, c'est-à-dire amener un espace de données d'entrée X dans un espace de données de sortie Y. Dans le contexte de notre tâche, ce seront respectivement du texte et de l'audio avec de la parole.

Prétraitement des données


Pour commencer, nous déterminerons ce que nous avons en entrée et ce que nous voulons obtenir en sortie.

L'entrée sera du texte et la sortie sera un spectrogramme à la craie . Il s'agit d'une représentation de bas niveau obtenue en appliquant la transformée de Fourier rapide à un signal audio discret. Il convient de noter tout de suite que les spectrogrammes ainsi obtenus doivent encore être normalisés en compressant la plage dynamique. Cela vous permet de réduire la relation naturelle entre le son le plus fort et le plus silencieux de l'enregistrement. Dans nos expériences, l'utilisation de spectrogrammes réduits à la gamme [-4; 4] s'est avérée la meilleure.


Figure 1: Spectrogramme à la craie du signal audio de la parole réduit à la plage [-4; 4].

En tant qu'ensemble de données d' entraînement, nous avons choisi l' ensemble de données LJSpeech , qui contient 13 100 pistes audio pendant 2 à 10 secondes. et un fichier avec du texte correspondant à la parole en anglais enregistré sur audio.

Le son utilisant les transformations ci-dessus est codé en spectrogrammes à la craie. Le texte est symbolisé et transformé.

en une séquence d'entiers. Je dois tout de suite souligner que les textes sont normalisés: tous les nombres sont écrits verbalement, et les abréviations possibles sont déchiffrées, par exemple: «Mme Robinson »-« Missis Robinson ».

Ainsi, après le prétraitement, nous obtenons des ensembles de tableaux numpy de séquences numériques et de spectrogrammes de craie enregistrés dans des fichiers npy sur disque.

Pour qu'au stade de l'entraînement, toutes les dimensions des tenseurs de patch coïncident, nous ajouterons des rembourrages à de courtes séquences. Pour les séquences sous forme de textes, celles-ci seront réservées au remplissage 0, et aux spectrogrammes, trames dont les valeurs sont légèrement inférieures aux spectrogrammes minimaux déterminés par nos soins. Ceci est recommandé pour isoler ces rembourrages, les séparer du bruit et du silence.

Nous avons maintenant des données représentant du texte et de l'audio qui peuvent être traitées par un réseau neuronal artificiel. Examinons l'architecture du réseau de prédiction de fonctionnalités, qui, sous le nom de l'élément central de l'ensemble du système de synthèse, sera appelé Tacotron2.

L'architecture


Tacotron 2 n'est pas un réseau, mais deux: réseau de prédiction de fonctionnalités et NNet -vocoder WaveNet . L'article original, ainsi que notre propre vision du travail effectué, nous permet de considérer Feature prediction net comme le premier violon, tandis que le vocodeur WaveNet joue le rôle d'un système périphérique.

Tacotron2 est une architecture séquence à séquence . Il se compose d'un encodeur (encodeur), qui crée une représentation interne du signal d'entrée (jetons de symbole), et d'un décodeur (décodeur), qui transforme cette représentation en un spectrogramme à la craie. Un autre élément extrêmement important du réseau est le soi-disant PostNet , qui est conçu pour améliorer le spectrogramme généré par le décodeur.


Figure 2: Architecture du réseau Tacotron 2.

Examinons plus en détail les blocs de réseau et leurs modules.

La première couche d' encodeur est la couche d'intégration. Basé sur une séquence de nombres naturels représentant des caractères, il crée des vecteurs multidimensionnels (512 dimensions).

Ensuite, les vecteurs d'intégration sont introduits dans un bloc de trois couches convolutives unidimensionnelles. Chaque couche comprend 512 filtres de longueur 5. Cette valeur est une bonne taille de filtre dans ce contexte, car elle capture un certain caractère, ainsi que ses deux voisins précédents et deux suivants. Chaque couche convolutionnelle est suivie d'une normalisation en mini-lots et d'une activation ReLU.

Les tenseurs obtenus après le bloc convolutionnel sont appliqués à des couches LSTM bidirectionnelles, 256 neurones chacune. Les résultats avant et arrière sont concaténés.

Le décodeur a une architecture récurrente, c'est-à-dire qu'à chaque étape suivante, la sortie de l'étape précédente est utilisée. Ici, ils seront une image du spectrogramme. Un autre élément important, sinon essentiel, de ce système est le mécanisme de l' attention douce (entraînée) - une technique relativement nouvelle qui gagne de plus en plus en popularité. A chaque étape du décodeur, attention à former un vecteur de contexte et à mettre à jour le poids d'attention utilise:

  • la projection de l'état caché précédent du réseau RNN du décodeur sur une couche entièrement connectée,
  • projection de la sortie du codeur sur une couche entièrement connectée,
  • ainsi que des poids d'attention supplémentaires (accumulés à chaque pas de temps du décodeur).

L'idée d'attention doit être comprise comme suit: «quelle partie des données du codeur doit être utilisée à l'étape actuelle du décodeur».


Figure 3: Schéma du mécanisme d'attention.

À chaque étape du décodeur, le vecteur de contexte C i est calculé (indiqué dans la figure ci-dessus comme "sorties codeur assisté"), qui est un produit de la sortie codeur ( h ) et des pondérations d'attention ( α ):



α ij sont les poids d'attention calculés par la formule:



e ij est la soi-disant "énergie", dont la formule de calcul dépend du type de mécanisme d'attention que vous utilisez (dans notre cas, ce sera un type hybride, utilisant à la fois l'attention basée sur la localisation et l'attention basée sur le contenu). L'énergie est calculée par la formule:

e ij = v aT tanh (Ws i-1 + Vh j + Uf i, j + b)

où:
  • s i-1 - état caché précédent du réseau LSTM du décodeur,
  • α i-1 - poids d'attention précédents,
  • h j est le jème état caché de l'encodeur,
  • W , V , U , v a et b sont des paramètres d'apprentissage,
  • f i, j - signes de localisation calculés par la formule:

    f i = F * α i-1

    F est l'opération de convolution.


Pour bien comprendre ce qui se passe, nous ajoutons que certains des modules décrits ci-dessous supposent l'utilisation des informations de l'étape précédente du décodeur. Mais s'il s'agit de la première étape, les informations seront des tenseurs de valeurs nulles, ce qui est une pratique courante lors de la création de structures de récurrence.

Considérons maintenant l'algorithme d'opération .

Tout d'abord, la sortie du décodeur du pas de temps précédent est introduite dans un petit module PreNet, qui est un empilement de deux couches entièrement connectées de 256 neurones, en alternance avec des couches de décrochage avec un taux de 0,5. Une caractéristique distinctive de ce module est que le décrochage y est utilisé non seulement au stade de la formation du modèle, mais également au stade de la sortie.

La sortie PreNet en concaténation avec le vecteur de contexte obtenu à la suite du mécanisme d'attention est envoyée à l'entrée dans un réseau LSTM unidirectionnel à deux couches, 1024 neurones dans chaque couche.

Ensuite, la concaténation de la sortie des couches LSTM avec le même vecteur de contexte (et éventuellement différent) est introduite dans une couche entièrement connectée avec 80 neurones, ce qui correspond au nombre de canaux du spectrogramme. Cette dernière couche du décodeur forme le spectrogramme prévu trame par trame. Et déjà sa sortie est fournie en entrée au prochain pas de temps du décodeur dans PreNet.

Pourquoi avons-nous mentionné dans le paragraphe précédent que le vecteur de contexte peut déjà être différent? Une des approches possibles est de recalculer le vecteur de contexte après que l'état latent du réseau LSTM est obtenu à cette étape. Cependant, dans nos expériences, cette approche ne s'est pas justifiée.

En plus de la projection sur une couche entièrement connectée à 80 neurones, la concaténation de la sortie des couches LSTM avec un vecteur de contexte est introduite dans une couche entièrement connectée avec un neurone, suivie d'une activation sigmoïde - il s'agit d'une couche de «prédiction de jeton d'arrêt». Il prédit la probabilité que la trame créée à cette étape du décodeur soit définitive. Cette couche est conçue pour générer un spectrogramme de longueur non fixe mais arbitraire à l'étape de sortie du modèle. C'est-à-dire qu'au niveau de la sortie, cet élément détermine le nombre de pas du décodeur. Il peut être considéré comme un classificateur binaire.

La sortie du décodeur de toutes ses étapes sera le spectrogramme prévu. Mais ce n'est pas tout. Pour améliorer la qualité du spectrogramme, il est transmis via le module PostNet, qui est une pile de cinq couches convolutionnelles unidimensionnelles avec 512 filtres dans chacune et avec une taille de filtre de 5. La normalisation des lots et l'activation tangente suivent chaque couche (sauf la dernière). Pour revenir à la dimension du spectrogramme, nous passons les données de sortie post-net à travers une couche entièrement connectée avec 80 neurones et ajoutons les données reçues avec le résultat initial du décodeur. Nous obtenons le spectrogramme à la craie généré à partir du texte. Bénéfice

Tous les modules convolutifs sont régularisés avec des couches de décrochage avec un taux de 0,5, et des couches de récurrence avec la nouvelle méthode Zoneout avec un taux de 0,1. C'est assez simple: au lieu d'appliquer l'état latent et l'état de cellule obtenus à l'étape en cours à l'étape de temps suivante du réseau LSTM, nous remplaçons une partie des données par les valeurs de l'étape précédente. Cela se fait à la fois au stade de la formation et au stade du retrait. Dans ce cas, seul l'état caché (qui est passé à l'étape LSTM suivante) est exposé à la méthode Zoneout à chaque étape, tandis que la sortie de la cellule LSTM à l'étape actuelle reste inchangée.

Nous avons choisi PyTorch comme cadre d'apprentissage en profondeur. Bien qu'au moment de la mise en œuvre du réseau, il était dans un état de pré-version, mais c'était déjà un outil très puissant pour la construction et la formation de réseaux de neurones artificiels. Dans notre travail, nous utilisons d'autres frameworks tels que TensorFlow et Keras. Cependant, ce dernier a été rejeté en raison de la nécessité d'implémenter des structures personnalisées non standard, et si nous comparons TensorFlow et PyTorch, alors lors de l'utilisation du second, il n'y a aucun sentiment que le modèle est arraché au langage Python. Cependant, nous ne nous engageons pas à affirmer que l'un d'eux est meilleur et l'autre pire. L'utilisation d'un cadre particulier peut dépendre de divers facteurs.

Le réseau est entraîné par la méthode de propagation arrière. ADAM est utilisé comme optimiseur, l'erreur quadratique moyenne avant et après PostNet, ainsi que l'entropie croisée binaire sur les valeurs réelles et prévues de la couche Stop Token Prediction, sont utilisées comme fonctions d'erreur. L'erreur résultante est une simple somme de ces trois.

Le modèle a été formé sur un seul GPU GeForce 1080Ti avec 11 Go de mémoire.

Visualisation


Lorsque vous travaillez avec un si grand modèle, il est important de voir comment se déroule le processus d'apprentissage. Et ici, TensorBoard est devenu un outil pratique. Nous avons suivi la valeur de l'erreur dans les itérations de formation et de validation. De plus, nous avons affiché les spectrogrammes cibles, les spectrogrammes prédits au stade de la formation, les spectrogrammes prédits au stade de la validation et l'alignement, qui est un poids d'attention cumulé cumulé à toutes les étapes de la formation.

Il est possible qu'au début votre attention ne soit pas trop informative:


Figure 4: Un exemple d'échelles d'attention mal formées.

Mais après que tous vos modules commencent à fonctionner comme une montre suisse, vous obtiendrez enfin quelque chose comme ceci:


Figure 5: Exemple d'échelles d'attention correctement formées.

Que signifie ce tableau? A chaque étape du décodeur, nous essayons de décoder une trame du spectrogramme. Cependant, il n'est pas clair quelles informations le codeur doit utiliser à chaque étape du décodeur. On peut supposer que cette correspondance sera directe. Par exemple, si nous avons une séquence de texte d'entrée de 200 caractères et un spectrogramme correspondant de 800 images, alors il y aura 4 images pour chaque caractère. Cependant, vous devez admettre que la parole générée sur la base d'un tel spectrogramme serait complètement dépourvue de naturel. Nous prononçons certains mots plus rapidement, certains plus lentement, quelque part nous nous arrêtons, mais quelque part nous ne le faisons pas. Et considérer tous les contextes possibles n'est pas possible. C'est pourquoi l'attention est un élément clé de tout le système: elle établit la correspondance entre l'étape du décodeur et les informations du codeur afin d'obtenir les informations nécessaires pour générer une trame spécifique. Et plus les pondérations d'attention sont élevées, plus «une attention doit être portée» à la partie correspondante des données du codeur lors de la génération de la trame du spectrogramme.

Au stade de la formation, il sera également utile de générer de l'audio, et pas seulement d'évaluer visuellement la qualité des spectrogrammes et l'attention. Cependant, ceux qui ont travaillé avec WaveNet conviendront que son utilisation comme vocodeur au stade de la formation serait un luxe inacceptable en termes de temps. Par conséquent, il est recommandé d'utiliser l'algorithme Griffin-Lim , qui permet une reconstruction partielle du signal après des transformations de Fourier rapides. Pourquoi partiellement? Le fait est que lorsque nous convertissons le signal en spectrogrammes, nous perdons des informations de phase. Cependant, la qualité de l'audio ainsi obtenue sera tout à fait suffisante pour comprendre dans quelle direction vous vous déplacez.

Leçons apprises


Ici, nous partagerons quelques réflexions sur la construction du processus de développement, en les soumettant sous forme de conseils. Certains d'entre eux sont assez généraux, d'autres sont plus spécifiques.

À propos de l'organisation du workflow :

  • Utilisez le système de contrôle de version, décrivez clairement et clairement tous les changements. Cela peut sembler une recommandation évidente, mais quand même. Lors de la recherche de l'architecture optimale, des changements se produisent constamment. Et après avoir reçu un résultat intermédiaire satisfaisant, assurez-vous de vous faire un point de contrôle afin que vous puissiez effectuer les modifications suivantes en toute sécurité.
  • De notre point de vue, dans de telles architectures, il faut respecter les principes d'encapsulation: une classe - un module Python. Cette approche n'est pas courante dans les tâches ML, mais elle vous aidera à structurer votre code et à accélérer le débogage et le développement. Dans le code et dans votre vision de l'architecture, divisez-le en blocs, blocs en modules et modules en couches. Si le module a du code qui remplit un rôle spécifique, combinez-le dans une méthode de classe de module. Ce sont des vérités courantes, mais nous n'avons pas été trop paresseux pour en parler à nouveau.
  • Fournissez de la documentation aux classes de style numpy . Cela simplifiera considérablement le travail pour vous et vos collègues qui liront votre code.
  • Dessinez toujours l'architecture de votre modèle. D'une part, il vous aidera à lui donner un sens, et d'autre part, une vue latérale de l'architecture et des hyperparamètres du modèle vous permettra d'identifier rapidement les inexactitudes de votre approche.
  • Mieux vaut travailler en équipe. Si vous travaillez seul, rassemblez toujours des collègues et discutez de votre travail. Au minimum, ils peuvent vous poser une question qui vous amènera à quelques réflexions, mais au maximum ils pointeront vers une inexactitude spécifique qui ne vous permettra pas de réussir la formation du modèle.
  • Une autre astuce utile est déjà associée au prétraitement des données. Supposons que vous décidiez de tester une hypothèse et d'apporter les modifications appropriées au modèle. Mais recommencer l'entraînement, surtout avant le week-end, sera risqué. L'approche peut être initialement erronée et vous perdrez du temps. Que faire alors? Augmentez la taille de la fenêtre Transformation de Fourier rapide. Le paramètre par défaut est 1024; l'augmenter de 4, voire 8 fois. Cela «pressera» les spectrogrammes dans le nombre approprié de fois et accélérera considérablement l'apprentissage. Le son récupéré aura une qualité inférieure, mais ce n'est pas votre tâche maintenant? En 2-3 heures, vous pouvez déjà obtenir l'alignement («alignement» des échelles d'attention, comme le montre la figure ci-dessus), cela témoignera de l'exactitude architecturale de l'approche et elle peut déjà être testée sur des données volumineuses.

Modèles de construction et de formation :

  • Nous avons suggéré que si les lots n'étaient pas formés de manière aléatoire, mais en fonction de leur longueur, ils accéléreraient le processus d'apprentissage du modèle et amélioreraient les spectrogrammes générés. L'hypothèse logique, qui est basée sur l'hypothèse que plus un signal utile (et non de remplissage) est envoyé au réseau d'entraînement, mieux c'est. Cependant, cette approche ne se justifiait pas; dans nos expériences, nous n'avons pas pu former le réseau de cette manière. Cela est probablement dû à la perte d'aléatoire dans la sélection des instances pour la formation.
  • Utilisez des algorithmes d'initialisation des paramètres réseau modernes avec des états initiaux optimisés. Par exemple, dans nos expériences, nous avons utilisé l'initialisation du poids uniforme Xavier. Si dans votre module vous devez utiliser la normalisation par mini-batch et une fonction d'activation, ils doivent alterner les uns avec les autres dans cet ordre. En effet, si nous appliquons, par exemple, l'activation ReLU, nous perdons immédiatement tout le signal négatif qui devrait être impliqué dans le processus de normalisation des données d'un lot particulier.
  • À partir d'une étape d'apprentissage spécifique, utilisez un taux d'apprentissage dynamique. Cela aide vraiment à réduire la valeur d'erreur et à augmenter la qualité des spectrogrammes générés.
  • Après avoir créé le modèle et tenté sans succès de le former sur des lots à partir de l'ensemble de données, il sera utile d'essayer de le recycler sur un lot. , alignment, ( ). , , .

    . . , – . , . , .
  • RNN- . . , . ? LSTM- -.
  • , LSTM-, « »: « , LSTM-. «» bf. , , , LSTM- ft 1/2. , : , «» 1/2, . bf , 1 2: ft ».
  • seq2seq- . — , . ? , ( ).
  • Maintenant une recommandation spécifique pour le framework PyTorch. Bien que la couche LSTM dans le décodeur soit essentiellement sa propre cellule LSTM, qui reçoit des informations pour un seul élément de la séquence à chaque étape du décodeur, il est recommandé d'utiliser la classe torch.nn.LSTM plutôt que torch.nn.LSTMCell . La raison en est que le backend LSTM est implémenté dans la bibliothèque CUDNN en C et LSTMCell en Python. Cette astuce vous permettra d'augmenter considérablement la vitesse du système.

Et à la fin de l'article, nous partagerons des exemples de génération de discours à partir de textes qui n'étaient pas contenus dans l'ensemble de formation.

Source: https://habr.com/ru/post/fr436312/


All Articles