点击上方
在企业应用中,大量的数据以复杂的多模形态而存在,所以构建一个企业级的AI搜索或者RAG(检索增强生成)应用时,多模态数据特别是图像数据的索引与检索就是一个无法回避也是较复杂的问题,而借助嵌入向量的语义检索是常见方法。本文将探讨在广为所知的文本嵌入基础上,如何借助更强大的多模态嵌入,更简洁的组合多形态的企业信息,实现跨模态的检索应用。
文本嵌入的问题与不足
多模态嵌入模型
基于多模态嵌入的RAG实例
01
文本嵌入的问题与不足
当前有一些针对RAG应用中多模态内容的处理方法,即:组合使用文本嵌入模型与多模态视觉大模型(VLM)。借助视觉大模型将图片做“文本化“,生成图片的描述、摘要甚至关联的上下文,再借助文本嵌入模型实现这些信息的语义检索。大致的处理流程在之前的文章中曾经有过介绍:
借助文档解析工具与视觉大模型将图片提取并理解成文本
采用与文本处理相同的方式对其进行嵌入并存入向量空间
在生成阶段通过文本进行语义检索,获得关联上下文并输入LLM
也可以更进一步的关联检索出原始图片并输入多模态LLM进行生成
这里的解决方案本质上仍然是文本到文本的语义检索,其问题是:
索引过程较为繁琐,性能较差,成本较高
由于需要借助VLM实现多模态内容的理解与“文本化”,如识别图片文字、生成内容摘要、甚至生成图片相关的上下文等,导致索引过程性能较低,且VLM的使用成本一般较高。
容易产生语义损失与偏离,检索精度不足
尽管多模态视觉大模型得到了长足的进步,但在将图片做“文本化”的过程中,仍可能存在一定的语义损失与偏离(受到图片质量、类型、甚至提示词的影响),导致后续的检索精度降低。
无法支持文本到图像、图像到图像的混合检索
这种方案在检索阶段是一种文本到文本的语义检索,而非直接将输入文本与图像连接(当然你可以借助元数据关联出原始图像),所以更无法支持图像到图像的检索。但在实际企业应用中往往存在这样的应用场景:
上传自己喜欢的商品照片,检索外观相似的产品及其详细信息
上传报错的截屏图片,在知识库中检索类似的错误及其解决方法
用文字描述产品特征而非产品名称,以搜索产品并给出介绍
这些场景在现有的方案中无法得到有效的支持。
02
多模态嵌入模型
文本嵌入已经被普遍应用于大量的AI应用中,但它们的问题是无法处理图像数据。一种可行的解决方案是借助于逐渐成熟的多模态嵌入模型(Multimodal Embedding Model),能够直接对文字与图像生成嵌入,并在单一的向量库/向量空间中保存生成的嵌入向量数据:
这里的主要收益包括:
更简单且直接的将多模态内容中的信息向量化,有更高的性能与更低的成本。这有助于提取企业内部保存在图像中的大量信息的价值,可用来快速准确的检索多模态资产,如分析图表、产品图片、设计文档等,对于构建企业智能搜索或者RAG应用有重要意义。
在单一向量空间保存多种模态的嵌入,你无需在不同的库中保存不同模态的数据,简化了集成的复杂性。而在检索时,也无需考虑特定的模态,直接考虑数据背后的语义即可。
在RAG应用中,对知识库中图片信息的处理流程更简洁,有助于提高后续语义检索的精度与相关度。通过提供更相关的上下文(包括检索出的文本或图片内容)给LLM,可以提高响应质量。
支持更强大的混合模态搜索,而不再局限在文本到文本的搜索,更可以实现文本到图像或图像到图像的检索。现在你可以更方便的允许客户使用产品特征或照片作为输入,以获得产品的全面信息。
03
基于多模态嵌入的RAG实例
现在,我们将尝试创建一个实际用例:借助于多模态的嵌入模型,实现一个基于多模态知识的RAG应用。总体流程如下:
用例将要展示的步骤包括:
1. 解析输入文档并借助多模态嵌入模型做嵌入与索引。核心方法与工具:
使用LlamaIndex的Llama-parse解析输入的文档
使用Cohere的embed-multilingual-v3.0模型做多模态嵌入
使用Qdrant作为存放文本与图形统一嵌入的向量数据库
2. 进行多模态的检索测试:
文本到图片的检索
图像到图像的检索
3. 将检索到的文本与图像交给VLM生成最终响应。
这里借助更面向RAG应用的LlamaIndex框架来实现代码。
【准备测试文档】
我们用一个简单的word文档做测试,其中包含了若干产品介绍信息,混合了文字与图片(由于Cohere的测试API-Key存在每分钟的API调用限制,因此测试时文档中的图片不宜太多):
【多模态嵌入与索引】
这是关键的阶段,借助Cohere的embed-multilingual-v3.0这个强大的多模态嵌入模型,以及LlamaIndex中的多模态相关组件,可以很轻松的完成索引过程,简单描述代码的处理过程为:
借助Llama_parse提取原始文档中的文本与图片到临时目录
使用LlamaIndex组件加载临时目录中的文本与图片文件
加载后的Document对象用来嵌入与创建索引。在此过程中,框架会自动对文本做一定的分割后,并借助多模态嵌入模型统一生成文本与图像的嵌入,并存储到向量库。
核心代码如下:
...省略import,以及相关的API_KEY设置...
# 定义文件路径
FILE_NAME = "xiaomi_products.docx"
IMAGES_DOWNLOAD_PATH = "parsed_data"
# 解析文档并提取文本和图像节点,llamaparse的用法可参考官方文档
def parse_doc():
# 初始化LlamaParse解析器,设置输出格式为markdown
parser = LlamaParse(
api_key=LLAMA_CLOUD_API_KEY,
language='ch_sim',
result_type="markdown",
)
# 解析文档并提取JSON数据
json_objs = parser.get_json_result(FILE_NAME)
json_list = json_objs[0]["pages"]
# 文本节点
text_nodes = [TextNode(text=page["text"], metadata={"page": page["page"]}) for page in json_list]
# 文本节点保存成txt文件
texts = [node.text for node in text_nodes]
all_text = "\n\n".join(texts)
# 如果目录不存在则创建
os.makedirs(IMAGES_DOWNLOAD_PATH, exist_ok=True)
with open(f"{IMAGES_DOWNLOAD_PATH}/extracted_texts.txt", "w", encoding="utf-8") as file:
file.write(all_text)
# 图像节点保存成图片文件
parser.get_images(json_objs, download_path=IMAGES_DOWNLOAD_PATH)
parse_doc()
# 在设置中配置嵌入模型,注意这里使用多模态的嵌入模型
Settings.embed_model = CohereEmbedding(api_key=COHERE_API_KEY,model_name="embed-multilingual-v3.0")
# 从临时目录加载文档,包括图片和文本文件
documents = SimpleDirectoryReader("parsed_data/",
required_exts=[".jpg", ".png", ".txt"],
exclude_hidden=False).load_data()
# 设置Qdrant向量库
client = qdrant_client.QdrantClient(path="furniture_db")
vector_store = QdrantVectorStore(client=client, collection_name="mycollection")
storage_context = StorageContext.from_defaults(vector_store=vector_store, image_store=vector_store)# 创建索引,包括分割文本、生成嵌入、存入向量库,由框架完成
index = MultiModalVectorStoreIndex.from_documents(
documents,
storage_context=storage_context,
transformations=[TokenTextSplitter(separator="------------------------",chunk_size=300)],
image_embed_model=Settings.embed_model,
)
print("索引创建完成,可用于检索。")
【多模态检索测试】
成功完成这里的步骤后(注意install与import必要的库,以及有效的API_key,并确保相关库都为最新版本),可以在指定的parsed_data目录下看到处理过的文本与图片,这些都已经被成功嵌入与索引:
现在可以创建一个检索器来测试多模态的检索能力,这里重点测试文本到图片,以及图片到图片的测试。使用如下代码:
from llama_index.core.response.notebook_utils import display_source_node
from llama_index.core.schema import ImageNode
import matplotlib.pyplot as plt
from PIL import Image
#一个辅助函数:显示检索到的图片信息
def display_images(file_list, grid_rows=2, grid_cols=3, limit=9):
plt.figure(figsize=(16, 9))
count = 0
for idx, file_path in enumerate(file_list):
if os.path.isfile(file_path) and count < limit:
img = Image.open(file_path)
plt.subplot(grid_rows, grid_cols, count + 1)
plt.imshow(img)
plt.axis('off')
count += 1
plt.tight_layout()
plt.show()
#创建检索器,并进行检索retriever_engine = index.as_retriever(similarity_top_k=3, image_similarity_top_k=2)
query = "一款可折叠屏幕的手机"
retrieval_results = retriever_engine.retrieve(query)
#由于检索到的结果可能混合了文本与图片,这里进行分离与显示
retrieved_image = []
for res_node in retrieval_results:
if isinstance(res_node.node, ImageNode):
retrieved_image.append(res_node.node.metadata["file_path"])
else:
display_source_node(res_node, source_length=500)
display_images(retrieved_image)
我们查看以上代码检索的输出结果:
可以看到,检索到的图片与文本的确都是与输入信息相关,并进行了正确的排名。现在我们把输入修改成如下:
query = "一款白色的设备,有多个天线"
现在输出的检索结果如下:
然后,我们把输入信息修改成图片,来测试多模态嵌入模型的图像到图像的语义匹配与检索的能力,只需要修改上面代码中的两行代码即可:
query_image = 'router.png'
retrieval_results = retriever_engine.image_to_image_retrieve(query_image)
这里的query_image为自己准备的输入图片,以下展示检索的效果,上图为输入图片,下图为检索结果的输出:
以上的检索测试表明:使用多模态嵌入模型创建的索引可以在文本到文本、文本到图像和图像到图像任务中提供了较好的检索体验。其最大区别是:
在单一向量空间实现了多种模态内容的嵌入
混合模态检索能力,从文本到图像,从图像到图像
【检索结果用于RAG生成】
现在我们创建一个RAG查询引擎,用来把检索到的文本与图片发送给VLM,以生成最终响应结果。借助于LlamaIndex的as_query_engine可以快速的从向量索引创建查询引擎。使用如下代码:
import logging
from llama_index.core.schema import NodeWithScore, ImageNode, MetadataMode
from llama_index.core.prompts import PromptTemplate
from llama_index.multi_modal_llms.dashscope import (
DashScopeMultiModal,
DashScopeMultiModalModels,
)
# 定义带有明确指示的模板
qa_tmpl_str = (
"以下是上下文信息:\n"
"---------------------\n"
"{context_str}\n"
"---------------------\n"
"请仅基于提供的上下文和输入的图片(不要使用先验知识)回答问题。"
"请按以下格式回答:\n"
"Result: [基于上下文的回答]\n"
"---------------------\n"
"我的问题: {query_str}\n"
)
qa_tmpl = PromptTemplate(qa_tmpl_str)
# 使用多模态模型,以支持可能检索到的相关图片
multimodal_llm = OpenAIMultiModal(model="gpt-4o-mini", temperature=0.0, max_tokens=1024)
#创建查询引擎,输入:多模态模型、检索的参数、提示模板;剩下的交给框架query_engine = index.as_query_engine(
llm=multimodal_llm,
text_qa_template=qa_tmpl,
similarity_top_k=5,
image_similarity_top_k=2
)
result = query_engine.query("介绍小米的高性能游戏笔记本")
#输出结果,打印出本地查询检索到的第一个图片
print(result)
display_images([result.metadata["image_nodes"][0].metadata["file_path"]])
输出如下,这里我们用代码显示了本次查询所引用的图片,这个图片由查询引擎从上面已经创建好的多模态向量库中检索得到:
显然,借助于多模态嵌入模型,我们已经成功的将文本与图像嵌入在单一空间存放,并进行跨模态的统一检索,最后将检索出的文本与图片输入给多模态的LLM进行理解与输出。
以上演示了借助于强大的多模态嵌入模型(用的是cohere的embed-multilingual-v3.0模型),可以对企业数据中的文本与图像进行统一的嵌入表示、存放与检索,大大降低了构建复杂知识环境下的RAG应用的复杂性。
此外,在企业AI搜索、个性化推荐、图像内容审核等其他涉及多模态数据检索任务的场景下也具有较大的应用潜力,我们也期待看到更强大的多模态嵌入模型的出现。
END