跳转至

476_文档理解与分析


一句话说明

文档理解(Document Understanding)让AI读懂带有版式结构的文档(PDF、表格、发票等),不只是纯文本,还要理解布局和视觉信息。


核心知识点

  • 文档 vs 纯文本:文档包含版式(布局)、表格、图片、页眉页脚等结构信息
  • 多模态模型:LayoutLM、DocFormer等同时编码文本+位置(坐标)+视觉特征
  • 关键信息抽取(KIE):从发票、合同中抽取特定字段(金额、日期等)
  • 表格理解:识别表格结构,转换为结构化数据
  • 文档分类:按文档类型自动分类(合同/发票/简历)

经典模型对比

模型特点适用任务
LayoutLM v1文本+2D位置embedding表单KIE
LayoutLM v2增加视觉特征(图像patch)表格/文档QA
LayoutLM v3统一文本和图像patch token端到端文档理解
Donut无需OCR,端到端图像→文字表单解析
DocFormer多模态Transformer通用文档理解

代码示例

# ---- 方案1:使用 LayoutLMv3 做文档问答 ----
# pip install transformers Pillow pytesseract

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

# 加载处理器(同时处理文本和图像)
processor = LayoutLMv3Processor.from_pretrained(
    "microsoft/layoutlmv3-base",
    apply_ocr=True   # 自动用OCR提取文本
)
model = LayoutLMv3ForQuestionAnswering.from_pretrained("microsoft/layoutlmv3-base")

# 加载文档图像(假设是扫描发票)
image = Image.open("invoice.png").convert("RGB")
question = "What is the total amount?"

# 处理器自动完成:OCR提取文字→分词→提取bbox坐标→图像特征
encoding = processor(image, question, return_tensors="pt")

with torch.no_grad():
    outputs = model(**encoding)
    start_logits = outputs.start_logits  # 答案起始位置
    end_logits = outputs.end_logits      # 答案结束位置
    start = start_logits.argmax()
    end = end_logits.argmax() + 1

# 解码答案
answer = processor.tokenizer.decode(
    encoding['input_ids'][0][start:end]
)
print(f"答案: {answer}")

# ---- 方案2:PDFPlumber 提取PDF表格 ----
# pip install pdfplumber
import pdfplumber
import pandas as pd

with pdfplumber.open("report.pdf") as pdf:
    for page_num, page in enumerate(pdf.pages):
        # 提取当前页所有表格
        tables = page.extract_tables()
        for i, table in enumerate(tables):
            df = pd.DataFrame(table[1:], columns=table[0])  # 第一行作表头
            print(f"第{page_num+1}页 表格{i+1}:")
            print(df.head())

        # 提取纯文本
        text = page.extract_text()
        print(f"文本前100字: {text[:100] if text else '无文本'}")

# ---- 方案3:Donut(无需OCR的端到端文档解析)----
from transformers import DonutProcessor, VisionEncoderDecoderModel
import re

processor = DonutProcessor.from_pretrained("naver-clova-ix/donut-base-finetuned-cord-v2")
model = VisionEncoderDecoderModel.from_pretrained("naver-clova-ix/donut-base-finetuned-cord-v2")

# Donut直接从图像生成结构化输出(JSON格式)
image = Image.open("receipt.png").convert("RGB")
pixel_values = processor(image, return_tensors="pt").pixel_values

# 设置解码任务提示
task_prompt = "<s_cord-v2>"  # CORD收据解析任务的起始token
decoder_input_ids = processor.tokenizer(
    task_prompt, add_special_tokens=False, return_tensors="pt"
).input_ids

outputs = model.generate(
    pixel_values,
    decoder_input_ids=decoder_input_ids,
    max_length=model.decoder.config.max_position_embeddings,
    early_stopping=True,
    pad_token_id=processor.tokenizer.pad_token_id,
    eos_token_id=processor.tokenizer.eos_token_id,
)

# 解码输出的JSON结构
sequence = processor.batch_decode(outputs)[0]
sequence = sequence.replace(processor.tokenizer.eos_token, "").replace(
    processor.tokenizer.pad_token, ""
)
print("解析结果:", sequence)

# ---- 方案4:简单规则抽取(发票关键字段)----
import re

def extract_invoice_fields(text):
    """正则表达式从OCR文本中抽取发票字段"""
    fields = {}
    # 匹配金额
    amount_pattern = r'(?:总金额|合计|Total)[::]\s*[¥¥$]?\s*(\d+\.?\d*)'
    match = re.search(amount_pattern, text)
    if match:
        fields['total_amount'] = float(match.group(1))
    # 匹配日期
    date_pattern = r'(\d{4})[年/\-](\d{1,2})[月/\-](\d{1,2})日?'
    match = re.search(date_pattern, text)
    if match:
        fields['date'] = f"{match.group(1)}-{match.group(2):0>2}-{match.group(3):0>2}"
    return fields

面试常问点

  1. LayoutLM和普通BERT的区别?
  2. LayoutLM额外添加2D位置embedding(x1,y1,x2,y2 bbox坐标),让模型感知文字在文档中的位置

  3. OCR-free文档理解模型(Donut)的优势?

  4. 避免OCR错误传播、端到端训练、可直接处理低质量扫描件

  5. 表格理解的难点?

  6. 合并单元格、跨页表格、复杂表头层级(多级表头)

  7. 文档理解在金融/医疗中的应用?

  8. 合同关键条款抽取、处方/病历结构化、银行对账单解析

速查表

任务工具/模型
PDF文字提取pdfplumber / PyMuPDF
表格提取pdfplumber / Camelot
文档QALayoutLMv3
无OCR解析Donut
通用OCRPaddleOCR / Tesseract