Zephyrnet-logo

Verfijning van transformatormodel voor factuurherkenning

Datum:

Verfijning van transformatormodel voor factuurherkenning

De auteur presenteert een stapsgewijze handleiding, van annotatie tot training.


By Walid Amamo, oprichter van UBIAI

Figuur
Factuurherkenning

Introductie

 
Voortbouwend op mijn recente tutorial over hoe u PDF's en gescande afbeeldingen kunt annoteren voor NLP-toepassingen, zullen we proberen de onlangs uitgebrachte Microsoft's te verfijnen Indeling LM-model op een geannoteerde aangepaste dataset die Franse en Engelse facturen bevat. Terwijl de vorige tutorials zich concentreerden op het gebruik van het publiek beschikbare FUNSD-gegevensset Om het model te verfijnen, laten we hier het hele proces zien, beginnend bij annotatie en voorverwerking tot training en gevolgtrekking.

LayoutLM-model

 
Het LayoutLM-model is gebaseerd op de BERT-architectuur, maar met twee extra typen invoerinsluitingen. De eerste is een 2D-positie-inbedding die de relatieve positie van een token in een document aangeeft, en de tweede is een afbeeldingsinbedding voor gescande tokenafbeeldingen in een document. Dit model behaalde nieuwe state-of-the-art resultaten bij verschillende downstream-taken, waaronder het begrijpen van formulieren (van 70.72 tot 79.27), het begrijpen van ontvangsten (van 94.02 tot 95.24) en de classificatie van documentafbeeldingen (van 93.07 tot 94.42). Voor meer informatie, zie de originele artikel.

Gelukkig was het model open source en beschikbaar in de Huggingface-bibliotheek. Bedankt, Microsoft!

Voor deze tutorial zullen we het model rechtstreeks vanuit de Huggingface-bibliotheek klonen en het verfijnen op onze eigen dataset. Maar eerst moeten we de trainingsgegevens creëren.

Factuurannotatie

 
De UBIAI-hulpmiddel voor tekstannotatie, Ik heb ongeveer 50 persoonlijke facturen geannoteerd. Ik ben geïnteresseerd om zowel de sleutels als de waarden van de entiteiten te extraheren; In de volgende tekst 'Datum: 06/12/2021' annoteren we bijvoorbeeld 'Datum' als DATE_ID en '06/12/2021' als DATE. Door zowel de sleutels als de waarden te extraheren, kunnen we de numerieke waarden aan hun attributen correleren. Hier zijn alle entiteiten die zijn geannoteerd:

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

Hier volgen enkele definities van entiteiten:

MONTANT_HT: Totale prijs vóór belasting
TTC: Totale prijs inclusief belasting
TVA: Belastingbedrag

Hieronder ziet u een voorbeeld van een geannoteerde factuur met behulp van UBIAI:

Figuur
Factuurannotatie in UBIAI

 

Na annotatie exporteren we de trein- en testbestanden rechtstreeks vanuit UBIAI in het juiste formaat, zonder enige vorm van annotatie voorbewerkingsstap. De export bevat drie bestanden voor elke trainings- en testgegevensset en één tekstbestand met alle labels met de naam labels.txt:

Train/Test.txt 2018 O Sous-totaal O en O EUR O 3,20 O € O TVA S-TVA_ID (0%) O 0,00 € S-TVA Totaal B-TTC_ID en I-TTC_ID EUR E-TTC_ID 3,20 ,XNUMX S-TTC € O Diensten O soumis O au O mécanisme O d'autoliquidation O - O


Train/Test_box.txt (bevat een selectiekader voor elk token):

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


Train/Test_image.txt (bevat het selectiekader, de documentgrootte en de naam):

€ 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 1 image500.jpg Totaal 457 531 466 1653 2339 1 image534.jpg en 459 549 466 1653 2339 1 image553.jpg EUR 457 578 466 1653 2339 1 image3,20.jpg 882 457 911 467 1653 2339 1 image912.jpg € 457 920 466 1653 2339 1 80 image486.jpg Diensten 133 495 1653 2339 1 136 image487.jpg soumis 182 495 1653 2339 1 185 image488.jpg au 200 495 1653 2339 1 204 image486.jpg mécanisme 276 495 1653 2339 1 279 image 486.jpg d'autoliquidatie 381 497 1653 2339 1 383 image490.jpg - 388 492 1653 2339 1 XNUMX afbeeldingXNUMX.jpg


labels.txt:

B-DATE_ID B-FACTUUR_ID B-INVOICE_NUMBER B-MONTANT_HT B-MONTANT_HT_ID B-VERKOPER B-TTC B-DATUM B-TTC_ID B-TVA B-TVA_ID E-DATUM_ID E-DATUM E-INVOICE_ID E-INVOICE_NUMBER E-MONTANT_HT E- MONTANT_HT_ID E-VERKOPER E-TTC E-TTC_ID E-TVA E-TVA_ID I-DATE_ID I-DATUM I-VERKOPER I-FACTUUR_ID I-MONTANT_HT_ID I-TTC I-TTC_ID I-TVA_ID O S-DATE_ID S-DATE S-INVOICE_ID S-FACTUUR_NUMBER S-MONTANT_HT_ID S-MONTANT_HT S-SELLER S-TTC S-TTC_ID S-TVA S-TVA_ID


LayoutLM-model nauwkeurig afstemmen:

 
Hier gebruiken we Google Colab met GPU om het model te verfijnen. De onderstaande code is gebaseerd op de originele lay-outLM-papier en deze tutorial 

Installeer eerst het layoutLM-pakket…

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


…evenals het transformatorpakket waaruit het model zal worden gedownload:

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


Maak vervolgens een lijst met de unieke labels uit labels.txt:

van torch.nn importeer CrossEntropyLoss def get_labels(path): met open(path, "r") as f: labels = f.read().splitlines() if "O" niet in labels: labels = ["O"] + labels return labels labels = get_labels("./labels.txt") num_labels = len(labels) label_map = {i: label voor i, label in enumerate(labels)} pad_token_label_id = CrossEntropyLoss().ignore_index


Maak vervolgens een pytorch-dataset en dataloader:

van transformers importeer LayoutLMTokenizer van layoutlm.data.funsd importeer FunsdDataset, InputFeatures van torch.utils.data importeer DataLoader, RandomSampler, SequentialSampler args = {'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',} # klasse om de sleutels van een dictaat om te zetten in attributen klasse AttrDict(dict): def __init__(self, *args, **kwargs): super(AttrDict, self).__init__(*args, **kwargs) self.__dict__ = self args = AttrDict(args) tokenizer = LayoutLMTokenizer.from_pretrained("microsoft/layoutlm- base-uncased") # de LayoutLM-auteurs hebben al een specifieke FunsdDataset gedefinieerd, dus we gaan deze hier gebruiken 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 = volgende(iter(train_dataloader)) input_ids = batch[0][0] tokenizer.decode(input_ids)


Laad het model vanuit knuffelgezicht. Dit wordt verfijnd op de dataset.

van transformatoren importeer LayoutLMForTokenClassification import torch device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model = LayoutLMForTokenClassification.from_pretrained("microsoft/layoutlm-base-uncased", num_labels=num_labels) model. naar apparaat)


Start ten slotte de training:

rom transformers importeer AdamW van tqdm import tqdm optimizer = AdamW(model.parameters(), lr=5e-5) global_step = 0 num_train_epochs = 50 t_total = len(train_dataloader) * num_train_epochs # totaal aantal trainingsstappen #zet het model in training mode model.train() voor tijdperk binnen bereik(num_train_epochs): voor batch in tqdm(train_dataloader, desc="Training"): input_ids = batch[0].to(apparaat) bbox = batch[4].to(apparaat) aandacht_masker = batch[1].to(apparaat) token_type_ids = batch[2].to(apparaat) labels = batch[3].to(apparaat) # forward pass outputs = model(input_ids=input_ids, bbox=bbox, aandacht_mask= aandacht_masker, token_type_ids=token_type_ids, labels=labels) loss = outputs.loss # printverlies elke 100 stappen als global_step % 100 == 0: print(f"Verlies na {global_step} stappen: {loss.item()}") # achterwaartse pas om de gradiënten loss.backward() te krijgen #print("Verlopen op classificatiekop:") #print(model.classifier.weight.grad[6,:].sum()) # update optimizer.step() optimizer .zero_grad() globale_stap += 1


Je zou in staat moeten zijn om de voortgang van de training te zien en het verlies wordt bijgewerkt.

Figuur
Lay-out LM-training wordt uitgevoerd

 

Evalueer na de training de prestaties van het model met de volgende functie:

mport numpy as np from seqeval.metrics import ( Classification_report, f1_score, Precision_score, Recall_score, ) eval_loss = 0.0 nb_eval_steps = 0 preds = Geen out_label_ids = Geen # zet model in evaluatiemodus model.eval() voor batch in tqdm(eval_dataloader, desc ="Evalueren"): met torch.no_grad(): input_ids = batch[0].to(apparaat) bbox = batch[4].to(apparaat) aandacht_masker = batch[1].to(apparaat) 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) # krijg het verlies en logits tmp_eval_loss = outputs.loss logits = outputs.logits eval_loss += tmp_eval_loss.item() nb_eval_steps += 1 # bereken de voorspellingen als preds Geen is: 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 ) # bereken het gemiddelde evaluatieverlies eval_loss = eval_loss / nb_eval_steps preds = np.argmax(preds, axis=2) out_label_list = [[] voor _ binnen bereik(out_label_ids. shape[0])] preds_list = [[] voor _ binnen bereik(out_label_ids.shape[0])] voor i binnen bereik(out_label_ids.shape[0]): voor j binnen bereik(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]]) resultaten = { "loss": eval_loss, "precision": precisie_score(out_label_list, preds_list), "recall": recall_score(out_label_list, preds_list), "f1": f1_score(out_label_list, preds_list), }


Met slechts 50 documenten behalen we de volgende scores:

Figuur
Evaluatiescore na de training

 

Met meer annotaties zouden we zeker hogere scores moeten halen.

Bewaar het model ten slotte voor toekomstige voorspellingen:

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


Gevolgtrekking:

 
Nu komt het leuke gedeelte: laten we een factuur uploaden, OCR-en en de relevante entiteiten extraheren. Voor deze test gebruiken we een factuur die niet in de trainings- of testdataset zat. Om de tekst van de factuur te ontleden, gebruiken we het open source pakket Tesseract. Laten we het pakket installeren:

!sudo apt install tesseract-ocr !pip installeer pytesseract


Voordat we voorspellingen kunnen uitvoeren, moeten we de tekst uit de afbeelding ontleden en de tokens en selectiekaders vooraf verwerken tot objecten. Om dit te doen, heb ik een voorbewerkt Python-bestand gemaakt layoutLM_preprocess.py dat maakt het gemakkelijker om de afbeelding voor te bewerken:

mport sys sys.path.insert(1, './drive/MyDrive/UBIAI_layoutlm') van layoutlm_preprocess import * image_path='./content/invoice_test.jpg' afbeelding, woorden, dozen, actuele_boxes = preprocess(image_path)


Laad vervolgens het model en ontvang woordvoorspellingen met hun selectiekaders:

model_path='./drive/MyDrive/trained_layoutlm/layoutlm_UBIAI.pt' model=model_load(model_path,num_labels) word_level_predictions, final_boxes=convert_to_features(afbeelding, woorden, dozen, actuele_dozen, model)


Geef ten slotte de afbeelding weer met de voorspelde entiteiten en selectiekaders:

draw = ImageDraw.Draw(afbeelding) font = ImageFont.load_default() def iob_to_label(label): if label != 'O': return label[2:] else: return "" label2color = {'data_id':'green' ,'date':'green','invoice_id':'blue','factuurnummer':'blue','montant_ht_id':'black','montant_ht':'black','seller_id':'red',' verkoper':'red', 'ttc_id':'grey','ttc':'grey','':'violet', 'tva_id':'orange','tva':'orange'} voor voorspelling, doos in zip(word_level_predictions, final_boxes): voorspelde_label = iob_to_label(label_map[voorspelling]).lower() draw.rectangle(box, overzicht=label2color[voorspeld_label]) draw.text((box[0] + 10, box[1] - 10), tekst=voorspeld_label, vulling=label2kleur[voorspeld_label], lettertype=lettertype) afbeelding


En hier is het:

Figuur
Extractie van factuurentiteit met behulp van LayoutLM

 

Het model kon de verkoper, het factuurnummer, de datum en de TTC correct extraheren, maar maakte een fout door het TTC-label aan een gekocht artikel toe te wijzen. De resultaten zijn indrukwekkend en veelbelovend gezien het lage aantal geannoteerde documenten (slechts 50)! Met meer geannoteerde facturen kunnen we hogere F-scores en nauwkeurigere voorspellingen bereiken. 

Conclusie:

 
Over het geheel genomen zijn de resultaten van het LayoutLM-model veelbelovend en demonstreren ze het nut van transformatoren bij het analyseren van semi-gestructureerde tekst. Het model kan worden verfijnd op alle andere semi-gestructureerde documenten zoals rijbewijzen, contracten, overheidsdocumenten, financiële documenten, enz.

Als je vragen hebt, aarzel dan niet om ze hieronder te stellen of stuur ons een e-mail op admin@ubiai.tools.

Als je dit artikel leuk vond, klap, like en deel dan! 

 
Bio: Walid Amamo is de oprichter van UBIAI, een annotatietool voor NLP-toepassingen, en is gepromoveerd in natuurkunde.

Zie ook:

Coinsmart. Beste Bitcoin-beurs in Europa
Bron: https://www.kdnuggets.com/2021/06/fine-tuning-transformer-model-invoice-recognition.html

spot_img

Laatste intelligentie

spot_img

Chat met ons

Hallo daar! Hoe kan ik u helpen?