Une intelligence artificielle qui ne fait plus la différence entre un PDF scanné et un document numérique. Et si la solution était sous nos yeux depuis le début ?

GEMMA 4 : L'IA QUI VOIT LES PDF COMME DES IMAGES

Imaginez un modèle de langage visuel (un VLM, pour Visual Language Model) capable de lire un PDF comme on regarde une photo. Pas besoin de logiciel complexe pour extraire le texte : Gemma 4 analyse directement l'image du document, qu'il s'agisse d'un fichier numérique parfait ou d'un scan flou de fax. La différence entre document scanné et document numérique n'existe plus. Plus de conversion en texte, plus de perte de qualité, plus de réglages à faire selon le type de fichier. Juste une analyse directe, rapide, et surtout sans aucune configuration préalable.

« Traiter les PDF comme des images dissout la distinction entre document scanné et document numérique. Chaque PDF devient une image à analyser, point final. »

INSTALLER GEMMA 4 : LES ÉTAPES CLÉS POUR DÉMARRER

Pour utiliser Gemma 4, il faut d'abord préparer son environnement. Le modèle nécessite Python 3.10 ou supérieur, ainsi que quelques bibliothèques essentielles. Voici les étapes à suivre, étape par étape, sans rien oublier :

# Vérifier la version de Python (doit être 3.10 ou plus)
python --version

# Créer un environnement virtuel pour isoler les dépendances
python -m venv gemma4-env

# Activer l'environnement virtuel
source gemma4-env/bin/activate       # Sur macOS ou Linux
# Ou sur Windows :
gemma4-env\Scripts\activate

# Installer les paquets nécessaires
pip install \
  "transformers>=4.51.0" \
  "torch>=2.3.0" \
  "accelerate>=0.30.0" \
  "pymupdf>=1.24.0" \
  "Pillow>=10.0.0" \
  "bitsandbytes>=0.43.0"

# Se connecter à Hugging Face (coller votre token de lecture quand demandé)
pip install huggingface_hub
huggingface-cli login

Une fois ces étapes terminées, votre environnement est prêt à accueillir Gemma 4. Le téléchargement du modèle prendra environ 10 Go d'espace disque lors de la première utilisation. Les téléchargements suivants seront beaucoup plus rapides grâce au cache.

VÉRIFIER SON Matériel : GPU OU CPU, GEMMA 4 S'ADAPTE

Avant de charger le modèle, il est conseillé de vérifier quel matériel est disponible sur votre machine. Gemma 4 peut fonctionner sur un GPU NVIDIA, un processeur Apple Silicon (MPS), ou même un simple CPU en cas de besoin. Voici un script Python qui détecte automatiquement votre configuration :

# device_check.py
# Exécutez ce script avant de charger Gemma 4 pour confirmer votre environnement.
# Sauvegardez-le sous devicecheck.py et lancez : python devicecheck.py

def detect_device():
    """
    Détecte le meilleur périphérique de calcul disponible.
    Retourne (devicestr, dtype, loadkwargs) pour une utilisation avec from_pretrained.
    """
    try:
        import torch
    except ImportError:
        raise RuntimeError("PyTorch non trouvé. Installez-le avec : pip install torch")

    if torch.cuda.is_available():
        name = torch.cuda.getdevicename(0)
        vram = torch.cuda.getdeviceproperties(0).total_memory / 1e9
        print(f"GPU CUDA détecté : {name} ({vram:.1f} Go de VRAM)")
        return "cuda", torch.bfloat16, {"devicemap": "auto", "torchdtype": torch.bfloat16}

    elif hasattr(torch.backends, "mps") and torch.backends.mps.is_available():
        print("Puce Apple Silicon MPS détectée")
        return "mps", torch.float16, {"devicemap": "mps", "torchdtype": torch.float16}

    else:
        print("Aucun GPU trouvé -- basculement en CPU (lent mais fonctionnel)")
        return "cpu", None, {"device_map": "cpu"}


if __name__ == "__main__":
    device, dtype, kwargs = detect_device()
    print(f"Périphérique : {device}")
    print(f"Type de données : {dtype}")
    print(f"Prêt pour le chargement de Gemma 4 avec : {kwargs}")

Ce script affiche clairement quel matériel est utilisé et prépare les paramètres nécessaires pour charger le modèle. Si vous avez un GPU NVIDIA récent, vous verrez son nom et sa quantité de mémoire vidéo (VRAM). Sur un Mac avec puce Apple Silicon, le modèle utilisera le processeur graphique intégré. Sinon, il passera en mode CPU, ce qui sera plus lent mais toujours fonctionnel.

TRANSFORMER UN PDF EN IMAGE : LA CLÉ DE GEMMA 4

Le cœur de la méthode repose sur la conversion des pages de PDF en images. Plutôt que d'extraire le texte directement (ce qui pose souvent des problèmes avec les scans ou les documents mal formatés), Gemma 4 analyse chaque page comme une image. Pour cela, un outil appelé PDFRenderer est utilisé. Voici comment il fonctionne :

# pdf_renderer.py
# Prérequis : pip install pymupdf Pillow
# Utilisation : importez et instanciez PDFRenderer ; appelez renderpage() ou renderall()

import pymupdf
from PIL import Image
from pathlib import Path


class PDFRenderer:
    """
    Convertit les pages de PDF en images PIL pour une utilisation avec les VLM.

    Aucune dépendance externe en dehors de PyMuPDF -- ni Poppler, ni Ghostscript.
    Les images de sortie sont en mode RGB, prêtes pour une utilisation directe avec le processeur Auto de Gemma 4.
    """

    def __init__(self, dpi: int = 200):
        """
        Args:
            dpi : Résolution de rendu.
                 150 -- passage rapide pour la classification (moins de jetons, qualité inférieure)
                 200 -- standard de production pour les textes tapés et les documents imprimés
                 300 -- haute fidélité, recommandé pour les écritures manuscrites ou les petits glyphes
        """
        self.dpi = dpi
        # PyMuPDF utilise un facteur de zoom relatif à la baseline de 72 DPI du PDF.
        # zoom=1.0 = 72 DPI, zoom=2.78 = 200 DPI, zoom=4.17 = 300 DPI.
        self._zoom = dpi / 72.0
        self.matrix = pymupdf.Matrix(self.zoom, self._zoom)

    def renderpage(self, pdfpath: str, page_index: int = 0) -> Image.Image:
        """
        Rendu d'une seule page de PDF en une image PIL.

        Args:
            pdf_path : Chemin vers le fichier PDF
            page_index : Index de la page (0 = première page)

        Retourne :
            Image PIL en mode RGB, prête pour le processeur de Gemma 4

        Lance :
            IndexError : Si page_index est hors limites pour ce PDF
            FileNotFoundError : Si le chemin du PDF n'existe pas
        """
        path = Path(pdf_path)
        if not path.exists():
            raise FileNotFoundError(f"PDF non trouvé : {pdf_path}")

        doc = pymupdf.open(str(path))

        if page_index >= len(doc):
            doc.close()
            raise IndexError(
                f"Index de page {page_index} hors limites -- "
                f"ce PDF contient {len(doc)} page(s)"
            )

        page = doc[page_index]

        # get_pixmap rend la page avec la matrice de zoom définie dans __init__.
        # Le pixmap résultant contient les octets RGB bruts à la résolution de self.dpi.
        pix = page.getpixmap(matrix=self.matrix)
        doc.close()

        # Convertir les octets bruts en image PIL -- c'est le format attendu par Gemma 4
        return Image.frombytes("RGB", [pix.width, pix.height], pix.samples)

    def renderall(self, pdfpath: str) -> list[Image.Image]:
        """
        Rendu de toutes les pages d'un PDF en une liste d'images PIL.

        Retourne les pages dans l'ordre : index 0 = première page, index -1 = dernière page.
        La liste est entièrement matérialisée -- pour les très grands PDF, utilisez render_range
        pour traiter par blocs.
        """
        doc = pymupdf.open(pdf_path)
        images = []

        for i in range(len(doc)):
            pix = doc[i].getpixmap(matrix=self.matrix)
            images.append(Image.frombytes("RGB", [pix.width, pix.height], pix.samples))

        doc.close()
        return images

    def render_range(
        self, pdf_path: str, start: int, end: int
    ) -> list[Image.Image]:
        """
        Rendu d'une plage spécifique de pages (start inclus, end exclus).

        Utilisez ceci pour l'étape d'extraction dans le pipeline à deux passes -- seules les pages
        que l'étape de classification a identifiées comme contenant du contenu doivent être
        rendues en haute résolution.
        """
        doc = pymupdf.open(pdf_path)
        images = []
        end = min(end, len(doc))  # Limite à la quantité réelle de pages

        for i in range(start, end):
            pix = doc[i].getpixmap(matrix=self.matrix)
            images.append(Image.frombytes("RGB", [pix.width, pix.height], pix.samples))

        doc.close()
        return images

    def pagecount(self, pdfpath: str) -> int:
        """Retourne le nombre de pages dans un PDF sans rendre aucune d'entre elles."""
        doc = pymupdf.open(pdf_path)
        count = len(doc)
        doc.close()
        return count

Ce script permet de convertir n'importe quel PDF en une série d'images, avec un contrôle précis sur la résolution. Par exemple, une résolution de 200 DPI est idéale pour la plupart des documents imprimés ou tapés. Pour des écritures manuscrites ou des petits caractères, une résolution de 300 DPI est recommandée. Le rendu est rapide et ne nécessite aucune dépendance externe comme Poppler ou Ghostscript.

# Test rapide -- ajoutez ceci après la définition de la classe et exécutez le fichier directement
if __name__ == "__main__":
    import sys
    if len(sys.argv) .= 2:
        print("Usage: python pdf_renderer.py ")
        sys.exit(1)

    pdf_path = sys.argv[1]
    renderer = PDFRenderer(dpi=200)
    pages = renderer.renderall(pdfpath)
    print(f"{len(pages)} page(s) rendue(s)")
    for i, img in enumerate(pages):
        print(f"  Page {i} : {img.width} x {img.height} pixels")
    # Sauvegarder la page 0 en PNG pour vérification visuelle
    pages[0].save("page0preview.png")
    print("page0preview.png sauvegardé -- vérifiez qu'il est correct avant d'exécuter l'inférence")

Pour tester, il suffit d'exécuter : python pdfrenderer.py votrefacture.pdf. Le script génère des images de chaque page et sauvegarde la première en PNG pour vérification. C'est une étape cruciale avant de passer à l'analyse par l'IA.

CHARGER GEMMA 4 : LE MODÈLE QUI COMPREND LES IMAGES

Une fois les PDF convertis en images, il faut charger le modèle Gemma 4 et son processeur associé. Ce modèle est spécialement conçu pour analyser des images de documents et en extraire le sens. Voici comment le charger :

# gemma4_loader.py
# Prérequis : pip install transformers>=4.51.0 torch accelerate
# Exécutez : python gemma4_loader.py
# La première exécution télécharge ~10 Go de poids -- les exécutions suivantes utilisent le cache

import re
import torch
from PIL import Image
from transformers import AutoProcessor, Gemma4ForConditionalGeneration

MODEL_ID = "google/gemma-4-E4B-it"
# Utilisez "google/gemma-4-E2B-it" pour les machines avec 6 Go de VRAM


def loadmodel(modelid: str = MODEL_ID):
    """
    Charge Gemma 4 et son processeur.
    device_map="auto" distribue sur tous les GPU disponibles,
    ou passe en CPU si aucun n'est trouvé.
    """
    print(f"Chargement de {model_id}.")

    processor = AutoProcessor.frompretrained(modelid)

    model = Gemma4ForConditionalGeneration.from_pretrained(
        model_id,
        torch_dtype=torch.bfloat16,   # Type de données d'entraînement -- meilleur équilibre qualité/mémoire
        device_map="auto",            # Distribution automatique sur les GPU ou passage en CPU
    )
    model.eval()
    print(f"Modèle prêt sur : {model.device}")
    return model, processor


def querydocumentpage(
    model,
    processor,
    page_image: Image.Image,
    prompt: str,
    token_budget: int = 1120,
    enable_thinking: bool = False,
    maxnewtokens: int = 1024,
) -> str:
    """
    Envoie une seule page de document (image) + une invite textuelle à Gemma 4.

    Args:
        page_image : Image PIL de la page de PDF (provenant de PDFRenderer)
        prompt : Ce que vous voulez extraire ou demander sur cette page
        token_budget : Budget de jetons visuels -- 70/140/280/560/1120.
                      Plus élevé = plus de détails, plus de VRAM, inférence plus lente.
                      Utilisez 1120 pour les factures denses, 280 pour une classification rapide.
        enable_thinking : Si True, le modèle raisonne étape par étape avant de répondre.
                         Améliore la précision sur les mises en page complexes au prix d'une latence accrue.
        maxnewtokens : Nombre maximal de jetons à générer dans la réponse

    Retourne :
        Réponse du modèle sous forme de chaîne de caractères, avec le bloc  supprimé si présent
    """
    messages = [
        {
            "role": "user",
            "content": [
                # L'image doit venir en premier -- requis pour des performances optimales avec Gemma 4
                {"type": "image", "image": page_image},
                {"type": "text",  "text": prompt},
            ],
        }
    ]

    # applychattemplate formate les messages et injecte le budget de jetons visuels
    inputs = processor.applychattemplate(
        messages,
        addgenerationprompt=True,
        tokenize=True,
        return_dict=True,
        return_tensors="pt",
        # token_budget contrôle combien de jetons visuels représentent l'image
        # Budget plus élevé = plus de détails spatiaux préservés = meilleur pour les documents denses
        numimagetokens=token_budget,
        # Active le raisonnement en chaîne de pensée du modèle avant la réponse finale
        enablethinking=enablethudget,
    ).to(model.device)

    with torch.no_grad():
        output_ids = model.generate(
            **inputs,
            maxnewtokens=maxnewtokens,
            temperature=0.1,   # Température basse pour une extraction structurée -- déterministe
            top_p=0.95,
            do_sample=True,
        )

    # Décoder uniquement les jetons nouvellement générés, pas l'invite d'entrée
    newtokens = outputids[0][inputs["input_ids"].shape[-1]:]
    raw = processor.decode(newtokens, skipspecial_tokens=True).strip()

    # Supprimer le bloc de raisonnement lorsque le mode thinking était activé.
    # Le contenu . est le processus de raisonnement du modèle,
    # pas une partie de la sortie structurée. Les appelants n'ont besoin que de la réponse finale.
    return re.sub(r"(?s).*?", "", raw, count=1).strip()


# ── Test rapide ──────────────────────────────────────────────────────────

if __name__ == "__main__":
    from pdf_renderer import PDFRenderer
    import sys

    if len(sys.argv) .= 2:
        print("Usage: python gemma4_loader.py ")
        sys.exit(1)

    model, processor = load_model()
    renderer = PDFRenderer(dpi=200)

    # Rendre la première page
    pageimg = renderer.renderpage(sys.argv[1], page_index=0)
    print(f"Page rendue : {pageimg.width}x{pageimg.height} pixels")

    # Invite de description simple -- bon test avant l'extraction structurée
    description = querydocumentpage(
        model, processor,
        pageimage=pageimg,
        prompt="Décris ce que tu vois dans ce document. Quel type de document est-ce et quelles informations contient-il ?",
        token_budget=560,
        enable_thinking=False,
    )
    print("\n── Description du document ──")
    print(description)

Ce script charge le modèle Gemma 4 et son processeur. Le modèle est distribué automatiquement sur tous les GPU disponibles, ou utilise le CPU en fallback. Le processeur transforme les images et les invites textuelles en un format compréhensible par le modèle. La fonction querydocumentpage permet d'interroger le modèle sur une page spécifique, avec des paramètres comme le budget de jetons visuels ou l'activation du raisonnement étape par étape.

Pour tester, exécutez : python gemma4loader.py votrefacture.pdf. Le script affiche une description du document, ce qui permet de vérifier que tout fonctionne correctement avant de passer à l'extraction structurée de données.

EXTRAIRE LES DONNÉES D'UNE FACTURE : LE CODE QUI FAIT TOUT

Le cœur de la solution réside dans l'extraction structurée des données d'une facture. Pour cela, un script appelé invoice_parser.py a été développé. Il utilise Gemma 4 pour analyser chaque page d'une facture et en extraire les informations clés : nom du fournisseur, numéro de facture, date, montants, etc. Voici comment il fonctionne :

# invoice_parser.py
# Prérequis : pdfrenderer.py et gemma4loader.py dans le même répertoire
# Exécutez : python invoice_parser.py 

import re
import json
import torch
from dataclasses import dataclass, field
from pathlib import Path
from typing import Optional
from PIL import Image
from transformers import AutoProcessor, Gemma4ForConditionalGeneration

from pdf_renderer import PDFRenderer
from gemma4loader import loadmodel, querydocumentpage

MODEL_ID = "google/gemma-4-E4B-it"

# ── Modèle de données ────────────────────────────────────────────────────────────────

@dataclass
class LineItem:
    description: str
    quantity: Optional[float]
    unit_price: Optional[str]
    total: Optional[str]

@dataclass
class ParsedInvoice:
    vendor_name: Optional[str]
    invoice_number: Optional[str]
    invoice_date: Optional[str]
    due_date: Optional[str]
    lineitems: list[LineItem] = field(defaultfactory=list)
    subtotal: Optional[str] = None
    tax: Optional[str] = None
    total_due: Optional[str] = None
    currency: Optional[str] = None
    # Champs où le modèle a retourné None, vide ou "inconnu"
    # -- redirigez ces champs vers une révision humaine
    lowconfidencefields: list[str] = field(default_factory=list)
    raw_output: str = ""

# ── Invite d'extraction ─────────────────────────────────────────────────────────

EXTRACTION_PROMPT = """Il s'agit d'une page d'une facture fournisseur. Extrais toutes les informations disponibles et retourne-les sous forme d'un seul objet JSON.

Format JSON requis :
{
  "vendor_name": "chaîne ou null",
  "invoice_number": "chaîne ou null",
  "invoice_date": "chaîne ou null",
  "due_date": "chaîne ou null",
  "line_items": [
    {
      "description": "chaîne",
      "quantity": nombre ou null,
      "unit_price": "chaîne ou null",
      "total": "chaîne ou null"
    }
  ],
  "subtotal": "chaîne ou null",
  "tax": "chaîne ou null",
  "total_due": "chaîne ou null",
  "currency": "chaîne ou null"
}

Règles :
- Ne retourne QUE l'objet JSON -- pas de préambule, pas d'explication, pas de blocs de code markdown.
- Si un champ n'est pas visible sur cette page, mets-le à null.
- N'invente pas de valeurs. Si tu ne peux pas lire clairement un nombre, mets-le à null.
- Préserve le symbole de devise original (ex: $, €, £, ₦) dans les champs monétaires.
- Inclus TOUS les éléments de ligne que tu peux voir, même si le tableau dépasse de la zone visible."""

# ── Analyseur de sortie ─────────────────────────────────────────────────────────────

def extractjsonblock(text: str) -> Optional[dict]:
    """
    Trouve le premier objet JSON dans la sortie du modèle.
    Gère à la fois les blocs JSON nus et les blocs markdown ``json . `,
    car certains modèles incluent des blocs malgré l'instruction contraire.
    """
    # Essayer d'abord les blocs markdown
    fence = re.search(r"`(?:json)?\s(\{.?\})\s*``", text, re.DOTALL)
    if fence:
        try:
            return json.loads(fence.group(1))
        except json.JSONDecodeError:
            pass
    # Retourner en fallback vers tout objet JSON nu dans la sortie
    bare = re.search(r"(\{.*\})", text, re.DOTALL)
    if bare:
        try:
            return json.loads(bare.group(1))
        except json.JSONDecodeError:
            pass
    return None

def buildparsedinvoice(raw_output: str) -> ParsedInvoice:
    """
    Convertit la sortie brute du modèle en un ParsedInvoice typé.
    Ne lève jamais d'exception -- les champs qui ne peuvent pas être analysés prennent la valeur None
    et sont ajoutés à lowconfidencefields pour un traitement ultérieur.
    """
    data = extractjsonblock(raw_output)

    if data is None:
        return ParsedInvoice(
            vendorname=None, invoicenumber=None,
            invoicedate=None, duedate=None,
            lowconfidencefields=["all_fields -- échec de l'analyse JSON"],
            rawoutput=rawoutput,
        )

    low_conf = []

    def safe_str(key: str) -> Optional[str]:
        """Extrait un champ de type chaîne ; ajoute à low_confidence si manquant ou placeholder."""
        val = data.get(key)
        if val is None or str(val).strip().lower() in ("", "null", "unknown", "n/a"):
            low_conf.append(key)
            return None
        return str(val).strip()

    # Analyser les éléments de ligne à partir du tableau JSON
    items = []
    for item in data.get("line_items", []):
        if not isinstance(item, dict):
            continue
        qty = item.get("quantity")
        items.append(LineItem(
            description=str(item.get("description", "")).strip(),
            quantity=float(qty) if qty is not None else None,
            unitprice=item.get("unitprice"),
            total=item.get("total"),
        ))

    return ParsedInvoice(
        vendorname=safestr("vendor_name"),
        invoicenumber=safestr("invoice_number"),
        invoicedate=safestr("invoice_date"),
        duedate=safestr("due_date"),
        line_items=items,
        subtotal=safe_str("subtotal"),
        tax=safe_str("tax"),
        totaldue=safestr("total_due"),
        currency=safe_str("currency"),
        lowconfidencefields=low_conf,
        rawoutput=rawoutput,
    )

# ── Parseur de facture ────────────────────────────────────────────────────────────

class InvoiceParser:
    """
    Extraction locale de factures de bout en bout utilisant Gemma 4.
    Traite chaque page de PDF comme une image -- fonctionne aussi bien sur les PDF scannés que numériques.
    """

    def __init__(self, modelid: str = MODELID, dpi: int = 200):
        self.model, self.processor = loadmodel(modelid)
        self.renderer = PDFRenderer(dpi=dpi)

    def parse(self, pdfpath: str, tokenbudget: int = 1120) -> ParsedInvoice:
        """
        Analyse une seule facture PDF.
        Pour les factures multi-pages, extrait des toutes les pages et fusionne les résultats,
        les pages suivantes complétant les champs non trouvés sur les pages précédentes.

        Args:
            pdf_path : Chemin vers la facture PDF
            token_budget : Budget de jetons visuels par page (560 ou 1120 recommandé)

        Retourne :
            ParsedInvoice avec tous les champs extraits et la liste lowconfidencefields
        """
        path = Path(pdf_path)
        if not path.exists():
            raise FileNotFoundError(f"Fichier non trouvé : {pdf_path}")

        pages = self.renderer.renderall(pdfpath)
        print(f"Traitement de {len(pages)} page(s) de {path.name}.")

        merged: Optional[ParsedInvoice] = None

        for i, page_img in enumerate(pages):
            print(f"  Extraction de la page {i + 1}/{len(pages)}.")
            raw = querydocumentpage(
                self.model, self.processor,
                pageimage=pageimg,
                prompt=EXTRACTION_PROMPT,
                tokenbudget=tokenbudget,
                enable_thinking=False,   # Utilisez thinking=True pour les mises en page multi-colonnes complexes
            )
            pageresult = buildparsed_invoice(raw)

            if merged is None:
                merged = page_result
            else:
                # Fusion : les pages suivantes remplissent les champs qui étaient null sur les pages précédentes.
                # Les éléments de ligne sont toujours accumulés sur toutes les pages (gère les tableaux multi-pages).
                merged = self.merge(merged, pageresult)

        return merged or ParsedInvoice(
            vendorname=None, invoicenumber=None,
            invoicedate=None, duedate=None,
            lowconfidencefields=["aucunepagetraitée"],
        )

    def _merge(self, base: ParsedInvoice, update: ParsedInvoice) -> ParsedInvoice:
        """
        Fusionne deux résultats ParsedInvoice.
        Champs scalaires : garde la valeur de base sauf si elle est None (update remplit).
        line_items : accumule depuis les deux pages.
        lowconfidencefields : intersection des deux -- un champ n'est "confiant"
        que s'il a été trouvé sur au moins une page.
        """
        def pick(a, b):
            return a if a is not None else b

        alllowconf = list(set(base.lowconfidencefields) & set(update.lowconfidencefields))

        return ParsedInvoice(
            vendorname=pick(base.vendorname, update.vendor_name),
            invoicenumber=pick(base.invoicenumber, update.invoice_number),
            invoicedate=pick(base.invoicedate, update.invoice_date),
            duedate=pick(base.duedate, update.due_date),
            lineitems=base.lineitems + update.line_items,
            subtotal=pick(base.subtotal, update.subtotal),
            tax=pick(base.tax, update.tax),
            totaldue=pick(base.totaldue, update.total_due),
            currency=pick(base.currency, update.currency),
            lowconfidencefields=alllowconf,
            raw_output="",
        )

Ce script est le cœur de la solution. Il définit un modèle de données pour représenter une facture (nom du fournisseur, numéro, date, éléments de ligne, etc.). L'invite d'extraction est cruciale : elle demande au modèle de retourner un objet JSON structuré contenant toutes les informations de la facture. Le script buildparsedinvoice analyse cette sortie JSON et la transforme en un objet Python structuré. Enfin, la classe InvoiceParser gère l'extraction sur toutes les pages d'une facture, en fusionnant les résultats pour obtenir une vue complète.

COMMENT ÇA MARCHE ? L'ALGORITHME QUI CASSE LES CODES

La méthode repose sur une approche en deux temps :

1. Conversion du PDF en images : Chaque page devient une image, quel que soit son origine (scannée ou numérique). 2. Analyse par Gemma 4 : Le modèle lit l'image comme une photo et en extrait les informations textuelles, sans avoir besoin de convertir le PDF en texte brut au préalable.

Cette approche élimine tous les problèmes classiques des logiciels d'extraction de texte :

  • Plus besoin de distinguer un PDF scanné d'un PDF numérique.
  • Plus de perte de qualité due à une mauvaise reconnaissance optique de caractères (OCR).
  • Plus de réglages manuels selon le type de document.

Le modèle Gemma 4 est spécialement entraîné pour comprendre les images de documents. Il peut analyser des mises en page complexes, des tableaux, des écritures manuscrites, et même des documents partiellement flous. Le budget de jetons visuels permet de contrôler la quantité de détails conservés : plus le budget est élevé, plus l'image est détaillée, mais plus l'inférence est lente et gourmande en mémoire.

UNE SOLUTION POUR TOUS LES CAS : DES FACTURES AUX DOCUMENTS LÉGAUX

La force de cette méthode réside dans sa généralité. Elle fonctionne aussi bien sur :

  • Une facture imprimée au laser, parfaitement nette.
  • Un fax en basse résolution, presque illisible pour un OCR classique.
  • Un document avec des tableaux complexes ou des mises en page inhabituelles.
  • Un contrat ou un document légal avec des petits caractères.

Contrairement aux outils traditionnels qui nécessitent des réglages spécifiques pour chaque type de document, Gemma 4 s'adapte automatiquement. Il suffit de fournir le PDF et de laisser l'IA faire le travail. Les résultats sont retournés sous forme de JSON structuré, prêt à être intégré dans une base de données, un tableur, ou un système de gestion.

« Avec cette méthode, plus besoin de se demander si un document est scanné ou numérique. Gemma 4 le lit comme une image, point. »

PERFORMANCES ET LIMITES : CE QU'IL FAUT SAVOIR

Comme toute solution, celle-ci a ses forces et ses faiblesses. Voici ce qu'il faut garder à l'esprit :

  • Avantages :
    • Pas de configuration nécessaire : un seul paramètre (la résolution) peut être ajusté selon le besoin.
    • Résultats immédiats : pas d'étape de conversion en texte brut, l'analyse est directe.
    • Robustesse : fonctionne même avec des documents de mauvaise qualité ou mal formatés.
    • Flexibilité : peut être utilisé pour extraire des informations de n'importe quel type de document, pas seulement des factures.
  • Limites :
    • Ressources matérielles : le modèle Gemma 4-E4B nécessite environ 10 Go de VRAM pour fonctionner correctement. Une version allégée (E2B) existe pour les machines avec 6 Go de VRAM.
    • Temps d'inférence : l'analyse d'un document peut prendre quelques secondes à plusieurs minutes selon sa complexité et la résolution choisie.
    • Champs non détectés : certains champs peuvent être manqués si le document est trop flou ou mal structuré. Ces champs sont marqués comme "low confidence" pour une révision humaine.

En pratique, cette solution est idéale pour les entreprises qui traitent un grand volume de documents variés, ou pour les particuliers qui veulent automatiser la gestion de leurs factures et contrats sans dépendre d'un service cloud coûteux.

EXEMPLE CONCRET : EXTRAIRE UNE FACTURE EN 30 SECONDES

Prenons un exemple concret. Vous avez une facture scannée en basse résolution, avec un tableau de produits et des montants. Voici comment procéder :

  1. Installez l'environnement comme décrit plus haut.
  2. Placez votre facture dans le même dossier que les scripts.
  3. Exécutez : python invoiceparser.py votrefacture.pdf
  4. Le script analyse chaque page et retourne un JSON structuré avec toutes les informations extraites.

Par exemple, pour une facture contenant :

  • Nom du fournisseur : Acme Corp
  • Numéro de facture : FACT-2024-001
  • Date : 15/05/2024
  • Éléments de ligne :
    • Description : Clavier mécanique, Quantité : 2, Prix unitaire : 89,99 €, Total : 179,98 €
    • Description : Souris sans fil, Quantité : 1, Prix unitaire : 29,99 €, Total : 29,99 €
  • Sous-total : 209,97 €
  • TVA : 16,80 €
  • Total dû : 226,77 €

Le script retournera un JSON comme celui-ci :

{
  "vendor_name": "Acme Corp",
  "invoice_number": "FACT-2024-001",
  "invoice_date": "15/05/2024",
  "due_date": null,
  "line_items": [
    {
      "description": "Clavier mécanique",
      "quantity": 2.0,
      "unit_price": "89,99 €",
      "total": "179,98 €"
    },
    {
      "description": "Souris sans fil",
      "quantity": 1.0,
      "unit_price": "29,99 €",
      "total": "29,99 €"
    }
  ],
  "subtotal": "209,97 €",
  "tax": "16,80 €",
  "total_due": "226,77 €",
  "currency": "€",
  "lowconfidencefields": []
}

Ce JSON peut ensuite être utilisé pour remplir automatiquement un tableur, une base de données, ou un logiciel de comptabilité.

POURQUOI CETTE MÉTHODE EST RÉVOLUTIONNAIRE ?

Traditionnellement, extraire des données d'un PDF nécessitait une chaîne complexe d'outils :

  1. Convertir le PDF en texte brut avec un OCR (comme Tesseract).
  2. Analyser le texte brut pour identifier les champs (nom, date, montant, etc.).
  3. Structurer les données extraites dans un format utilisable (JSON, CSV, etc.).

Cette méthode est fragile : si l'OCR se trompe, toute la chaîne d'extraction est faussée. De plus, chaque type de document (facture, contrat, relevé bancaire) nécessite des règles spécifiques, ce qui rend les solutions coûteuses à maintenir.

Avec Gemma 4, la chaîne est simplifiée à son maximum :

  1. Convertir le PDF en images (une étape simple et rapide).
  2. Envoyer les images à l'IA pour analyse directe.

Plus besoin d'OCR, plus besoin de règles complexes. L'IA fait tout le travail, et elle le fait bien, même avec des documents de mauvaise qualité. C'est une révolution dans le traitement des documents.

« Avant, extraire des données d'un PDF, c'était comme essayer de lire un livre à travers une vitre sale. Aujourd'hui, c'est comme si quelqu'un lisait le livre à voix haute pour nous. »

ET DEMAIN ? LES PERSPECTIVES DE CETTE TECHNOLOGIE

Cette méthode n'est qu'un début. Les modèles de langage visuel comme Gemma 4 ouvrent la voie à de nombreuses applications :

  • Automatisation des processus métiers : Extraction automatique de données de contrats, de bons de commande, de relevés bancaires, etc.
  • Archivage intelligent : Indexation automatique de documents pour une recherche rapide et précise.
  • Accessibilité : Lecture de documents pour les personnes malvoyantes, avec une description textuelle générée par l'IA.
  • Traduction automatique : Extraction et traduction simultanée de documents dans n'importe quelle langue.
  • Analyse de données : Extraction de tableaux et de graphiques pour une analyse plus poussée.

Les limites ne sont plus techniques, mais créatives. Avec des outils comme Gemma 4, l'extraction de données devient accessible à tous, sans expertise en programmation ou en traitement du langage naturel. Il suffit de savoir utiliser un terminal et de suivre un tutoriel.

À l'avenir, ces modèles pourraient être intégrés directement dans les logiciels de bureautique (Word, Excel, etc.), ou dans les services cloud comme Google Drive ou Dropbox. Plus besoin de télécharger des outils externes : l'extraction de données deviendra une fonctionnalité standard, aussi simple que de faire une recherche dans un document.

COMMENT ESSAYER SOI-MÊME ? LE GUIDE PRATIQUE

Vous voulez tester cette méthode par vous-même ? Voici un résumé des étapes à suivre :

  1. Préparer son environnement :
    • Installer Python 3.10 ou supérieur.
    • Créer un environnement virtuel.
    • Installer les dépendances nécessaires.
    • Se connecter à Hugging Face.
  2. Convertir le PDF en images :
    • Utiliser le script pdf_renderer.py pour convertir chaque page en image.
    • Vérifier visuellement les images générées.
  3. Charger le modèle Gemma 4 :
    • Utiliser le script gemma4_loader.py pour charger le modèle et son processeur.
    • Tester avec une invite simple pour vérifier que tout fonctionne.
  4. Extraire les données :
    • Utiliser le script invoice_parser.py pour extraire les informations de la facture.
    • Vérifier le JSON généré et corriger manuellement les champs marqués comme "low confidence".

C'est tout . En quelques minutes, vous pouvez transformer n'importe quel PDF en données structurées, sans configuration complexe ni expertise technique.

« Avec Gemma 4, extraire des données d'un PDF devient aussi simple que de prendre une photo avec son téléphone. »

EN CONCLUSION : UNE AVANCÉE MAJEURE POUR L'AUTOMATISATION

Gemma 4 représente une révolution dans le traitement des documents. En traitant les PDF comme des images, il élimine tous les problèmes classiques des logiciels d'extraction de texte. Plus besoin de distinguer les documents scannés des documents numériques, plus besoin de réglages manuels, plus besoin d'OCR coûteux et fragile.

Cette méthode est robuste, flexible et accessible. Elle fonctionne sur tous les types de documents, avec une qualité constante, quel que soit leur état. Et surtout, elle est locale : vos données restent sur votre machine, sans dépendre d'un service cloud externe.

Pour les entreprises, c'est une opportunité de réduire les coûts et d'automatiser des processus qui étaient jusqu'à présent manuels et coûteux. Pour les particuliers, c'est une solution simple pour gérer ses documents sans dépendre d'outils complexes ou de services payants.

L'avenir du traitement des documents est là. Et il est plus simple que jamais.

« Gemma 4 ne lit pas les PDF. Il les regarde, les comprend, et en extrait les données. Une approche radicalement nouvelle qui change tout. »
Sources :
  • KDnuggets

L'indépendance de CLODCO est votre garantie.

Pour que l'actualité de l'IA reste sans filtre et sans concession, votre soutien est indispensable. Votre contribution est le seul moteur de notre liberté éditoriale.

Soutenir CLODCO