NLP & fastai | French Language Model

Pierre Guillou
23 min readSep 16, 2019

--

Avez-vous déjà aidé une personne à finir sa phrase? Si oui, vous avez en fait utilisé votre Language Model (LM), cad la modélisation de votre langue entraînée et enregistrée dans votre réseau de neurones. En intelligence artificielle appliquée au NLP, il est également possible d’entraîner un LM dans la langue de son choix en lui faisant prédire le prochain mot d’une phrase à partir des mots déjà exprimés. Dans ce post, j’explique comment j’ai créé un LM bidirectionnel du français en entraînant un modèle de Deep Learning.

Autres posts de la série NLP & fastai: Topic Modeling | Sentiment Classification | Language Model | Transfer Learning | ULMFiT | MultiFiT | Portuguese Model Language | RNN | LSTM & GRU | SentencePiece | Sequence-to-Sequence Model (seq2seq) | Attention Mechanism | Transformer Model | GPT-2

Tous les notebooks, paramètres des modèles et vocab sont à télécharger sur github (lien).

IMPORTANT (23 septembre 2019): l’architecture du premier LM créé et présenté dans ce post est un AWD-LSTM et son vocabulaire a été créé par le tokenizer spaCy (60 000 tokens et fréquence minimale d’occurrence 2). Après avoir lu le nouveau papier sur MultiFiT (septembre 2019) de Jeremy Howard, j’ai créé 2 autres LM utilisant une architecture QRNN et le tokenizer SentencePiece (15 000 tokens). Les performances de ces 2 LM sont meilleures que celles du premier, en particulier le 3ième qui a été entraîné selon la configuration MultiFiT. Vous trouverez toutes les informations utiles sur ce second et troisième LM à la fin de ce post.

Motivation

Participant depuis 2017 aux cours fastai de Jeremy Howard et Rachel Thomas sur le Deep Learning (DL), je m’intéresse en particulier aux possibilités du DL appliqué au NLP (Deep Learning appliqué au Natural Language Processing) à travers le nouveau cours fastai de Rachel Thomas : A code-first introduction to NLP (juillet 2019).

Note: afin de partager ma compréhension de ce cours, j’ai publié une série de posts NLP & fastai (liens dans le post “Deep Learning in practice — DL series”).

Un moment clé de ce cours correspond à la vidéo intitulée “ULMFit for non-English Languages (NLP Video 10)” dans laquelle Jeremy Howard montre comment entraîner un Language Model (LM) dans la langue de son choix sur la base de 2 notebooks créant le LM bidirectionnel du vietnamien (nn-vietnamese.ipnb et nn-vietnamese-bwd.ipynb).

Souhaitant vérifier les possibilités en NLP d’un LM bidirectionnel du français et contribuer ainsi à la communauté NLP française, j’ai alors créé un LM Général bidirectionnel du français afin de pouvoir disposer d’un modèle général de la langue française.

Note: cherchant à développer des applications NLP sur des documents en français (classification de sentiments, traduction, résumé de texte, question-réponse, génération de textes…), j’ai ensuite créé un classificateur de sentiments entraîné sur des commentaires d’achats sur Amazon en appliquant les techniques de Transfer Learning et fine-tuning de la méthode ULMFiT.

Publication

J’ai publié le code utilisé pour entraîner mes 3 modèles dans le répertoire github de mes Language Models.

Le LM Bidirectionnel du français entraîné suivant la configuration MultiFiT est le plus performant des 3 (cf. comparaison des performances sur le github des Language Models).

Architecture AWD-LSTM / Tokenisation par spaCy

  • lm-french.ipynb: LM Général bidirectionnel du français (AWD-LSTM, spaCy).
  • lm-french-generator.ipynb: générateur de textes en français utilisant le LM Général précédent.
  • lm-french-classifier-amazon.ipynb: un LM Spécialisé du français aux commentaires d’achats sur Amazon et un classificateur de sentiments de ces mêmes commentaires finement entraînés à parti du LM Général bidirectionnel précédent.

Architecture QRNN / Tokenisation par SentencePiece

  • lm2-french.ipynb: LM Général bidirectionnel du français (QRNN, SentencePiece).
  • lm2-french-classifier-amazon.ipynb: un LM Spécialisé du français aux commentaires d’achats sur Amazon et un classificateur de sentiments de ces mêmes commentaires finement entraînés à parti du LM Général bidirectionnel précédent.

Architecture MultiFiT (4 QRNN / 1550 hidden parameters by layer / Tokenisation par SentencePiece (15 000 tokens))

  • lm3-french.ipynb: LM Général bidirectionnel du français (MultiFiT).
  • lm3-french-classifier-amazon.ipynb: un LM Spécialisé du français aux commentaires d’achats sur Amazon et un classificateur de sentiments de ces mêmes commentaires finement entraînés à parti du LM Général bidirectionnel précédent.

J’ai également publié les paramètres et/ou vocabulaires de mes 3 modèles (Bidirectionnel LM Général AWD-LSTM/spaCy, Bidirectionnel LM Général QRNN/SentencePiece et MultiFit) sur github afin que chacun puisse vérifier mes résultats et je l’espère les améliorer. Dans ce cas, n’oubliez pas svp de publier vos propres résultats et ainsi aider la communauté NLP française à progresser.

Lien vers le répertoire github des paramètres et ou vocabulaires des modèles

Coût pour créer un LM Général Bidirectionnel du français

Lors de la création de l’instance utilisée, GCP donne une estimation de son coût mensuel. Pour la configuration que j’ai utilisée, voici les estimations de GCP:

8 vCPUs + 52 GB memory: $73.00/month
1 NVIDIA Tesla V100 GPU: $540.20/month
200 GB standard persistent disk: $8.00/month
Total: $621.20/month
$0.85 hourly

Ramené à un coût horaire (environ 730 heures dans 1 mois), notre instance coûte 0.85 dollars (soit 0.77 euros) par heure (ce coût est plutôt faible car nous avons choisi une instance Preemptible, cad une instance qui peut être arrêtée par la plateforme GCP suivant ses propres besoins).

Architecture AWD-LSTM / Tokenisation par spaCy

Comme nous avons consommé 22h40mn (cf. chiffres ci-dessous), notre coût total est donc de: 22h40 x 0.85 = 19.26 dollars (soit 17.41 euros).

Pour rappel: après 22h 40mn (1h15 de téléchargement et préparation des données + 3mn de création d’un corpus d’environ 100 millions de tokens + 22mn de création des 2 dataBunch + 21h d’entraînement des 2 learner), nous obtenons:

  • un LM Général forward du français avec une performance (accuracy) de 36.43% et une perplexité (perplexity) de 25.62.
  • un LM Général backward du français avec une performance (accuracy) de 42.65% et une perplexité (perplexity) de 27.09.

Architecture QRNN / Tokenisation par SentencePiece

Comme nous avons consommé 12h (cf. chiffres ci-dessous), notre coût total est donc de: 12h x 0.85 = 10.20 dollars (soit 9.26 euros).

Après 12h (1h15 de téléchargement et préparation des données + 3mn de création d’un corpus d’environ 100 millions de tokens + 40mn de création des 2 dataBunch + 10h d’entraînement des 2 learner), nous obtenons:

  • un LM Général forward du français avec une performance (accuracy) de 40.99% et une perplexité (perplexity) de 19.96.
  • un LM Général backward du français avec une performance (accuracy) de 47.19% et une perplexité (perplexity) de 19.47.

Architecture MultiFit

Comme nous avons consommé 18h (cf. chiffres ci-dessous), notre coût total est donc de: 18h x 0.85 = 15.30 dollars (soit 13.93 euros).

Après 18h (1h15 de téléchargement et préparation des données + 3mn de création d’un corpus d’environ 100 millions de tokens + 1h20mn de création des 2 dataBunch + 15h20 d’entraînement des 2 learner), nous obtenons:

  • un LM Général forward du français avec une performance (accuracy) de 43.77% et une perplexité (perplexity) de 16.09.
  • un LM Général backward du français avec une performance (accuracy) de 49.29% et une perplexité (perplexity) de 16.58.

Implémentation d’un LM… dans la vraie vie

Dans le paragraphe suivant, nous verrons les différentes étapes d’implémentation d’un LM en python… dans une configuration sans erreurs comme dans les vidéos de Rachel Thomas et Jeremy Howard.

Cependant, dans la “vraie vie” et avant de trouver la bonne configuration de votre environnement de travail, vous allez rencontrer les erreurs du type manque de RAM CPU pour créer le DataBunch, manque de RAM GPU pour lancer le learner du LM, arrêt du notebook par déconnexion SSH de la part de la plateforme GPU, arrêt de l’instance après 24h alors que l’entraînement de notre LM demande davantage de temps, etc.

Ayant rencontré toutes les erreurs possibles (j’espère!) lors de l’entraînement de mon LM Général du français, je vais les mentionner à la fin de ce post en Annexe ainsi que les solutions en espérant vous éviter (j’espère!) quelques journées d’énervement (…).

Implémentation en Python d’un LM Général bidirectionnel du français

Voici ci-dessous les étapes d’implémentation que j’ai suivies.

Note: j’ai décidé de créer un LM Général bidirectionnel car disposer à la fois d’un LM forward (prédire le prochain mot) et aussi backward (prédire le mot précédent) améliore les performances des applications utilisant le LM Général comme un classificateur de textes.

1. Pré-requis | Installation d’une instance avec GPU en ligne et de l’environnement fastai v1 de Deep Learning

J’ai utilisé le service GCP (Google Cloud Platform) avec une installation du framework fastai v1 installée en suivant le guide Start GCP (puis le guide Returning to GCP) de la manière suivante:

  • J’ai tout d’abord créé mon instance GCP en modifiant le script de fastai dans le guide GCP de la manière suivante (ZONE=”us-west1-a” et — accelerator=”type=nvidia-tesla-v100,count=1"):
export IMAGE_FAMILY="pytorch-latest-gpu"
export ZONE="us-west1-a"
export INSTANCE_NAME="my-fastai-instance"
export INSTANCE_TYPE="n1-highmem-8"
gcloud compute instances create $INSTANCE_NAME \
--zone=$ZONE \
--image-family=$IMAGE_FAMILY \
--image-project=deeplearning-platform-release \
--maintenance-policy=TERMINATE \
--accelerator="type=nvidia-tesla-v100,count=1" \
--machine-type=$INSTANCE_TYPE \
--boot-disk-size=200GB \
--metadata="install-nvidia-driver=True" \
--preemptible
  • Après m’être connecté en ssh à mon instance et avant d’installer fastai v1, j’ai fait (plusieurs fois) un update de conda via la commande suivante:
conda update -n base -c defaults conda
  • Puis, j’ai lancé la commande suivante pour installer fastai v1 jusqu’à obtenir le message “# All requested packages already installed”:
sudo /opt/anaconda3/bin/conda install -c fastai fastai

J’ai alors installé par git clone le répertoire course-nlp (dans le répertoire ~/tutorials/fastai/) par la commande suivante:

git clone https://github.com/fastai/course-nlp.git

Enfin, dans le répertoire course-nlp, j’ai dupliqué le notebook nn-vietnamese.ipnb en lm-french.ipynb qui est mon notebook d’entraînement de mon LM Général bidirectionnel du français.

AVERTISSEMENT: mes problèmes techniques sur GCP ont alors débuté lorsque j’ai commencé à faire tourner mon notebook lm-french.ipynb. Par conséquent, je vous invite avant de continuer à faire tourner votre notebook suivant les paragraphes suivants à lire toutes les solutions à la fin de ce post en Annexe, puis de revenir lire ce post à partir d’ici.

2. Caractéristiques de l’instance

Avant de poursuivre, j’ai visualisé l’état de la configuration de notre instance avec les commandes suivantes dans un terminal Ubuntu de mon instance:

python -m fastai.utils.show_install

=== Software ===
python : 3.7.4
fastai : 1.0.57
fastprogress : 0.1.21
torch : 1.2.0
nvidia driver : 410.104
torch cuda : 10.0.130 / is available
torch cudnn : 7602 / is enabled

=== Hardware ===
nvidia gpus : 1
torch devices : 1
— gpu0 : 16130MB | Tesla V100-SXM2–16GB

=== Environment ===
platform : Linux-4.9.0–9-amd64-x86_64-with-debian-9.9
distro : #1 SMP Debian 4.9.168–1+deb9u5 (2019–08–11)
conda env : base
python : /opt/anaconda3/bin/python
sys.path : /home/jupyter/tutorials/fastai/course-nlp
/opt/anaconda3/lib/python37.zip
/opt/anaconda3/lib/python3.7
/opt/anaconda3/lib/python3.7/lib-dynload
/opt/anaconda3/lib/python3.7/site-packages
/opt/anaconda3/lib/python3.7/site-packages/IPython/extensions

# use of the CPU RAM
free -h
# use of the GPU RAM
nvidia-smi

3. Notebook lm-french.ipynb

Rappel (23 septembre 2019): le notebook lm-french.ipynb est celui du premier modèle que j’ai entraîné mais j’ai ensuite entraîné 2 autres modèles dans la recherche d’une meilleure performance. Ainsi, le modèle le plus performant est celui qui utilise la configuration MultiFiT et dont le notebook est lm3-french.ipynb. Les explications ci-dessous sur les étapes suivies pour l’entraînement du modèle lm-french.ipynb restent cependant valables pour celui du modèle lm3-french.ipynb (MultiFiT).

Vous pouvez trouver en ligne mon notebook lm-french.ipynb dont voici les 5 grandes étapes:

  1. Initialisation de l’environnement dans le notebook
  2. Téléchargement du dataset Wikipedia en français et traitement
  3. Obtention d’un corpus d’entraînement de 100 millions de tokens
  4. Création des DataBunch forward et backward du LM Général bidirectionnel
  5. Création et entraînement des learner forward et backward du LM Général bidirectionnel

Voici à présent les explications détaillées et le code de chacune de ces étapes.

1. Initialisation de l’environnement dans le notebook

Les premières lignes du Jupyter Notebook servent à importer les bibliothèques qui seront utilisées et à définir les valeurs des paramètres principaux. Nous utilisons comme bibliothèque de Deep Learning fastai v1.

%reload_ext autoreload
%autoreload 2
%matplotlib inline
from fastai import *
from fastai.text import *
from fastai.callbacks import *
bs=128
torch.cuda.set_device(0)
data_path = Config.data_path()lang = 'fr'name = f'{lang}wiki'
path = data_path/name
path.mkdir(exist_ok=True, parents=True)
lm_fns = [f'{lang}_wt', f'{lang}_wt_vocab']
lm_fns_bwd = [f'{lang}_wt_bwd', f'{lang}_wt_vocab_bwd']

2. Téléchargement du dataset Wikipedia en français et traitement

J’ai créé le fichier nlputils2.py à partir du fichier nlputils.py afin de dissocier ses méthodes qui permettent l’importation du dataset et son traitement (ce qui me permet ainsi de les lancer séparément dans le notebook et de voir leur temps de traitement respectif) et j’ai ajouté une méthode de nettoyage des fichiers textes: clean_files().

Note: nlputils.py n’était pas compatible avec une plateforme Windows, ce qui a été corrigé dans nlputils2.py même si cela n’est pas utile pour notre instance qui tourne sous Linux sur GCP.

Voici le code utilisé:

from nlputils2 import *%%time
get_wiki_download(path,lang)
%%time
get_wiki_unzip(path,lang)
%%time
get_wiki_extract(path,lang)
%%time
dest = split_wiki(path,lang)
%%time
folder = "docs"
clean_files(path,folder)

Voici les résultats obtenus avec leur temps d’exécution:

  • 36mn 42s | Téléchargement du fichier frwiki-latest-pages-articles.xml.bz2 (4.4 Go)
  • 13mn 33s | Unzip vers le fichier frwiki-latest-pages-articles.xml (20 Go)
  • 15mn 54s | Extraction des textes de taille supérieur à 1800 caractères vers le fichier frwiki (3.2 Go)
  • 1mn 18s | Split de chaque texte dans un fichier title.txt dans le répertoire docs (512 659 fichiers avec au total plus de 20.5 millions de lignes de texte et une taille globale du répertoire docs de 3.9 Go)
  • 8mn 37s | Nettoyage des fichiers du répertoire docs (suppression du titre et de la balise </doc>)

TOTAL: environ 1h 15mn pour obtenir 512 659 fichiers en français de taille globale de 3.9 Go et contenant au total plus de 20.5 millions de lignes de texte.

3. Obtention d’un corpus d’entraînement de 100 millions de tokens

Comme le précise Jeremy Howard, le modèle de LM utilisé dans ce notebook n’est pas suffisamment profond en terme de nombre de couches et de paramètres pour profiter un corpus d’entraînement de taille supérieur à 100 millions de tokens (le terme token signifie ici mot, ce qui n’est pas le cas de manière générale). Or, notre corpus en français téléchargé de Wikipedia avait plus de 492 millions de tokens.

A l’aide de méthodes écrites dans le fichier nlputils2.py, (get_num_tokens() et get_corpus()), j’ai donc supprimé un certain nombres d’articles pour le descendre à un corpus d’environ 100 millions de tokens.

Note: il n’est jamais conseillé d’éliminer des données d’entraînement en Deep Learning mais dans notre cas, il aurait fallu alors changer l’architecture de notre LM (AWD-LSTM) pour une architecture NLP plus profonde du type GPT-2 ou BERT. Or, pour une application ultérieure de notre LM Général afin de spécialiser un classificateur (cf. post), les architectures profondes de NLP peuvent être sur-dimensionnées.

# path to Wikipedia corpus
dest = path/'docs'
# get number of tokens from wikipedia corpus
num_files, num_tokens = get_num_tokens(dest)
print(f'{num_files} files - {num_tokens} tokens')
# create new corpus of 100 millions tokens and get path
path_corpus = get_corpus(dest, num_tokens, obj_tokens=1e8)
# VERIFICATION of the number of tokens of new corpus
num_files_corpus, num_tokens_corpus = get_num_tokens(path_corpus)
print(f'{num_files_corpus} files - {num_tokens_corpus} tokens')

TOTAL: environ 3mn pour obtenir un corpus d’environ 100 millions de tokens à partir de notre corpus Wikipedia qui avait près de 500 millions de tokens.

4. Création des DataBunch forward et backward du LM Général bidirectionnel

L’objet databunch dans fastai v1 permet de préparer les données d’entraînement et de validation pour le learner du modèle à entraîner (le tokenizer par défaut de fastai a été utilisé: il s’agit de spaCy avec un vocab de 60 000 tokens et une fréquence min d’occurence de 2). Nous avons créé 2 databunchs, un pour le LM Général forward (prédiction du prochain mot) et l’autre pour le LM Général backward (prédire le mot précédent), ce qui me permettra d’entraîner ensuite 2 learners et donc de créer un LM Général bidirectionnel.

Note: j’ai séparé la création du DataBunch en étapes unitaires en utilisant le Data Block Api pour voir leur temps de traitement respectif.

10% des données ont été utilisées pour la validation et le batch size (bs) est de 128.

Databunch du LM Général foward

%%time
data = TextList.from_folder(dest)
%%time
data = data.split_by_rand_pct(0.1, seed=42)
%%time
data = data.label_for_lm()
%%time
data = data.databunch(bs=bs, num_workers=1))
%%time
data.save(f'{path}/{lang}_databunch_corpus_100')

Voici les résultats obtenus avec leur temps d’exécution:

  • 78.9ms | data = TextList.from_folder(dest)
  • 11.1ms | data = data.split_by_rand_pct(0.1, seed=42)
  • 8mn 22s | data = data.label_for_lm()
  • 68.1ms | data = data.databunch(bs=bs, num_workers=1))

TOTAL: environ 8mn 30s pour obtenir et sauver le DataBunch qui sera utilisé par le learner de notre LM Général forward.

Databunch du LM Général backward

%%time
data = (TextList.from_folder(dest)
.split_by_rand_pct(0.1, seed=42)
.label_for_lm()
.databunch(bs=bs, num_workers=1, backwards=True))
data.save(f'{path}/{lang}_databunch_corpus_100_bwd'))

TOTAL: environ 14mn pour obtenir et sauver le DataBunch qui sera utilisé par le learner de notre LM Général backward.

5. Création et entraînement des learner forward et backward du LM Général bidirectionnel

Nous importons dans nos learner forward et backward la structure du modèle AWS-LSTM (un réseau RNN avec 5 dropouts différents) mais pas les valeurs de ses paramètres entraînés sur un corpus en anglais puisque nous cherchons à les obtenir pour un corpus en français. Nous démarrons donc l’entraînement de nos 2 learner avec des valeurs aléatoires pour leurs paramètres.

Par ailleurs, nous divisons par 2 les valeurs par défaut de dropout du modèle AWS-LSTM (ie, moins de régularisation) pour améliorer la performance de notre modèle et nous passons notre learner en notation mixed par la fonction to_fp16() afin d’accélérer son entraînement.

De plus, comme nous devons entraîner à partir de valeurs aléatoires la totalité des paramètres du learner, nous vérifions que unfreeze() lui est appliquée et nous lançons l’entraînement sur 10 epochs.

IMPORTANT | Sauvez votre meilleur modèle à la fin de chaque epoch. Vous allez certainement avoir un TTE (Traing Time by Epoch) important avec des problèmes possibles de GPU (CUDA: out of memory par exemple) ou d’arrêt de l’instance par GCP (nous avons opté pour l’option Preemptible On car avec cette option Off, le coût du GPU est 3 fois plus élevé). Si vous ne sauvegardez pas régulièrement votre meilleur modèle, vous risquez alors de perdre le résulta de plusieurs heures de son entraînement. Pour éviter cela, pensez donc à enregistrer votre meilleur modèle à la fin de chaque epoch avec le callback SaveModelCallback(learn.to_fp32()). Il vous suffira alors de récupérer votre learner avec le code suivant:

# forward
lm_fns[0] = 'bestmodel'
learn = language_model_learner(data, AWD_LSTM, drop_mult=0.5, pretrained_fnames=lm_fns, metrics=[error_rate, accuracy, perplexity]).to_fp16()
# backward
lm_fns_bwd[0] = 'bestmodel'
learn = language_model_learner(data, AWD_LSTM, drop_mult=0.5, pretrained_fnames=lm_fns_bwd, metrics=[error_rate, accuracy, perplexity]).to_fp16()

Modèle forward

data = load_data(path, f'{lang}_databunch_corpus_100', bs=bs)perplexity = Perplexity()
learn = language_model_learner(data, AWD_LSTM, drop_mult=0.5, pretrained=False, metrics=[error_rate, accuracy, perplexity]).to_fp16()
learn.lr_find()
learn.recorder.plot()
lr = 1e-3
lr *= bs/48 # Scale learning rate by batch size
learn.unfreeze()learn.fit_one_cycle(10, lr, moms=(0.8,0.7), callbacks=[ShowGraph(learn),SaveModelCallback(learn.to_fp32(), monitor='accuracy')])mdl_path = path/'models'
mdl_path.mkdir(exist_ok=True)
learn.to_fp32().save(mdl_path/lm_fns[0], with_opt=False)
learn.data.vocab.save(mdl_path/(lm_fns[1] + '.pkl'))

En un temps de 10h 30mn, nous obtenons un LM Général forward du français avec une performance (accuracy) de 36.43% et une perplexité (perplexity) de 25.62.

Modèle backward

data = load_data(path, f'{lang}_databunch_corpus_100_bwd', bs=bs, backwards=True)perplexity = Perplexity()
learn = language_model_learner(data, AWD_LSTM, drop_mult=0.5, pretrained=False, metrics=[error_rate, accuracy, perplexity]).to_fp16()
learn.lr_find()
learn.recorder.plot()
lr = 3e-3
lr *= bs/48 # Scale learning rate by batch size
learn.unfreeze()learn.fit_one_cycle(10, lr, moms=(0.8,0.7), callbacks=[ShowGraph(learn),SaveModelCallback(learn.to_fp32(), monitor='accuracy')])mdl_path = path/'models'
mdl_path.mkdir(exist_ok=True)
learn.to_fp32().save(mdl_path/lm_fns_bwd[0], with_opt=False)
learn.data.vocab.save(mdl_path/(lm_fns_bwd[1] + '.pkl'))

En un temps de 10h 30mn, nous obtenons un LM Général backward du français avec une performance (accuracy) de 42.65% et une perplexité (perplexity) de 27.09.

Application | Génération de texte à partir du LM

Même si un LM avec une architecture AWD-LSTM peu profonde et entraîné sur un corpus relativement petit (100 millions de tokens) n’est pas adapté pour générer des textes, il est intéressant de regarder ses possibilités en comparaison d’un LM non entraîné.

data = load_data(path, f'{lang}_databunch_corpus_100', bs=bs)# LM without pretraining
learn = language_model_learner(data, AWD_LSTM, pretrained=False)
TEXT = "Nadal a gagné le tournoi de" # original text
N_WORDS = 100 # number of words to predict following the TEXT
N_SENTENCES = 1 # number of different predictions
print("\n".join(learn.predict(TEXT, N_WORDS, temperature=0.75) for _ in range(N_SENTENCES)))# LM pretrained in french
learn_fr = language_model_learner(data, AWD_LSTM, pretrained_fnames=lm_fns)
TEXT = "Nadal a gagné le tournoi de" # original text
N_WORDS = 100 # number of words to predict following the TEXT
N_SENTENCES = 1 # number of different predictions
print("\n".join(learn_fr.predict(TEXT, N_WORDS, temperature=0.75) for _ in range(N_SENTENCES)))

Voici ce qui a été généré par le LM non entraîné:

Nadal a gagné le tournoi de accum brisent codon dabo divinisation suscitent rafale hung özil 353 melanie pingouin balustres shogunat sebastiano présentations installés reconstruction civic ésotériques cortex répertorier néologisme dialectales s'exprimait disparus l’importation partenkirchen œuvrent immédiat ghavam cordon flake protohistorique heidegger impossibilité d'agenda marginalisés gascogne granier death moravie cahuzac débuts luberon vigoureusement tibétaines lagardère étaples s’inspirant germanie d'assumer phonèmes daloz midwest sault profanation avertissements incompétence énoncées longwy fire agrippa pisani philippin ireland décentralisation germanisation stagnent touchaient bénéficieront toccata dévot profilés l’anxiété préface 1160 ying schopenhauer millions voyous manèges l'opep frégates barrett infections vestibule fragmentées crépin faveurs crolles dignitaires s'interposer 2050 donation égarés samnites maintiennent délaissent périphérie

Voici ce qui a été généré par le LM entraîné:

Nadal a gagné le tournoi de Roland - Garros en 1998 . Il a remporté des titres en simple et en double puis en double . 
En juin 2008 , il remporte le Championnat de France de Grand Chelem en double avec Roland - Garros , avec Andy Roddick , Stanislas Wawrinka , Juan Martín del Potro et Roger Federer . Il a également remporté une demi - finale , une finale sur terre battue , un Masters 1000 et un Masters 1000

En comparant ces 2 textes, il est clair que l’entraînement de notre LM forward du français lui a permis de (commencer à) comprendre la langue française (grammaire, syntaxe, relation entre un mot et son pronom personnel, etc.) ainsi qu’une connaissance issue de celle du corpus utilisé (ex: tournoi de Roland-Garros).

A l’inverse, nous pouvons constater des erreurs de sens (ex: il remporte le Championnat de France de Grand Chelem) et des répétitions (ex: en double puis en double). Pour une meilleure génération de textes, il faudrait davantage utiliser des modèles comme GPT-2 ou BERT.

2nd Language Model | Architecture QRNN et tokenizer SentencePiece

Après avoir lu le nouveau papier sur MultiFiT (septembre 2019) de Jeremy Howard et la liste des valeurs des hyperparamètres d’entraînement en dernière page, j’ai créé un second LM Bidirectionnel du français utilisant une architecture QRNN et le tokenizer SentencePiece (15 000 tokens) et reprenant ces valeurs des hyperparamètres.

Voici les liens vers le notebook lm2-french.ipynb et les poids+vocab à télécharger via des liens sur github.

Voici le code pour le databunch et le learner du LM Général forward (pour ce qui concerne le backward, regardez le notebook). En plus du changement de tokenizer et d’architecture, les principaux changements sont:

  • (batch size) bs = 50
  • (QRNN) 3 QRNN (default: 3) with 1152 hidden parameters each one (default: 1152)
  • (SentencePiece) vocab of 15000 tokens
  • (dropout) mult_drop = 0
  • (weight decay) wd = 0.01
  • (number of training epochs) 10 epochs
# databunch forward with the tokenizer SentencePiece (15k tokens)
data = (TextList.from_folder(dest, processor=[OpenFileProcessor(), SPProcessor(max_vocab_sz=15000)])
.split_by_rand_pct(0.1, seed=42)
.label_for_lm()
.databunch(bs=bs, num_workers=1))
# Change the model to QRNN
config = awd_lstm_lm_config.copy()
config['qrnn'] = True
# learner
perplexity = Perplexity()
learn = language_model_learner(data, AWD_LSTM, config=config, drop_mult=0., pretrained=False, metrics=[error_rate, accuracy, perplexity]).to_fp16()
# learning rate
lr = 1e-3
lr *= bs/48 # Scale learning rate by batch size
# Training
learn.unfreeze()
wd = 0.01
learn.fit_one_cycle(10, lr, wd=wd, moms=(0.8,0.7), callbacks=[ShowGraph(learn),SaveModelCallback(learn.to_fp32(), monitor='accuracy', name='bestmodel_sp15')])

Après 12h (1h15 de téléchargement et préparation des données + 3mn de création d’un corpus d’environ 100 millions de tokens + 40mn de création des 2 dataBunch + 10h d’entraînement des 2 learner), nous obtenons:

  • un LM Général forward du français avec une performance (accuracy) de 40.99% et une perplexité (perplexity) de 19.96.
  • un LM Général backward du français avec une performance (accuracy) de 47.19% et une perplexité (perplexity) de 19.47.

3ième Language Model | Architecture MultiFiT

Après avoir lu le nouveau papier sur MultiFiT (septembre 2019) de Jeremy Howard et avoir tout d’abord entraîné un second LM en changeant seulement architecture AWD-LSTM pour QRNN et le tokenizer spaCy pour SentencePiece, j’ai ensuite entraîné un troisième LM Bidirectionnel du français sur selon la configuration MultiFiT, toujours sur un corpus d’environ 100 000 millions de tokens extrait du téléchargement de Wikipedia en français (presque 500 millions de tokens).

Ce LM Bidirectionnel du français entraîné suivant la configuration MultiFiT est le plus performant des 3 (cf. comparaison des performances sur le github des Language Models).

Voici les liens vers le notebook lm3-french.ipynb et les poids+vocab à télécharger via des liens sur github.

Voici le code pour le databunch et le learner du LM Général forward (pour ce qui concerne le backward, regardez le notebook). En plus du changement de tokenizer et d’architecture, les principaux changements sont:

  • (batch size) bs = 50
  • (QRNN) 4 QRNN (default: 3) with 1550 hidden parameters each one (default: 1152)
  • (SentencePiece) vocab of 15000 tokens
  • (dropout) mult_drop = 0
  • (weight decay) wd = 0.01
  • (number of training epochs) 10 epochs
# databunch forward with the tokenizer SentencePiece (15k tokens)
data = (TextList.from_folder(dest, processor=[OpenFileProcessor(), SPProcessor(max_vocab_sz=15000)])
.split_by_rand_pct(0.1, seed=42)
.label_for_lm()
.databunch(bs=bs, num_workers=1))
# Change the model to QRNN
config = awd_lstm_lm_config.copy()
config['qrnn'] = True
config['n_hid'] = 1550 #default 1152
config['n_layers'] = 4 #default 3
# learner
perplexity = Perplexity()
learn = language_model_learner(data, AWD_LSTM, config=config, drop_mult=0., pretrained=False, metrics=[error_rate, accuracy, perplexity]).to_fp16()
# learning rate
lr = 3e-3
lr *= bs/48 # Scale learning rate by batch size
# Training
learn.unfreeze()
wd = 0.01
learn.fit_one_cycle(10, lr, wd=wd, moms=(0.8,0.7), callbacks=[ShowGraph(learn),SaveModelCallback(learn.to_fp32(), monitor='accuracy', name='bestmodel_sp15_multifit')])

Après 18h (1h15 de téléchargement et préparation des données + 3mn de création d’un corpus d’environ 100 millions de tokens + 1h20mn de création des 2 dataBunch + 15h20 d’entraînement des 2 learner), nous obtenons:

  • un LM Général forward du français avec une performance (accuracy) de 43.77% et une perplexité (perplexity) de 16.09.
  • un LM Général backward du français avec une performance (accuracy) de 49.29% et une perplexité (perplexity) de 16.58.

Utiliser le classificateur pour prédire la classe de textes

La bibliothèque fastai v1, comme toutes les bibliothèques Deep Learning, vous permet d’utiliser le modèle entraîné sur de nouvelles données pour obtenir des prédictions de classe (dans le cas où le modèle est un classificateur), mais elle possède également une fonction show_intrinsic_attention() qui fournit une interprétation de la notation basée sur la sensibilité d’entrée.

Plus le mot ombré dans l’exemple ci-dessous est sombre, plus il contribue à la classification. Cool!

Utilisation du classifieur pour prédire le sentiment des commentaires sur un produit amazon (notebook: lm-french-classifier-amazon.ipynb)

Annexe | Erreurs et solutions pour réussir à faire tourner un notebook d’entraînement d’un LM sur une instance GPU en ligne

Vue d’ensemble des solutions

Voici un résumé des solutions que j’ai utilisées face aux différente problèmes rencontrés dans l’entraînement de mon LM Général bidirectionnel du français (détails des erreurs et solutions dans les paragraphes suivants):

  • Si problème pour lancer votre instance à cause du manque de ressources dans sa zone, soit essayer de nouveau dans les heures qui suivent, soit en créer une similaire dans une autre zone.
  • Lors de la création d’une instance similaire, choisir une autre zone, un GPU avec au moins 16Go de RAM (ex: NVIDIA Tesla V100) et si ce n’est pas déjà le cas, un serveur d’instance de type n1-highmem-8 (8vCPU avec 52Go de RAm CPU). Autre possibilité: choisir la valeur Off pour l’option Preemptible pour éviter les arrêts d’instance par GCP mais attention: le prix horaire d’utilisation du GPU est multiplié par 3.
  • Installation de 4Go de mémoire Swap qui viennent s’ajouter au 52Go de RAM CPU pour permettre la création du DataBunch du LM (problème inhérent à fastai v1 et qui devrait être réglé dans fastai v2).
  • Mettre à jour gcloud sur votre ordinateur (zone et région dans la configuration par défaut, fichier config-ssh, composants SDK)
  • Mettre à jour la configuration de son instance avec sa clé SSH publique.
  • Puis, se connecter à sa nouvelle instance en suivant le guide d’usage de tmux afin de lancer le serveur Jupyter notebook de l’instance dans une autre session que celle utilisée pour la connexion SSH.

Erreur n°1 | Impossible de lancer l’instance

J’ai rencontré régulièrement des difficultés après la création de l’instance à lancer l’instance: la plupart du temps, le message d’erreur suivant apparaissait sur ma console GCP:

The zone 'projects/clever-hangar-234310/zones/us-west1-a' does not have enough resources available to fulfill the request. Try a different zone, or try again later.

Solution n°1 | Tenter, tenter et tenter de nouveau

Le message d’erreur de GCP est clair: tentez de nouveau plus tard de lancer votre instance. C’est (un peu) énervant mais vous arriverez en effet à relancer votre instance mais peut-être que plusieurs heures plus tard.

Solution n°2 | Créer une instance similaire dans une autre zone

Vous pouvez aussi créer une instance similaire dans une autre zone (cliquer sur le bouton CREATE SIMILAR dans la page descriptive de l’instance initiale) comme la zone us-central1-a par exemple (choisir d’abord la région, puis la zone). En effet, il n’est pas possible de changer la zone d’une instance déjà créée.

Suite à cette nouvelle création, il vous faudra réinstaller vos notebooks et recommencer leur entraînement à partir de zéro (sauf si vous aviez sauvegarder hors de votre instance initiale les résultats et fichiers intermédiaires).

Erreur n°2 | MEMORY ERROR lors de la création du DataBunch

Lors de ma première tentative d’utilisation de mon notebook lm-french.ipynb, j’ai obtenu une MEMORY ERROR (ie, RAM CPU insuffisante) lors de la création du DataBunch (précisément, lors de la création des objets LabelList de train et valid par la fonction label_for_lm()).

Solution | Installation de 4Go de mémoire Swap

Au lieu de diminuer le batch size, j’ai décidé d’ajouter 4 Go de RAM de type Swap Space (guide: How To Add Swap Space on Ubuntu 16.04) au 52 Go initiaux comme le suggère ce post, soit un total de 56 Go (ce problème de mémoire RAM CPU devrait être réglé dans fastai v2).

Erreur n° 3 | “CUDA out of memory” lors de la recherche du Learning Rate

Le GPU initial de mon instance était NVIDIA Tesla P4 (8 Go de RAM) mais il manquait 2 Go pour lancer la fonction de recherche du meilleur Learning Rate (learn.lr_find()). Par ailleurs, l’entraînement du learner nécessite 13 Go de RAM GPU dans notre configuration avec batch size de 128.

Solution | Choisir un GPU NVIDIA avec au moins 16 Go de RAM

Lors de la création de votre instance similaire (cliquer sur le bouton CREATE SIMILAR dans la page descriptive de l’instance initiale) ou lors de la création initiale de votre instance, en plus des autres options modifiées présentées avant, choisissez un GPU NVIDIA Tesla v100 avec 16 Go RAM GPU.

Erreur n°4 | ERROR SSH lors de l’entraînement du learner

Je pensais que mes problèmes étaient terminés mais non. Ma première tentative d’entraîner le learner s’est arrêtée au bout de 3h27mn35s (cf. capture d’écran ci-dessous) du fait d’une déconnexion par la plateforme GCP à cause d’une ERROR SSH (mais sans arrêt de l’instance).

Arrêt de l’entrainement du learner par la plateforme GCP après 3h27mn35s

Le message d’erreur était le suivant:

Connection reset by xx.xxx.xx.xx port 22
ERROR: (gcloud.compute.ssh) [/usr/bin/ssh] exited with return code [255].

Via une recherche sur Internet, j’ai trouvé le post de GCP sur TroubleShooting SSH et un post sur I’m Coder listant toutes les solutions possibles à cette erreur. Voici celles que j’ai implémentées:

  • Mettre à jour la région et zone de ma nouvelle instance dans la configuration gcloud par défaut de mon ordinateur par les commandes suivantes (us-west1 et us-west1-a sont des exemples de région et de zone):
gcloud config set compute/region us-west1
gcloud config set compute/zone us-west1-a
  • Mettre à jour le fichier de config-ssh sur mon ordinateur avec la nouvelle zone de mon instance par la commande suivante (après s’être connectée au moins une fois à la nouvelle instance par gcloud compute ssh):
sudo gcloud compute config-ssh
  • Mettre à jour les composants SDK de gcloud sur mon ordinateur avec la commande suivante:
`sudo gcloud components update
  • Mettre sa clé publique SSH dans la configuration de l’instance utilisée selon ce guide.
  • Lancer le Jupyter notebook server de votre instance à partir d’une session tmux de votre instance. En effet, en cas de déconnexion SSH mais sans arrêt de votre instance, votre serveur de notebook continuera à fonctionner et donc tous les notebooks en cours d’entraînement. Voici un guide en ligne sur cette méthode pour la plateforme GCP (option: Jeremy Howard propose aussi d’utiliser un VPN pour lancer les Jupyter Notebook sur un Firefox lancé directement sur l’instance, ce qui évite toute perte causée par exemple par l’arrêt de votre ordinateur. J’ai publié un guide en ligne à ce propos).

Erreur n°5 | Arrêt de l’instance par la plateforme GCP

Une fois que vous avez mis en place toutes les solutions précédentes, il reste un problème possible: l’arrêt de votre instance par la plateforme elle-même.

En effet, par défaut, le script du guide Start GCP crée une instance Preemptible On, cad une instance qui ne peut pas tourner plus de 24h et qui peut être arrêtée par GCP à tout moment. Pour tester les notebooks du cours fastai, ce type d’instance est tout à fait adaptée mais pour entraîner des modèles de Deep Learning comme LM, il vous faut de la stabilité et du temps. Pour empêcher l’arrêt de votre instance par GCP, il faut passer l’option Preemptible à Off. Cependant, c’est un choix à faire en ayant en tête que le coût à l’heure de votre GPU sera multiplié par 3. Je la conseille donc de manière optionnelle (dans mon cas, je ne l’ai pas choisie).

Solution (option) | Créer une instance avec l’option Preemptible à Off

Vous pouvez faire ce choix lors de la création par le script de fastai (enlever l’option: — preemptible) ou lors de la création d’une instance similaire (cliquer sur le bouton CREATE SIMILAR dans la page descriptive de l’instance initiale) en cliquant sur le lien “Management, security, disks, networking, sole tenancy”, ce qui ouvrira différentes options dont l’optionPreemptibility. Choisir alors Off avant de cliquer sur le bouton Create en bas de page.

Explications sur le guide fastai “Start GCP” à propos de l’option Preemptible sur GCP

À propos de l’auteur: Pierre Guillou est consultant en Intelligence Artificielle au Brésil et en France. Merci de le contacter via son profil Linkedin.

--

--

Pierre Guillou

AI, Generative AI, Deep learning, NLP models author | Europe (Paris, Bruxelles, Liège) & Brazil