Par Mahir Uysal

Comment construire une intelligence artificielle conversationnelle à la pointe de la technologie avec l'apprentissage par transfert

Il y a quelques années, la création d'un chatbot (comme elle l'était à l'époque) pouvait prendre des mois 🗓, de l'élaboration des règles à la rédaction de milliers de réponses couvrant certains sujets de conversation.

Avec les récents progrès en apprentissage en profondeur de la PNL, nous pouvons maintenant nous débarrasser de ce petit travail et construire une AI conversationnelle beaucoup plus puissante en quelques heures à peine, comme vous le verrez dans ce tutoriel.

Nous avons mis en place une démonstration exécutant le modèle préconçu que nous construirons ensemble dans ce tutoriel à l'adresse convai.huggingface.co. Assurez-vous de vérifier!
La démonstration en ligne du modèle de pré-entraînement que nous allons construire dans ce tutoriel à l'adresse convai.huggingface.co. Les «suggestions» (en bas) sont également optimisées par le modèle qui se met à la place de l'utilisateur.

Voici ce que nous allons apprendre et avec lequel nous allons jouer aujourd'hui:

  • Comment utiliser Transfer Learning pour créer un agent de dialogue ultramoderne basé sur les modèles de langage OpenAI GPT et GPT-2 Transformer,
  • Comment vous pouvez reproduire le modèle que nous avons utilisé dans le concours de dialogue ConvAI2 de NeurIPS 2018, qui a remporté le titre de métrique automatique,
  • Comment nous avons distillé 3k + code de lignes de compétition en moins de 250 lignes de code de formation commenté (avec options distribuées et FP16!), Et
  • Comment former ce modèle pour moins de 20 USD sur une instance cloud, ou simplement utiliser notre modèle pré-formé open-source.
Avec cet article, nous avons publié une base de code propre et commentée avec un modèle pré-entraîné! Vérifiez le dépôt Github ici

L'histoire de ce billet a commencé il y a quelques mois à Montréal où Hugging Face a terminé premier de la piste automatique du Conversational Intelligence Challenge 2 (ConvAI2), un concours de dialogue organisé au NeurIPS 2018.

Notre sauce secrète était un modèle de langage pré-formé à grande échelle, OpenAI GPT, associé à une technique de réglage précis de Transfer Learning.

Avec la rapidité de la concurrence, nous avons fini avec plus de 3 000 lignes de code explorant de nombreuses variantes de formation et d’architecture.

De toute évidence, la publication d'un tel code brut n'aurait pas été juste.

Entre-temps, nous avions commencé à créer et à ouvrir un référentiel de modèles d'apprentissage par transfert appelé pytorch-pretrained-BERT, qui avait été téléchargé plus de 150 000 fois et proposé de mettre en œuvre des modèles de langage à grande échelle comme OpenAI GPT et son successeur. GPT-2

Il y a quelques semaines, j'ai décidé de reformuler notre code de concurrence dans une base de code propre et commentée, construite sur le dessus de pytorch-pretrained-BERT, et d'écrire un article de blog détaillé expliquant notre approche et notre code.

Nous y voilà donc, plongons dans

Une IA avec une personnalité

Nous allons construire une IA conversationnelle avec un personnage.

Notre agent de dialogue disposera d'une base de connaissances pour stocker quelques phrases décrivant son identité (personnage) et un historique de dialogue. Lorsqu'un nouvel énoncé sera reçu d'un utilisateur, l'agent combinera le contenu de cette base de connaissances avec l'énoncé nouvellement reçu pour générer une réponse.

Voici le schéma général:

Lorsque nous formons des agents de dialogue basés sur un apprentissage en profondeur, nous faisons face à un problème majeur:

Les jeux de données de dialogue sont petits et il est difficile d’en apprendre suffisamment sur la langue et le bon sens pour pouvoir générer des réponses fluides et pertinentes.

Certaines approches tentent de résoudre ce problème en filtrant la sortie du modèle pour améliorer la qualité à l'aide de la recherche intelligente. Ici, nous emprunterons une autre voie qui a suscité un vif intérêt au cours des derniers mois: l’apprentissage par transfert.

L'idée derrière cette approche est assez simple:

  • commencez par pré-former un modèle de langage sur un très grand corpus de texte pour pouvoir générer de longues portions de texte cohérent et contigu,
  • peaufinez ce modèle de langage pour l’adapter à notre tâche finale: le dialogue.

La formation préalable d’un modèle linguistique est une opération coûteuse; il est donc généralement préférable de partir d’un modèle qui a déjà fait l’objet d’une formation préalable et d’un logiciel libre.

Quel serait un bon modèle de pré-entraînement pour notre objectif?

Le plus gros est le mieux, mais nous avons également besoin d'un modèle capable de générer du texte. Le modèle de PNL pré-formé le plus utilisé, BERT, ne s'entraîne que sur des phrases complètes et n'est pas en mesure de compléter des phrases inachevées. Deux autres modèles, à code source ouvert par OpenAI, sont plus intéressants pour notre cas d'utilisation: GPT et GPT-2.

Voyons-les rapidement

Modèles OpenAI GPT et GPT-2

En 2018 et 2019, Alec Radford, Jeffrey Wu et leurs collègues d'OpenAI ont ouvert deux modèles linguistiques à source ouverte formés sur une très grande quantité de données: GPT et GPT-2 (où GPT signifie Generative Pretrained Transformer).

Un décodeur / causal Transformer assiste au contexte de gauche pour générer les mots suivants

GPT et GPT-2 sont deux modèles de langage très similaires basés sur Transformer. Ces modèles sont appelés modèles de décodeur ou causaux, ce qui signifie qu'ils utilisent le contexte de gauche pour prédire le mot suivant (voir la figure de gauche).

De nombreux articles et articles de blogs décrivent les modèles Transformers et expliquent comment ils utilisent les mécanismes d’attention pour traiter les entrées séquentielles afin que je ne passe pas de temps à les présenter en détail. Quelques astuces si vous n’êtes pas familier avec ces modèles: les diapositives EMNLP d’Emma Strubell sont mes préférées et «Illustrated Transformer» de Jay Alammar est une introduction très détaillée.

Pour notre propos, un modèle de langage sera simplement un modèle qui prend en entrée une séquence de jetons et génère une distribution de probabilité sur le vocabulaire du prochain jeton suivant la séquence en entrée. Les modèles de langage sont généralement formés de manière parallèle, comme illustré sur la figure ci-dessus, en prédisant le jeton suivant chaque jeton dans une longue séquence d'entrée.

La formation préalable de ces modèles sur un corpus volumineux est une opération coûteuse. Nous commencerons donc par un modèle et un tokenizer préalablement formés par OpenAI. Le tokenizer se chargera de scinder une chaîne d'entrée en jetons (mots / sous-mots) et de convertir ces jetons en indices numériques corrects du vocabulaire du modèle.

Dans pytorch-pretrained-BERT, le modèle OpenAI GPT et son tokenizer peuvent être facilement créés et chargés à partir du point de contrôle pré-entraîné comme ceci:

Vous avez probablement remarqué que nous avons chargé un modèle appelé OpenAI GPT Double Heads Model, qui semble un peu plus complexe que le modèle de langage dont nous venons de parler et vous avez raison!

En effet, nous devons adapter notre modèle au dialogue. Voyons comment cela se passe!

Adapter un modèle de langage à une tâche de dialogue

Notre modèle de langue est formé avec une seule entrée: une séquence de mots.

Mais comme nous l'avons vu précédemment, dans un paramètre de dialogue, notre modèle devra utiliser plusieurs types de contextes pour générer une séquence de sortie:

  • une ou plusieurs personas,
  • l'historique du dialogue avec au moins le dernier mot de l'utilisateur,
  • les jetons de la séquence de sortie qui ont déjà été générés puisque nous générons la séquence de sortie mot par mot.
Comment pouvons-nous créer une entrée pour notre modèle à partir de ces divers contextes?

Une réponse simple consiste simplement à concaténer les segments de contexte en une seule séquence, en plaçant la réponse à la fin. Nous pouvons alors générer une complétion du jeton de réponse par jeton en continuant la séquence:

Séquence d'entrée: concaténation de persona (bleu), d'histoire (rose) et de réponse (vert) avec des délimiteurs (rose pâle). Ici, nous générons le mot «vous» pour compléter la réponse.

Il y a deux problèmes avec cette installation simple:

  • Notre transformateur est daltonien! Les jetons délimiteurs ne lui donnent qu'une idée faible du segment auquel chaque mot appartient. Par exemple, le mot «NYC» est indiqué en bleu (persona) dans notre illustration, mais notre modèle aura du mal à extraire cette information des seuls délimiteurs: nous devrions ajouter plus d'informations sur les segments.
  • Notre transformateur est aveugle en position! L'attention est un produit scalaire symétrique, nous devons donc ajouter des informations de position pour chaque jeton.

Un moyen simple d'ajouter ces informations consiste à créer trois séquences d'entrée parallèles pour le mot, la position et les segments, puis à les fusionner en une seule séquence, en sommant trois types d'intégration: les incorporations de mots, de positions et de segments:

Faire la somme de trois types d'entrées imbriquées indiquant les mots (gris), la position (dégradé) et les segments (bleu / rose / vert)

Comment mettons-nous cela en œuvre?

Premièrement, nous ajouterons des jetons spéciaux à notre vocabulaire pour les délimiteurs et les indicateurs de segment. Ces jetons ne faisant pas partie de la pré-formation de notre modèle, nous devrons donc créer et former de nouveaux embarquements pour eux.

Ajouter des jetons spéciaux et de nouvelles intégrations au vocabulaire / modèle est assez simple avec les classes pytorch-pretrained-BERT. Ajoutons cinq jetons spéciaux au vocabulaire de notre tokenizer et à l’intégration du modèle:

Ces méthodes de jetons spéciaux ajoutent respectivement nos cinq jetons spéciaux au vocabulaire du tokéniseur et créent cinq incorporations supplémentaires dans le modèle.

Nous avons maintenant tout ce dont nous avons besoin pour construire notre séquence d’entrée à partir des contextes de personnalité, d’histoire et de début de réponse. Voici un exemple simple:

Losses Pertes multi-tâches

Nous avons maintenant initialisé notre modèle de pré-entraînement et construit nos entrées d’entraînement, il ne reste plus qu’à choisir une perte à optimiser lors de l’ajustement.

Nous utiliserons une modélisation multilingue de pertes combinant plusieurs pertes avec un objectif de prédiction de phrase suivante.
L'objectif de prédiction de la phrase suivante fait partie de la formation préalable du BERT. Elle consiste à échantillonner au hasard des éléments de distraction de l'ensemble de données et à former le modèle afin de déterminer si une séquence d'entrée se termine par une réponse en or ou par un élément de distraction. Il entraîne le modèle à examiner les segments globaux en plus du contexte local.

Vous voyez maintenant pourquoi nous avons chargé un modèle à «double tête». Une tête calculera les prédictions de modélisation du langage tandis que l'autre prédira les étiquettes de classification de la phrase suivante. Voyons comment les pertes sont calculées:

Objectif de formation multitâche - le modèle est fourni avec deux têtes pour la prédiction de modélisation de langage (orange) et la classification de phrase suivante (bleu)

La perte totale sera la somme pondérée de la perte de modélisation du langage et de la perte de prédiction de la phrase suivante, calculées comme suit:

  • Modélisation du langage: nous projetons l'état caché sur la matrice d'incorporation des mots pour obtenir des logits et appliquons une perte d'entropie croisée sur la partie de la cible correspondant à la réponse en or (étiquettes vertes sur la figure ci-dessus).
  • Prédiction de phrase suivante: nous passons l'état caché du dernier jeton (le jeton de fin de séquence) à travers une couche linéaire pour obtenir un score et appliquons une perte d'entropie croisée pour classer correctement une réponse en or parmi les distracteurs.

Voyons comment nous pouvons coder ceci:

Nous avons maintenant toutes les entrées requises par notre modèle et nous pouvons exécuter un passage en aval du modèle pour obtenir les deux pertes et la perte totale (sous forme de somme pondérée):

Nous sommes prêts à commencer la formation

Formation sur un jeu de données de dialogue

Le concours ConvAI2 a utilisé un jeu de données intéressant publié par Facebook l'année dernière: PERSONA-CHAT.

C’est un jeu de données de dialogue assez volumineux (dialogues de 10 000 dialogues) qui a été créé par le crowdsourcing de phrases de personnalité et en demandant à des ouvriers de la paire de bavarder en bavardant tout en jouant le rôle d’un personnage donné (un exemple est donné à la figure de gauche).

Ce jeu de données est disponible en format texte brut tokenisé dans la jolie bibliothèque ParlAI de Facebook. Pour vous amorcer, nous avons également téléchargé une version au format JSON que vous pouvez télécharger et convertir en token à l’aide de tokenizer de GPT, comme suit:

La version JSON de PERSONA-CHAT donne un accès rapide à toutes les entrées pertinentes pour la formation de notre modèle en tant que dictionnaire de listes imbriqué:

Organisation de la version JSON de PERSONA-CHAT

Grâce à l’impressionnant framework PyTorch ignite et à la nouvelle API de précision mixte automatique (FP16 / 32) fournie par l’apex de NVIDIA, nous avons pu distiller nos codes de concurrence + 3k en moins de 250 codes de formation avec options distribuées et FP16!

Nous avons couvert les parties essentielles du code dans les grandes lignes ci-dessus, je vous laisse donc lire le code commenté pour voir comment tout cela s’agence.

Le code de formation (train.py) est ici

La formation de ce modèle sur une instance AWS avec 8 GPU V100 prend moins d’une heure (actuellement moins de 25 USD sur la plus grande instance AWS p3.16xlarge) et donne des résultats proches de la SOTA obtenue lors de la compétition ConvAI2 avec Hits @ 1 sur 79, perplexité. de 20,5 et F1 de 16,5.

Quelques différences expliquent les scores légèrement inférieurs par rapport à notre modèle de concurrence. Elles sont détaillées dans le readme du dépôt de code ici et consistent principalement à peaufiner les intégrations de position et à utiliser un décodeur différent.

Parler avec le modèle - le décodeur

La chose étonnante à propos des modèles de dialogue est que vous pouvez parler avec eux

Pour interagir avec notre modèle, nous devons ajouter une chose: un décodeur qui générera des séquences complètes à partir des prédictions de jetons suivantes de notre modèle.

Maintenant, il y a eu des développements très intéressants dans les décodeurs ces derniers mois et je voulais les présenter rapidement ici pour vous tenir au courant.

Les deux décodeurs les plus courants pour la génération de langage étaient autrefois le décodage gourmand et la recherche de faisceaux.

Générer une phrase mot à mot (source)

Le décodage gourmand est le moyen le plus simple de générer une phrase: à chaque pas de temps, nous sélectionnons le prochain jeton le plus probable en fonction du modèle jusqu'à atteindre les jetons de fin de séquence. Un risque lié au décodage glouton est qu’un jeton très probable puisse se cacher après un jeton à faible probabilité et être omis.

Beam-search essaye d'atténuer ce problème en conservant un faisceau de plusieurs séquences possibles que nous construisons mot à mot. À la fin du processus, nous sélectionnons la meilleure phrase parmi les faisceaux. Au cours des dernières années, la recherche de faisceau a été l'algorithme de décodage standard pour presque toutes les tâches de génération de langage, y compris le dialogue (voir le récent [1]).

Cependant, plusieurs développements se sont produits en 2018 / début 2019. Premièrement, il y avait de plus en plus de preuves que la recherche de faisceau était fortement sensible à la longueur des sorties et que les meilleurs résultats pouvaient être obtenus lorsque la longueur de sortie était prédite avant le décodage ([2, 3] à EMNLP 2018). Bien que cela soit logique pour les tâches à faible entropie telles que la traduction où la longueur de la séquence de sortie peut être prédite grossièrement à partir de l'entrée, cela semble arbitraire pour les tâches à forte entropie telles que la génération de dialogues et d'histoires dans lesquelles des sorties de longueurs très différentes sont généralement également valides.

En parallèle, au moins deux articles influents ([4, 5]) sur les tâches de génération à entropie élevée ont été publiés, dans lesquels le décodage en mode glouton / recherche de faisceaux était remplacé par un échantillonnage de la distribution de jetons suivante à chaque pas de temps. Ces documents utilisaient une variante d'échantillonnage appelée Top-k sampling dans laquelle le décodeur échantillonne uniquement les k-tokens les plus probables (k est un hyper-paramètre).

L'étude publiée récemment par Ari Holtzman et al. Constitue la dernière pierre de cette tendance récente dans les travaux. [6] ont montré que la distribution des mots dans les textes générés à l'aide de la recherche de faisceau et du décodage glouton est très différente de la distribution des mots dans les textes générés par l'homme. Clairement, la recherche de faisceau et le décodage glouton ne reproduisent pas certains aspects de la distribution des textes humains, comme cela a également été noté dans [7, 8] dans le contexte des systèmes de dialogue:

Gauche: probabilité attribuée aux jetons générés par des humains et recherche de faisceau à l'aide de GPT-2 (notez la forte variance dans le texte humain non reproduite par la recherche de faisceau). Droite: Distributions de N-grammes dans les textes générés par l'homme et par la machine (Notez la séparation complète entre les méthodes de recherche gourmande / faisceau et de décodage d'échantillonnage).

Actuellement, les deux candidats les plus prometteurs pour réussir le décodage glouton / recherche gloutonne sont les échantillons top k et noyau (ou top p). Le principe général de ces deux méthodes est d’échantillonner à partir de la distribution des jetons suivants après avoir filtré cette distribution afin de ne conserver que les k premiers p).

Voici comment nous pouvons décoder en utilisant l'échantillonnage top-k et / ou nucleus / top-p:

Nous sommes maintenant prêts à parler avec notre modèle

Le script interactif est ici (interact.py) et si vous ne voulez pas exécuter le script, vous pouvez également jouer avec notre démonstration en direct qui se trouve ici

Voici un exemple de dialogue:

Exemple d'utilisation des scripts interactifs avec les paramètres par défaut - Personnalité du bot: Je lis vingt livres par an. Je suis un cascadeur double comme mon deuxième emploi. Je ne mange que casher. J'ai été élevé dans une famille monoparentale.

Conclusion

Nous arrivons à la fin de cet article en vous expliquant comment construire une intelligence artificielle conversationnelle simple et ultramoderne à l'aide de l'apprentissage par transfert et d'un modèle de langage à grande échelle comme OpenAI GPT.

Comme nous l’avons appris à Hugging Face, mettre au point rapidement votre intelligence artificielle conversationnelle est la meilleure recette du succès; nous espérons donc que cela aidera certains d’entre vous à y parvenir!

Assurez-vous de consulter la démo et le code associés:

  • la démo en direct est ici et
  • le code open source et les modèles pré-entraînés sont ici.

Comme toujours, si vous avez aimé cet article, donnez-nous quelques pour nous le faire savoir et partager les nouvelles autour de vous!

Références:

[1] ^ Importance d'une stratégie de recherche dans la modélisation du dialogue neural par Ilya Kulikov, Alexander H. Miller, Kyunghyun Cho, Jason Weston (http://arxiv.org/abs/1811.00907)

[2] ^ Correction du biais de longueur dans la traduction automatique neurale par Kenton Murray, David Chiang (http://arxiv.org/abs/1808.10006).

[3] ^ Briser le fléau de la recherche sur les faisceaux: étude des méthodes de (ré) notation et des critères d'arrêt pour la traduction automatique neurale par Yilin Yang, Liang Huang, Mingbo Ma (https://arxiv.org/abs/1808.09582).

[4] ^ Génération d'histoires de neurones hiérarchiques par Angela Fan, Mike Lewis et Yann Dauphin (https://arxiv.org/abs/1805.04833)

[5] ^ Les modèles linguistiques sont des apprenants multitâches non supervisés d’Alec Radford, de Jeff Wu, de Rewon Child, de David Luan, de Dario Amodei et d’Ilya Sutskever (https://openai.com/blog/better-language-models/).

[6] ^ Le cas curieux de la dégénérescence neuronale du texte par Ari Holtzman, Jan Buys, Maxwell Forbes et Yejin Choi (https://arxiv.org/abs/1904.09751)

[7] ^ Récupérer et affiner: Modèles améliorés de génération de séquence pour dialogue par Jason Weston, Emily Dinan et Alexander H. Miller (https://arxiv.org/abs/1808.04776).

[8] ^ Le deuxième défi d'intelligence conversationnelle (ConvAI2) par Emily Dinan et al. (https://arxiv.org/abs/1902.00098)