多模态检索增强生成(RAG)技术深度解析与实现
多模态检索增强生成(Multimodal Retrieval Augmented Generation,简称 Multimodal RAG)是一种新兴的设计范式,允许 AI 模型与文本、图像、视频等多种信息存储接口进行交互。随着大语言模型(LLM)能力的扩展,单一文本模态已无法满足复杂场景需求,多模态 RAG 成为构建现代智能系统的关键技术。
多模态检索增强生成(Multimodal RAG)允许 AI 模型交互文本、图像、视频等多种信息。传统 RAG 原理,阐述了共享向量空间、单一基础模态及独立检索三种多模态构建方法,并通过 Google Gemini 和 CLIP 模型演示了音频、图像及文本的联合检索与生成流程,分析了实施中的挑战与最佳实践,为构建现代多模态系统提供技术参考。

多模态检索增强生成(Multimodal Retrieval Augmented Generation,简称 Multimodal RAG)是一种新兴的设计范式,允许 AI 模型与文本、图像、视频等多种信息存储接口进行交互。随着大语言模型(LLM)能力的扩展,单一文本模态已无法满足复杂场景需求,多模态 RAG 成为构建现代智能系统的关键技术。
本文将首先介绍什么是检索增强生成(RAG)及多模态概念,随后探讨如何将两者结合以构建多模态 RAG 系统。在理解基本概念后,我们将使用 Google Gemini 和类似 CLIP 的编码模型来构建一个实际的多模态 RAG 示例。
在深入多模态之前,先回顾传统的检索增强生成(RAG)。RAG 的核心概念是找到与用户查询相关的信息,然后将这些信息注入到提示词(Prompt)中并传递给语言模型,从而利用外部知识库增强模型的生成能力。
RAG 系统的基本原理是根据用户的查询检索相关信息,然后将这些信息与用户的查询结合(称为增强)后传递给语言模型。

RAG 系统的检索通常是通过'嵌入'(Embedding)实现的。为了嵌入某些内容,我们使用高级 AI 模型将信息转换为代表该信息的向量。这个过程是通过一组参考文档以及用户的查询来完成的。可以计算这些向量之间的距离,文档与用户查询之间距离最小的被认为是最相关的。

*注:AI 模型会提取一些文本序列并创建一个代表该文本序列的向量。
一旦 RAG 系统检索到足够相关的信息,用户的查询和相关文档将用于构建一个增强提示,然后将其传递给语言模型进行生成。
"Answer the customers prompt based on the folowing context:
==== context: {document title} ====
{document content}
...
prompt: {prompt}"
这种通用系统通常预设整个知识库由可以传递给语言模型的文本组成,但许多知识来源不仅仅是文本。可能还有音频、视频、图像等。这就是多模态 RAG 的作用。
在数据科学中,'模态'(Modality)本质上是数据的一种类型。文本、图像、音频、视频、表格等都可以被视为不同的'模态'。长期以来,这些不同类型的数据被视为彼此分离,数据科学家需要为文本、视频等分别创建模型。近年来,这种概念逐渐消失,能够理解和处理多种模态的模型变得更加高效且易于访问。
多模态模型的概念通常围绕'联合嵌入'展开。基本上,联合嵌入是一种建模策略,迫使模型同时学习不同类型的数据。这个领域的标志性论文之一是 CLIP(Contrastive Language–Image Pre-training),它创建了一个能够处理图像和文本任务的强大模型。

*注:CLIP 风格的模型使用复杂的训练过程使多个模型组件协同工作,以理解图像和文本。
自 CLIP 以来,各种建模策略已被创建,以某种方式对齐图像和文本。到处都有可以处理多种类型的数据新模型问世。
多模态 RAG 的概念是允许 RAG 系统以某种方式将多种形式的信息注入多模态模型中。因此,多模态 RAG 系统不仅可以根据用户提示检索文本片段,还可以检索文本、图像、视频和其他不同模态的数据。
目前主要有三种流行的方法可以实现多模态 RAG:
多模态 RAG 的一种方法是使用能够处理多种模态的嵌入(类似于前面讨论的 CLIP)。基本上,你可以通过一组设计为相互协作的编码器传递数据,然后在所有模态中检索与用户查询最相似的数据。

使用多模态嵌入系统的多模态 RAG,例如 Google Vertex 提供的编码器。这些编码器旨在协同工作,将来自不同模态的数据放置在同一个嵌入空间中。这种方法的优势在于可以直接比较不同模态之间的相似度。
多模态 RAG 的另一种方法是将所有数据模态转换为单一模态,通常是文本。

使用基础模态的多模态 RAG。所有模态在传递到单个编码器之前都会转换为单个模态。虽然这种策略在理论上存在信息在转换过程中丢失的风险,但实际上我们发现对于许多应用来说,这种方法以相对较小的复杂性实现了非常高质量的结果。
第三种方法是使用一组旨在处理不同模态的模型。在这种情况下,你会在不同的模型上多次进行检索,然后结合它们的结果。

使用独立检索的多模态 RAG。有多种方法可以将独立检索结果合并为一个用于增强和生成的检索结果。最简单的方法是将它们全部合并并传递给多模态模型。更常见的是使用一个'重新排序'(Re-ranking)模型或系统,设计用于根据与查询的相关性组织数据。
如果你希望能够构建和优化多种模型以适应不同的模态,或者你只是使用现有建模解决方案中不可用的模态,那么这将很有用。
现在我们已经对多模态 RAG 有了大致的了解,让我们用一个简单的例子来实验一下。我们将对三部分信息进行检索:
使用这些数据,我们将构建一个简单的 RAG 系统来回答问题'谁是我最喜欢的竖琴家?'
首先让我们进行一些准备工作。我们将使用 pydub 来处理音频,并使用 transformers 库加载模型。
!pip install pydub transformers torch requests pillow
然后我们可以配置 API 密钥以使用 Google Gemini。
import os
import google.generativeai as genai
from google.colab import userdata
os.environ["GOOGLE_API_KEY"] = userdata.get('GeminiAPIKey')
genai.configure(api_key=os.environ["GOOGLE_API_KEY"])
我们需要从远程资源获取数据。首先,我们可以从数据集下载图像,并将其保存到文件系统以备后用。
import requests
from PIL import Image
from IPython.display import display
import os
# Loading image
url = 'https://github.com/DanielWarfield1/MLWritingAndResearch/blob/main/Assets/Multimodal/MMRAG/Lorenz_Ro28-200px.png?raw=true'
response = requests.get(url, stream=True)
image = Image.open(response.raw).convert('RGB')
# Save the image locally as JPG
save_path = 'image.jpg'
image.save(save_path, 'JPEG')
display(image)
接下来,我们可以下载音频文件并进行格式转换。注意,后续模型可能需要特定的采样率。
from pydub import AudioSegment
import numpy as np
import io
import matplotlib.pyplot as plt
import wave
import requests
# Downloading audio file
url = "https://github.com/DanielWarfield1/MLWritingAndResearch/blob/main/Assets/Multimodal/MMRAG/audio.mp3?raw=true"
response = requests.get(url)
audio_data = io.BytesIO(response.content)
# Converting to wav and loading
audio_segment = AudioSegment.from_file(audio_data, format="mp3")
# Downsampling to 16000 Hz (this is necessary because a future model requires it to be at 16000Hz)
sampling_rate = 16000
audio_segment = audio_segment.set_frame_rate(sampling_rate)
# Exporting the downsampled audio to a wav file in memory
wav_data = io.BytesIO()
audio_segment.export(wav_data, format="wav")
wav_data.seek(0) # Back to beginning of IO for reading
wav_file = wave.open(wav_data, 'rb')
# converting the audio data to a numpy array
frames = wav_file.readframes(-1)
audio_waveform = np.frombuffer(frames, dtype=np.int16).astype(np.float32)
# Rendering audio waveform
plt.plot(audio_waveform)
plt.title("Audio Waveform")
plt.xlabel("Sample Index")
plt.ylabel("Amplitude")
plt.show()
最后是我们的维基百科文本摘录:
import requests
# URL of the text file
url = "https://github.com/DanielWarfield1/MLWritingAndResearch/blob/main/Assets/Multimodal/MMRAG/Wiki.txt?raw=true"
response = requests.get(url)
text_data = response.text
# truncating length for compatability with an encoder that accepts a small context
# a different encoder could be used which allows for larger context lengths
text_data = text_data[:300]
print(text_data)
现在,我们有了一个音频文件(其中包含我说出我最喜欢的竖琴家是谁),以及一个图像和文本文件。
可以找到支持音频、图像和文本的编码器,但它们比仅支持图像和文本的编码器要复杂一些。为了简化我们的操作,现在我们将使用语音转文本(ASR)模型将音频转为文本。
import torch
from transformers import Speech2TextProcessor, Speech2TextForConditionalGeneration
#the model that generates text based on speech audio
model = Speech2TextForConditionalGeneration.from_pretrained("facebook/s2t-medium-librispeech-asr")
#a processor that gets everything set up
processor = Speech2TextProcessor.from_pretrained("facebook/s2t-medium-librispeech-asr")
#passing through model
inputs = processor(audio_waveform, sampling_rate=sampling_rate, return_tensors="pt")
generated_ids = model.generate(inputs["input_features"], attention_mask=inputs["attention_mask"])
#turning model output into text
audio_transcription = processor.batch_decode(generated_ids, skip_special_tokens=True)[0]
audio_transcription
如你所见,语音转文本的翻译效果并不理想。Turlough O'Carolan 是 17 世纪的凯尔特竖琴家,我根本不知道怎么发音。我尝试了几次,因此转录结果非常荒谬。这在实际应用中是一个常见的挑战,即 ASR 错误传播问题。
现在我们已经将音频转换为文本表示,可以使用 CLIP 风格模型对图像和文本进行编码。如果你不熟悉 CLIP 风格模型,它们是一种理解如何表示图像和文本,使得相似的事物具有相似向量的模型。
我们可以使用其中一种来编码我们的图像和文本。首先,让我们定义我们的查询:
query = 'who is my favorite harpist?'
然后让我们用 huggingface 的 CLIP 风格模型嵌入所有内容。
from transformers import CLIPProcessor, CLIPModel
# Load the model and processor
model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32")
processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")
# Encode the image
inputs = processor(images=image, return_tensors="pt")
image_embeddings = model.get_image_features(**inputs)
# Encode the text
inputs = processor(text=[query, audio_transcription, text_data], return_tensors="pt", padding=True)
text_embeddings = model.get_text_features(**inputs)
接下来,我们可以解包这些结果,获取文本、图像、音频和查询的编码,并计算数据与查询之间的差异。在这种情况下,我们将使用余弦相似度,它本质上是两个向量之间角度的度量。对于余弦相似度,如果两个向量指向相同的方向,它们的余弦相似度就很高。
import torch
from torch.nn.functional import cosine_similarity
# unpacking individual embeddings
image_embedding = image_embeddings[0]
query_embedding = text_embeddings[0]
audio_embedding = text_embeddings[1]
text_embedding = text_embeddings[2]
# Calculate cosine similarity
cos_sim_query_image = cosine_similarity(query_embedding.unsqueeze(0), image_embedding.unsqueeze(0)).item()
cos_sim_query_audio = cosine_similarity(query_embedding.unsqueeze(0), audio_embedding.unsqueeze(0)).item()
cos_sim_query_text = cosine_similarity(query_embedding.unsqueeze(0), text_embedding.unsqueeze(0)).item()
# Print the results
print(f"Cosine Similarity between query and image embedding: {cos_sim_query_image:.4f}")
print(f"Cosine Similarity between query and audio embedding: {cos_sim_query_audio:.4f}")
print(f"Cosine Similarity between query and text embedding: {cos_sim_query_text:.4f}")
在这里,我们可以看到,从音频转录得到的嵌入被认为是最相关的。
现在我们有了每个数据的嵌入,我们可以进行'检索增强生成'。
在这个简化的示例中,我们可以使用一些 if 语句来实现这一点:
# putting all the similarities in a list
similarities = [cos_sim_query_image, cos_sim_query_audio, cos_sim_query_text]
result = None
if max(similarities) == cos_sim_query_image:
#image most similar, augmenting with image
model = genai.GenerativeModel('gemini-1.5-pro')
result = model.generate_content([query, Image.open('image.jpeg')])
elif max(similarities) == cos_sim_query_audio:
#audio most similar, augmenting with audio. Here I'm using the transcript
#rather than the audio itself
model = genai.GenerativeModel('gemini-1.5-pro')
result = model.generate_content([query, 'audio transcript (may have inaccuracies): '+audio_transcription])
elif max(similarities) == cos_sim_query_text:
#text most similar, augmenting with text
model = genai.GenerativeModel('gemini-1.5-pro')
result = model.generate_content([query, text_data])
print(result.text)
Gemini 确实得为你爆料一下。
尽管多模态 RAG 展示了巨大的潜力,但在实际落地时仍面临诸多挑战:
虽然在这个快速变化的领域很难说任何事情,但似乎多模态 RAG 可能是即将到来的人工智能产品化浪潮中的一项关键技能。我认为可以公平地说,RAG 作为一个整体,无论是多模式还是其他方式,都将随着技术的需求将技术水平推向新的高度,并且新的构建范式会变得更加容易接近。
在目前的多模态 RAG 中,我们触及了所有的重点:
通过掌握这些技术,开发者可以更有效地构建能够理解世界多样性的智能应用。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online