Tutoriel : Comment former un modèle linguistique RoBERTa pour l'espagnol
SpanBERTa : Comment nous avons formé le modèle linguistique RoBERTa pour l'espagnol à partir de zéro
Publié à l'origine par Chris Tran, stagiaire en recherche sur l'apprentissage automatique chez Skim AI.
Introduction¶
Les méthodes d'auto-apprentissage avec des modèles de transformateurs ont atteint des performances de pointe pour la plupart des tâches de TAL. Cependant, comme leur entraînement est coûteux en termes de calcul, la plupart des modèles de transformateurs pré-entraînés actuellement disponibles ne concernent que l'anglais. C'est pourquoi, afin d'améliorer les performances des tâches de TAL dans le cadre de nos projets sur l'espagnol, mon équipe à la Skim AI a décidé de former un RoBERTa pour l'espagnol à partir de zéro et l'appeler SpanBERTa.
SpanBERTa a la même taille que RoBERTa-base. Nous avons suivi le schéma d'entraînement de RoBERTa pour entraîner le modèle sur 18 Go de OSCARen 8 jours en utilisant 4 GPU Tesla P100.
Dans cet article de blog, nous allons parcourir un processus de bout en bout pour former un modèle de langage de type BERT à partir de zéro en utilisant transformateurs
et les tokenizers
par Hugging Face. Il existe également un notebook Google Colab pour exécuter directement les codes de cet article. Vous pouvez également modifier le notebook en conséquence pour entraîner un modèle de type BERT pour d'autres langues ou l'affiner sur votre ensemble de données personnalisé.
Avant de poursuivre, je tiens à remercier chaleureusement l'équipe de Hugging Face pour avoir mis à la portée de tous des modèles de PNL à la pointe de la technologie.
Mise en place¶
1. Installer les dépendances¶
Dans [0] :
%pture !pip uninstall -y tensorflow !pip install transformers==2.8.0
2. Données¶
Nous avons pré-entraîné SpanBERTa sur OSCARen espagnol. La taille totale de l'ensemble de données est de 150 Go et nous avons utilisé une partie de 18 Go pour l'entraînement.
Dans cet exemple, pour des raisons de simplicité, nous utiliserons un ensemble de données de sous-titres de films espagnols provenant de OpenSubtitles. Cet ensemble de données a une taille de 5,4 Go et nous nous entraînerons sur un sous-ensemble de ~300 Mo.
Dans [0] :
import os # Télécharger et décompresser l'ensemble des données relatives aux substituts de films if not os.path.exists('data/dataset.txt' ;): !wget "https://object.pouta.csc.fi/OPUS-OpenSubtitles/v2016/mono/es.txt.gz" -O dataset.txt.gz !gzip -d dataset.txt.gz !mkdir data !mv dataset.txt data
--2020-04-06 15:53:04-- https://object.pouta.csc.fi/OPUS-OpenSubtitles/v2016/mono/es.txt.gz Résolution de object.pouta.csc.fi (object.pouta.csc.fi)... 86.50.254.18, 86.50.254.19 Connexion à object.pouta.csc.fi (object.pouta.csc.fi)|86.50.254.18|:443... connecté. Requête HTTP envoyée, réponse attendue... 200 OK Longueur : 1859673728 (1.7G) [application/gzip] Sauvegarde dans : 'dataset.txt.gz' dataset.txt.gz 100%[===================>] 1.73G 17.0MB/s en 1m 46s 2020-04-06 15:54:51 (16.8 MB/s) - 'dataset.txt.gz' sauvegardé [1859673728/1859673728]
Dans [0] :
# Nombre total de lignes et quelques lignes aléatoires !wc -l data/dataset.txt !shuf -n 5 data/dataset.txt
179287150 data/dataset.txt Sabes, pensé que tenías más pelotas que para enfrentarme a través de mi hermano. Les enfants de l'école sont des enfants de la rue, des enfants de la rue, des enfants de la rue, des enfants de la rue, des enfants de la rue, des enfants de la rue, des enfants de la rue, des enfants de la rue, des enfants de la rue et des enfants de la rue. Précédemment dans Blue Bloods : Y quiero que prometas que no habrá ningún trato con Daniel Stafford. Fue comiquísimo.
Dans [0] :
# Obtenir un sous-ensemble des 1 000 000 premières lignes pour la formation TRAIN_SIZE = 1000000 #@param {type : "integer"} !(head -n $TRAIN_SIZE data/dataset.txt) > data/train.txt
Dans [0] :
# Obtenir un sous-ensemble des 10 000 lignes suivantes pour validation VAL_SIZE = 10000 #@param {type : "integer"} !(sed -n {TRAIN_SIZE + 1},{TRAIN_SIZE + VAL_SIZE}p data/dataset.txt) > data/dev.txt
3. Former un tokeniseur¶
L'implémentation originale de l'ORET utilise un tokenizer WordPiece avec un vocabulaire de 32K unités de sous-mots. Cette méthode peut toutefois introduire des jetons "inconnus" lors du traitement de mots rares.
Dans cette implémentation, nous utilisons un tokenizer BPE au niveau de l'octet avec un vocabulaire de 50 265 unités de sous-mots (le même que RoBERTa-base). L'utilisation de BPE au niveau de l'octet permet d'apprendre un vocabulaire de sous-mots de taille modeste qui peut encoder n'importe quelle entrée sans obtenir de jetons "inconnus".
Parce que ByteLevelBPETokenizer
produit 2 fichiers ["vocab.json", "merges.txt"]
alors que BertWordPieceTokenizer
ne produit qu'un seul fichier vocab.txt
il y aura une erreur si nous utilisons BertWordPieceTokenizer
pour charger les sorties d'un tokenizer BPE.
Dans [0] :
%%time from tokenizers import ByteLevelBPETokenizer path = "data/train.txt" # Initialisation d'un tokenizer tokenizer = ByteLevelBPETokenizer() # Personnaliser l'entraînement tokenizer.train(files=chemin, taille_du_vocabulaire=50265, min_frequency=2, special_tokens=["", "", "", "", ""]) # Enregistrer les fichiers sur le disque !mkdir -p "models/roberta" tokenizer.save("models/roberta")
Temps CPU : utilisateur 1min 37s, sys : 1.02 s, total : 1min 38s Temps au mur : 1min 38s
Super rapide ! Il ne faut que 2 minutes pour s'entraîner sur 10 millions de lignes.
Modèle linguistique de formation à partir de zéro¶
1. Architecture du modèle¶
RoBERTa a exactement la même architecture que BERT. Les seules différences sont les suivantes :
- RoBERTa utilise un tokenizer BPE au niveau de l'octet avec un vocabulaire de sous-mots plus important (50k contre 32k).
- RoBERTa met en œuvre un masquage dynamique des mots et abandonne la tâche de prédiction de la phrase suivante.
- Les hyperparamètres de formation de RoBERTa.
D'autres configurations d'architecture peuvent être trouvées dans la documentation (RoBERTa, BERT).
Dans [0] :
import json config = { "architectures" : [ "RobertaForMaskedLM" ], "attention_probs_dropout_prob" : 0.1, "hidden_act" : "gelu", "hidden_dropout_prob" : 0.1, "hidden_size" : 768, "initializer_range" : 0.02, "taille_intermédiaire" : 3072, "layer_norm_eps" : 1e-05, "max_position_embeddings" : 514, "model_type" : "roberta", "num_attention_heads" : 12, "num_hidden_layers" : 12, "type_vocab_size" : 1, "vocab_size" : 50265 } avec open("models/roberta/config.json", 'w' ;) as fp : json.dump(config, fp) tokenizer_config = {"max_len" : 512} avec open("models/roberta/tokenizer_config.json", 'w' ;) as fp : json.dump(tokenizer_config, fp)
2. Hyperparamètres de formation¶
Hyperparam | BERT-base | RoBERTa-base |
---|---|---|
Longueur de la séquence | 128, 512 | 512 |
Taille du lot | 256 | 8K |
Taux d'apprentissage maximal | 1e-4 | 6e-4 |
Nombre maximal de pas | 1M | 500K |
Étapes de l'échauffement | 10K | 24K |
Diminution du poids | 0.01 | 0.01 |
Adam $epsilon$ | 1e-6 | 1e-6 |
Adam $beta_1$ | 0.9 | 0.9 |
Adam $beta_2$ | 0.999 | 0.98 |
Découpage des dégradés | 0.0 | 0.0 |
Notez que la taille du lot lors de l'apprentissage de RoBERTa est de 8000. Par conséquent, bien que la base RoBERTa ait été entraînée pour 500 000 étapes, son coût de calcul est 16 fois supérieur à celui de la base BERT. Dans le Papier RoBERTaIl est démontré que l'entraînement avec de grands lots améliore la perplexité pour l'objectif de modélisation du langage masqué, ainsi que la précision à la fin de la tâche. Il est possible d'obtenir une taille de lot plus importante en modifiant les paramètres suivants gradient_accumulation_steps
.
En raison des contraintes de calcul, nous avons suivi le schéma d'entraînement de BERT-base et entraîné notre modèle SpanBERTa en utilisant 4 GPU Tesla P100 pour 200K étapes en 8 jours.
3. Début de la formation¶
Nous allons former notre modèle à partir de zéro en utilisant run_language_modeling.py
un script fourni par Hugging Face, qui va prétraiter, tokeniser le corpus et entraîner le modèle sur Modélisation du langage masqué tâche. Le script est optimisé pour s'entraîner sur un seul grand corpus. Par conséquent, si votre ensemble de données est volumineux et que vous souhaitez le diviser pour l'entraîner de manière séquentielle, vous devrez modifier le script, ou être prêt à obtenir une machine monstre avec une mémoire élevée.
Dans [0] :
# Mise à jour du 22 avril 2020 : Hugging Face a mis à jour le script run_language_modeling.py. # Veuillez utiliser cette version avant la mise à jour. !wget -c https://raw.githubusercontent.com/chriskhanhtran/spanish-bert/master/run_language_modeling.py
--2020-04-24 02:28:21-- https://raw.githubusercontent.com/chriskhanhtran/spanish-bert/master/run_language_modeling.py Résolution de raw.githubusercontent.com (raw.githubusercontent.com)... 151.101.0.133, 151.101.64.133, 151.101.128.133, ... Connexion à raw.githubusercontent.com (raw.githubusercontent.com)|151.101.0.133|:443... connecté. Requête HTTP envoyée, en attente de réponse... 200 OK Longueur : 34328 (34K) [text/plain] Sauvegarde dans : 'run_language_modeling.py' run_language_modeli 100%[===================>] 33.52K --.-KB/s en 0.003s 2020-04-24 02:28:21 (10.1 MB/s) - 'run_language_modeling.py' sauvegardé [34328/34328]
Arguments importants
--line_by_line
Indique si les lignes de texte distinctes de l'ensemble de données doivent être traitées comme des séquences distinctes. Si chaque ligne de votre jeu de données est longue et contient ~512 tokens ou plus, vous devriez utiliser ce paramètre. Si chaque ligne est courte, le prétraitement de texte par défaut concatène toutes les lignes, les tokenise et découpe les sorties tokenisées en blocs de 512 tokens. Vous pouvez également diviser vos ensembles de données en petits morceaux et les prétraiter séparément. Le traitement de 3 Go de texte prendra environ 50 minutes avec le traitement par défaut.TextDataset
classe.--doit_continuer
Continuer ou non à partir du dernier point de contrôle dans output_dir.--nom_du_modèle_ou_chemin
Le point de contrôle du modèle pour l'initialisation des poids. Laissez None si vous voulez former un modèle à partir de zéro.--mlm
S'entraîner avec une perte de modélisation de la langue masquée au lieu d'une modélisation de la langue.--config_name, --tokenizer_name
Configuration préformée optionnelle et nom du tokenizer ou chemin d'accès s'il n'est pas le même que model_name_ou_path. Si les deux sont None, une nouvelle configuration est initialisée.--per_gpu_train_batch_size
Taille du lot par GPU/CPU pour l'entraînement. Choisissez le plus grand nombre de lots que vous pouvez faire tenir sur vos GPU. Une erreur s'affiche si la taille du lot est trop importante.--gradient_accumulation_steps
Nombre d'étapes de mise à jour à accumuler avant d'effectuer une passe de retour en arrière/mise à jour. Vous pouvez utiliser cette astuce pour augmenter la taille des lots. Par exemple, siper_gpu_train_batch_size = 16
etgradient_accumulation_steps = 4
la taille totale du lot de train sera de 64.--overwrite_output_dir
Remplacer le contenu du répertoire de sortie.--no_cuda, --fp16, --fp16_opt_level
Arguments en faveur de l'entraînement sur GPU/CPU.- Les autres arguments sont les chemins du modèle et les hyperparamètres d'apprentissage.
Il est fortement recommandé d'inclure le type de modèle (par exemple "roberta", "bert", "gpt2", etc.) dans le chemin d'accès au modèle car le script utilise la fonction AutoModels
pour deviner la configuration du modèle à l'aide d'une recherche de motifs sur le chemin fourni.
Dans [0] :
# Chemins d'accès au modèle MODEL_TYPE = "roberta" #@param ["roberta", "bert"] MODEL_DIR = "models/roberta" #@param {type : "string"} OUTPUT_DIR = "models/roberta/output" #@param {type : "string"} TRAIN_PATH = "data/train.txt" #@param {type : "string"} EVAL_PATH = "data/dev.txt" #@param {type : "string" } EVAL_PATH = "data/dev.txt" #@param {type : "string"}
Pour cet exemple, nous nous entraînerons pour 25 étapes seulement sur un GPU Tesla P4 fourni par Colab.
Dans [0] :
!nvidia-smi
Mon Apr 6 15:59:35 2020 +-----------------------------------------------------------------------------+ | NVIDIA-SMI 440.64.00 Version du pilote : 418.67 CUDA Version : 10.1 | |-------------------------------+----------------------+----------------------+ | Nom du GPU Persistance-M| Bus-Id Disp.A | Volatile Uncorr. ECC | Temp. ventilateur Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |===============================+======================+======================| | 0 Tesla P4 Off | 00000000:00:04.0 Off | 0 | | N/A 31C P8 7W / 75W | 0MiB / 7611MiB | 0% Default | +-------------------------------+----------------------+----------------------+ +-----------------------------------------------------------------------------+ | Processus : Mémoire GPU | GPU PID Type Nom du processus Utilisation |=============================================================================| | Aucun processus en cours n'a été trouvé. +-----------------------------------------------------------------------------+
Dans [0] :
# Ligne de commande cmd = """python run_language_modeling.py --output_dir {output_dir} --model_type {model_type} --mlm --config_name {nom_config} --tokenizer_name {nom_du_tokenizer} {ligne_par_ligne} {should_continue} {nom_du_modèle_ou_chemin} --train_data_file {train_path} --eval_data_file {eval_path} --do_train {do_eval} {evaluate_during_training} --overwrite_output_dir --block_size 512 --max_step 25 --warmup_steps 10 --learning_rate 5e-5 --per_gpu_train_batch_size 4 --gradient_accumulation_steps 4 --weight_decay 0.01 --adam_epsilon 1e-6 --max_grad_norm 100.0 --save_total_limit 10 --save_steps 10 --logging_steps 2 --seed 42 """
Dans [0] :
# Arguments en faveur d'une formation à partir de zéro. Je désactive l'option evaluate_during_training, # ligne_par_ligne, should_continue, et model_name_or_path. train_params = { "output_dir" : OUTPUT_DIR, "model_type" : MODEL_TYPE, "nom_config" : MODEL_DIR, "tokenizer_name" : MODEL_DIR, "train_path" : TRAIN_PATH, "eval_path" : TRAIN_PATH, "eval_path" : EVAL_PATH EVAL_PATH, "do_eval" : "--do_eval", "evaluate_during_training" : "", "line_by_line" : "", "should_continue" : "", "nom_du_modèle_ou_chemin" : "", }
Si vous vous entraînez sur une machine virtuelle, vous pouvez installer tensorboard pour surveiller le processus d'entraînement. Voici notre Tensorboard pour la formation de SpanBERTa.
pip install tensorboard==2.1.0 tensorboard dev upload --logdir runs
Après 200 000 étapes, la perte a atteint 1,8 et la perplexité 5,2.
Maintenant, commençons l'entraînement !
Dans [ ] :
!{cmd.format(**train_params)}
04/06/2020 15:59:55 - INFO - __main__ - Création de caractéristiques à partir du fichier data/roberta_cached_lm_510_train
04/06/2020 16:04:43 - INFO - __main__ - Enregistrement des caractéristiques dans le fichier en cache data/roberta_cached_lm_510_train.txt
04/06/2020 16:04:46 - INFO - __main__ - ***** Exécution de l'entraînement *****
04/06/2020 16:04:46 - INFO - __main__ - Nombre d'exemples = 165994
04/06/2020 16:04:46 - INFO - __main__ - Num Epochs = 1
04/06/2020 16:04:46 - INFO - __main__ - Taille instantanée du lot par GPU = 4
04/06/2020 16:04:46 - INFO - __main__ - Taille totale du lot de train (avec parallèle, distribué et accumulation) = 16
04/06/2020 16:04:46 - INFO - __main__ - Pas d'accumulation de gradient = 4
04/06/2020 16:04:46 - INFO - __main__ - Nombre total d'étapes d'optimisation = 25
Epoque : 0% 0/1 [00:00< ??, ?it/s]
Itération : 0% 0/41499 [00:00< ?, ?it/s]
Itération : 0% 1/41499 [00:01<13:18:02, 1.15s/it]
Itération : 0% 2/41499 [00:01<11:26:47, 1.01it/s]
Itération : 0% 3/41499 [00:02<10:10:30, 1.13it/s]
Itération : 0% 4/41499 [00:03<9:38:10, 1.20it/s]
Itération : 0% 5/41499 [00:03<8:52:44, 1.30it/s]
Itération : 0% 6/41499 [00:04<8:22:47, 1.38it/s]
Itération : 0% 7/41499 [00:04<8:00:55, 1.44it/s]
Itération : 0% 8/41499 [00:05<8:03:40, 1.43it/s]
Itération : 0% 9/41499 [00:06<7:46:57, 1.48it/s]
Itération : 0% 10/41499 [00:06<7:35:35, 1.52it/s]
Itération : 0% 11/41499 [00:07<7:28:29, 1.54it/s]
Itération : 0% 12/41499 [00:08<7:41:41, 1.50it/s]
Itération : 0% 13/41499 [00:08<7:34:28, 1.52it/s]
Itération : 0% 14/41499 [00:09<7:28:46, 1.54it/s]
Itération : 0% 15/41499 [00:10<7:23:29, 1.56it/s]
Itération : 0% 16/41499 [00:10<7:38:06, 1.51it/s]
Itération : 0% 17/41499 [00:11<7:29:13, 1.54it/s]
Itération : 0% 18/41499 [00:12<7:24:04, 1.56it/s]
Itération : 0% 19/41499 [00:12<7:21:59, 1.56it/s]
Itération : 0% 20/41499 [00:13<7:38:06, 1.51it/s]
04/06/2020 16:06:23 - INFO - __main__ - ***** Exécution de l'évaluation *****
04/06/2020 16:06:23 - INFO - __main__ - Nombre d'exemples = 156
04/06/2020 16:06:23 - INFO - __main__ - Taille du lot = 4
Évaluation : 100% 39/39 [00:08<00:00, 4.41it/s]
04/06/2020 16:06:32 - INFO - __main__ - ***** Résultats de l'évaluation *****
04/06/2020 16:06:32 - INFO - __main__ - perplexité = tenseur(6077.6812)
4. Prédire les mots masqués¶
Après avoir entraîné votre modèle linguistique, vous pouvez le télécharger et le partager avec la communauté. Nous avons téléchargé notre modèle SpanBERTa sur le serveur de Hugging Face. Avant d'évaluer le modèle sur des tâches en aval, voyons comment il a appris à remplir des mots masqués en fonction du contexte.
Dans [0] :
%pture %%time from transformers import pipeline fill_mask = pipeline( "fill-mask", model="chriskhanhtran/spanberta", tokenizer="chriskhanhtran/spanberta" )
Je choisis une phrase de l'article de Wikipédia sur le COVID-19.
La phrase originale est "Laver fréquemment les mains avec de l'eau et du savon," signifiant "Se laver fréquemment les mains avec de l'eau et du savon.“
Le mot masqué est "jabón" (savon) et les 5 premiers pronostics sont savon, sel, vapeur, citron et vinaigre. Il est intéressant de constater que le modèle apprend en quelque sorte qu'il faut se laver les mains avec des produits qui peuvent tuer les bactéries ou contenir de l'acide.
Dans [0] :
fill_mask("Lavarse frecuentemente las manos con agua y .")
Out[0] :
[{'score': 0.6469631195068359, 'sequence' : 'Lavarse frecuentemente las manos con agua y jabón.' ;, 'token': 18493}, {'score': 0.06074320897459984, 'sequence' : 'Lavarse frecuentemente las manos con agua y sal.' ;, 'token' : 619}, {'score': 0.029787985607981682, 'sequence' : 'Lavarse frecuentemente las manos con agua y vapor.' ;, 'token': 11079}, {'score': 0.026410052552819252, 'sequence' : 'Lavarse frecuentemente las manos con agua y limón.' ;, 'token': 12788}, {'score': 0.017029203474521637, 'sequence' : 'Lavarse frecuentemente las manos con agua y vinagre.' ;, 'token': 18424}]
Conclusion¶
Nous avons vu comment entraîner un modèle de langue BERT pour l'espagnol à partir de zéro et constaté que le modèle a appris des propriétés de la langue en essayant de prédire des mots masqués en fonction d'un contexte. Vous pouvez également suivre cet article pour affiner un modèle BERT pré-entraîné sur votre ensemble de données personnalisé.
Ensuite, nous mettrons en œuvre les modèles pré-entraînés sur des tâches en aval, notamment la classification des séquences, le NER, l'étiquetage POS et le NLI, et nous comparerons les performances du modèle à celles de certains modèles non-BERT.
Restez à l'écoute de nos prochains articles !