Document AI Structured Data Extraction from Scans

We design and deploy artificial intelligence systems: from prototype to production-ready solutions. Our team combines expertise in machine learning, data engineering and MLOps to make AI work not in the lab, but in real business.
Showing 1 of 1 servicesAll 1566 services
Document AI Structured Data Extraction from Scans
Medium
~5 business days
FAQ
AI Development Areas
AI Solution Development Stages
Latest works
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_logo-advance_0.png
    B2B Advance company logo design
    561
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    823
  • image_logo-aider_0.jpg
    AIDER company logo development
    762
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    848

Реализация извлечения структурированных данных из сканов документов (Document AI)

Document AI — задача преобразования сканов и фотографий документов в структурированные данные: JSON, CSV, записи в базе данных. Это уровень выше простого OCR: система понимает семантику документа и маппирует извлечённый текст на поля схемы данных.

LayoutLM и его производные

Ключевое отличие Document AI от обычного NLP: модель обрабатывает не только текст, но и визуальное расположение элементов (координаты слов) и изображение документа. LayoutLMv3 объединяет все три модальности.

from transformers import LayoutLMv3Processor, LayoutLMv3ForTokenClassification
from PIL import Image
import torch

class DocumentFieldExtractor:
    def __init__(self, model_path: str, labels: list[str]):
        self.processor = LayoutLMv3Processor.from_pretrained(model_path)
        self.model = LayoutLMv3ForTokenClassification.from_pretrained(
            model_path,
            num_labels=len(labels) * 2 + 1  # BIO tagging
        )
        self.labels = labels
        self.model.eval()

    @torch.no_grad()
    def extract(self, image_path: str) -> dict:
        image = Image.open(image_path).convert('RGB')

        # Processor сам запускает OCR (через Tesseract) и нормализует координаты
        encoding = self.processor(
            image,
            return_tensors='pt',
            truncation=True,
            padding='max_length',
            max_length=512
        )

        outputs = self.model(**encoding)
        predictions = outputs.logits.argmax(-1).squeeze().tolist()

        # Извлечение сущностей по BIO меткам
        return self._decode_entities(encoding, predictions)

Построение кастомного экстрактора

Для каждого типа документа (инвойс, договор, накладная) обучаем отдельный экстрактор на размеченном датасете:

Разметка данных: Label Studio с поддержкой Document AI. Разметчик выделяет области на скане и назначает тип поля. Минимум 200–500 размеченных документов для базового качества.

Fine-tuning LayoutLMv3:

from transformers import TrainingArguments, Trainer
from datasets import load_dataset

training_args = TrainingArguments(
    output_dir='./invoice_extractor',
    num_train_epochs=20,
    per_device_train_batch_size=2,
    per_device_eval_batch_size=2,
    learning_rate=5e-5,
    warmup_steps=100,
    weight_decay=0.01,
    fp16=True,
    evaluation_strategy='epoch',
    save_strategy='best',
    metric_for_best_model='f1'
)

trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=train_dataset,
    eval_dataset=eval_dataset,
    compute_metrics=compute_ner_metrics
)
trainer.train()

Пример: универсальный экстрактор инвойсов

class InvoiceExtractor:
    """Извлечение данных из инвойсов любого формата"""

    FIELDS = [
        'invoice_number', 'invoice_date', 'due_date',
        'vendor_name', 'vendor_address', 'vendor_tin',
        'buyer_name', 'buyer_address', 'buyer_tin',
        'line_item_description', 'line_item_quantity',
        'line_item_unit_price', 'line_item_amount',
        'subtotal', 'tax_rate', 'tax_amount', 'total_amount',
        'payment_terms', 'bank_account'
    ]

    def extract(self, scan_path: str) -> dict:
        # 1. OCR
        ocr_result = self.ocr.extract_text(scan_path)

        # 2. LayoutLMv3 извлечение полей
        fields = self.layoutlm.extract(scan_path)

        # 3. Постобработка и валидация
        processed = self._postprocess(fields)

        # 4. Проверка контрольных сумм (subtotal + tax = total)
        processed['validation'] = self._validate_amounts(processed)

        return processed

    def _postprocess(self, fields: dict) -> dict:
        # Нормализация дат
        if fields.get('invoice_date'):
            fields['invoice_date'] = normalize_date(fields['invoice_date'])

        # Нормализация сумм (удаление пробелов, замена запятых)
        for amount_field in ['total_amount', 'tax_amount', 'subtotal']:
            if fields.get(amount_field):
                fields[amount_field] = parse_amount(fields[amount_field])

        return fields

Качество на реальных датасетах

Датасет Поле F1 (LayoutLMv3)
FUNSD (формы) Entity extraction 92.0%
CORD (чеки) Поля транзакций 95.5%
SROIE (инвойсы) 4 ключевых поля 96.6%
DocVQA (QA на документах) ANLS 83.5%
Тип проекта Срок
Один тип документа, 500+ размеченных сканов 4–6 недель
3–5 типов документов 8–12 недель
Универсальный экстрактор с дообучением 12–18 недель