Mathorcup C题建模核心:分段动力学与三维坐标系重构
2026/10/19 7:31:15
| 文档类型 | 占比 | 信息价值 | 传统 RAG 处理效果 |
|---|---|---|---|
| 纯文本(.txt/.md) | 15% | 低 | ✅ 良好 |
| Word/PPT(含图) | 25% | 中 | ⚠️ 图片丢失 |
| PDF(含表格/图) | 40% | 高 | ❌ 表格变乱码 |
| 扫描件/照片 | 15% | 极高 | ❌ OCR 错误率高 |
| 视频/音频 | 5% | 高 | ❌ 完全无法处理 |
💡核心洞察:越重要的文档,越可能是多模态的(财报、合同、图纸)。
✅关键突破:不止转文字,更要保留“视觉语义”
| 工具 | 优势 | 劣势 | 适用场景 |
|---|---|---|---|
| Unstructured (开源) | 支持 PDF/Word/PPT,内置布局分析 | 表格识别一般 | 通用文档 |
| PaddleOCR + PP-Structure | 中文表格 SOTA,开源免费 | 部署复杂 | 财报/报表 |
| Adobe Extract API | 商业级精度 | 付费,需上传数据 | 高安全要求 |
| Donut (开源模型) | 端到端文档理解 | 需 GPU 微调 | 定制化表单 |
✅本文方案:Unstructured + PaddleOCR 混合(兼顾通用性与表格精度)
# table_parser.py from paddleocr import PPStructure, save_structure_res def extract_tables_from_pdf(pdf_path: str) -> list[dict]: """从 PDF 提取结构化表格""" table_engine = PPStructure( show_log=False, recovery=True, # 保留表格结构 lang="ch" ) # 将 PDF 转为图像(每页一张) images = convert_pdf_to_images(pdf_path) all_tables = [] for img in images: result = table_engine(img) for item in result: if item["type"] == "table": # 获取 HTML 表格(可转 JSON) html_table = item["res"]["html"] df = pd.read_html(html_table)[0] all_tables.append(df.to_dict("records")) return all_tables📊效果:
- 财报表格识别准确率>92%;
- 输出为结构化 JSON,可直接存入数据库。
# multimodal_loader.py from unstructured.partition.pdf import partition_pdf def load_multimodal_pdf(pdf_path: str): """解析 PDF,保留文本+图像+布局""" elements = partition_pdf( filename=pdf_path, strategy="hi_res", # 高精度模式 infer_table_structure=True, # 启用表格推理 extract_images_in_pdf=True, # 提取内嵌图片 chunking_strategy="by_title" ) chunks = [] for element in elements: if hasattr(element, "text") and element.text.strip(): # 文本块:记录内容 + 类型 + 位置 chunks.append({ "type": element.category, "content": element.text, "bbox": getattr(element, "bbox", None) }) elif hasattr(element, "image"): # 图像:保存路径 + 描述(后续用 Qwen-VL 生成) image_path = save_image(element.image) chunks.append({ "type": "image", "image_path": image_path, "description": "" # 待填充 }) return chunks✅输出结构:
[ {"type": "Title", "content": "2025年Q3财报"}, {"type": "Table", "content": "[[营收, 1.2亿], [利润, 3000万]]"}, {"type": "Image", "image_path": "/tmp/chart1.png"} ]
Qwen-VL是阿里推出的多模态大模型,支持图像理解 + 图文对话。
# image_understanding.py from qwen_vl_utils import get_qwen_vl qwen_vl = get_qwen_vl(api_key=os.getenv("QWEN_API_KEY")) def describe_image(image_path: str) -> str: """生成图像语义描述""" response = qwen_vl.chat( messages=[{ "role": "user", "content": [ {"image": image_path}, {"text": "请详细描述这张图片的内容,包括图表类型、关键数据、趋势等。"} ] }] ) return response[0]["content"]🖼️示例输入:一张柱状图
输出:“该图展示了2025年Q1-Q3各季度营收,Q3最高达1.2亿元,呈逐季上升趋势。”
对于敏感图片,可本地部署Donut 模型(Document Understanding Transformer):
# donut_local.py from transformers import DonutProcessor, VisionEncoderDecoderModel processor = DonutProcessor.from_pretrained("naver-clova-ix/donut-base-finetuned-cord-v2") model = VisionEncoderDecoderModel.from_pretrained("naver-clova-ix/donut-base-finetuned-cord-v2") def donut_inference(image_path: str) -> dict: image = Image.open(image_path).convert("RGB") pixel_values = processor(image, return_tensors="pt").pixel_values task_prompt = "<s_cord-v2>" 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 ) decoded = processor.batch_decode(outputs, skip_special_tokens=True)[0] return json.loads(decoded)🔒优势:数据不出内网,适合金融/医疗场景。
| 内容类型 | 向量化方法 |
|---|---|
| 文本块 | bge-large-zhEmbedding |
| 表格数据 | 将 JSON 转为自然语言后 Embedding |
| 图像 | Qwen-VL的图像 Embedding 或CLIP |
# multimodal_embedding.py def embed_multimodal_chunk(chunk: dict) -> np.ndarray: if chunk["type"] == "image": # 使用 Qwen-VL 获取图像 embedding return qwen_vl.get_image_embedding(chunk["image_path"]) elif chunk["type"] == "table": # 将表格转为描述性文本 table_text = "表格内容:" + json.dumps(chunk["content"], ensure_ascii=False) return text_embedder.encode(table_text) else: return text_embedder.encode(chunk["content"])# milvus_store.py from pymilvus import connections, Collection, FieldSchema, DataType # 创建多模态 Schema fields = [ FieldSchema(name="id", dtype=DataType.INT64, is_primary=True, auto_id=True), FieldSchema(name="content", dtype=DataType.VARCHAR, max_length=65535), FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=1024), FieldSchema(name="doc_type", dtype=DataType.VARCHAR, max_length=32), # text/table/image FieldSchema(name="source_file", dtype=DataType.VARCHAR, max_length=256) ] collection = Collection("multimodal_kb", fields) collection.create_index("embedding", {"index_type": "IVF_FLAT", "metric_type": "L2"})✅优势:
- 支持十亿级向量;
- 可按
doc_type过滤(如只搜表格)。
用户问:“找一张展示 Q3 营收增长的图表”
# 文字 query → 图像 embedding 空间检索 query_emb = text_embedder.encode("Q3 营收增长图表") results = collection.search( data=[query_emb], anns_field="embedding", param={"metric_type": "L2", "params": {"nprobe": 10}}, limit=3, expr='doc_type == "image"' # 只搜图片 )用户上传一张架构图,问:“这是哪个系统的架构?”
# 图片 → Qwen-VL embedding → 文本库检索 img_emb = qwen_vl.get_image_embedding(uploaded_image_path) results = collection.search( data=[img_emb], anns_field="embedding", limit=5, expr='doc_type in ["text", "title"]' )# multimodal_rag_chain.py def create_multimodal_rag_chain(): def retrieve_context(question: str): # 1. 文本检索 text_results = vector_db.search(question, filter='doc_type != "image"') # 2. 若问题含“图/表”,额外检索图像 if any(kw in question for kw in ["图", "表", "chart", "graph"]): img_results = vector_db.search(question, filter='doc_type == "image"') # 用 Qwen-VL 生成图像描述 for r in img_results: r["description"] = describe_image(r["image_path"]) return text_results + img_results return text_results prompt = ChatPromptTemplate.from_template( "你是一个多模态助手,请结合以下信息回答问题:\n" "{context}\n\n问题:{question}" ) chain = ( {"context": retrieve_context, "question": RunnablePassthrough()} | prompt | qwen_vl # 使用 Qwen-VL 生成图文答案 | StrOutputParser() ) return chain✅输出示例:
“根据您提供的架构图(见附件),这是订单管理系统的微服务架构,包含用户服务、商品服务、支付服务三个核心模块。”
# table_to_sql.py def store_table_to_sql(table_data: list, table_name: str): df = pd.DataFrame(table_data) df.to_sql(table_name, con=sqlite_conn, if_exists="replace", index=False) # 用户问:“Q3 利润率是多少?” # Agent 自动执行: # SELECT 利润/营收 AS 利润率 FROM q3_financial WHERE 季度='Q3'# 用户上传财报表格截图 response = qwen_vl.chat([ {"image": "q3_table.png"}, {"text": "Q3 的净利润是多少?"} ]) # 输出:“Q3 净利润为 3000 万元。”✅无需 OCR + 结构化,端到端问答!
| 文档类型 | 处理方式 | 成本 | 延迟 |
|---|---|---|---|
| 纯文本 | 直接 Embedding | $0 | <100ms |
| 清晰 PDF 表格 | PaddleOCR + 结构化 | $0 | ~1s |
| 扫描件/模糊图 | Qwen-VL API | $0.02/张 | ~2s |
| 敏感图片 | 本地 Donut | GPU 成本 | ~3s |
💡建议:对高频文档预处理,实时请求只处理新文件。
# 缓存图像描述和表格 JSON @lru_cache(maxsize=1000) def cached_describe_image(image_hash: str): return describe_image(get_path_by_hash(image_hash))场景:用户上传一份扫描版合同 PDF,问:“甲方是谁?签约日期?”
系统执行:
✅全程自动化,准确率 >88%(实测 100 份合同)
| 问题 | 解决方案 |
|---|---|
| PDF 表格线断裂 | 启用 PaddleOCR 的recovery=True |
| 手写体识别差 | 优先使用 Qwen-VL 端到端理解 |
| 多页表格跨页 | 后处理合并相邻表格 |
| 图像隐私泄露 | 敏感图片走本地 Donut,不传云端 |
| 向量维度不一致 | 统一用 Qwen-VL 的 1024 维 embedding |
| 能力 | 文本 RAG | 多模态 RAG |
|---|---|---|
| 理解纯文本 | ✅ | ✅ |
| 提取表格数据 | ❌ | ✅ |
| 理解图表语义 | ❌ | ✅ |
| 处理扫描件 | ❌ | ✅ |
| 跨模态检索 | ❌ | ✅ |
未来方向:
- 视频 RAG:从会议录像中提取决策;
- 3D 模型理解:工业设计图纸问答;
- 实时多模态 Agent:边看屏幕边操作软件……