DéveloppeurWeb.Com
    DéveloppeurWeb.Com
    • Agile Zone
    • AI Zone
    • Cloud Zone
    • Database Zone
    • DevOps Zone
    • Integration Zone
    • Web Dev Zone
    DéveloppeurWeb.Com
    Home»AI Zone»Ajustement du modèle LayoutLM de Microsoft pour la reconnaissance des factures
    AI Zone

    Ajustement du modèle LayoutLM de Microsoft pour la reconnaissance des factures

    novembre 15, 2021
    Ajustement du modèle LayoutLM de Microsoft pour la reconnaissance des factures
    Share
    Facebook Twitter Pinterest Reddit WhatsApp Email

    Crédit d’image

    introduction

    Sur la base de mon récent tutoriel sur la façon d’annoter des PDF et des images numérisées pour les applications NLP, nous tenterons d’affiner le modèle Layout LM de Microsoft récemment publié sur un ensemble de données personnalisé annoté qui comprend des factures en français et en anglais. Alors que les didacticiels précédents se concentraient sur l’utilisation de l’ensemble de données FUNSD accessible au public pour affiner le modèle, nous montrerons ici l’ensemble du processus, de l’annotation et du pré-traitement à la formation et à l’inférence.

    Modèle LayoutLM

    Le modèle LayoutLM est basé sur l’architecture BERT mais avec deux types supplémentaires d’intégrations d’entrée. Le premier est une incorporation de position 2-D qui dénote la position relative d’un jeton dans un document, et le second est une incorporation d’image pour les images de jeton numérisées dans un document. Ce modèle a obtenu de nouveaux résultats de pointe dans plusieurs tâches en aval, notamment la compréhension des formulaires (de 70,72 à 79,27), la compréhension des reçus (de 94,02 à 95,24) et la classification des images de documents (de 93,07 à 94,42). Pour plus d’informations, reportez-vous à l’article d’origine.

    Heureusement, le modèle était open source et mis à disposition dans la bibliothèque huggingface. Merci Microsoft !

    Pour ce tutoriel, nous allons cloner le modèle directement à partir de la bibliothèque huggingface et l’affiner sur notre propre jeu de données. Voici un lien vers Google Colab, mais nous devons d’abord créer les données d’entraînement.

    Annotation de la facture

    À l’aide d’un outil d’annotation de texte, j’ai annoté une cinquantaine de factures personnelles. Je suis intéressé à extraire à la fois les clés et les valeurs des entités ; par exemple, dans le texte suivant « Date : 06/12/2021 », nous annoterions « Date » comme DATE_ID et « 06/12/2021 » comme DATE. L’extraction des clés et des valeurs nous aidera à corréler les valeurs numériques à leurs attributs. Voici toutes les entités qui ont été annotées :

    DATE_ID, DATE, INVOICE_ID, INVOICE_NUMBER,SELLER_ID, SELLER, MONTANT_HT_ID, MONTANT_HT, TVA_ID, TVA, TTC_ID, TTC

    Voici quelques définitions d’entités :

    MONTANT_HT: Total price pre-taxTTC: Total price with taxTVA: Tax amount

    Voici un exemple de facture annotée utilisant UBIAI :

    Facture annotée

    Après annotation, nous exportons les fichiers de train et de test d’UBIAI directement dans le bon format sans aucune étape de pré-traitement. L’exportation comprendra trois fichiers pour chaque ensemble de données d’entraînement et de test et un fichier texte contenant toutes les étiquettes nommées labels.txt :

    Train/Test.txt

    2018    O
    Sous-total    O
    en    O
    EUR    O
    3,20    O
    €    O
    TVA    S-TVA_ID
    (0%)    O
    0,00 €    S-TVA
    Total    B-TTC_ID
    en    I-TTC_ID
    EUR    E-TTC_ID
    3,20    S-TTC
    €    O
    Services    O
    soumis    O
    au    O
    mécanisme    O
    d'autoliquidation    O
    -    O

    Train/Test_box.txt (contient un cadre de délimitation pour chaque jeton) :

    €    912 457 920 466
    Services    80 486 133 495
    soumis    136 487 182 495
    au    185 488 200 495
    mécanisme    204 486 276 495
    d'autoliquidation    279 486 381 497
    -    383 490 388 492

    Train/Test_image.txt (contient un cadre de délimitation, la taille du document et le nom) :

    € 912 425 920 434 1653 2339 image1.jpg
    TVA 500 441 526 449 1653 2339  image1.jpg
    (0%) 529 441 557 451 1653 2339  image1.jpg
    0,00 € 882 441 920 451 1653 2339  image1.jpg
    Total 500 457 531 466 1653 2339  image1.jpg
    en 534 459 549 466 1653 2339  image1.jpg
    EUR 553 457 578 466 1653 2339  image1.jpg
    3,20 882 457 911 467 1653 2339  image1.jpg
    € 912 457 920 466 1653 2339  image1.jpg
    Services 80 486 133 495 1653 2339  image1.jpg
    soumis 136 487 182 495 1653 2339  image1.jpg
    au 185 488 200 495 1653 2339  image1.jpg
    mécanisme 204 486 276 495 1653 2339  image1.jpg
    d'autoliquidation 279 486 381 497 1653 2339  image1.jpg
    - 383 490 388 492 1653 2339  image1.jpg

    labels.txt :

    B-DATE_ID
    B-INVOICE_ID
    B-INVOICE_NUMBER
    B-MONTANT_HT
    B-MONTANT_HT_ID
    B-SELLER
    B-TTC
    B-DATE
    B-TTC_ID
    B-TVA
    B-TVA_ID
    E-DATE_ID
    E-DATE
    E-INVOICE_ID
    E-INVOICE_NUMBER
    E-MONTANT_HT
    E-MONTANT_HT_ID
    E-SELLER
    E-TTC
    E-TTC_ID
    E-TVA
    E-TVA_ID
    I-DATE_ID
    I-DATE
    I-SELLER
    I-INVOICE_ID
    I-MONTANT_HT_ID
    I-TTC
    I-TTC_ID
    I-TVA_ID
    O
    S-DATE_ID
    S-DATE
    S-INVOICE_ID
    S-INVOICE_NUMBER
    S-MONTANT_HT_ID
    S-MONTANT_HT
    S-SELLER
    S-TTC
    S-TTC_ID
    S-TVA
    S-TVA_ID

    Ajustement du modèle LayoutLM

    Ici, nous utilisons Google Colab avec GPU pour affiner le modèle. Le code ci-dessous est basé sur le papier layoutLM original et ce tutoriel.

    Tout d’abord, installez le package layoutLM.

    ! rm -r unilm! git clone -b remove_torch_save https://github.com/NielsRogge/unilm.git! cd unilm/layoutlm! pip install unilm/layoutlm

    Ainsi que le package du transformateur à partir duquel le modèle sera téléchargé :

    ! rm -r transformers! git clone https://github.com/huggingface/transformers.git! cd transformers! pip install ./transformers

    Ensuite, créez une liste contenant les étiquettes uniques à partir de labels.txt :

    from torch.nn import CrossEntropyLossdef get_labels(path):
        with open(path, "r") as f:
            labels = f.read().splitlines()
        if "O" not in labels:
            labels = ["O"] + labels
        return labelslabels = get_labels("./labels.txt")
    num_labels = len(labels)
    label_map = {i: label for i, label in enumerate(labels)}
    pad_token_label_id = CrossEntropyLoss().ignore_index

    Ensuite, créez un ensemble de données PyTorch et un chargeur de données :

    from transformers import LayoutLMTokenizer
    from layoutlm.data.funsd import FunsdDataset, InputFeatures
    from torch.utils.data import DataLoader, RandomSampler, SequentialSamplerargs = {'local_rank': -1,
            'overwrite_cache': True,
            'data_dir': '/content/data',
            'model_name_or_path':'microsoft/layoutlm-base-uncased',
            'max_seq_length': 512,
            'model_type': 'layoutlm',}# class to turn the keys of a dict into attributes
    class AttrDict(dict):
        def __init__(self, *args, **kwargs):
            super(AttrDict, self).__init__(*args, **kwargs)
            self.__dict__ = selfargs = AttrDict(args)tokenizer = LayoutLMTokenizer.from_pretrained("microsoft/layoutlm-base-uncased")# the LayoutLM authors already defined a specific FunsdDataset, so we are going to use this here
    train_dataset = FunsdDataset(args, tokenizer, labels, pad_token_label_id, mode="train")
    train_sampler = RandomSampler(train_dataset)
    train_dataloader = DataLoader(train_dataset,
                                  sampler=train_sampler,
                                  batch_size=2)eval_dataset = FunsdDataset(args, tokenizer, labels, pad_token_label_id, mode="test")
    eval_sampler = SequentialSampler(eval_dataset)
    eval_dataloader = DataLoader(eval_dataset,
                                 sampler=eval_sampler,
                                batch_size=2)batch = next(iter(train_dataloader))input_ids = batch[0][0]tokenizer.decode(input_ids)

    Chargez le modèle depuis huggingface. Cela sera affiné sur l’ensemble de données.

    from transformers import LayoutLMForTokenClassification
    import torchdevice = torch.device("cuda" if torch.cuda.is_available() else "cpu")model = LayoutLMForTokenClassification.from_pretrained("microsoft/layoutlm-base-uncased", num_labels=num_labels)
    model.to(device)

    Enfin, commencez la formation :

    from transformers import AdamW
    from tqdm import tqdmoptimizer = AdamW(model.parameters(), lr=5e-5)global_step = 0
    num_train_epochs = 50
    t_total = len(train_dataloader) * num_train_epochs # total number of training steps#put the model in training mode
    model.train()
    for epoch in range(num_train_epochs):
      for batch in tqdm(train_dataloader, desc="Training"):
          input_ids = batch[0].to(device)
          bbox = batch[4].to(device)
          attention_mask = batch[1].to(device)
          token_type_ids = batch[2].to(device)
          labels = batch[3].to(device)# forward pass
          outputs = model(input_ids=input_ids, bbox=bbox, attention_mask=attention_mask, token_type_ids=token_type_ids,
                          labels=labels)
          loss = outputs.loss# print loss every 100 steps
          if global_step % 100 == 0:
            print(f"Loss after {global_step} steps: {loss.item()}")# backward pass to get the gradients 
          loss.backward()#print("Gradients on classification head:")
          #print(model.classifier.weight.grad[6,:].sum())# update
          optimizer.step()
          optimizer.zero_grad()
          global_step += 1

    Vous devriez être en mesure de voir les progrès de l’entraînement et la perte se mettre à jour.

    Formation LM Layout en cours

    Après la formation, évaluez les performances du modèle avec la fonction suivante :

    import numpy as np
    from seqeval.metrics import (
        classification_report,
        f1_score,
        precision_score,
        recall_score,
    )eval_loss = 0.0
    nb_eval_steps = 0
    preds = None
    out_label_ids = None# put model in evaluation mode
    model.eval()
    for batch in tqdm(eval_dataloader, desc="Evaluating"):
        with torch.no_grad():
            input_ids = batch[0].to(device)
            bbox = batch[4].to(device)
            attention_mask = batch[1].to(device)
            token_type_ids = batch[2].to(device)
            labels = batch[3].to(device)# forward pass
            outputs = model(input_ids=input_ids, bbox=bbox, attention_mask=attention_mask, token_type_ids=token_type_ids,
                            labels=labels)
            # get the loss and logits
            tmp_eval_loss = outputs.loss
            logits = outputs.logitseval_loss += tmp_eval_loss.item()
            nb_eval_steps += 1# compute the predictions
            if preds is None:
                preds = logits.detach().cpu().numpy()
                out_label_ids = labels.detach().cpu().numpy()
            else:
                preds = np.append(preds, logits.detach().cpu().numpy(), axis=0)
                out_label_ids = np.append(
                    out_label_ids, labels.detach().cpu().numpy(), axis=0
                )# compute average evaluation loss
    eval_loss = eval_loss / nb_eval_steps
    preds = np.argmax(preds, axis=2)out_label_list = [[] for _ in range(out_label_ids.shape[0])]
    preds_list = [[] for _ in range(out_label_ids.shape[0])]for i in range(out_label_ids.shape[0]):
        for j in range(out_label_ids.shape[1]):
            if out_label_ids[i, j] != pad_token_label_id:
                out_label_list[i].append(label_map[out_label_ids[i][j]])
                preds_list[i].append(label_map[preds[i][j]])results = {
        "loss": eval_loss,
        "precision": precision_score(out_label_list, preds_list),
        "recall": recall_score(out_label_list, preds_list),
        "f1": f1_score(out_label_list, preds_list),
    }

    Avec seulement 50 documents, nous obtenons les scores suivants :

    Note d'évaluation après la formation

    Avec plus d’annotations, nous devrions certainement obtenir des scores plus élevés.

    Enfin, enregistrez le modèle pour une prévision future :

    PATH='./drive/MyDrive/trained_layoutlm/layoutlm_UBIAI.pt'torch.save(model.state_dict(), PATH)

    Inférence

    Vient maintenant la partie amusante, téléchargeons une facture, procédons à la reconnaissance optique de caractères et extrayons les entités pertinentes. Pour ce test, nous utilisons une facture qui ne figurait pas dans l’ensemble de données d’entraînement ou de test. Pour analyser le texte de la facture, nous utilisons le package open source Tesseract. Installons le paquet :

    !sudo apt install tesseract-ocr!pip install pytesseract

    Avant d’exécuter des prédictions, nous devons analyser le texte de l’image et pré-traiter les jetons et les cadres de délimitation en fonctionnalités. Pour ce faire, j’ai créé un fichier de prétraitement python layoutLM_preprocess.py qui facilitera le prétraitement de l’image :

    import sys
    sys.path.insert(1, './drive/MyDrive/UBIAI_layoutlm')
    from layoutlm_preprocess import *image_path="./content/invoice_test.jpg"image, words, boxes, actual_boxes = preprocess(image_path)

    Ensuite, chargez le modèle et obtenez des prédictions de mots avec leurs cadres de délimitation :

    model_path="./drive/MyDrive/trained_layoutlm/layoutlm_UBIAI.pt"model=model_load(model_path,num_labels)word_level_predictions, final_boxes=convert_to_features(image,...
    Share. Facebook Twitter Pinterest LinkedIn WhatsApp Reddit Email
    Add A Comment

    Leave A Reply Cancel Reply

    Catégories

    • Politique de cookies
    • Politique de confidentialité
    • CONTACT
    • Politique du DMCA
    • CONDITIONS D’UTILISATION
    • Avertissement
    © 2023 DéveloppeurWeb.Com.

    Type above and press Enter to search. Press Esc to cancel.