Pipeline de normalisation post-OCR pour corpus historiques français du XIXe siècle issus de Gallica.
Développé sur l'Annuaire de l'Institut de droit international (1877), généralisable à tout corpus OCR similaire.
Conçu pour le traitement reproductible des corpus historiques, avec contrôle philologique explicite et validation humaine intégrée.
normalisation OCR ; corrections auditables ; tests ; validation humaine ; rapports détaillés.
Les textes récupérés sur Gallica au format texte brut sont souvent inutilisables directement pour l'analyse. L'OCR produit des erreurs systématiques et prévisibles : apostrophes non standard, tirets typographiques variés, ordinaux mal formés, ligatures manquantes, ponctuations collées, guillemets parasites, chiffres romains déformés. Sur un corpus de 116 000 mots, ces erreurs représentent plusieurs milliers de corrections à apporter.
On pourrait confier ce nettoyage à un grand modèle de langage. On ne le fait pas. Voici pourquoi.
Ce que fait un LLM : il produit un résultat visuellement convaincant en faisant des choix dont la logique est opaque. On ne sait pas exactement ce qu'il a changé, pourquoi, ni s'il a introduit des erreurs en corrigeant d'autres. Sur un corpus destiné à la recherche, c'est inacceptable.
Ce que font ces scripts : chacun fait une chose précise, documentée, vérifiable. Chaque règle a été testée sur le corpus réel. Les faux positifs ont été comptés et documentés dans le code. Les règles trop dangereuses ont été supprimées avec explication. Le résultat est reproductible et auditable — on peut retracer chaque modification.
C'est ce qu'on pourrait appeler une philologie computationnelle explicite : les opérations sont visibles, les décisions sont justifiées, les limites sont nommées.
Cette démarche a aussi une dimension pédagogique explicite. Ces scripts sont conçus pour être lus autant que pour être utilisés. Chaque règle conservée dans le code est documentée — avec les cas qu'elle traite, les cas qu'elle ne traite pas, et les raisons pour lesquelles certaines règles initialement envisagées ont été abandonnées. Un étudiant ou un thésard en histoire qui ouvre 08_abrev.py trouvera non seulement le code, mais l'explication de pourquoi par → par. a été supprimé (927 faux positifs, 0 vrai positif sur le corpus de test), pourquoi cl n'est pas traité comme une abréviation (c'est une erreur OCR pour et dans ce corpus), et ce que cela implique pour les décisions à prendre sur un autre corpus.
L'enjeu n'est pas d'apprendre Python. C'est d'apprendre à travailler son matériau — à ne pas déléguer les choix techniques à un outil dont on ne comprend pas les décisions, à documenter ce qu'on a fait et pourquoi, à distinguer ce qu'on sait de ce qu'on suppose. Ce sont les mêmes exigences que la critique des sources, appliquées à l'outillage numérique.
Cette approche s'inscrit dans une réflexion plus large sur ce que devrait être la formation des historiens à l'ère des grands modèles de langage. La tentation est forte de confier le nettoyage, l'extraction, l'analyse à des outils puissants et accessibles — et les résultats sont souvent visuellement convaincants. Mais un résultat convaincant n'est pas un résultat contrôlé. Sur un corpus destiné à la recherche, la différence est essentielle : ce qu'on ne peut pas expliquer, on ne peut pas publier.
- Corriger toutes les erreurs OCR — seulement celles qui sont systématiques et prévisibles
- Remplacer la relecture humaine
- Fonctionner sans ajustement sur n'importe quel corpus XIXe
Les scripts 15 et 16 intègrent une validation humaine obligatoire à chaque cycle. Ce n'est pas un défaut de conception — c'est le moment où le jugement disciplinaire de l'historien entre dans la boucle, là où aucun outil ne peut le remplacer. Le script 17 articule deux modes de supervision (ex-post et ex-ante) en fonction de la valeur de la distance de Levenshtein.
PostOCR/ scripts/ ← scripts de normalisation 01Normalise.py 02apost.py 03tirets.py 04_controle.py 05_espaces.py 06_ordinaux.py 07_mois.py 08_abrev.py 09_ponctuation.py 10_virgules.py 11_romains.py 12_refs.py 13_guillemets.py 14_ligatures.py 15_decoupage.py 16_inconnus.py 17_levenshtein.py postocr.py ← pipeline complet (scripts 02-14)
test_corpus.py ← audit sur un corpus utilisateur
Lexiq/
lefff_formes.txt ← dictionnaire Lefff (à télécharger séparément)
corpus/
raw/ ← fichiers OCR Gallica bruts
mondoc.txt
processed/ ← sorties du pipeline
rapports/ ← rapports de modifications (.md)
modeles/ ← modèles d'apprentissage scripts 15 et 16
modele_decoupe.json
modele_formes_inconnues.json
Convention de nommage : AAAA_nom.txt pour les fichiers corpus (ex : 1877_jette.txt).
Python 3.8 ou supérieur. Aucune bibliothèque externe pour les scripts 02 à 14 — stdlib uniquement.
Les scripts 10, 15 et 16 nécessitent le dictionnaire Lefff (Lexique des Formes Fléchies du Français, environ 110 000 entrées). À télécharger séparément et placer dans scripts/Lexiq/lefff_formes.txt.
Les scripts 15 et 16 acceptent optionnellement langid pour la détection de langue sur les corpus multilingues : Le script 17 nécessite l'usage de langid pour éviter la multiplication des faux-positifs.
bash
pip install langid
Si langid n'est pas installé, les scripts 14, 15 et 16 fonctionnent avec des heuristiques de repli.
Le dépôt inclut un script test_corpus.py permettant
d’évaluer le comportement du pipeline sur un corpus utilisateur.
Défini à partir d'un matériau qui a ses particularités propres
il peut s'avérer moins utile en d'autres contextes, ou nécessitant
des modifications.
bash
python scripts/test_corpus.py corpus/raw/votre_corpus.txt
Pour obtenir un audit détaillé des corrections appliquées :
bash
python scripts/test_corpus.py corpus/raw/votre_corpus.txt --audit
Pour limiter le nombre d’exemples affichés :
bash
python scripts/test_corpus.py corpus/raw/votre_corpus.txt --audit --max 30
Ce script permet notamment :
- la vérification de l’intégrité structurelle des fichiers ;
- le contrôle de la préservation des paragraphes ;
- l’inspection des corrections appliquées ;
- l’identification de transformations potentiellement problématiques ;
- un audit manuel sur corpus réel.
L’objectif n’est pas de proposer une suite de tests unitaires exhaustive, mais un outil d’évaluation méthodologique adapté aux usages des humanités numériques et au contrôle philologique des corrections post-OCR.
Ce script applique une normalisation Unicode NFC à l’ensemble du corpus.
Cette étape garantit une représentation cohérente des caractères accentués, ligatures et diacritiques avant l’application des règles post-OCR.
Elle permet notamment :
- l’uniformisation des caractères composés ;
- la stabilisation des expressions régulières ;
- la cohérence des traitements lexicaux et statistiques ;
- la réduction des ambiguïtés liées aux encodages OCR.
Cette opération constitue un pré-traitement fondamental du pipeline et doit être exécutée avant les autres scripts.
Il est possible de lancer les scripts individuellement ou bien d'utiliser l'orchestrateur postocr
Le script postocr.py constitue le point d’entrée principal du pipeline
de post-traitement OCR.
Il applique automatiquement les scripts de normalisation déterministes (02 à 14, hors ancien script 10) sur un corpus texte brut issu de Gallica ou d’un autre OCR historique.
Le pipeline produit :
- un fichier texte normalisé ;
- un rapport Markdown documentant les transformations appliquées ;
- un résumé du nombre de corrections par étape ;
- des exemples contextualisés de modifications.
Exemple :
bash
python scripts/postocr.py corpus/raw/mon_corpus.txt
Avec rapport détaillé :
bash
python scripts/postocr.py corpus/raw/mon_corpus.txt --rapport
Le pipeline applique successivement :
| Étape | Fonction |
|---|---|
| 01 | normalisation Unicode |
| 02–14 | corrections déterministes |
| 15–16 | validation semi-automatique manuelle |
| 17 | rapprochement probabiliste par distance lexicale |
Les scripts 15 et 16 restent volontairement interactifs afin de préserver le contrôle philologique des corrections ambiguës.
Le script 15 détecte les mots fusionnés par l'OCR (ledroit → le droit) et propose des découpages à valider.
bash
python scripts/15_decoupage.py corpus/processed/1877_jette_postocr.txt
Le script exporte un fichier TSV à valider dans Numbers ou Excel :
y— découpe correcten— faux positif, ne plus proposer ce motc— découpe incorrecte, saisir la bonne dans la colonnecorrection?— incertain, reviendra au cycle suivant
Appuyer sur Entrée une fois le fichier validé. Répéter jusqu'à satisfaction. Le modèle d'apprentissage est sauvegardé dans modeles/modele_decoupe.json et persiste entre les sessions.
Le script 16 détecte les tokens absents du Lefff qui apparaissent plusieurs fois — probablement des erreurs OCR systématiques (congrés → congrès).
bash
python scripts/16_inconnus.py corpus/processed/1877_jette_postocr.txt
Même logique de validation que le script 15. Pour chaque forme marquée y, saisir la correction dans la colonne correction.
Script 17 — correction probabiliste des hapax
Le script 17 introduit un troisième régime de correction fondé sur la distance de Damerau-Levenshtein appliquée aux hapax absents du Lefff.
Contrairement aux scripts 02–14 (règles déterministes) et aux scripts 15–16 (validation humaine cas par cas), ce script distingue deux niveaux de confiance :
- corrections à distance 1 : appliquées automatiquement avec audit post-hoc ;
- corrections à distance 2 : soumises à une décision globale du chercheur après inspection d’un échantillon représentatif.
Le script formalise ainsi explicitement différents régimes de supervision humaine selon le niveau d’incertitude algorithmique.
|--------|----------|:---:|:---:|
| Script | Fonction | Corrections sur jette (1877) | Faux positifs |
|---|---|---|---|
test_corpus.py |
Audit du corpus sur lequel vous souhaitez tester le pipeline | — | — |
postocr.py |
Orchestrateur du pipeline (scripts 02–14) | — | — |
01Normalise.py |
Normalisation Unicode — préalable à tout traitement | — | — |
02apost.py |
Apostrophes non standard → U+0027 | 0 | 0 |
03tirets.py |
Tirets U+2013/2014/2212 → tiret ASCII | 2 841 | 0 |
04_controle.py |
Caractères de contrôle, BOM | 1 | 0 |
05_espaces.py |
Espaces multiples et spéciaux | 0 | 0 |
06_ordinaux.py |
1ere→1re, 2me→2e, 3me→3e… | 285 | 0 |
07_mois.py |
Mois avec majuscule → minuscule | 107 | 0 |
08_abrev.py |
M→M., Dr→Dr., pp→pp., etc. | 113 | 0 |
09_ponctuation.py |
Espaces autour de :;!? | 1 217 | 0 |
10_virgules.py |
Virgules collées (filtre Lefff) | ~45 | 0 |
11_romains.py |
Vil→VII, T. Il→T. II, T. Vit→T. VII | 12 | 0 |
12_refs.py |
T.VI→T. VI, pp.N→pp. N, et ss,→et ss. | 34 | 0 |
13_guillemets.py |
Guillemets droits parasites OCR | 36 | 0 |
14_ligatures.py |
oeuvre→œuvre, voeu→vœu, coeur→cœur | 79 | 0 |
15_decoupage.py |
Mots collés — cycle interactif | variable | ~25% |
16_inconnus.py |
Formes inconnues — cycle interactif | variable | variable |
17_levenshtein.py |
Correction probabiliste des hapax | variable | variable |
Script 10 : nécessite le Lefff. Sans le dictionnaire, retourne le texte inchangé avec un avertissement.
Scripts 15 et 16 : les faux positifs sont gérés par la validation humaine — le modèle apprend à ne plus les proposer.
Le script 09 traite les ponctuations doubles (:;!?) qui ont des règles typographiques uniformes en français. La virgule a été volontairement exclue : ses exceptions sont trop nombreuses (nombres décimaux, abréviations, listes bibliographiques). Le script 10 traite séparément le cas mot,mot avec le filtre Lefff comme garde-fou.
Le Lefff est un lexique de référence : chaque entrée a été vérifiée. Il ne fait pas de généralisation probabiliste. Sur un corpus du XIXe siècle avec des noms propres étrangers (Rolin-Jaequemyns, Holtzendorff, Mancini), un modèle de langue ferait des suppositions difficiles à contrôler. Le Lefff dit exactement ce qu'il sait et rien d'autre.
Sur quarante annuaires similaires, les erreurs OCR sont souvent les mêmes d'un volume à l'autre. Un modèle cumulatif signifie que les décisions prises sur l'annuaire 1877 s'appliquent automatiquement sur les suivants — sans revalider ce qu'on a déjà traité.
- Script 10 original (points de suspension) : exclu définitivement — les points de conduite dans les tableaux et tables des matières seraient détruits.
- Ligature æ : désactivée — tous les
aedu corpus sont des noms propres flamands (Jaequemyns ×40). par→par.comme abréviation : 927 faux positifs sur le corpus de test, 0 vrai positif. Supprimé.- Correction des coupures de mots en fin de ligne : nécessite une validation humaine ligne par ligne — trop hétérogène pour être automatisé de façon sûre.
Chaque script expose ses paramètres ajustables en tête de fichier, avant tout le code. Les principaux :
06_ordinaux.py — roman=False par défaut : les ordinaux romains (XIXme) ne sont pas corrigés sans activer explicitement ce mode.
10_virgules.py — MIN_LONGUEUR = 2 : longueur minimale des tokens traités. Règle complémentaire : si les deux tokens font ≤ 2 chars simultanément, la virgule n'est pas corrigée (de,la reste intact).
15_decoupage.py — SEUIL_MIN, LIMITE_EXPORT, NB_CYCLES_MAX, PREFIXE_SORTIE.
16_inconnus.py — SEUIL_MIN = 2, SEUIL_MAX = 10 : seules les formes apparaissant entre 2 et 10 fois sont proposées. En dessous : trop de bruit. Au-delà : probablement un terme du domaine.
Annuaire de l'Institut de droit international, première année, 1877.
Source : Gallica BnF — OCR texte brut.
763 276 caractères, ~116 000 mots, 4 920 paragraphes.
L'Institut de droit international est une organisation savante fondée en 1873, réunissant des juristes internationaux pour codifier le droit international. L'Annuaire contient les statuts, les travaux des sessions, un tableau chronologique des faits internationaux, les textes de traités, et une bibliographie. Structure en cinq parties stables d'un volume à l'autre — ce qui rend le corpus particulièrement adapté à une généralisation sur les quarante volumes de la collection. Pour une étude de l'institution exploitant ses annuaires voir Rygiel, Philippe. L'ordre des circulations?. L'Institut de Droit international et la régulation des migrations (1870-1920). Sorbonne (Editions de la Sorbonne), 2021.'
Scripts : MIT.
Corpus OCR Gallica : domaine public (documents antérieurs à 1900).
Lefff : licence LGPLLR — voir la documentation du Lefff.