Blog perso d'Ozwald - misc category
Logo Blog perso d'Ozwald

CamemBERT

Par Oz le - misc
I.A. python

Parmi les domaines qui bougent beaucoup en ce moment, on ne peut pas passer à côté de l'intelligence articifielle. Les avancées théoriques et pratiques sont tellement rapides dans le domaine que, presque à chaque fois que je me repenche dessus, je suis abasourdi par les nouveautés disponibles. J'en veux pour exemple les avancées récentes dans la manipulation de langues écrites. Il y a encore 10 ans, aucune machine ne pouvait se targuer de comprendre ou de générer du texte correctement. Aujourd'hui, plusieurs réseaux de neurones sont capables de comprendre des textes, de les résumer, voire d'en générer de plutôt convaincants à partir de "rien" !

"Camembert" - CC BY SA by "Steven Lilley" on Flickr

Et le plus fort dans tout ça, c'est qu'il est possible de télécharger des réseaux pré-entrainé a priori très convaincants. J'ai longtemps hésité à me jeter sur un réseau GPT2 (qui semble être ce qui se fait de mieux en génération) ou sur un réseau BERT (qui semble être ce qui se fait de mieux pour du traitement de langage écrit polyvalent : compréhension, résumé, génération, etc.) puis j'ai trouvé un réseau BERT qui avait été entrainé en français. Comme j'aime bien jouer en français, notamment pour le traitement du langage, j'ai donc jeté mon dévolu sur cette option.

Installer le réseau de neurones

Le réseau en question s'appelle camemBERT et est disponible librement. Cependant, comme souvent dans ce domaine de l'informatique à la pointe de la recherche, on va devoir installer beaucoup de dépendances dans des versions très récentes1, pour le faire fonctionner. Je recommande donc vivement d'utiliser un environnement virtuel Python si vous souhaitez conserver un système stable :

user@oziris:~$ python3 -m venv camembert.venv
user@oziris:~$ source camembert.venv/bin/activate

Maintenant qu'on est dans un environnement virtuel un minimum cloisonné, regardons ce par quoi le tutoriel va commencer :

import torch
camembert = torch.hub.load('pytorch/fairseq', 'camembert')
camembert.eval()  # disable dropout (or leave in train mode to finetune)

Logiquement, ça commence par un import de torch. À moins que vous n'ayez déjà installé PyTorch dans votre environnement global, il va donc falloir l'installer dans notre environnement virtuel :

(camembert.venv) user@oziris:~$ # D'abord, on vérifie que, effectivement, on n'a pas installé PyTorch précédemment :
(camembert.venv) user@oziris:~$ python3 -c 'import torch'
Traceback (most recent call last):
  File "<string>", line 1, in <module>
ModuleNotFoundError: No module named 'torch'
(camembert.venv) user@oziris:~$ # Maintenant qu'on est fixé, on commence à peupler notre environnement virtuel
(camembert.venv) user@oziris:~$ pip install torch  # Et oui : le paquet "pytorch" s'appelle "torch"...
Collecting torch
(...)
Installing collected packages: typing-extensions, numpy, torch
Successfully installed numpy-1.20.0 torch-1.7.1 typing-extensions-3.7.4.3

On voit que, en plus de PyTorch, on s'est pris numpy et typing-extensions. On a bien fait de se mettre dans un environnement séparé du système. Donc, l'import de torch ne remonte plus d'erreur maintenant, c'est bon signe :

(camembert.venv) user@oziris:~$ python3 -c 'import torch' && echo "Victoire"
Victoire

On peut passer à la seconde ligne du tutorial qui semble être le (télé)chargement du réseau de neurone CamemBERT :

>>> import torch
>>> camembert = torch.hub.load('pytorch/fairseq', 'camembert')
(...)
RuntimeError: Missing dependencies: hydra-core, omegaconf, regex, requests

Visiblement, il nous manque quelques dépendances. Qu'à cela ne tienne, on est dans un environnement jetable exprès donc on peut se permettre d'ajouter pleins de librairies plus ou moins instables :

(camembert.venv) user@oziris:~$ pip install hydra-core omegaconf regex requests
(...)
ModuleNotFoundError: No module named 'sentencepiece'
(...)
ImportError: Please install sentencepiece with: pip install sentencepiece

Encore un p'tit module qui manque. Pas grave, on l'ajoute :

(camembert.venv) user@oziris:~$ pip install sentencepiece
(...)
    ./build_bundled.sh: 15: ./build_bundled.sh: cmake: not found
    make: *** Pas de cible spécifiée et aucun makefile n'a été trouvé. Arrêt.
    make: *** Aucune règle pour fabriquer la cible « install ». Arrêt.
    env: « pkg-config »: Aucun fichier ou dossier de ce type
    Failed to find sentencepiece pkg-config
(...)

Okay, mon mauvais2, je réalise cet article sur une Debian stable et j'ai visiblement trop l'habitude d'être sur Gentoo où tous les outils de compilation du monde sont forcément installés. Il me manque cmake et pkg-config, donc je répare cet oubli (en installant cmake et pkg-config au niveau de mon système, donc on déborde de l'environnement virtual python là) puis je re-lance l'installation de sentencepiece dans l'environnement virtuel :

root@oziris:~# apt install cmake pkg-config
(...)
(camembert.venv) user@oziris:~$ pip install sentencepiece
(...)
Successfully installed sentencepiece-0.1.95

Ça s'est plutôt correctement passé, on peut donc revenir au tutoriel et tenter, à nouveau de (télé)charger le réseau camemBERT :

>>> import torch
>>> camembert = torch.hub.load('pytorch/fairseq', 'camembert')
(...)
[...]camembert.venv/lib/python3.7/site-packages/torch/cuda/__init__.py:52: UserWarning: CUDA initialization: Found no NVIDIA driver on your system. Please check that you have an NVIDIA GPU and installed a driver from http://www.nvidia.com/Download/index.aspx (Triggered internally at  /pytorch/c10/cuda/CUDAFunctions.cpp:100.)
  return torch._C._cuda_getDeviceCount() > 0
Unable to build Cython components. Please make sure Cython is installed if the torch.hub model you are loading depends on it.

Installer CUDA

Cette fois il y a un minuscule piège : le message d'erreur qui apparait en dernier n'est pas le bon. En effet, j'ai déjà cython installé dans mon système, mais pas encore Cuda. Là aussi, on va déborder de l'environnement virtuel python et on va installer des paquets systèmes (en suivant les instructions officielles :

root@oziris:~# apt install nvidia-cuda-dev nvidia-cuda-toolkit
(...)
Après cette opération, 2 534 Mo d'espace disque supplémentaires seront utilisés.
Souhaitez-vous continuer ? [O/n]
(...)
update-initramfs: Generating /boot/initrd.img-***censored*** 
gzip: stdout: No space left on device 
E: mkinitramfs failure cpio 141 gzip 1
update-initramfs: failed for /boot/initrd.img-***censored*** with 1.
dpkg: erreur de traitement du paquet initramfs-tools (--configure) :
 installed initramfs-tools package post-installation script subprocess returned error exit status 1
Des erreurs ont été rencontrées pendant l'exécution :
 initramfs-tools
E: Sub-process /usr/bin/dpkg returned an error code (1)

On pourrait croire que ce sont les 2,5Go de paquets qui posent problème mais pas du tout. C'est ma partition /boot qui est pleine :

root@oziris:~# df -h|grep boot
/dev/***censored***            236M    202M   22M  91% /boot

Heureusement, ce problème et ses solutions semblent bien documentées sur debian-like. Dans mon cas un simple apt autoremove suffira.

root@oziris:~# apt autoremove && df -h|grep boot
/dev/***censored***            236M    110M  114M  50% /boot

On peut donc relancer l'installation de Cuda, constater qu'on ne prend plus l'erreur, et retourner au tutorial :

>>> import torch
>>> camembert = torch.hub.load('pytorch/fairseq', 'camembert')
[...]camembert.venv/lib/python3.7/site-packages/torch/cuda/__init__.py:52: UserWarning: CUDA initialization: The NVIDIA driver on your system is too old (found version 10010). Please update your GPU driver by downloading and installing a new version from the URL: http://www.nvidia.com/Download/index.aspx Alternatively, go to: https://pytorch.org to install a PyTorch version that has been compiled with your version of the CUDA driver. (Triggered internally at  /pytorch/c10/cuda/CUDAFunctions.cpp:100.)
  return torch._C._cuda_getDeviceCount() > 0
Unable to build Cython components. Please make sure Cython is installed if the torch.hub model you are loading depends on it. 

Forcément. J'avais déjà eu ce problème à chaque fois que je faisais du CUDA (et plus particulièrement sur Debian) : les drivers évoluent vite et abandonnent rapidement la rétro-compatibilité. Avant de se lancer dans une course à la mise à jour (puisqu'on est hors environnement virtuel et que je veux conserver un système stable) on peut vérifier les versions de CUDA et de PyTorch qu'on a installé puis chercher si, à tout hasard, une version de pytorch relativement récente serait compatible. En faisant ça, je réalise que, pour une version de PyTorch donné, il existe parfois plusieurs paquets différents pour être compatible avec des versions différentes de CUDA. Cool ! Je tente donc d'installer la dernière version de PyTorch, mais compatible avec le CUDA 9.2 que j'ai installé sur ma debian stable :

root@oziris:~# apt show nvidia-cuda-dev 2>/dev/null|grep Version  # On vérifie la version de CUDA qu'on a
Version: 9.2.148-7
(camembert.vent) user@oziris:~$ pip freeze|grep torch  # On vérifie la version de PyTorch qu'on avait
torch==1.7.1
(camembert.vent) user@oziris:~$ pip uninstall torch
(...)
(camembert.vent) user@oziris:~$ pip install torch==1.7.1+cu92 -f https://download.pytorch.org/whl/torch_stable.html
(...)
Successfully installed torch-1.7.1+cu92

Tester CamemBERT

On re-tente le tutorial en espérant que, cette fois, le (télé)chargement de camemBERT va bien se passer et qu'on va pouvoir taper le 3ème ligne du tutorial :

>>> import torch
>>> camembert = torch.hub.load('pytorch/fairseq', 'camembert')
Using cache found in /home/user/.cache/torch/hub/pytorch_fairseq_master
Unable to build Cython components. Please make sure Cython is installed if the torch.hub model you are loading depends on it.
>>> masked_line = 'Le camembert est <mask> :)'
>>> camembert.fill_mask(masked_line, topk=3)
[('Le camembert est délicieux :)', 0.49091005325317383, ' délicieux'), ('Le camembert est excellent :)', 0.10556904226541519, ' excellent'), ('Le camembert est succulent :)', 0.0345332995057106, ' succulent')]

YEAH ! On a un camembert qui fonctionne3. En résumé : on lui donne un texte avec un trou représenté par <mask>, et il nous répond avec des propositions pour remplir le trou. Comme on peut le voir ci-dessus, ces propositions sont plutôt très pertinentes. Maintenant qu'on a réussi à reproduire l'exemple du tuto, on peut jouer avec de pleins de façons différentes. Par exemple en vérifiant ce qu'il pense de différents smileys :

>>> import torch
>>> camembert = torch.hub.load('pytorch/fairseq', 'camembert')                          
>>> camembert.fill_mask('Le camembert est <mask> :-)')[0][0]
'Le camembert est délicieux :-)'
>>> camembert.fill_mask('Le camembert est <mask> :-/')[0][0]
'Le camembert est introuvable :-/'
>>> camembert.fill_mask('Le camembert est <mask> :-(')[0][0]
'Le camembert est introuvable :-('

C'est plutôt pas mal du tout cette histoire ! CamemBERT a bien réalisé que ce qui était après le "trou" avait changé, et il en a déduit un bouche-trou plus pertinent. Il faut savoir qu'une des forces des réseaux BERT, c'est de prendre en compte du contexte avant et apres un mot (ou un trou) considéré. On peut donc tenter de modifier également ce qu'il y a en début de phrase. Par exemple, vérifions comment il réagit lorsqu'on modifie légèrement le sujet :

>>> camembert.fill_mask('Le gros camembert est <mask> :-)')[0][0]
'Le gros camembert est arrivé :-)'
>>> camembert.fill_mask('Le petit camembert est <mask> :-)')[0][0]
'Le petit camembert est délicieux :-)'
>>> camembert.fill_mask('Le gros camembert est <mask> :-(')[0][0]
'Le gros camembert est arrivé :-('
>>> camembert.fill_mask('Le petit camembert est <mask> :-(')[0][0]
'Le petit camembert est délicieux :-('
>>> camembert.fill_mask('Le petit bateau est <mask> :-(')[0][0]
'Le petit bateau est mort :-('
>>> camembert.fill_mask('Le petit bateau est <mask> :-)')[0][0]
'Le petit bateau est arrivé :-)'

Tout n'est pas comme on aurait pu l'imaginer, mais rien de toute ceci ne semble totalement délirant (après tout, il a le droit d'être légitimement heureux et triste que le "gros camembert" soit arrivé).

Et si on s'éloigne franchement de l'exemple du tuto, est-ce-que ça tient encore la route tout ça ?

>>> camembert.fill_mask('Bonjour mon cher <mask> !')[0][0]
'Bonjour mon cher ami !'
>>> camembert.fill_mask('Comment allez <mask> ?')[0][0]
'Comment allez vous ?'
>>> camembert.fill_mask("J'espère que vous <mask> bien.")[0][0]
"J'espère que vous allez bien."
>>> camembert.fill_mask("Que pensez-vous de cette belle <mask> ?")[0][0]
'Que pensez-vous de cette belle exposition ?'

Franchement bluffant...Mais ces phrases peuvent sembler assez "évidentes" et je soupçonne qu'un algo très con mais disposant d'un nombre de livres assez conséquent serait parvenu au même résultat. Voyons-voir ce qu'il se passe si on tend des pièges :

>>> camembert.fill_mask("Après une grosse journée de travail j'aime me <mask>.")[0][0]
"Après une grosse journée de travail j'aime me reposer."
>>> camembert.fill_mask("Avant une grosse journée de travail j'aime me <mask>.")[0][0]
"Avant une grosse journée de travail j'aime me détendre."
>>> camembert.fill_mask("J'aime la choucroute avec une bonne <mask>.")[0][0]  # Moins pertinent...BIERE !
J'aime la choucroute avec une bonne sauce."
>>> camembert.fill_mask("Hisser les voiles <mask>.")[0][0]  # Pardon ?
'Hisser les voiles sur.'
>>> camembert.fill_mask("Hisser les voiles <mask>!")[0][0]  # Pas mal.
'Hisser les voiles maintenant!'
>>> camembert.fill_mask("Hisser les voiles, <mask>!")[0][0]  # Décidément, je n'arriverai pas à avoir "moussaillons" :-p
'Hisser les voiles, bonjour!'

Conclusion

Même s'il n'est pas parfait, camemBERT est vraiment bluffant. Par contre, en décidant de partir sur un réseau BERT plutôt que sur un réseau GPT2 je n'avais pas anticipé un petit souci : le coeur de camemBERT est capable de beaucoup de choses (compréhension de texte notamment) mais, si on ne veut/peut pas mettre les mains dans le cambouis, on doit se contenter de ce qui est livré dans le tuto (à savoir le remplissage de texte à trou). Finalement, peut-être que jouer avec un GPT2 m'aurait donné accès à un "jouet" plus utile... Affaire à suivre ?

  1. Comprendre : très instables.
  2. Francisation à la hache de my bad
  3. Oui...cette phrase est étrange.