在机器学习领域,模型曾经存在限制——它们一次只能处理一种类型的数据。然而,机器学习的最终愿望是与人类大脑的认知能力相媲美,人类大脑可以毫不费力地同时理解各种数据模式。最近的突破(以 GPT-4V 等模型为代表)现在已经证明了同时处理多种数据模式的卓越能力。这为开发人员打造能够无缝管理不同类型数据的人工智能应用程序(称为多模式应用程序)提供了令人兴奋的可能性。
多模态图像搜索是一个引人注目且广受欢迎的用例。它允许用户通过分析特征或视觉内容来找到相似的图像。由于计算机视觉和深度学习的快速发展,图像搜索变得异常强大。
在本文中,我们将使用 Hugging Face 库中的模型构建多模式图像搜索应用程序。在深入实际实施之前,让我们先回顾一些基础知识,为我们的探索奠定基础。
什么是多模式系统?
多模式系统是指可以使用多种交互或通信模式的任何系统。它意味着一个系统可以同时处理和理解不同类型的输入,例如文本、图像、语音,有时甚至是触摸或手势,并且还可以以多种方式返回结果。
例如,GPT-4V(打开新窗口,由OpenAI开发,是一种先进的多模态模型,可以同时处理文本和图像输入的多种“模态” .当提供带有描述性查询的图像时,模型可以根据提供的文本分析视觉内容。
什么是多模态嵌入?
多模态嵌入是一种先进的机器学习技术,是以矢量格式生成图像、文本和音频等多种模态的数字表示的过程。与在向量空间中仅表示一种数据类型的基本嵌入技术不同,多模态嵌入可以在统一的向量空间中表示各种数据类型。例如,这允许将文本描述与相应的图像相关联。借助多模态嵌入,系统可以分析图像并将其与相关文本描述相关联,反之亦然。
现在,我们来讨论一下如何开发这个项目以及我们将使用的技术。
工具和技术
我们将使用CLIP(打开新窗口, a>MyScale(打开新窗口) 和 Unsplash-25k 数据集 (打开新窗口) 在这个项目中。让我们详细看看它们。
- CLIP:您将使用预先训练的多模态 CLIP (打开新窗口)由 Hugging Face 的 OpenAI 开发。该模型将用于集成文本和图像。
- MyScale:MyScale 是一个 SQL 矢量数据库,用于以优化的方式存储和处理结构化和非结构化数据。您将使用 MyScale 存储矢量嵌入并查询相关图像。
- Unsplash-25k 数据集:Unsplash 提供的数据集包含约 25,000 张图像。其中包括一些复杂的场景和物体。
如何设置 Hugging Face 和 MyScale
要在本地环境中开始使用 Hugging Face 和 MyScale,您需要安装一些 Python 包。打开终端并输入以下 pip
命令:
pip 安装数据集 clickhouse-connect 请求变压器 torch tqdm
安装完成后,您可以通过在终端中输入以下命令进行验证。
pip 冻结 | egrep '(数据集|clickhouse-connect|请求|变压器|火炬|tqdm)'
它将打印新安装的依赖项及其版本。
下载并加载数据集
第一步是下载数据集并将其提取到本地。您可以通过在终端中输入以下命令来完成此操作。
# 下载数据集
wget https://unsplash-datasets.s3.amazonaws.com/lite/latest/unsplash-research-dataset-lite-latest.zip
# 将下载的文件解压到临时目录中
解压 unsplash-research-dataset-lite-latest.zip -d tmp
让我们将提取的文件中所需的数据加载到 Python 数据框中。
# 导入 pandas
将 pandas 导入为 pd
# 从目录加载照片文件
df_photos = pd.read_csv("tmp/photos.tsv", sep='\t', header=0)
df_photos
我们正在从目录加载 photos
文件,其中包含有关数据集中照片的信息。照片个人资料如下所示:
<标题>
标题>
<正文>
表>
photo_url
和 photo_image_url
之间的区别在于 photo_url
包含图像描述页面的 URL,告诉作者和其他人照片的元信息。 photo_image_url
仅包含图像的 URL,我们将使用它来下载图像。
加载模型并获取嵌入
加载数据集后,我们首先加载 clip-vit -base-patch32 (打开新窗口)建模并编写一个 Python 函数以将图像转换为矢量嵌入。该函数将使用 CLIP 模型来表示嵌入。
# 导入 pytorch
进口火炬
# 导入变压器以从 Hugging Face 加载模型和处理器
从 Transformer 导入 CLIPProcessor、CLIPModel
# 从 Hugging Face 加载 CLIP 模型
模型 = CLIPModel.from_pretrained('openai/clip-vit-base-patch32')
# 加载用于预处理图像的处理器并使它们与模型兼容
处理器 = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")
# 定义方法
def create_embeddings(图像=无,文本=无):
# 初始化嵌入
图像嵌入=无
文本嵌入=无
# 处理图像(如果提供)
如果图像不是无:
image_embeddings = extract_image_features(图像)
image_embeddings = torch.tensor(image_embeddings)
image_embeddings = image_embeddings / image_embeddings.norm(dim=-1, keepdim=True)
# 处理文本(如果提供)
如果文本不是“无”:
text_inputs = 处理器(text=[text], return_tensors="pt", padding=True)
使用 torch.no_grad():
text_outputs = model.get_text_features(**text_inputs)
text_embeddings = text_outputs / text_outputs.norm(dim=-1, keepdim=True)
text_embeddings = text_embeddings.squeeze(0).tolist()
# 如果同时提供了图像和文本,则合并嵌入,并标准化
如果 image_embeddings 不是 None 并且 text_embeddings 不是 None:
组合嵌入 = (图像嵌入 + torch.tensor(文本嵌入)) / 2
组合嵌入 = 组合嵌入 / 组合嵌入.norm(dim=-1, keepdim=True)
返回combined_embeddings.tolist()
# 仅返回图像或文本嵌入(如果提供了其中之一)
如果 text_embeddings 是 None,则返回 image_embeddings.tolist() text_embeddings
上面的代码旨在单独或同时处理文本和图像输入,并返回相应的嵌入。让我们看看它们是如何工作的:
- 如果您同时提供图像和文本,代码将返回一个结合了两者嵌入的向量。
- 如果您提供文本或图像(但不能同时提供两者),则代码仅返回所提供文本或图像的嵌入。
注意:我们使用一种基本方法来合并两个嵌入,只是为了关注多模式概念。但是有一些更好的方法来合并嵌入,例如串联和注意机制。
我们将加载、下载数据集中的前 1000 张图像并将其传递到上面的 create_embeddings
函数。返回的嵌入将存储在新列 photo_embed
中。
# 导入Image moduke进行图像处理
从 PIL 导入图像
# 导入requests模块用于发出HTTP请求
导入请求
# 导入tqdm用于处理条形图可视化
从 tqdm.auto 导入 tqdm
# 获取前1000张图像
photo_ids = df_photos['photo_id'][:1000]
# 过滤DataFrame以获取所需的列
df_photos = df_photos.loc[photo_ids.index, ['photo_id', 'photo_image_url']]
# 创建一个会话来发出HTTP请求
会话 = requests.Session()
# 定义用于下载和获取嵌入的 Python 函数
def process_image(url):
尝试:
# 发出 GET 请求来下载图像
响应 = session.get(url, 流=True)
响应.raise_for_status()
图像 = Image.open(response.raw)
# 获取嵌入并返回
返回create_embeddings(图像)
除了 requests.RequestException:
返回无
# 构造一个URL来下载较小尺寸的图片
df_photos['photo_image_url'] = df_photos['photo_image_url'].apply(lambda x: x + "?q=75&fm=jpg&w=200&fit=max")
# 将图像一张一张地传递到“process_image”并将嵌入保存到新创建的列“photo_embed”
df_photos['photo_embed'] = [tqdm 中 url 的 process_image(url)(df_photos['photo_image_url'],total=len(df_photos))]
# 删除图像处理失败的行
df_photos.dropna(子集=['photo_embed'], inplace=True)
# 重置索引并将“id”列重命名为“index”
df_photos = df_photos[df_photos['photo_id'].isin(photo_ids)].reset_index().rename(columns={'index': 'id'})
# 关闭会话
session.close()
注意:此过程需要一些时间,并且还取决于您的网速。
经过此过程,我们的数据集就完成了。下一步是创建一个新表并将数据存储在 MyScale 中。
与 MyScale 连接
要将应用程序与 MyScale 连接,您需要完成几个设置和配置步骤。
- 创建帐户:首先在 MyScale< 上创建一个帐户span>(打开新窗口。
- 集群创建:接下来,您需要创建一个集群。为此,您可以参考“创建集群< span>(打开新窗口)”MyScale 提供的文档,其中包含详细说明。
- 获取连接详细信息:设置集群后,下一步是获取连接详细信息 (打开新窗口)以建立连接您的应用程序和 MyScale 集群之间的连接。
获得连接详细信息后,您可以替换以下代码中的值:
导入clickhouse_connect
客户端 = clickhouse_connect.get_client(
主机='您的主机地址',
端口=443,
username='您的用户名',
密码='您的密码'
)
创建表
建立连接后,下一步就是创建表。现在,让我们首先使用以下命令查看我们的数据框:
df_photos
数据框如下所示:
<标题>
标题>
<正文>
表>
让我们根据数据框创建一个表格。
# 检查是否存在同名表。如果存在,则删除该表
client.command("如果存在则删除表default.myscale_photos")
# 创建一个照片表
客户端.命令("""
创建表default.myscale_photos
(
id UInt64,
照片_id 字符串,
photo_image_url 字符串,
photo_embed 数组(Float32),
约束向量_len 检查长度(photo_embed)= 512
)
按 ID 排序
""")
上述命令将在您的 MyScale 集群中创建一个表。
插入数据
让我们将数据插入到新创建的表中:
# 从数据集中上传数据
client.insert("default.myscale_photos", df_photos.to_records(index=False).tolist(),
column_names=df_photos.columns.tolist())
# 检查插入数据的数量
print(f"照片数量:{client.command('SELECT count(*) FROM default.myscale_photos')}")
# 用余弦创建向量索引
客户端.命令("""
更改表default.myscale_photos
添加矢量索引 photo_embed_index photo_embed
类型 MSTG
('metric_type=余弦')
“””)
# 检查向量索引的状态,确保向量索引已准备好并处于“已构建”状态
get_index_status="从 system.vector_indices 中选择状态,其中 name='photo_embed_index'"
print(f"索引构建状态:{client.command(get_index_status)}")
上面的代码将数据插入表中并使用MSTG
算法创建索引。创建索引是为了从表中快速检索数据。最后一条命令用于确认索引是否创建成功。如果是,您将看到“索引构建状态:已构建。”
注意:MSTG 算法 由 MyScale 创建,它比 IVF 和 HNSW 等其他索引算法要快得多。
如何查询 MyScale
插入数据后,我们就可以使用 MyScale 来查询数据并使用多模态来获取图像。因此,我们首先尝试从表中获取随机图像。
导入请求
将 matplotlib.pyplot 导入为 plt
从 PIL 导入图像
从 io 导入 BytesIO
# 下载图片及其 url
默认下载(网址):
响应 = requests.get(url)
返回 Image.open(BytesIO(response.content))
def show_image(url, 标题=无):
img = 下载(网址)
图 = plt.figure(figsize=(4, 4))
plt.imshow(img)
plt.show()
random_image = client.query("SELECT * FROM default.myscale_photos ORDER BY rand() LIMIT 1")
target_image_url = random_image.first_item["photo_image_url"]
print("正在加载目标图像...")
显示图像(目标图像网址)
上面的代码应该从表格中随机搜索图像并将其显示在代码编辑器上。
如何使用文本和图像获取相关图像
正如您所知,多模态模型可以同时处理多种数据模态。同样,我们的模型可以同时处理图像和文本,提供相关图像。我们将提供以下图片和文字:“一个男人站在海滩上。”
image_url="https://images.unsplash.com/photo-1701443478334-c1a4bfda91ff?q=80&w=1936&auto=format&fit=crop&ixlib=rb-4.0.3&ixid= M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
query_text="一个男人站在海滩上"
embeddings=create_embeddings(download(url),query_text)
下一步是编写查询以从数据集中获取 top_k
相关结果。