NLP & fastai | Language Model

Pierre Guillou
10 min readSep 10, 2019

--

Language Modeling: savoir prédire le prochain mot dans une phrase (Image Credit: A Comprehensive Guide to Build your own Language Model in Python!)

Ce post concerne les vidéos 8, 9, et 10 du cours fastai de Rachel Thomas sur NLP (A code-first introduction to NLP) ainsi que les parties des vidéos 3 (notes) et 4 (notes) à propos du NLP du cours de Jeremy Howard (Introduction to Machine Learning for Coders). Son objectif est d’expliquer les concepts clés du Language Model (LM) présentés dans ces vidéos et leurs notebooks 5-nn-imdb.ipynb, nn-imdb-more.ipynb, nn-vietnamese et nn-vietnamese-bwd associés.

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

Motivation

Avez-vous déjà aidé une personne à finir sa phrase comme par exemple un étranger qui s’exprime lentement dans votre langue native? Si oui, vous avez en fait utilisé votre Language Model (LM ou modélisation d’une langue), cad la zone de votre réseau de neurones entraînée à comprendre votre langue. En intelligence artificielle appliquée au NLP, il est également possible de créer un LM en lui faisant prédire le prochain mot à partir des mots déjà exprimés.

Nous pouvons citer aussi comme exemples d’application d’un Language Model l’autocomplétion d’un moteur de recherche comme Google ou celle de l’application pour mobile SwiftKey. Basées sur un LM, ces applications vous proposent les mots qui ont la plus grande probabilité d’être ceux que vous allez taper au clavier du fait de ceux que vous avez commencer à taper.

AutoCompletion du moteur de recherche Google utilise un Language Model (Image Credit: How Google autocomplete works in Search)

De nombreuses autres applications NLP (Natural Language Processing) peuvent utiliser un LM pré-entraîné comme l’analyse de texte, la classification, la traduction et même la génération de nouveaux textes. En effet, il semblerait que la technique d’apprentissage d’un LM (prédiction du prochain mot) lui permette d’acquérir une compréhension du langage du corpus d’entraînement. Il est donc intéressant de mieux comprendre son architecture, sa technique d’apprentissage et comment l’implémenter pour l’entraîner dans votre langue tel que le présentent Rachel Thomas et Jeremy Howard dans leur cours NLP.

Language Model

Un Language Model (LM) est un modèle qui apprend à prédire le prochain mot d’une phrase en fonction de sa connaissance des mots précédents. En faisant cela, le LM apprend à comprendre la langue du corpus d’entraînement.

Note: l’expression “comprendre la langue” exprime tout le potentiel d’un LM qui grâce à sa compréhension générale d’une langue peut alors être utilisé ensuite pour des tâches spécifiques (analyse de texte, classification, traduction, etc.). En effet, un LM apprend lors de son entraînement la grammaire, l’orthographe et la syntaxe de la langue du corpus utilisé mais aussi des concepts du type du sens d’un mot ou d’une expression par rapport à un contexte donné ainsi que du contenu informationnel présent dans le corpus.

Il est à noter qu’il existe 2 types de LM:

  • LM Général (implémentation en python ci-dessous), ce qui revient à entraîner votre LM à partir de zéro en utilisant un corpus d’entraînement suffisamment large et varié du type Wikipedia.
  • LM Spécialisé (en utilisant le Transfer Learning, cf. cet autre post sur ce sujet), ce qui revient à entraîner votre LM à partir du LM Général de la même langue mais en utilisant à présent un corpus d’entraînement d’un domaine particulier du type critiques de filmes.

Par ailleurs, le Language Modeling est une technique d’apprentissage Semi-Supervisé (Semi-Supervised Learning ou SSL): le label ou target (cad l’étiquette ou l’objectif à atteindre par le modèle) est intrinsèque au dataset d’entraînement et de validation (ici, le prochain mot d’une phrase). Le modèle va alors être entraîné sous une forme supervisée (Supervised Learning) mais sans qu’il y ait eu besoin d’étiqueter “manuellement” les données, ce que traduit l’expression SSL.

Voici l’architecture d’un LM:

  • Il s’agit d’un modèle de Deep Learning soit RNN comme un AWD LSTM — Average SGD Weight-Dropped LSTM — avec une régularisation dans 5 parties du modèle avec pour chacune des valeurs différentes de dropout, soit d’un Transformer.
  • Encoder: il s’agit de l’ensemble des premières couches d’un LM qui à partir d’une phrase en entrée (dont les mots vont tout d’abord être transformés en vecteurs d’embedings) va produire un vecteur d’activations qui représente sa compréhension par le modèle.
  • Classifier: il s’agit de l’ensemble des dernières couches d’un LM qui à partir du vecteur d’activations précédent va prédire un mot sous la forme d’un vecteur d’embeddings, ce mot étant selon le modèle le mot suivant ceux de la phrase présentée en entrée.
LM par Jeremy Howard (Image Credit: vidéo 10 du cours NLP de fastai)

LM Général d’une langue autre que l’anglais

Ressources à consulter en particulier: vidéo LM in other language than English (NLP Video 10) et notebook nn-vietnamese.ipynb

Le LM Général en anglais utilisé par fastai comme modèle pré-entraîné pour créer ensuite un LM Spécialisé dans un domaine (par exemple, le domaine des commentaires sur les films) est téléchargeable en ligne (AWD-LSTM sur github ou via la librairie fastai). Il a été entraîné à partir de Wikipedia en anglais.

Et les autres langues? Comment obtenir un LM Général dans une autre langue que l’anglais? Jeremy Howard nous enseigne dans ce cours comme faire avec l’exemple de création d’un LM Général en vietnamien.

Implémentation en Python d’un LM Général

Le code ci-dessous utilise Python et différentes bibliothèques (dont la principale est fastai v1) dans un Jupyter Notebook sur une instance avec GPU (cf. tous les tutoriaux d’installation de fastai v1 sur GPU).

Note: pour l’installation de fastai v1 sur votre ordinateur Windows 10 avec GPU, lire “How to install fastai v1 on Windows 10”. Cette installation est utile pour faire des tests à partir de petits corpus mais si vous souhaitez entraîner un LM sur un gros corpus, il est recommandé de choisir un GPU en ligne du type NVIDIA T4 ou même V100.

Nous n’y utilisons pas toutes les options possibles de codage utilisées par Rachel Thomas et Jeremy Howard afin d’attirer l’attention du lecteur sur les points essentiels.

Les 4 étapes basiques (hors étape d’application) de la création d’un Language Model Général que nous détaillons ci-après sont ainsi les suivantes:

  1. Initialisation (importation des bibliothèques de notre environnement de travail)
  2. Téléchargement du corpus d’entraînement et vérification
  3. Création du DataBunch (tokenisation, puis numérisation)
  4. Création et entraînement du learner du Language Model
  5. Application | Génération de textes par un Language Model

Voici à présent des explications détaillées sur ces 4 étapes ainsi que sur une application d’un LM à la génération de textes.

1. Initialisation

Il s’agit de construire notre environnement de travail en important les bibliothèques que nous utiliserons dans le Jupyter Notebook.

Nous allons importer la bibliothèque fastai v1 (et son module fastai.text) qui permet de manipuler des corpus de texte de l’importation du corpus textuel à la création du DataBunch et imposer l’autoreload des fichiers de la bibliothèque ainsi que l’affichage des graphiques matplotlib dans le notebook. Nous définissons également notre batch size en tenant compte de la performance du GPU que nous allons utilisé (ie, batch size de 48 pour GPU de faible performance et de 128 dans le cas contraire), notre chemin path et répertoire pour les données à importer ainsi que la langue (lang = ‘vi’ ici pour vietnamien) de notre corpus général.

Voici le code correspondant à implémenter:

# import fastai and fastai text
from fastai import *
from fastai.text import *
%reload_ext autoreload
%autoreload 2 # reload fastai files when they have been changed
%matplotlib inline # plot graphics in the notebook
bs=128 # choose batch size
torch.cuda.set_device(0) # choose GPU if more than one
data_path = Config.data_path() # path to datalang = 'vi' # corpus language
name = f'{lang}wiki'
path = data_path/name
path.mkdir(exist_ok=True, parents=True)
lm_fns = [f'{lang}_wt', f'{lang}_wt_vocab']

2. Téléchargement du corpus d’entraînement et vérification

Pour entraîner notre LM, il est nécessaire de télécharger un corpus important de texte dans la langue choisie, ce que permet de faire Wikipedia (par exemple, le Vietnamien qui a un grand nombre d’articles avec une fréquence d’édition élevée et qui jusqu’au début du cours fastai NLP ne disposait pas de LM à télécharger en ligne). Pour cela, Jeremy a créé des scripts dans le fichier nlputils.py (cf. cidéo à ce timeline mais attention: les scripts de ce fichier ne fonctionnent que sur une plateforme Linux, pas Windows).

Ce fichier nlputils.py contient une fonction get_wiki() (téléchargement, unzip, extraction) et une fonction split_wiki() qui crée un fichier texte par article dans un répertoire docs. Le téléchargement du corpus se fait à partir de la page Wikimedia Downloads via le code suivant:

# extract from get_wiki() method in nlputils.py
name = f'{lang}wiki'
xml_fn = f"{lang}wiki-latest-pages-articles.xml"
zip_fn = f"{xml_fn}.bz2"
# download link
https://dumps.wikimedia.org/{name}/latest/{zip_fn}', path/zip_fn)

Les recherches de Jeremy Howard montrent que Wikipedia a 2 types de pages: les Dump Pages qui sont des pages d’organisation de Wikipedia (autour de 1000 caractères) et les pages de textes (entre 3000 et 4000 caractères en moyenne). Comme on ne souhaite pas garder ces pages, Jeremy Howard les filtre par nombre de caractère de la page en ne gardant que celles qui ont plus de 1800 caractères (option min_test_length dans WikiExtractor).

Note: nous avons créé une version modifiée de nlputils.py (nlputils2.py) qui permet d’utiliser séparément les différentes méthodes (utile pour évaluer le temps respectif de chacune dans le notebook) avec une méthode supplémentaire clean_files() qui permet pour chaque fichier de supprimer le titre en début de texte ainsi que la balise </doc> en fin de texte. Par ailleurs, cette version fonctionne également sous Windows et pas uniquement sous Unix.

from nlputils import split_wiki,get_wiki# download, unzip, extract
get_wiki(path,lang)
# display start of the global text file
!head -n4 {path}/{name}
# create 1 file by article
dest = split_wiki(path,lang)

3. Création du DataBunch (tokenisation, puis numérisation)

Afin de pouvoir manipuler nos données textuelles à des fins d’entraînement d’un modèle de Deep Learning, il nous faut avant toute chose créer un vocabulaire en les tokénisant (un token peut représenter un début ou fin de phrase, un mot, un effet de style comme une ou des majuscules, un symbole de ponctuation, un symbole pour les mots non retenus, etc.) puis les numériser en remplaçant chaque token d’une phrase par son id dans le vocabulaire généré. Ces opérations se font lors de la création du DataBunch du corpus dans la bibliothèque fastai.

Note: par défaut, la bibliothèque fastai utilise le tokenizer SpaCy (vocab de 60 000 caractères par défaut). Depuis juillet 2019, elle dispose aussi de SentencePiece (vocab de 30 000 tokens par défaut).

DataBunch

Nous utilisons ici le Data Block Api de fastai.text pour créer le DataBunch du Language Model.

#Inputs: all the text files in dest
#We randomly split and keep 10% (10,000 reviews) for validation
#We want to do a language model so we label accordingly
data_lm = (TextList.from_folder(dest)
.split_by_rand_pct(0.1, seed=42)
.label_for_lm()
.databunch(bs=bs, num_workers=1))
# Save DataBunch
data_lm.save(f'{path}/lm_databunch')

Exploration

# get train and valid datasets tokenized and numericalized by fastai, and the vocab of tokens
train_ds = data_lm.train_ds
valid_ds = data_lm.valid_ds
vocab = data_lm.vocab
# number of texts in train and valid
len(train_ds), len(valid_ds)
# size of vocab
len(vocab.itos), len(vocab.stoi)
# display the 10 first tokens
vocab.itos[:10]
# display the first train text
article = train_ds.x[0]
article
# display the id token list of the train article 0
tokens = article.data
tokens
# from a token list, display the corresponding article
' '.join(np.array(vocab.itos)[tokens])
# Finally, show beginning of a batch
data_lm.show_batch()

Résultat de la tokénisation d’un article:

Text xxbos xxmaj tiếng xxma jViệt , còn gọi tiếng xxmaj xxmaj Việt xxmaj Nam xxunk xxmaj Việt xxunk ngữ

Nous pouvons constater que la tokénisation a en fait inséré des tokens spécifiques dont la liste est en ligne. Par exemple, xxunk (unknown) remplace les mots ou ensembles de caractères non retenus dans le vocabulaire du corpus (du fait par exemple de leur faible fréquence d’occurrence).

Note: nous pouvons obtenir un token à partir de son index (liste vocab.itos) et inversement, nous pouvons obtenir l’index du token de tout mot ou ensemble de caractères du corpus (dictionnaire vocab.stoi).

4. Création et entraînement du learner du Language Model

A cette étape, nous importons dans notre learner l’architecture du Language Model AWD-LSTM (ASGD Weight-Dropped LSTM) mais pas ses poids, ni son vocab (pretrained=False) qui ont été entraînés à partir d’un corpus en anglais alors que nous cherchons à entraîner un LM de la langue vietnamienne. Les poids de notre LM sont donc initialisés avec des valeurs aléatoires.

Note: le Language Model AWD-LSTM a été entraîné à partir du corpus WikiText-103 créé par Stephen Merity. Ce corpus est un sous-ensemble de Wikipedia en anglais qui contient plus de 100 millions de tokens dont ceux de ponctuation. AWS-LSTM représente donc un LM Général de la langue anglaise.

Création du learner

# load DataBunch
data_lm = load_data(path, 'lm_databunch', bs=bs)
# Create LM learner
learn_lm = language_model_learner(data_lm, AWD_LSTM, drop_mult=0.5, pretrained=False).to_fp16()

Note: nous diminuons par 2 les 5 dropouts par défaut du modèle AWD-LSTP (drop_mult = 0.5) car nous disposons d’un corpus relativement important et nous passons notre learner en Mixed Precision via la fonction to_fp16() afin d’accélérer son entraînement.

Entraînement du learner

Nous pouvons à présent rechercher le meilleur Learning Rate pour entraîner notre learner.

learn_lm.lr_find()
learn_lm.recorder.plot()
lr = 1e-2
lr *= bs/48

Nous pouvons à présent entraîner l’ensemble de couches de notre learner (unfreeze()) sur 10 epochs puis le sauver ainsi que le vocabulaire.

learn_lm.unfreeze()
learn_lm.fit_one_cycle(10, lr, moms=(0.8,0.7))
mdl_path = path/'models'
mdl_path.mkdir(exist_ok=True)
learn_lm.to_fp32().save(mdl_path/lm_fns[0], with_opt=False)
learn_lm.data.vocab.save(mdl_path/(lm_fns[1] + '.pkl'))

Performance d’un LM Général

L’objectif quand Wikipedia est utilisé comme corpus est d’obtenir un LM avec une performance autour de 40%.

Application | Génération de textes par un Language Model

A présent que nous disposons d’un Language Model (en l’occurrence un LM Général ici), nous pouvons l’utiliser pour créer des applications comme par exemple un générateur de textes. Il suffit pour cela d’utiliser le code suivant:

# Load databunch and learner with pretrained model + vocab
data_lm = load_data(path, 'lm_databunch', bs=bs)
learn_lm = language_model_learner(data_lm, AWD_LSTM, pretrained_fnames=lm_fns)
TEXT = "Mặc dù tiếng Việt có một" # original text
N_WORDS = 40 # number of words to predict following the TEXT
N_SENTENCES = 2 # number of different predictions
print("\n".join(learn_lm.predict(TEXT, N_WORDS, temperature=0.75) for _ in range(N_SENTENCES)))

Note: l’argument temperature de la fonction predict() est un nombre décimal non nul et inférieur ou égal à 1. Plus sa valeur est petite, moins les mots prédits sont aléatoires.

À 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