文档了解是文档解决和提取中最重要的步骤。这是从非结构化或半结构化文档中提取信息并将其转换为结构化模式的过程。提取后的结构化示意能够反对各种上游工作,例如信息检索,汇总,分类等。有许多不同的办法能够了解文档,但它们都有一个独特的指标:创立文档内容的结构化示意,以便用于进一步的解决。
对于半结构化文档,例如发票,收款或合同,Microsoft的Layoutlm模型能够良好的进行工作。
在本文中,咱们将在微软的最新Layoutlm V3上进行微调,并将其性能与Layoutlm V2模型进行比拟。
LayoutLM v3
LayoutLM v3绝对于其前两个版本的次要劣势是多模态transformer 架构,它以对立的形式将文本和图像嵌入联合起来。文档图像不依赖CNN进行解决,而是将图像补丁块示意为线性投影,而后线性嵌入与文本标记对齐,如下图所示。这种办法的次要长处是缩小了所需的参数和整体计算量。
论文的作者示意,“LayoutLMv3不仅在以文本为核心的工作(包含表单了解、票据了解和文档视觉问题答复)中实现了最先进的性能,而且还在以图像为核心的工作(如文档图像分类和文档布局剖析)中实现了最先进的性能。”
微调LayoutLM v3
咱们将应用雷同的220个带正文的发票数据集来微调layoutLM v3模型。为了进行标注,我应用了UBIAI文本正文工具,因为它反对OCR解析,原生PDF/图像正文,并能够用LayoutLM模型兼容的格局导出,这样就能够节俭前期解决的工作。
从UBIAI导出正文文件后,咱们将应用谷歌colab进行模型训练和推理。源代码地址在最初提供,咱们这里简述工作的流程
第一步是关上colab,装置相应的库。与layoutLMv2不同,咱们没有应用detectron 2包对实体提取的模型进行微调。然而对于布局检测(不在本文探讨范畴内),须要应用detectorn 2包:
from google.colab import drivedrive.mount('/content/drive')!pip install -q git+https://github.com/huggingface/transformers.git!pip install -q git+https://github.com/huggingface/datasets.git "dill<0.3.5" seqeval
接下来,应用preprocess.py脚本来解决从UBIAI导出的ZIP文件:
! rm -r layoutlmv3FineTuning! git clone -b main https://github.com/UBIAI/layoutlmv3FineTuning.git#!/bin/bashIOB_DATA_PATH = "/content/drive/MyDrive/LayoutLM_data/Invoice_Project_mkWSi4Z.zip"! cd /content/! rm -r data! mkdir data! cp "$IOB_DATA_PATH" data/dataset.zip! cd data && unzip -q dataset && rm dataset.zip! cd ..
运行预处理脚本:
#!/bin/bashTEST_SIZE = 0.33DATA_OUTPUT_PATH = "/content/"! python3 layoutlmv3FineTuning/preprocess.py --valid_size $TEST_SIZE --output_path $DATA_OUTPUT_PATH
加载解决后数据集:
from datasets import load_metricfrom transformers import TrainingArguments, Trainerfrom transformers import LayoutLMv3ForTokenClassification,AutoProcessorfrom transformers.data.data_collator import default_data_collatorimport torchfrom datasets import load_from_disktrain_dataset = load_from_disk(f'/content/train_split')eval_dataset = load_from_disk(f'/content/eval_split')label_list = train_dataset.features["labels"].feature.namesnum_labels = len(label_list)label2id, id2label = dict(), dict()for i, label in enumerate(label_list): label2id[label] = i id2label[i] = label
定义评估指标:
metric = load_metric("seqeval")import numpy as npreturn_entity_level_metrics = Falsedef compute_metrics(p): predictions, labels = p predictions = np.argmax(predictions, axis=2) true_predictions = [ [label_list[p] for (p, l) in zip(prediction, label) if l != -100] for prediction, label in zip(predictions, labels) ] true_labels = [ [label_list[l] for (p, l) in zip(prediction, label) if l != -100] for prediction, label in zip(predictions, labels) ]results = metric.compute(predictions=true_predictions, references=true_labels,zero_division='0') if return_entity_level_metrics: # Unpack nested dictionaries final_results = {} for key, value in results.items(): if isinstance(value, dict): for n, v in value.items(): final_results[f"{key}_{n}"] = v else: final_results[key] = value return final_results else: return { "precision": results["overall_precision"], "recall": results["overall_recall"], "f1": results["overall_f1"], "accuracy": results["overall_accuracy"], }
对模型进行训练和评估:
model = LayoutLMv3ForTokenClassification.from_pretrained("microsoft/layoutlmv3-base", id2label=id2label, label2id=label2id)processor = AutoProcessor.from_pretrained("microsoft/layoutlmv3-base", apply_ocr=False)NUM_TRAIN_EPOCHS = 50PER_DEVICE_TRAIN_BATCH_SIZE = 1PER_DEVICE_EVAL_BATCH_SIZE = 1LEARNING_RATE = 4e-5training_args = TrainingArguments(output_dir="test", # max_steps=1500, num_train_epochs=NUM_TRAIN_EPOCHS, logging_strategy="epoch", save_total_limit=1, per_device_train_batch_size=PER_DEVICE_TRAIN_BATCH_SIZE, per_device_eval_batch_size=PER_DEVICE_EVAL_BATCH_SIZE, learning_rate=LEARNING_RATE, evaluation_strategy="epoch", save_strategy="epoch", # eval_steps=100, load_best_model_at_end=True, metric_for_best_model="f1") trainer = Trainer( model=model, args=training_args, train_dataset=train_dataset, eval_dataset=eval_dataset, tokenizer=processor, data_collator=default_data_collator, compute_metrics=compute_metrics,)trainer.train()trainer.evaluate()
训练实现后,对测试数据集进行评估。以下为评估后的模型得分:
{'epoch': 50.0, 'eval_accuracy': 0.9521988527724665, 'eval_f1': 0.6913439635535308, 'eval_loss': 0.41490793228149414, 'eval_precision': 0.6362683438155137, 'eval_recall': 0.756857855361596, 'eval_runtime': 9.7501, 'eval_samples_per_second': 9.846, 'eval_steps_per_second': 9.846}
该模型f1得分为0.69,召回率为0.75,准确率为0.63。
让咱们在不属于训练数据集的新发票上运行模型。
应用LayoutLM v3进行预测
为了进行预测,咱们将应用Tesseract对发票进行OCR,并将信息输出到训练好的模型中进行预测。为了简化这一过程,我创立了一个自定义脚本,其中只蕴含几行代码,容许接管OCR输入并应用模型运行预测。
第一步,让咱们导入一些重要的库并加载模型:
from google.colab import drivedrive.mount('/content/drive')!pip install -q git+https://github.com/huggingface/transformers.git! sudo apt install tesseract-ocr! sudo apt install libtesseract-dev! pip install pytesseract! git clone https://github.com/salmenhsairi/layoutlmv3FineTuning.gitimport osimport torchimport warningsfrom PIL import Imagewarnings.filterwarnings('ignore')os.makedirs('/content/images',exist_ok=True)for image in os.listdir(): try: img = Image.open(f'{os.curdir}/{image}') os.system(f'mv "{image}" "images/{image}"') except: pass model_path = "/content/drive/MyDrive/LayoutLM_data/layoutlmv3.pth" # path to Layoutlmv3 modelimag_path = "/content/images" # images folderif model_path.endswith('.pth'): layoutlmv3_model = torch.load(model_path) model_path = '/content/pre_trained_layoutlmv3' layoutlmv3_model.save_pretrained(model_path)
应用模型进行预测
咱们应用了220张带标注的发票进行训练,该模型可能正确预测卖方名称、日期、发票编号和总价(TTC)!
如果仔细观察,就会发现把笔记本电脑总价当作发票总价的做法是谬误的(上图)。这并不奇怪,咱们能够用更多的训练数据来解决这个问题。
比拟LayoutLM v2和LayoutLM v3
除了计算量更少之外,layoutLM V3是否比它的v2版本提供了性能晋升?为了答复这个问题,咱们比拟了雷同发票的两个模型输入。上面雷同数据下layoutLM v2输入:
v3模型可能正确地检测到大多数的我的项目,而v2不能检测invoice_ID、发票number_ID和Total_ID
v2型号谬误地将Total price $ 1445 .00标为MONTANT_HT(法语中是税前总价),而v3正确地预测了总价。
两个模型都谬误地将笔记本电脑的价格标为Total。
基于这个例子,layoutLM V3显示了更好的整体性能,但咱们须要在更大的数据集上进行测试。
总结
本文中展现了如何在发票数据提取的特定用例上微调layoutLM V3。而后将其性能与layoutLM V2进行了比拟,发现它的性能略有进步,但仍须要在更大的数据集上验证。
基于性能和计算收益,我强烈建议应用新的layoutLM v3。
本文的一些有用的材料:
https://avoid.overfit.cn/post/be399d8f17f542929155b8b2481ecaaa
作者:Walid Amamou