Extraire la structure et le contenu d'un PDF
Ce billet présente une module d’extraction d’information pour les règlements municipaux en format PDF, qui sert à alimenter un moteur de récherche. Il est la deuxième partie d’une série de billets qui détailleront la conception et implémentation de celle-là.
Analyse d’un règlement
Comme déjà mentionné, on se concentrera d’abord sur l’analyse d’une sorte précise de document, les règlements d’urbanisme de la ville de Sainte-Adèle. Ces documents ont en théorie une structure bien définie et énoncée explicitement dans leur premier chapitre:
Dans l’abstrait on peut alors considérer cela comme une instance d’analyse syntaxique. En partant d’un grammaire (essentiellement la rubrique ci-haut) il faut simplement trouver l’arborescence correspondant aux titres et textes dans un document spécifique. Comme on verra, il y a plusieurs détails qui rendent cela un peu plus compliqué mais c’est grosso modo la démarche à suivre.
On n’utilisera pas pour autant une logithèque existante d’analyse (parsing) puisque celles-ci sont habituellement conçues pour fonctionner au niveau des mots et sont limitées à des textes assez courts. À sa place, on passera par une phase d’analyse textuelle pour identifier les étendues de textes (titres, alinéas, etc) pertinentes, puis on construira par la suite l’arborescence avec quelque chose qui ressemble à l’analyse ascendante.
Au merveilleux pays des PDF
Comment alors identifier ces fameuses étendues de textes? C’est ici que ça se complique! Comme vous savez peut-être, le PDF est un format de présentation, c’est à dire que c’est la forme visuelle d’un document qui y est représentée et pas la forme textuelle. Un PDF (numérisé par exemple) peut même ne contenir aucun texte. Souvent, un PDF contient à la fois l’image numérisé du document et un texte “invisible” provenant d’une reconnaissance optique des caractères - c’est ce texte qu’on voit lorsqu’on fait un copier-coller d’un paragraphe. On peut le voir dans ce règlement par exemple.
On ne fera pas de OCR ici, on présume à la base qu’il existe une forme
textuelle accessible dans les fichiers PDF. Ce n’est pourtant pas
tout à fait simple d’extraire ce
texte.
Il en existe plusieurs logiciels
libres plus ou moins
performants, mais mon choix s’est arrêté sur
pdfplumber, ce qui peut
sembler illogique puisqu’il n’est pas le plus performant. Si vous
voulez tout simplement extraire le texte en soi avec la plus de
fidelité il est conseillé d’utiliser plutôt
pypdf ou
pypdfium2 qui
fournissent des fonctions rapides et simples à utiliser. L’avantage
de pdfplumber
est que, en plus d’extraire le texte, celui-ci est
relié à sa mise en page, qu’on utilisera pour mieux identifier les
titres et faire le lien entre le texte et les images.
L’autre avantage est que le code source de pdfplumber
est facilement
compréhensible, ce qui nous permet d’y ajouter de la fonctionalité.
Par exemple, ce n’est pas tout à fait vrai qu’il n’y a pas de
structure logique dans un PDF - en fait, il peut y en avoir, mais
encore, les autres logithèques ne permettent pas de facilement le
mettre en relation avec le texte. J’ai fini par contribuer des
fonctionalités qui le
permettent, et qui se trouvent pour l’instant dans une version
modifiée.
Chaîne de traitement
Comme est l’habitude des projets en traitement automatique de texte, le processus est décomposé dans une chaîne d’étapes successives (ou pipeline) qui rajoutent de l’analyse au texte brut. Ceci nous permet de tester et optimiser chaque sous-analyse séparément. Cette chaîne comprend, notamment:
- La conversion de PDF en séquence de mots, figures, et tableaux avec leur mise en page.
- La segmentation du flux de mots en blocs de texte (des titres, des alinéas, des listes et des tableaux).
- L’identification automatique des types de blocs.
- L’extraction de faits saillants du texte de chaque bloc (les numéros d’articles, sections et chapitres et les dates d’adoption de règlements)
- L’analyse syntaxique qui donne une structure textuelle.
On va se concentrer sur les trois premières fonctions pour le moment.
Annotation des données
Parce que les documents sont d’une taille assez importante et l’analyse en est de nature inexacte, il nous prend d’abord un sous-ensemble sur lequel on pourra en vérifier la précision en développant l’algorithme, puis un échantillon indépendant pour tester celui-ci afin de vérifier sa capacité de traiter des nouveaux documents. On est bien sûr dans une méthodologie typique de l’apprentissage automatique, mais même si on en utilise pas, on aura besoin d’un ensemble de données annotées.
Ceci nécessite qu’on définisse notre schéma d’annotation et les traits
(features) d’entrée pour la chaîne de traitement. Ce qui nous
fournisse pdfplumber
est une liste de tous les caractères pour
chaque page d’un document, avec leur positions, tailles, noms de
police, et couleurs. Il est également possible d’extraire les mots
d’un page (ou ce qui pourrait ressembler à des mots) avec la fonction
extract_words
.
Pour le moment, on va utiliser extract_words
au lieu de faire une
tokenisation nous-même en partant des caractères, mais celle-ci est
aussi une option à regarder afin de profiter de modèles du type
Transformer tel que CamemBERT, qui
agissent sur des sous-séquences de caractères plutôt que des mots
complets.
Les traits tels que fournis par pdfplumber
sont trop détaillés dans
certains aspects, alors on en fera un post-traitement avant de les
écrire dans des fichiers CSV. Ceci nous permettra d’en faire
l’annotation tout simplement avec un logiciel de feuille de calcul,
puisqu’il ne semble pas exister une meilleur solution à la fois libre
d’accès et gratuit pour cette tâche. Puisque Excel et LibreOffice ont
une fonction saisie automatique basée sur les autres valeurs d’une
colonne, il est assez facile de rentre les annotations, puis de les
« étirer » pour couvrir les mots adjacents.
Pour le schéma d’annotation, comme mentionné ci-haut, on se limite à
des blocs de texte. Par contre, puisque l’identification sera faite
sur ces mêmes blocs de texte, on peut aussi marquer le type de chaque
bloc. Nous allons faire l’annotation dans le format IOB
,
c’est-à-dire, le premier mot de chaque bloc est marqué avec B-
suivi
par le type de bloc, les mots subséquents avec I-
suivi par le type
de bloc, et les mots à exclure complètement (il n’y en a pas beaucoup)
avec O
. Voir par exemple, un exemple de fichier CSV
complété ici.
Les types de blocs annotés sont:
TOC
: Les tableaux de matières sont marqués au complet avec cette annotation. Pour le moment, on va les ignorer et simplement extraire la structure du texte lui-même.Titre
: Les titres qui ne correspondent pas à un élément structurel spécifique sont marqués ainsi.Chapitre
,Section
,SousSection
,Article
: pour les titres d’éléments spécifiques. Si nécessaire il est possible de les transformer enTitre
pour évaluer la segmentation toute seule.Alinea
: Les alinéas sans mise en page particulière.Liste
: Les éléments de listes (soit des énumérations ou des définitions). Chaque élément est marqué avec son propre bloc (c’est à dire avecB-Liste
au premier mot etI-Liste
pour les subséquents)Tete
,Pied
: Les en-têtes et pieds de pages. On ne les marque pas avecO
tout simplement puisque, même s’ils ne font pas partie du texte, ils peuvent contenir des informations utiles et faciles d’identifier, comme le titre du document ou le chapitre.
Dès qu’un document (ou même une partie d’un document) est annoté, il est possible d’entraîner un modèle là-dessus et l’utiliser pour accélérer l’annotation des documents suivants. Le prochain billet décrira un peu plus en détail le processus d’annotation en utilisant les outils ALEXI.