
多模态 Rag for All!使用 Ai(和 Python)处理任何文件类型的入门指南
初学者友好的指南,包含示例 (Python) 代码
这是关于多模态 AI 的 更大系列 的第三篇文章。在之前的帖子中,我们分别讨论了 多模态 LLMs 和 嵌入模型。在本文中,我们将结合这些思想,以便开发多模态 RAG 系统。我将首先回顾关键概念,然后分享实现此类系统的示例代码。
像 GPT、LLaMA 和 Claude 这样的语言模型通过其预训练学习了大量的世界知识。这使它们成为解决定制问题和回答复杂问题的强大工具。
然而,即使是最先进的语言模型也对某些知识一无所知。这包括组织内部的专有信息、模型预训练数据收集后发生的事件,以及在互联网上并不普遍的专业知识。
虽然这种无知限制了模型的开箱即用能力,但有 一种流行的技术可以克服这些限制:检索增强生成(或简称 RAG)。
什么是 RAG?
RAG 是一种 通过动态提供相关上下文来提高模型响应质量 的方法。以下是一个可能有帮助的例子。
假设,我忘记了同事在昨天会议上提到的一个 Python 库的名称。这不是 ChatGPT 可以帮助我的,因为它不知道会议的内容。
然而,RAG 可以通过获取我的问题(例如:“Rachel 在昨天的会议上提到的那个 Python 库叫什么名字?”),自动提取会议记录,然后将我的原始查询和记录提供给 LLM 来帮助解决这个问题。
RAG 系统的基本设计。图片由作者提供。
多模态 RAG
虽然通过 RAG 改进 LLM 可以解锁多个实际用例,但在某些情况下,相关信息存在于非文本格式中,例如图像、视频、图表和表格。在这种情况下,我们可以更进一步,构建 多模态 RAG 系统,能够处理文本和非文本数据的 AI 系统。
多模态 RAG 使得推理更加复杂,超越了单靠文本所传达的信息。例如,它可以分析某人的面部表情和语音语调,以为会议的转录提供更丰富的上下文。
多模态 RAG 系统的基本设计。图片来自作者。
3 Levels of MRAG
While there are several ways to implement a multimodal RAG (MRAG) system, here I will focus on three basic strategies at increasing levels of sophistication.
- Translate modalities to text.
- Text-only retrieval + MLLM
- Multimodal retrieval + MLLM
The following discussion assumes you already have a basic understanding of RAG and multimodal models. The following articles discussed these topics: RAG, Multimodal LLMs, and Multimodal Embeddings.
Level 1: 将模态翻译为文本
一种使 RAG 系统多模态化的简单方法是通过 在将其存储在知识库之前将新模态翻译为文本。这可以简单地通过将会议录音转换为文本记录、使用现有的多模态 LLM (MLLM) 生成图像标题,或将表格转换为可读的文本格式(例如,.csv 或 .json)来实现。
MRAG 第 1 级的视觉概述。图片由作者提供。
这种方法的主要优点是 对现有 RAG 系统的更改最小。此外,通过明确生成非文本模态的文本表示,可以更好地控制要提取的数据特征。例如,分析图形的标题可能同时包含描述和关键见解。
当然,这种策略的缺点是 模型的响应无法直接使用非文本数据,这意味着从图像到文本的转换可能会造成关键信息瓶颈。
Level 2: Text-only retrieval + MLLM
另一种方法是生成知识库中所有项的文本表示,例如,用于检索的描述和元标签,但将原始模态传递给多模态 LLM (MLLM)。例如,图像元数据用于检索步骤,相关的图像被传递给模型进行推理。
MRAG 第二级的视觉概述。图片由作者提供。
这保持了第一级的许多好处,同时减轻了其局限性。也就是说,知识库中项的文本特征可以针对搜索进行优化,但下游模型可以利用每个项原始模态的全部丰富性。
这种方法的关键区别在于它需要一个MLLM,即能够处理非文本数据的 LLM。这解锁了更高级的推理能力,如 GPT-4o 或 LLaMA 3.2 Vision 等模型所示。
Level 3: 多模态检索 + MLLM
虽然我们可以在第 1 级和第 2 级的检索过程中使用基于关键字的搜索,但通常的做法是使用所谓的 向量搜索。这包括 生成知识库中项目的向量表示(即,嵌入),然后 通过计算输入查询与知识库中每个项目之间的相似度得分来执行搜索。
传统上,这要求查询和知识库项目是基于文本的。然而,正如我们在本系列的 上一篇文章 中看到的,存在 多模态嵌入模型,它们 生成文本和非文本数据的对齐向量表示。
因此,我们可以使用多模态嵌入来执行多模态检索。这与基于文本的向量搜索的工作方式相同,但现在嵌入空间将相似概念共同定位,而不考虑其原始模态。这样的检索策略的结果可以直接传递给 MLLM。
MRAG 第 3 级的视觉概述。图片由作者提供。
示例代码:多模态博客问答助手
在基本了解多模态 RAG 的工作原理后,让我们看看如何构建这样的系统。在这里,我将创建一个可以访问本系列前两篇博客中的文本和图形的问答助手。
该示例的 Python 代码可以在 GitHub repo 上免费下载。
导入与数据加载
我们首先导入一些方便的库和模块。
import json
from transformers import CLIPProcessor, CLIPTextModelWithProjection
from torch import load, matmul, argsort
from torch.nn.functional import softmax
接下来,我们将从 多模态 LLMs 和 多模态嵌入 博客文章中导入文本和图像块。这些内容保存在 .json 文件中,可以作为字典列表加载到 Python 中。
with open('data/text_content.json', 'r', encoding='utf-8') as f:
text_content_list = json.load(f)
with open('data/image_content.json', 'r', encoding='utf-8') as f:
image_content_list = json.load(f)
虽然我在这里不会回顾数据准备过程,但我使用的代码在 GitHub 仓库 上。
我们还将加载每个 text_content_list
和 image_content_list
项目的多模态嵌入(来自 CLIP)。这些以 pytorch 张量形式保存。
text_embeddings = load('data/text_embeddings.pt', weights_only=True)
image_embeddings = load('data/image_embeddings.pt', weights_only=True)
print(text_embeddings.shape)
print(image_embeddings.shape)
打印这些张量的形状,我们看到它们通过 512 维嵌入表示。我们有 86 个文本块和 17 张图像。
多模态搜索
在加载我们的知识库后,我们现在可以为向量搜索定义查询。这将包括使用 CLIP 将输入查询转换为嵌入。我们这样做类似于上一篇文章中的示例。
query = "What is CLIP's contrastive loss function?"
model = CLIPTextModelWithProjection.from_pretrained("openai/clip-vit-base-patch16")
processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch16")
inputs = processor(text=[text], return_tensors="pt", padding=True)
outputs = model(**inputs)
query_embed = outputs.text_embeds
print(query_embed.shape)
打印形状时,我们看到有一个向量表示查询。
要在知识库上执行向量搜索,我们需要执行以下操作:
- 计算查询嵌入与所有文本和图像嵌入之间的相似性。
- 通过 softmax 函数将相似性重新缩放到 0 到 1 的范围内。
- 对缩放后的相似性进行排序,并返回前 k 个结果。
- 最后,过滤结果,只保留高于预定义相似度阈值的项目。
以下是文本块的代码示例。
k = 5
threshold = 0.05
text_similarities = matmul(query_embed, text_embeddings.T)
temp = 0.25
text_scores = softmax(text_similarities/temp, dim=1)
isorted_scores = argsort(text_scores, descending=True)[0]
sorted_scores = text_scores[0][isorted_scores]
itop_k_filtered = [idx.item()
for idx, score in zip(isorted_scores, sorted_scores)
if score.item() >= threshold][:k]
top_k = [text_content_list[i] for i in itop_k_filtered]
print(top_k)
top k 结果
[{'article_title': 'Multimodal Embeddings: An Introduction',
'section': 'Contrastive Learning',
'text': 'Two key aspects of CL contribute to its effectiveness'}]
上面,我们看到顶级文本结果。注意,即使 k = 5,我们只有一项。这是因为第 2 到第 5 项低于 0.1 的阈值。
有趣的是,这个项目似乎对我们最初的查询 “What is CLIP’s contrastive loss function?” 没有帮助。这突显了 向量搜索的关键挑战之一:与给定查询相似的项目可能不一定有助于回答它。
我们可以通过增加 k 并降低相似度 threshold 来减轻这个问题,从而对搜索结果施加更宽松的限制,然后希望 LLM 能够判断哪些是有用的,哪些不是。
为此,我将首先将向量搜索步骤封装到一个 Python 函数中。
def similarity_search(query_embed, target_embeddings, content_list,
k=5, threshold=0.05, temperature=0.5):
"""
Perform similarity search over embeddings and return top k results.
"""
similarities = torch.matmul(query_embed, target_embeddings.T)
scores = torch.nn.functional.softmax(similarities/temperature, dim=1)
sorted_indices = scores.argsort(descending=True)[0]
sorted_scores = scores[0][sorted_indices]
filtered_indices = [
idx.item() for idx, score in zip(sorted_indices, sorted_scores)
if score.item() >= threshold
][:k]
top_results = [content_list[i] for i in filtered_indices]
result_scores = [scores[0][i].item() for i in filtered_indices]
return top_results, result_scores
然后,设置更具包容性的搜索参数。
text_results, text_scores = similarity_search(query_embed, text_embeddings,
text_content_list, k=15, threshold=0.01, temperature=0.25)
image_results, image_scores = similarity_search(query_embed, image_embeddings,
image_content_list, k=5, threshold=0.25, temperature=0.5)
这将产生 15 个文本结果和 1 个图像结果。
- CL 的两个关键方面有助于其有效性。
- 要进行类预测,我们必须提取图像 logits 并评估哪个类对应于最大值。
- 接下来,我们可以导入版本的 clip 模型及其相关数据处理器。注意:处理器负责对输入文本进行分词和图像准备。
- 使用 CLIP 进行 0-shot 图像分类的基本思路是将图像传入模型,并附上可能的类标签集。然后,通过评估哪个文本输入与输入图像最相似来进行分类。
- 然后,我们可以通过提取文本 logits 并评估与最大值对应的图像,将最佳图像与输入文本匹配。
- 这些示例的代码在 GitHub 仓库中是公开可用的。
- 我们看到(再次)模型成功处理了这个简单的示例。但让我们尝试一些更棘手的示例。
- 接下来,我们将预处理图像/文本输入并将其传入模型。
- 像 CLIP 这样的模型的另一个实际应用是多模态 RAG,它包括对 LLM 的多模态上下文的自动检索。在本系列的下一篇文章中,我们将看到这一过程的内部工作原理,并回顾一个具体的示例。
- CLIP 的另一个应用本质上是用例 1 的逆过程。我们可以评估在一组图像中哪个图像最符合文本输入(即查询)——换句话说,执行对图像的搜索。
- 这激发了扩展 LLM 功能以包括多种模态的努力。
- GPT-4o — 输入:文本、图像和音频。输出:文本。FLUX — 输入:文本。输出:图像。Suno — 输入:文本。输出:音频。
- 对齐不同嵌入空间的标准方法是对比学习 (CL)。CL 的一个关键直觉是以相似的方式表示同一信息的不同视图 [5]。
- 尽管模型对这个预测的信心较低,概率为 54.64%,但它正确地暗示该图像不是一个 meme。
- [8] Mini-Omni2: Towards Open-source GPT-4o with Vision, Speech and Duplex Capabilities
图像搜索结果。
提示 MLLM
尽管这些文本项的结果似乎对我们的查询没有帮助,但图像结果正是我们所寻找的。尽管如此,鉴于这些搜索结果,让我们看看 LLaMA 3.2 Vision 对这个查询的响应。
我们首先将搜索结果结构化为格式良好的字符串。
text_context = ""
for text in text_results:
if text_results:
text_context = text_context + "**Article title:** "
+ text['article_title'] + "\\n"
text_context = text_context + "**Section:** "
+ text['section'] + "\\n"
text_context = text_context + "**Snippet:** "
+ text['text'] + "\\n\\n"
image_context = ""
for image in image_results:
if image_results:
image_context = image_context + "**Article title:** "
+ image['article_title'] + "\\n"
image_context = image_context + "**Section:** "
+ image['section'] + "\\n"
image_context = image_context + "**Image Path:** "
+ image['image_path'] + "\\n"
image_context = image_context + "**Image Caption:** "
+ image['caption'] + "\\n\\n"
请注意每个文本和图像项所附带的元数据。这将帮助 LLaMA 更好地理解内容的上下文。
接下来,我们将文本和图像结果交错在一个提示中。
prompt = f"""Given the query "{query}" and the following relevant snippets:
{text_context}
{image_context}
Please provide a concise and accurate answer to the query, incorporating
relevant information from the provided snippets where possible.
"""
最终的提示相当长,所以我不会在这里打印它。然而,它在 示例笔记本 上的 GitHub 中完全显示。
最后,我们可以使用 ollama 将这个提示传递给 LLaMA 3.2 Vision。
ollama.pull('llama3.2-vision')
response = ollama.chat(
model='llama3.2-vision',
messages=[{
'role': 'user',
'content': prompt,
'images': [image["image_path"] for image in image_results]
}]
)
print(response['message']['content'])
该图像描述了一个对比损失函数,用于对齐多模态模型中的文本和图像表示。该函数旨在最小化正样本对(文本-图像)和负样本对(文本-文本或图像-图像)之间相似度的差异。这个损失函数通常用于 CLIP,即对比语言-图像预训练。
关键组件:
- 正样本对: 文本-图像对,其中文本描述了一幅图像。
- 负样本对: 不属于同一类别的文本-文本或图像-图像对。
- 对比损失函数: 计算正样本对和负样本对相似度之间的差异。
工作原理:
- 文本-图像嵌入: 使用多模态编码器(例如 CLIP)生成文本和图像的嵌入。
- 正样本对相似度: 计算每个文本-图像对之间的相似度得分。
- 负样本对相似度: 计算所有负样本对之间的相似度得分。
- 对比损失计算: 通过最小化正样本对和负样本对相似度之间的差异来计算对比损失。
好处:
- 多模态对齐: 对齐文本和图像表示,以更好地理解文本描述中的视觉内容。
- 性能提升: 增强下游任务的性能,如图像分类、检索和生成。
模型正确地识别出图像包含所需的信息,并解释了其工作的一般直觉。然而,它误解了正样本对和负样本对的含义,认为负样本对对应于同一模态的对。
尽管我们逐步通过实现细节,但我将所有内容打包成一个漂亮的 UI,使用 Gradio 在这个 笔记本 上的 GitHub 存储库中。
结论
多模态 RAG 系统能够综合存储在各种格式中的知识,扩展了 AI 的可能性。在这里,我们回顾了开发此类系统的 3 种简单策略,然后看到了一个多模态博客问答助手的示例实现。
虽然这个示例在演示中工作得相当不错,但搜索过程显然存在一些限制。一些可能改善这一点的技术包括使用 重新排序器来优化相似度搜索 结果,以及通过 微调的多模态嵌入 来提高搜索质量。
更多关于多模态模型的信息 👇
[1] RAG
[2] 多模态 LLMs
[3] 多模态嵌入