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
面试常问点¶
- LayoutLM和普通BERT的区别?
LayoutLM额外添加2D位置embedding(x1,y1,x2,y2 bbox坐标),让模型感知文字在文档中的位置
OCR-free文档理解模型(Donut)的优势?
避免OCR错误传播、端到端训练、可直接处理低质量扫描件
表格理解的难点?
合并单元格、跨页表格、复杂表头层级(多级表头)
文档理解在金融/医疗中的应用?
- 合同关键条款抽取、处方/病历结构化、银行对账单解析
速查表¶
| 任务 | 工具/模型 |
|---|---|
| PDF文字提取 | pdfplumber / PyMuPDF |
| 表格提取 | pdfplumber / Camelot |
| 文档QA | LayoutLMv3 |
| 无OCR解析 | Donut |
| 通用OCR | PaddleOCR / Tesseract |