一、为什么多模态 RAG 需要图文混合检索

传统 RAG 系统仅处理文本,但当知识库中包含大量图表、流程图、产品图片时,纯文本检索会丢失视觉信息。例如,在医疗报告中,一张 X 光片可能比千字描述更有价值。多模态 RAG 的核心在于将非结构化数据(图片、PDF 中的图表)转化为统一的向量表示,并建立跨模态的语义关联。本文基于 DeepSeek API 的 embedding 能力和外部视觉模型,构建一个可落地的图文混合检索系统。

实现图文混合检索有两种主流路线:一是使用多模态 embedding 模型(如 CLIP)将图片和文本编码到同一向量空间;二是分别用文本和图像 embedding,再通过加权融合或重排序实现混合。考虑到 DeepSeek 当前主要提供文本能力,我们采用第二种方案,利用 CLIP 作为视觉编码器,DeepSeek-embedding 作为文本编码器,并通过一个轻量级融合层对齐向量。

二、技术选型与架构设计

整体架构由数据入库、查询处理、检索融合、重排序四部分组成。入库时,对文本切片和图片分别生成向量;查询时,将用户问题同时转换为文本向量和视觉提示向量(可选)。我们选用 FAISS 作为向量索引,支持多向量字段。为了保证工程性能,索引采用 IVF-PQ 量化,测试 10 万图文对时查询延迟可控制在 50ms 内。

在向量融合方面,直接拼接或简单平均效果不佳。我们采用基于注意力机制的融合层,使用一个可学习的矩阵 W 对文本和图像向量进行加权,公式为 v_final = softmax(W · [v_text; v_image])。该融合层在一个小型标注数据集上训练,收敛速度快,显著提升了检索的准确性。

三、图像向量化的工程实现

图像向量化我们使用 OpenAI 的 CLIP ViT-B/32 模型,其输出 512 维向量。考虑到部署成本,我们将其封装为一个 REST 服务,输入图像 URL,输出向量 JSON。为了解决图片预处理中的分辨率问题,统一缩放到 224×224,并保留原始纵横比。

以下代码展示了如何使用 CLIP 服务对本地图片向量化,并调用 DeepSeek API 对文本向量化。注意,DeepSeek embedding 接口需要将文本分段,且最大 token 限制为 512,需要提前切割。

import requests
import json
from sentence_transformers import SentenceTransformer

# 使用 DeepSeek embedding API(假设 /embeddings 端点)
DEEPSEEK_BASE = "https://api.deepseek.com"
API_KEY = "your-deepseek-api-key"

def embed_text(text):
    headers = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}
    data = {"model": "deepseek-chat", "input": text}
    resp = requests.post(f"{DEEPSEEK_BASE}/embeddings", headers=headers, json=data)
    return resp.json()["data"][0]["embedding"]

# 调用 CLIP 服务
def embed_image(image_path):
    # 假设 CLIP 服务在 localhost:8000/embed_image
    with open(image_path, "rb") as f:
        files = {"file": f}
        resp = requests.post("http://localhost:8000/embed_image", files=files)
    return resp.json()["embedding"][0]  # 假设返回向量列表

四、文本与图像切片的对齐策略

在知识库中,文档通常包含文本和图片,如何切分并建立对应关系是关键。我采用“标题感知切分”策略:识别文档中的标题,将每个标题下的文本段落和图片作为一个片段单元,这样图文紧密关联。例如,在一个技术手册中,标题“架构图”下的文本说明和架构图本身属于同一片段。

具体做法是用 PyMuPDF 提取文本和图片位置,按标题层级分组。切分后,每个片段包含文本内容和图片路径列表。为了对齐文本与图片向量,我们将文本向量和图片向量都附上片段 ID,在检索返回时按片段聚合。

五、混合检索的评分与融合逻辑

检索时,我们分别用文本向量和图像向量在 FAISS 中检索 Top-K,然后通过融合层计算综合分数。下面的伪代码展示了融合过程。需要注意,在过滤式检索中,文本和图像结果有同等重要性,但权重需要根据业务调整。

def hybrid_search(query, top_k=10):
    # 假设我们已计算 query_text_emb 和 query_image_emb(可选)
    # 1. 文本检索
    text_scores, text_indices = index_text.search(query_text_emb, top_k)
    # 2. 图像检索
    image_scores, image_indices = index_image.search(query_image_emb, top_k)
    # 3. 融合(简单加权)
    fused_scores = {}
    for idx, score in zip(text_indices, text_scores):
        fused_scores[idx] = fused_scores.get(idx, 0) + 0.6 * score
    for idx, score in zip(image_indices, image_scores):
        fused_scores[idx] = fused_scores.get(idx, 0) + 0.4 * score
    # 4. 排序取 Top-K
    sorted_items = sorted(fused_scores.items(), key=lambda x: x[1], reverse=True)[:top_k]
    return sorted_items

六、重排序:跨模态相关性校准

初检结果可能不精确,需要重排序。我们使用一个轻量级跨编码器(Cross-Encoder)对候选图文对进行打分,该编码器接受图像特征和文本特征,输出相关性分数。由于跨编码器计算量大,只对 Top-100 候选重排。

实践中,我们发现单纯依赖向量相似度排序,图片和文本的分数尺度不同,导致文本图片混合排序混乱。一个有效技巧是使用分位数归一化,将向量相似度转换为排名百分位,再计算综合排名。这能有效避免某一模态主导。

七、工程坑与优化方案

在开发中,我遇到的最大坑是向量维度不统一:CLIP 是 512 维,若 DeepSeek embedding 输出不同维度,融合时无法对齐。解决方案是加一个投影层将 DeepSeek 向量映射到 512 维,或反之。其次,图片存储使用 S3 访问延迟高,建议使用 CDN 加速。

另一个坑时当知识库图片较小时,向量化效果差。例如,一张 32×32 的图标放大到 224×224 会模糊,导致检索失效。解决办法是过滤过小图片(面积小于 100×100),或对图片进行超分辨率预处理。此外,图文混合检索在 GPU 上推理耗时,我们采用批处理与缓存策略,热点图片向量缓存到内存,降低 P99 延迟 40%。

八、性能对比与验证

我们在自建的 12000 条图文数据上对比了三种方案:仅文本检索、仅图像检索、混合检索。使用 Recall@10 和 MRR 作为指标。结果如下表:

方法Recall@10MRR平均时延
仅文本0.620.4532ms
仅图像0.540.3838ms
混合检索(加权)0.780.6155ms

可以看出混合检索显著提升效果,但时延有所增加,主要是图像向量化服务开销。优化后(重排序阶段只处理图像候选 Top-50)时延降到 48ms。

九、总结与未来方向

多模态 RAG 的难点不在单一模态的 embedding,而在于跨模态对齐与融合。本文提供了一套基于 DeepSeek 和 CLIP 的实践方案,并给出了关键代码。未来可以考虑使用更强大的多模态模型(如 GPT-4V)进行生成,或者引入知识图谱增强关系推理。

作为资深开发者,建议在小规模数据上验证融合策略,再逐步扩容。同时,关注 DeepSeek 后续多模态 API 的发布,有望进一步简化流程。希望本文能为你打开多模态 RAG 的大门。