AI知识库

53AI知识库

学习大模型的前沿技术与行业应用场景


多模态RAG应用
发布日期:2024-04-06 16:58:49 浏览次数: 2826 来源:向量检索实验室



人工智能(AI)已经与文本打交道相当一段时间了,但世界并不仅仅围绕着文字。如果你花点时间环顾四周,你会发现文本、图像、视频、音频及它们的组合。

今天我们要研究多模态,这个概念本质上赋予AI模型除了文本之外,同时感知、聆听和理解多种格式数据的能力。就像我们人类所做的那样!

在理想情况下,我们应该能够将不同类型的数据混合在一起,同时展示给一个生成式AI模型,并对其进行迭代。它可以像告诉AI模型这样简单,"嘿,几天前,我给你发了一张棕色小狗的照片。你能帮我找到那张照片吗?"然后模型应该给我们那张照片的细节。基本上,我们希望AI能更像人类那样理解事物,变得非常擅长处理和响应各种信息。

但这里的挑战是,让计算机理解一种数据格式及其相关参考,可能是文本、音频、热成像和视频的混合。为了实现这一点,我们使用了一种叫做嵌入(Embedding)的东西。它实际上是一个数值向量,包含了一堆对我们来说可能没有多大意义但机器很容易理解的数字。

猫等于猫

让我们先考虑文本部分,所以我们目前的目标是让我们的模型学会"狗"和"猫"这样的词与"宠物"这个词密切相关。使用嵌入模型很容易实现这种理解,该模型首先将这些文本词转换为各自的嵌入,然后训练模型遵循一个简单的逻辑:如果词语相关,它们在向量空间中彼此接近,如果不相关,它们将被适当的距离分开。

但为了帮助模型识别出"猫"的图像和"猫"这个词是相似的,我们依赖于多模态嵌入。为了简化一下,想象有一个魔盒,能够处理各种输入——图像、音频、文本等。

现在,当我们用一张"猫"的图像和文本"猫"来喂养这个盒子时,它施展魔法,生成两个数值向量。当这两个向量被输入机器时,机器会想:"根据这些数值,看起来它们都与'猫'有关。"这正是我们的目标!我们的目标是帮助机器识别"猫"的图像和文本"猫"之间的密切联系。然而,为了验证这个概念,当我们在向量空间中绘制这两个数值向量时,结果发现它们非常接近。这个结果与我们之前观察到的两个文本词"猫"和"狗"在向量空间中的接近度完全一致。

女士们先生们,这就是多模态的本质。 ?

所以我们让模型理解了"猫"的图像和"猫"这个词之间的联系。好吧,就是这样,我的意思是如果你能做到这一点,你就已经接收了音频、图像、视频以及"猫"这个词,模型就会理解猫在所有文件格式中是如何被描绘的。

RAG来了..

如果你不知道RAG是什么意思,我强烈建议你阅读我前几天写的这篇文章[1],很多人都喜欢,我不夸张,但是这篇文章很好地让事情启动了...

有一些令人印象深刻的模型,如DALLE-2,提供文本到图像的功能。本质上,你输入文本,模型就会为你生成相关的图像。但是,我们能否创建一个类似于多模态RAG的系统,使模型根据我们自己的数据生成输出图像?好吧,今天的目标是创建一个AI模型,当被问到类似"我的派对上有多少女孩?" ? 这样的问题时,不仅提供文本信息,还包括与之相关的图像。可以把它看作是一个简单RAG系统的扩展,但现在加入了图像。

在我们深入之前,请记住,多模态并不局限于文本到图像或图像到文本,因为它包含了输入和输出任何类型数据的自由。然而,目前,让我们专注于仅从图像到文本的交互。

对比学习

现在的问题是,那个盒子到底在做什么?它施展的魔法被称为对比学习。虽然这个术语听起来很复杂,但并不那么棘手。简单来说,考虑一个包含图像的数据集,以及描述图像内容的标题。

好了,现在发生的是:我们给文本-图像模型这些正样本和负样本,其中每个样本由一个图像和描述性文本组成。正样本是图像和文本正确对齐的样本——例如,一张猫的图片与文本"这是一张猫的图像"相匹配。相反,负样本涉及不匹配,如将狗的图像与文本"这是一张猫的图像"一起呈现。

现在我们训练文本-图像模型识别出正样本提供了准确的解释,而负样本具有误导性,应该在训练过程中被忽略。正式来说,这种技术被OpenAI引入的 CLIP[2] (对比语言-图像预训练)所称,作者在大约4亿对从互联网上获取的图像标题对上训练了一个图像-文本模型,每当模型犯错误时,对比损失函数就会增加并惩罚它,以确保模型训练良好。同样的原则也适用于其他模态组合,例如猫的声音与猫这个词是语音-文本模型的正样本,一段猫的视频与描述性文本"这是一只猫"是视频-文本模型的正样本。

表演时间

你不必从头开始构建那个盒子,因为人们已经为我们做了。有一个多模态嵌入模型,如OpenAI的 "ViT-L/14"。这个模型可以处理各种数据类型,包括文本、图像、视频、音频,甚至热数据和陀螺仪数据。现在,下一个问题是:我们如何存储这些嵌入?

为此,我们需要一个向量数据库,它可以高效地为我们获取、查询和检索相关的嵌入,最好是一个支持多模态数据且不会让我们倾家荡产的数据库。这就是LanceDB发挥作用的地方。

向量数据库

当我们谈论向量数据库时,当前市场上有很多选择,但LanceDB有一些特点使其成为向量数据库的最佳选择,据我使用,它解决了传统嵌入式数据库在处理AI/ML工作负载方面的局限性。当我说传统时,通常意味着那些与ML基础设施附带的大量计算使用不一致的数据库管理工具。

TLDR;LanceDB采用无服务器架构,这意味着存储和计算被分成两个不同的单元。这种设计使其在RAG用例中非常快,确保快速获取和检索。此外,它还有一些显著的优势——开源、利用其基于Apache Arrow构建的用于高效的Lance柱状数据格式、持久存储能力,以及结合自己的磁盘近似最近邻搜索。所有这些因素共同使LanceDB成为访问和处理多模态数据的理想解决方案。我爱你 LanceDB ❤️。

Data time

为了增加一些趣味,我制作了一个GTA-V图像字幕数据集,其中包含数千张图像,每张图像都配有描述图像内容的文本。现在,当我们训练魔盒时,期望很明确——如果我问盒子提供一张 "有停止标志的道路 "的图像,它应该提供一张带有停止标志的GTA-V道路图像。否则,还有什么意义呢,对吧?

FAQ

1.我们将使用 "ViT-L/14 "将我们的多模态数据转换为各自的嵌入。 2.LanceDB作为我们的向量数据库来存储相关的嵌入。 3.GTA-V图像字幕数据集用于我们的魔盒。

环境设置

我正在使用MacBook Air M1,需要注意的是,一些依赖项和配置可能因你运行的系统类型而有所不同,所以考虑这一点很重要。

以下是安装相关依赖项的步骤:

# 创建虚拟环境
python3 -m venv env 

# 激活虚拟环境
source env/bin/activate

# 升级虚拟环境中的pip
pip install --upgrade pip

#安装所需的依赖项
pip3 install lancedb clip torch datasets pillow
pip3 install git+https://github.com/openai/CLIP.git

别忘了从hugging face获取你的访问令牌以下载数据。

下载数据

使用datasets库可以轻松获取数据集。

import clip
import torch
import os
from datasets import load_dataset

ds = load_dataset("vipulmaheshwari/GTA-Image-Captioning-Dataset"
device = torch.device("mps")
model, preprocess = clip.load("ViT-L-14", device=device)

下载数据集可能需要一些时间,所以请花点时间放松一下,等这个过程完成。下载完成后,你可以像这样可视化一些样本点:

from textwrap import wrap
import matplotlib.pyplot as plt
import numpy as np

def plot_images(images, captions):
    plt.figure(figsize=(157)) 
    for i in range(len(images)):
        ax = plt.subplot(1, len(images), i + 1)
        caption = captions[i]
        caption = "\n".join(wrap(caption, 12))
        plt.title(caption)
        plt.imshow(images[i]) 
        plt.axis("off")

# 假设ds是一个字典,其中"train"键包含样本列表
sample_dataset = ds["train"]
random_indices = np.random.choice(len(sample_dataset), size=2, replace=False)
random_indices = [index.item() for index in random_indices]

# 获取随机图像及其标题
random_images = [np.array(sample_dataset[index]["image"]) for index in random_indices]
random_captions = [sample_dataset[index]["text"for index in random_indices] 

#绘制带标题的随机图像
plot_images(random_images, random_captions)

#显示图
plt.show()

存储嵌入

数据集由两个关键特征组成:图像及其相应的描述性文本。最初,我们的任务是创建一个LanceDB表来存储嵌入。这个过程很简单——你所需要做的就是定义相关的模式。在我们的例子中,列包括存储多模态嵌入的 "vector"、描述性文本的 "text "列和相应ID的 "label "列。

import pyarrow as pa
import lancedb
import tqdm

db = lancedb.connect('./data/tables')
schema = pa.schema(
  [
      pa.field("vector", pa.list_(pa.float32(), 512)),
      pa.field("text", pa.string()),  
      pa.field("id", pa.int32())
  ])
tbl = db.create_table("gta_data", schema=schema, mode="overwrite")

执行此操作将生成一个具有指定模式的表,它已准备好存储嵌入和相关列。就是这样简单——几乎太简单了!

编码图像

现在,我们将简单地从数据集中提取图像,将它们输入到利用我们多模态嵌入模型的编码函数中,并生成相应的嵌入。这些嵌入将存储在数据库中。

def embed_image(img):
    processed_image = preprocess(img) 
    unsqueezed_image = processed_image.unsqueeze(0).to(device)
    embeddings = model.encode_image(unsqueezed_image)
    
    # 分离,移至CPU,转换为numpy数组,并提取第一个元素作为列表
    result = embeddings.detach().cpu().numpy()[0].tolist()
    return result

所以我们的 embed_image 函数接受一个输入图像,通过我们的CLIP模型预处理器对其进行预处理,对预处理后的图像进行编码,并返回一个表示该图像嵌入的列表。这个返回的嵌入作为一个简洁的数值表示,捕捉图像中所有关键特征和模式,用于下游任务或分析。下一步是为所有图像调用这个函数,并将相关嵌入存储在数据库中。

data = []
for i in range(len(ds["train"])):
    img = ds["train"][i]['image'
    text = ds["train"][i]['text']
    
    # 编码图像
    encoded_img = embed_image(img)
data.append({"vector": encoded_img, "text": text, "id" : i})
    

在这里,我们只是获取一个列表,向其中添加数值嵌入、参考文本和当前索引id。剩下的就是将这个列表包含在我们的LanceDB表中。瞧,我们用于嵌入的数据湖已经建立并准备就绪了!

tbl.add(data)
tbl.to_pandas()

到目前为止,我们已经高效地将图像转换为各自的多模态嵌入,并将它们存储在LanceDB表中。现在LanceDB表提供了一个方便的功能:如果需要添加或删除图像,这非常简单。只需对新图像进行编码并添加即可,遵循我们之前对图像所做的相同步骤。

查询搜索

我们的下一步是使用与图像相同的多模态嵌入模型对文本查询进行嵌入。记得我之前提到的"盒子"吗?本质上,我们希望这个盒子为图像和文本创建嵌入,这确保了不同类型数据的表示以相同的方式进行。在此之后,我们只需启动搜索,找到与文本查询最匹配的最近图像嵌入。

def embed_txt(txt):
    tokenized_text = clip.tokenize([txt]).to(device)
    embeddings = model.encode_text(tokenized_text)
    
    # 分离,移至CPU,转换为numpy数组,并提取第一个元素作为列表
    result = embeddings.detach().cpu().numpy()[0].tolist()
    return result

res = tbl.search(embed_txt("a road with a stop")).limit(3).to_pandas()
res
0 | [0.064575195, .. ] | there is a stop sign...| 569 | 131.995728
1 | [-0.07989502, .. ] | there is a bus that... | 423 | 135.047852 
2 | [0.06756592, .. ]  | amazing view of a ... | 30  | 135.309937

让我们慢下来,理解刚刚发生了什么。简单地说,代码片段在其核心执行搜索算法,以查找与我们的文本查询最匹配的最相关图像嵌入。如上所示的输出结果给出了与我们文本查询最接近的嵌入。在结果中,第二列显示嵌入向量,而第三列包含与我们文本查询最匹配的图像描述。本质上,我们通过检查文本查询和图像的嵌入,确定哪个图像与文本查询最匹配。

这类似于说,如果这些数字代表单词"猫",我发现一个图像具有一组类似的数字,那么它很可能与"猫"的图像匹配。 ?

如果您正在寻找搜索如何进行的解释,我将在接下来的文章中写出详细的解释,因为研究引擎盖下的内容并了解搜索如何进行是非常令人兴奋的。本质上有一种叫做近似最近邻(ANN)的技术,用于在高维空间中有效地找到最近的点。ANN广泛应用于数据挖掘、机器学习、计算机视觉和NLP用例中。因此,当我们将嵌入的文本查询传递给搜索算法,并要求它在向量空间中为我们提供最近的样本点时,它使用一种ANN算法为我们获取它。具体来说,LanceDB利用DANN(深度近似最近邻)在其生态系统内搜索相关嵌入...

在我们的结果中,有五列。第一列是索引号,第二列是嵌入向量,第三列是与文本查询匹配的图像描述,第四列是图像的标签。但是,让我们关注最后一列——距离。当我提到ANN算法时,它只是在当前数据点(在我们的例子中是文本查询的嵌入)之间画一条线,并识别哪个数据点(图像嵌入)离它最近。如果您观察到结果中的其他数据点与顶部的数据点相比具有更大的距离,这表明它们与我们的查询稍微远一些或更不相关。为了说清楚,距离的计算是算法本身的一部分。

D-DAY

既然我们已经拥有了所有必要的信息,显示与查询最相关的图像就很简单了。只需获取与顶部匹配的嵌入向量的相关标签,并展示相应的图像即可。

data_id = int(res['id'][0])
display(ds["train"][data_id]['image'])
print(ds["train"][data_id]['text'])

there is a truck driving down a street with a stop sign

接下来呢?

为了让事情变得更有趣,我目前正在努力创建一个广泛的GTA-V字幕数据集。该数据集将包含更多的图像及其各自的参考文本,为我们提供更丰富的查询集进行探索和试验。尽管如此,总是有改进模型的空间。我们可以探索创建定制的CLIP模型,调整各种参数。增加训练轮数可能会给模型更多时间来掌握嵌入之间的相关性。此外,Meta开发了一个令人印象深刻的多模态嵌入模型,称为ImageBind。我们可以考虑尝试ImageBind作为当前多模态嵌入模型的替代方案,并比较结果。有众多选择可供选择,多模态RAG工作流背后的基本概念在很大程度上保持一致。

下面是在一个框架中如何将所有内容整合在一起的示意图,这是供您参考的Colab代码[3]




53AI,企业落地应用大模型首选服务商

产品:大模型应用平台+智能体定制开发+落地咨询服务

承诺:先做场景POC验证,看到效果再签署服务协议。零风险落地应用大模型,已交付160+中大型企业

联系我们

售前咨询
186 6662 7370
预约演示
185 8882 0121

微信扫码

与创始人交个朋友

回到顶部

 
扫码咨询