Automatiser l’extraction d’informations d’un PDF

Rapports internes, documents administratifs, bilans, plaquettes informationnelles, etc. Une société a souvent à sa disposition de nombreux documents PDFs riches en données. Pourtant ces documents sont souvent inexplorités : non structurés, ils peuvent contenir du texte comme des images, le tout présenté avec des mises en page et des formats très divers… Une multiplicité qui complique leur analyse et l’extraction de données.

Dans un projet récent il nous a été demandé d’automatiser l’extraction de certaines informations présentes dans des PDFs. Ces derniers étaient assez volumineux (entre 300 et 500 pages), alors que les données désirées ne concernaient que quelques pages. Un des enjeux principaux était donc de parcourir efficacement les documents pour ne retenir que les éléments intéressants. La base de PDFs était très hétérogène, ce qui rajoutait une difficulté supplémentaire. En effet les PDFs provenaient d’émetteurs différents et n’avaient pas les mêmes mises en forme, structures… Une même information pouvait être présentée très différemment d’un PDF à l’autre.

A priori simple, cette tâche s’est révélée très complexe et a demandé à mélanger à la fois des méthodes d’analyse NLP (Natural Language Processing) et de reconnaissance d’image. Dans cet article nous reviendrons sur les différentes étapes suivies pour automatiser ce process.

Comment lire un PDF ?

La première étape est avant toute chose d’ouvrir les fichiers, et d’en extraire les éléments qui vont nous intéresser : le texte, les images, et éventuellement des méta données portant sur leur mise en forme. Nous considérons ici les PDFs classiques, construits à partir d’éditeurs de texte et non les documents scannés (où toutes les pages sont des images). Notons que pour extraire le texte de ces scans, il faut plutôt passer par des méthodes d’OCR (optical character recognition) qui traduiront l’image en texte.

Plusieurs librairies existent pour ouvrir des PDF sur Python, mais toutes ne se valent pas ! Certaines n’arrivent pas à extraire correctement le texte dès qu’il a un format ou un encodage particulier.

  • PyPDF2 est sans doute la plus connue. Elle est cependant très instable car peu paramétrable. Surtout, elle ne fonctionne pas sur les PDFs en français, car elle n’est pas conçue pour lire les caractères spéciaux latins.
  • PdfMiner (ou PdfMiner.six) n’a quant à elle pas de problèmes pour extraire les textes en français, mais présente un défaut majeur : son temps d’exécution (1 minute pour lire 300 pages contre 1 seconde pour les autres librairies).
  • PdfLib peut s’avérer efficace et rapide mais peine sur certaines mises en forme. Notamment si le texte est présenté en colonnes, il sera quand même lu de gauche à droite, et renverra un résultat erroné.
  • PyMuPdf (anciennement appelé fitz) s’est, lors de notre benchmark, révélée être la plus efficace. Portée sur Python depuis C, elle est rapide, et fonctionne parfaitement sur les textes en français, quelle que soit la mise en page utilisée.

C’est pourquoi nous avons choisi d’utiliser PyMuPdf. Au-delà de l’extraction de texte, PyMuPdf présente en outre des features intéressantes permettant de lire le pdf page par page, de convertir une page en image, ou de lire une page au format XML. Une feature que nous avons beaucoup utilisée est celle retournant les blocs de texte. Pour chaque bloc est donné le texte et des coordonnées (x, y) permettant de le localiser sur la page.

Structurer le PDF grâce à la table des matières

Un moyen de mieux cibler les pages du PDF qui nous intéressent est d’en extraire la table des matières. Il peut arriver qu’un PDF possède une table des matières en méta données. Dans ce cas, à l’ouverture du PDF sur un viewer on verra un volet table des matières apparaître sur le côté. PyMuPDF propose une fonction pour récupérer cette table qui retourne pour chaque entrée le titre et le numéro de page. Si la table n’est pas écrite en métadonnées une autre solution est d’aller parcourir les premières ou dernières pages du PDF à la recherche d’un sommaire. Bien que l’aspect des tables des matières varie d’un document à l’autre, il existe des patterns :

  • Elles sont souvent introduites par certaines expressions comme “Sommaire”, “Table des matières”, etc.
  • Elles contiennent le plus souvent des paginations.
  • Elles sont toutes construites sur la répétition d’éléments de la forme “titre de partie /numéro de page”.

On peut donc facilement à l’aide de regex, et éventuellement avec un simple classifier, identifier la page de sommaire et en extraire les entrées.
Le sommaire nous permet de filtrer les pages qui nous intéressent. Même si nos PDFs étaient de structures différentes, il a été facile avec certains mot-clés de repérer les parties qui nous intéressaient, pour ensuite n’avoir à parcourir que les pages ciblées.

Cibler l’information à l’aide de la mise en page

Si la lecture de la table de matière a permis de réduire notre champ d’analyse, il nous reste encore à cibler l’endroit exact où se trouvent les informations voulues. En observant les documents nous avons remarqué des patterns, comme la présence de certaines phrases précédant les informations voulues. Ceci nous a permis de filtrer les pages restantes à l’aide de regex. Par ailleurs, il est apparu que ces informations étaient dans la grande majorité des cas présentées dans un tableau. Nous avons donc choisi d’orienter notre algorithme sur la détection et l’extraction de ces tables. L’enjeu principal était de délimiter ces tables quelque soit leur forme ou leur taille.

Pour repérer les tables nous avons préféré utiliser un modèle existant déjà entraîné. En python, la librairie Tabula-py est souvent utilisée pour extraire les tables d’un PDF, et les convertir au format CSV. Cependant cette librairie fonctionne uniquement lorsque les tables sont nettement dessinées, avec des bordures. Lors de nos essais nous avons noté que Tabula-py ne reconnaissait pas comme tables celles plus stylisées, dont les cellules n’étaient pas cadrées. Or ce type de tables était très fréquent dans notre base de PDF, qui regroupent des documents publics à but informatif, dont la mise en forme était travaillée.

C’est pourquoi nous avons préféré tester d’autres librairies, et notre choix s’est finalement porté sur Cascade TabNet. Cascade TabNet propose un CNN (Convolutional Neural Network) détectant deux types d’objets : les bordered tables (tables avec bordures), et les borderless tables (tables sans bordures). Pour l’utiliser il faut d’abord convertir la page du PDF en image, puis appliquer le modèle, qui renverra 2 probabilités: une pour chaque type de table. Le modèle retourne également les coordonnées de la table détectée.

L’avantage du modèle Cascade TabNet est qu’il a été entraîné sur les tables les plus simples comme les plus stylisées. Par ailleurs, la librairie est bien plus flexible que Tabula-Py, qui n’est qu’une black box. En proposant le CNN complet, elle offre à l’utilisateur la possibilité de jouer sur le seuil de détection, ou même de ré-entraîner le modèle si besoin.

Le seul désavantage de Cascade TabNet est qu’il s’agit d’un projet Git, plus lourd à installer que Tabula-py par exemple. Le modèle utilise des GPU, et nécessite l’installation de CUDA.

A la fin de cette étape nous savons localiser précisément la partie du PDF qui contient les informations recherchées.

Ici le ciblage dépendait de la présence de certains regex et d’un tableau, mais on peut très bien imaginer d’autres usages où l’on cherche à détecter une image, un graphique, etc… Quoiqu’il en soit, il est toujours possible de traiter une page de PDF en tant qu’image, pour ensuite y appliquer un modèle de reconnaissance de formes, de type CNN.

Récupérer les informations via un NER

Une fois la partie intéressante du texte ciblée, il reste à en extraire les informations désirées. Pour cela nous utilisons un NER (Named Entity Recognition ou Reconnaissance d’Entités Nommée). Il s’agit d’un modèle de classification qui s’applique sur un texte dans le but d’y identifier des entités définies au préalable: les NE (named entity, ou entitées nommées). Sorte d’entités lexicales, les NE correspondent à un mot ou à un groupe de mots. Ce sont des classes ouvertes, dont on ne peut lister tous les éléments. Exemples de NE: les dates, les noms de personne, les noms d’entreprise, les lieux, etc. C’est à l’utilisateur de définir, lorsqu’il construit son NER, les NE qu’il souhaite relever dans un texte.

L’objectif du NER est alors d’associer à chaque mot du texte un taggage IOB (Inside, Outside, Beginning), indiquant s’il correspond :

  • Au début d’une NE (B)
  • A l’intérieur ou à la fin d’une NE (I)
  • A aucune NE (O)

Par exemple le texte “Victor Hugo habitait à Paris.” pourrait donner les tags IOB suivants:

Victor Hugo habitait à Paris
B-Person I-Person O O B-Location

L’application du NER se fait alors en 4 étapes :

  • Tokenisation : le texte est séparé en tokens, qui la plupart du temps correspondent aux mots.
  • Word Embedding : les tokens sont représentés sous forme vectorielle à l’aide d’un modèle de word embeddings déjà entraîné (comme BERT, GLOVE, ou Word2Vec). Le modèle doit être entraîné sur un corpus de même langue ou être multilingue.
  • Classification : pour chaque token on cherche à prédire son tag IOB. Le modèle utilisé est souvent un réseau de neurones (deep learning).
  • Récupération des entités : les tokens sont si nécessaire rassemblés pour retrouver les entités complètes.

Entraîner notre ‘modèle’

L’entraînement du NER correspond de ce fait à l’entraînement du réseau utilisé lors de la classification. Il nécessite donc de labelliser un corpus de textes avec les NE choisies. Comme cette tâche peut s’avérer longue et laborieuse, il est conseillé d’utiliser un outil d’annotations de textes qui propose une interface pour labelliser les mots simplement, en les surlignant . Dans le cadre de notre projet nous avons décidé d’utiliser Label-studio. https://labelstud.io/

Label-studio est une librairie python open-source. Elle propose une interface pour labelliser des données textuelles, picturales, vidéos, temporelles ou même sonores. Une fois label-studio lancé depuis la ligne de commande, une interface s’ouvre sur votre navigateur. Dans un premier temps, il faut choisir quel type de classification on souhaite réaliser (ici: un NER), puis définir les différentes catégories (ici: les NE). Enfin il ne reste plus qu’à importer le corpus à annoter (ici: un corpus de textes rassemblés dans un JSON), et la labellisation peut commencer. L’interface de label-studio nous a paru intuitive et fonctionnelle, grâce à ces nombreux raccourcis claviers et à son code couleur. Une fois la labellisation achevée, il est possible d’exporter les résultats sous forme de JSON, de CSV, ou bien de CONLL (un format fréquemment utilisé par les librairies de NLP).

On peut observer le nombre appréciable de types d’annotations que propose LabelStudio, ne serait-ce que pour délimiter correctement des images aux formes complexes.

Voici une simple annotation que nous avons constitué sur un texte d’exemple

Après avoir construit les échantillons de train/ test à partir du corpus annoté, il faut entraîner le NER. Deux choix s’offrent à nous : on peut soit partir d’un modèle “vide”, soit ré-entraîner un modèle existant. Cette dernière option, appelée “Transfert Learning”, nécessite de trouver un modèle performant, entraîné sur un corpus de textes dans la même langue, et qui prédit des NE en partie similaires à celles qu’on souhaite repérer. Si cette option peut faire gagner du temps de calcul et augmenter nos performances, elle peut s’avérer contre-productive si le modèle de base est peu efficace ou entraîné sur un corpus trop différent du nôtre.

En Python beaucoup de librairies de NLP permettent de construire et d’entraîner des NER : Spacy, NLTK, Huggingface, Spark-NLP, etc. Elles proposent de plus pour la plupart des modèles déjà entraînés, sur des corpus d’open data (Wikipédia par exemple), et pour différentes langues.

Dans un premier temps, nous avons essayé de ré-entraîner un NER français proposé par Spacy. Les résultats étaient peu satisfaisants : ré-entrainer le modèle Spacy n’était pas beaucoup plus performant que de partir d’un modèle vide. Sans doute que le modèle français proposé par Spacy n’était pas assez efficace sur notre corpus. Par ailleurs, Spacy offre peu de flexibilité sur les étapes de prétraitements, et notamment sur la tokenisation. La librairie nous a paru peu transparente sur les changements qu’elle applique sur le texte avant l’apprentissage (la suppression de la ponctuation notamment). De même pour l’étape de Word Embeddings qui est difficile à personnaliser.

C’est pourquoi dans un second temps nous avons préféré utiliser Spark-NLP.

Disponible sur Py-Spark, SparkNLP propose divers outils d’analyse textuelle, et plusieurs modèles pré-entrainés. A l’instar des autres librairies Spark, SparkNLP fonctionne en pipeline. Une pipeline est une succession de transformations à appliquer sur un jeu de données. Dans le cas d’un NER, la pipeline se compose des étapes de tokenisation, de words embeddings, de classification et d’assemblage final. Si Spark NLP propose des fonctions pour chacune de ces étapes, l’utilisateur est également libre de personnaliser ou de construire chaque bloc. En plus de rendre le modèle plus clair et plus transparent, la pipeline le rend donc plus flexible et plus personnalisable.


Ici, il n’y a pas d’étape de tokenisation dans la pipeline, car celle-ci a eu lieu lors de l’écriture du fichier CONLL.

Pour notre NER nous avons utilisé le modèle Glove multilingual pour la partie embeddings, et sommes partis d’une base vide pour la classification. Dès nos premiers essais, le NER construit avec Spark-NLP a montré de meilleurs résultats que celui obtenu avec Spacy. Par la suite, nous avons amélioré ses performances en altérant les étapes de pré-traitements, et notamment la tokenisation. Plutôt que de simplement supprimer la ponctuation et scinder le texte sur les espaces, nous avons élaboré une méthode de tokenisation plus complexe tenant compte des acronymes, des noms composés, etc. Les performances du modèle final étaient très satisfaisantes avec un précision moyenne de 95% et un rappel autour des 90%.

En conclusion

Extraire des données spécifiques d’un PDF peut s’avérer complexe, notamment quand les documents sont très riches et hétérogènes. L’intérêt des PDF est qu’on peut tirer avantage de leur structure et analyser leur mise en page comme on analyserait une image, en utilisant des algorithmes de reconnaissance de forme comme des CNN. Une fois la partie intéressante du PDF ciblée il est nécessaire de construire un NER pour trier et identifier les informations restantes.

L’avantage de ces modèles est qu’ils requièrent souvent peu de données pour être efficaces (une centaine d’exemples par catégorie), car ils reposent en partie sur des modèles existants (pour la partie word embeddings, comme parfois pour la partie taggage).

Emma Montarsolo.
Senior Data Scientist

La Data Science au service de l’extraction d’informations non structurées soulage des tâches très fastidieuses.

Nous vous livrons des enseignements qui vous feront gagner un temps précieux pour votre analyse de patrimoine de documents (ou bien vous pouvez nous confier votre projet, nous en prendrons le plus grand soin).

contact@novagen.tech