Si vous voulez fouiller dans les entrailles d’un document PDF pour en
extraire des metadonnées, des images, et même du texte, j’ai des
superbes logiciels libres pour vous:
PLAYA-PDF et
PAVÉS. Si vous voulez savoir
comment je suis arrivé là, continuez à lire. Et si vous avez besoin
d’un consultant pour vos besoins d’intelligence documentaire je suis
bien sûr disponible pour des contrats de toute sorte!
«Vous n’êtes qu’un paquet d’objets indirects!»
Comme vous savez peut-être (ou pas), je suis chercheur en
linguistique
informatique
de formation et de
métier.
En 2021, fraîchement élu conseiller municipal dans une ville du grand
St-Jérôme que je ne nommerai pas, j’ai quitté mon poste de
scientifique principal chez une compagnie (que je ne nommerai pas non
plus), maintenant division de Microsoft, car il était impossible pour
moi de servir deux maîtres travailler à temps plein à Montréal
tout étant un élu efficace et à l’écoute. La chose municipale me
semblait aussi, à l’époque, bien plus intéressante que le raffinement
des modèles d’apprentissage automatique pour la compréhension du
langage naturel.
Entre-temps, il s’est passé des choses…
Un effet secondaire de ce changement de carrière plus ou moins bien
avisé est que je suis devenu (en tout humilité) expert en analyse et
manipulation des fichiers PDF, et ce, de la manière habituelle des
informaticiens de mon genre: j’ai parti un projet de logiciel libre.
Pourquoi?
Lorsqu’on regarde les défis de gestion des documents dans
une ville ou autre organisme, on se rend très vite compte que malgré
les grands efforts des promoteurs de ODP, OOXML, HTML et autres
formats universels, en fin de compte, le PDF, c’est la lingua franca
de tous les échanges documentaires. C’est la triste conséquence de la
domination du bureaucratique par nul autre que Microsoft, dont les
logiciels font exprès de multiplier les incompatibilités non seulement
avec d’autres produits (libres ou
pas), mais ne sont souvent
même pas compatibles entre eux-mêmes.
Comment?
J’avais des critères pour l’outil que je voulais utiliser, qui ne
correspondaient pas à l’état actuel des logiciels disponibles:
Licence libre et permissive (du genre BSD, MIT).
Écrit en Python et portable entre différentes plateformes.
Interface conviviale pour le programmeur.
Accès direct aux structures interne du PDF, avec la capacité
d’extraire non seulement du texte mais les éléments de mise en page
et les metadonnées.
Rapide et efficiente, autant que possible (c’est un peu en conflit
avec #2 mais bon).
Le logiciel qui se rapproche le plus de ces critères à l’époque était
pdfplumber, un très bon
logithèque qui satisfait néanmoins aux critères 1, 2 et 3! J’y ai
même contribué un module pour l’extraction des arborescences de
structure logique. Par contre, l’efficacité n’est pas trop au
rendez-vous, surtout parce que pdfplumber, comme d’autres projets
populaires dont sa logithèque sous-jacente
pdfminer.six, doit
analyser chaque page au complet et construire toutes les structures de
données avant de retourner des informations demandées.
De paresse et de parallélisme
C’est surtout ça l’innovation de PLAYA-PDF: il est
«paresseux»,
ne traitant que les informations nécessaires pour extraire
l’information que vous désirez. Si vous, par contre, vous êtes
paresseux·se, il possède aussi un interface qui peut convertir les
metadonnées d’un PDF en
JSON, et ce, très
rapidement:
with playa.open(path) as pdf:
json.dumps(playa.asobj(pdf))
L’autre élément clé, PLAYA-PDF prend en charge plusieurs cœurs de
processeur en
parallèle,
et ce, de manière très conviviale:
with playa.open(path, max_workers=4) as pdf:
texts =list(pdf.pages.map(playa.Page.extract_text))
Par-dessus la PLAYA, les PAVÉS!
Parce que les objectifs de PLAYA-PDF sont surtout l’efficacité et
l’absence de dépendances sur d’autres logiciels, il ne prend pas en
charge des tâches de plus haut niveau, nécessitant de l’imagerie, des
heuristiques ou des modèles d’apprentissage automatique.
Pour cette raison je suis aussi en train de construire
PAVÉS qui prendra de plus en plus
en charge:
L’analyse structurelle et textuelle des PDF, dont le traitement des
tableaux et l’extraction d’unités logiques de texte.
La
visualisation
des objets dans un PDF ainsi que la conversion des pages en images.
Ce deuxième logithèque est encore en chantier mais sert déjà à faire
l’analyse nécessaire pour alimenter mes projets tels que
ZONALDA et
SÈRAFIM.
Conclusion
Si vous faites partie du petit nombre de gens auxquels ça intéresse je
vous invite à l’essayer! J’ai publié entre autres de la
documentation et quelques carnets
Jupyter qui
démontrent la fonctionnalité.
Vous pouvez bien sûr aussi contribuer au développement sur
GitHub (notez qu’il se peut que je
le déplace bientôt vers Codeberg ou autre
hébergement indépendant et au-dehors des États-Unis, mais il restera
toujours disponible sur GitHub).
TL;DR: Vous pouvez utiliser
docling directement car ses
modèles sont les plus fiables et efficaces de ceux que j’ai testés.
Mais pour comprendre comment ça fonctionne, continuez à lire!
Nous allons voir ici comment utiliser des modèles de vision
computationelle pour faire de l’analyse de mise en page d’un PDF et en
extraire les titres de sections, alinéas, listes, et tableaux. Pour
ce faire, on fera appel à la logithèque libre
Transformers qui facilite
beaucoup le téléchargement et utilisation de ces modèles en Python.
On prendra d’abord un document très simple d’une seule
page,
fait sur mesure pour ce genre d’analyse. Ceci nous permettra plus
tard (dans un autre texte) de mettre en lumière des défaillances de
certains modèles.
On utilisera pillow pour manipuler et annoter des images des pages,
playa-pdf pour extraire des metadonnées des PDFs, et le reste sont
des nécessaires pour rouler les modèles de vision. On les installe
ici en mode CPU pour éviter de télécharger des gigaoctets de n’importe
quoi que nous impose NVidia, mais si vous avez un GPU pris en charge
(même un petit GT1030 suffit) vous pouvez enlever le --extra-index-url.
Il faut dire tout de suite que les programmeurs de NVidia non
seulement génèrent des logiciels obèses mais aussi peu fiables, alors,
sous Ubuntu, il faut aussi gosser quelques affaires pour éviter d’être
pris avec un fâchant problème de
C++ (en réalité, c’est
C++ le vrai problème, comme d’habitude):
Si on veut faire de la vision, bon, ça prend des images matricielles.
Pour le moment, on utilisera le bon vieux outil
Poppler qui est installé partout
sous GNU/Linux (et beaucoup moins ailleurs, désolé) pour convertir des
PDF en images de pages.
Ce qu’il faut comprendre avec des modèles d’apprentissage automatique
(et que les auteurs de certains logithèques populaires semblent
ignorer) c’est que, lorsque possible, ces modèles sont plus
performants lorsque les données qu’on leur demande de traiter
ressemblent à celles sur lesquelles ils sont entraînés. Puisque tous
les modèles d’analyse de mise en page sont entraînés sur
DocLayNet, qui est
composé d’images de page en format carré avec
anticrénelage,
et puisque PDF est un format vectoriel qui nous permet de générer
des images de n’importe quelle taille et format, il est préférable de
créer d’abord des images dans le format attendu.
D’ailleurs il ne sert à rien de créer des images de plus haute
résolution que celle prise en charge par le modèle! C’est tout
simplement du gaspillage d’énergie, de temps et de stockage (ou,
autrement dit, un crime contre le climat et l’économie) puisqu’il
faudra rééchantilloner par la suite ces images. Heureusement les bons
modèles comme ceux de Docling nous diront leur format préféré.
Alors, on va construire une simple fonction pour nous donner des images
du format souhaité, en utilisant les arguments -scale-to-x et
-scale-to-y de Poppler:
On utilisera un modèle
RT-DETR
pour identifier les éléments dans les images. Pour des raisons
inconnues, le groupe Docling n’a pas mis son modèle dans un endroit
standard, alors on ne peut malheureusement pas utiliser
AutoModel.from_pretrained. Ce n’est pas grave, on va tout
simplement télécharger les fichiers manuellement:
Et les noms des éléments qu’il peut extraire (mais lisez plus loin…):
id2label = model.config.id2label
On va télécharger le document:
importrequestsr = requests.get("https://ecolingui.ca/pdf_structure.pdf")
r.raise_for_status()
withopen("pdf_structure.pdf", "wb") as fh:
fh.write(r.data)
Et hop, utiliser le modèle est très simple:
importtorchwith torch.inference_mode():
for image in popple("pdf_structure.pdf"):
inputs = processor(images=image, return_tensors="pt")
outputs = model(**inputs)
Ces outputs ne sont pas dans un format super intéressant, alors on
va utiliser le RTDetrImageProcessorFast pour avoir des coordonnées
qui correspondent à l’image:
Ceci nous donne quelque chose plus intéressant, qu’on peut interpréter
en utilisant le id2label mentionné ci-haut (notez qu’il faut
extraire les valeurs des tensor qui nous retourne le modèle):
Mais oups! Les classifications sont un peu suspects, car il n’existe
aucune image (Picture) dans notre document! Bon, il semble que les
développeurs de DocLing se sont trompés un peu et il faut ajouter 1
aux indices des classes:
for score, label, box inzip(
img_results["scores"], img_results["labels"], img_results["boxes"]
):
label = id2label[label.item() +1]
box = [round(x) for x in box.tolist()]
score = score.item()
print(f"Élément: {label} à {box} avec confiance {score}")
Ce qui nous donne quelque chose d’intéressant:
Élément: Text à [59, 138, 575, 197] avec confiance 0.9866001009941101
Élément: List-item à [78, 309, 571, 369] avec confiance 0.9816325902938843
Élément: List-item à [97, 290, 234, 300] avec confiance 0.9407719969749451
Élément: List-item à [78, 271, 171, 279] avec confiance 0.9286511540412903
Élément: Table à [58, 400, 581, 438] avec confiance 0.9260659217834473
Élément: List-item à [78, 252, 170, 261] avec confiance 0.925058901309967
Élément: Section-header à [59, 216, 109, 226] avec confiance 0.9144930243492126
Élément: Section-header à [59, 384, 113, 393] avec confiance 0.9069509506225586
Élément: Section-header à [59, 98, 115, 109] avec confiance 0.8970564603805542
Élément: Section-header à [192, 59, 447, 76] avec confiance 0.8865164518356323
Élément: Text à [59, 119, 224, 128] avec confiance 0.8446756601333618
Élément: Text à [59, 235, 157, 242] avec confiance 0.8046810626983643
Élément: Section-header à [59, 235, 157, 242] avec confiance 0.5821080803871155
Élément: Section-header à [59, 119, 224, 128] avec confiance 0.5068144202232361
On peut vérifier que tout a été bien identifié en utilisant ImageDraw:
fromPILimport ImageDraw
draw = ImageDraw.Draw(image)
for label, box inzip(img_results["labels"], img_results["boxes"]):
label = id2label[label.item() +1]
box = [round(x) for x in box.tolist()]
draw.rectangle(box, outline="red")
draw.text((box[0], max(0, box[1] -12)), label, fill="red")
image.save("pdf_structure.png")
Et voilà:
Mais attend, cette image a un aspect un peu bizarre! Si on veut
vraiment utiliser les coordonnées, il faut plutôt les transformer pour
correspondre à la page originelle. On peut le faire facilement en
cherchant les informations avec PLAYA-PDF:
Nous avons vu comment utiliser un modèle de vision pour identifier les
éléments de mise en page dans un PDF. Pour la suite des choses, on
regardera comment trouver les textes qui correspondent à ces éléments.
Comme mentionné dans le dernier billet de ce blogue, il existe
d’autres modèles de vision qui fonctionnent essentiellement de la même
manière, mais qui sont en général beaucoup moins rapides et fiables.
Un prochain texte va en faire la comparaison.
Comme mentionné dorénavant, le format PDF est un format
de présentation, à la différence du HTML par exemple, qui sépare dans
la mésure du possible la structure sémantique du texte et sa mise en
page. Concrètement cela veut dire que, en théorie (on aimerait tous y
vivre!), l’extraction du texte même d’une page HTML très « visuelle »,
avec une mise en page comprenant des multiples colonnes, des images,
des figures, etc, se résume à simplement enlever les tags.
Comme aussi mentionné dorénavant, le standard PDF dans sa
déclinaison « universellement
accessible »
admet une extraction du texte et même de la structure sémantique
légèrement plus compliquée mais néanmoins faisable. Malheureusement,
il suffit qu’on oublie de cocher la case PDF/UA en sauvegardant un
fichier, ou qu’on sélectionne « imprimer en format PDF » au lieu de
« exporter », ou qu’on passe le PDF par un logiciel douteux, pour que
toute cette belle structure tombe à l’eau. On se retrouve avec des
fragments de texte positionnés absolument, souvent sans séparation
entre les mots et parfois même dans une ordre arbitraire.
Dont la nécessité d’une analyse de la mise en page, pour identifier et
séparer le texte, les figures, et les tableaux, mais aussi pour
identifier les éléments textuelles, dont les titres et listes, ainsi
que les artéfacts textuelles qui ne font pas partie du contenu, dont
les en-têtes, les pieds de page et les captions de figures. Étant
donné le fait que les éléments d’un PDF sont positionnés absolument
sur une page (sans référence à une grille ou autre structure
visuelle), la diversité de formats de papier, de polices de
caractères, de marges et entrelignes, entre autres, il est presque
impossible de concevoir des règles pour prendre en charge tout cela à
moins de le refaire pour chaque nouveau document ou presque.
Dont aussi la nécessité (il me semble que je me répète souvent!)
d‘utiliser… l’apprentissage machine (toé IA chose machin). On en a
déjà parlé un peu par rapport aux éléments du texte. On
peut catégoriser les types d’analyses propices à l’apprentissage.
Analyse textuelle
Ceci est le type d’analyse le plus simpliste et probablement le plus
répandu. En présumant une extraction préalable du texte,
correspondant le plus possible à la forme perçue par un lecteur, on
peut faire une analyse textuelle (parsing) ou une classification
de séquence pour répérer des éléments structurels.
Évidemment, puisque toute l’information provenant de la mise en page a
été évacué par le processus d’extraction de texte, on n’a peu de
chances de reconstruire la structure du document de façon robuste ou
fiable.
Analyse espacielle
Par défaut, ALEXI fait une
analyse espacielle, c’est à dire qu’il prend compte de l’emplacement
des éléments de texte ainsi que des attributs typographiques (taille
de police, caractères gras ou italiques, etc.) pour identifier des
éléments tels que titres de sections, éléments de listes, etc.
Bien qu’il utilise l’apprentissage machine, d’autres logiciels
utilisent aussi des méthodes algorithmiques ou heuristiques, par
exemple pdfminer.six ou
camelot.
Analyse visuelle
Dernièrement, il existe une tendance à faire une analyse purement
visuelle de la mise en page pour identifier les éléments
structurelles. Alors que l’analyse textuelle évacuait toute la mise
en page et les attributs visuels du document, une analyse visuelle
fait exactement le contraire - chaque page d’un PDF est transformé en
image matricielle, qui est par la suite analyser par un modèle de
vision tel que
DeformableDETR,
YOLOX ou
YOLO, qui a été
préalablement entraîner sur un corpus d’images semblables.
Quoiqu’il soit possible d’utiliser ces modèles par le biais de des
logithèques peu fiables provenant de compagnies qui préfèrent prendre
votre argent et/ou vos données personnelles, ceci n’est aucunement
nécessaire. En plus, il semble que ces logithèques libres
fonctionnent essentiellement comme appât pour les services payants /
espions. Nous allons donc, dans le prochain billet, regarder comment
utiliser les modèles directement pour éviter divers problèmes reliés à
ces logithèques.
(La seule exception dans cette bande là est probablement
DocLing qui provient d’un groupe
de récherche réputé.)