NSDT工具推荐: Three.js AI纹理开发包 - YOLO合成数据生成器 - GLTF/GLB在线编辑 - 3D模型格式在线转换 - 可编程3D场景编辑器 - REVIT导出3D模型插件 - 3D模型语义搜索引擎 - AI模型在线查看 - Three.js虚拟轴心开发包 - 3D模型在线减面 - STL模型在线切割 - 3D道路快速建模
最近需要研究图像相似性搜索。我想知道基于架构训练方法的嵌入之间是否存在差异。但是,很少有博客比较几种模型之间的嵌入。因此,在这篇博客中,我将使用 Flickr 数据集 [6] 比较 EfficientNet [1]、ViT [2]、DINO-v2 [3]、CLIP [4] 和 BLIP-2 [5] 的视觉嵌入进行图像相似性搜索。我将主要使用 Huggingface 和 Faiss 库进行实现。首先,我将简要介绍每个深度学习模型。接下来,我将向您展示代码实现和比较结果。
1、简介
在本节中,我将介绍几个用于实验的深度学习模型。请注意,我将使用具有相同含义的词,例如嵌入和特征。我只是为了符合论文的描述而使用它们。让我们深入研究它们吧!
1.1 EfficientNet
EfficientNet [1] 是一种卷积神经网络,专注于在保持计算效率的同时实现准确性。它被归类为监督学习。作者彻底研究了通道数(宽度)、总层数(深度)和输入分辨率,以解决模型大小、准确性和计算效率之间的权衡。与已经推出的计算机视觉模型(例如 ResNet)相比,它在 2019 年取得了最先进的成果。
根据模型大小,有几种变体,分别表示为 B0 到 B7,如下所示。模型尺寸越大,准确率越高。
如您所见,它们对 ImageNet 的准确率不错,但与最近的大型基础模型相比,模型尺寸非常紧凑。在这篇博客中,我将使用 EfficientNet-B7 进行实验。将要提取的嵌入是最后一个隐藏状态的输出,因为较深的层比浅层具有更多的语义信息。
1.2 Vision Transformer (ViT)
Vision Transformer [2] 是第一篇成功将 Transformer 架构改编到计算机视觉领域的论文,由 Google 开发。它也被归类为监督学习。它将输入图像分成几个补丁并将它们输入到 Transformer 编码器中。这些补丁相当于自然语言处理设置中的标记。对于分类任务,ViT 引入了一个名为 class-token 的 token,它在最后一个注意力层的输出中包含整个图像表示。架构图如下所示。
与 NLP Transformer 类似,它需要使用大型数据集进行预训练,并针对下游任务进行微调。与 CNN 相比,它的一个优点是,由于自注意力,它可以利用图像的全部信息。与 EfficientNet 一样,模型尺寸越大,能力就越强。
如您所见,较大的模型比 EfficientNet 更准确。在本博客中,我将使用 ViT-Large。将要提取的嵌入是类标记的输出,因为它具有整个图像语义信息。
1.3 DINO-v2
DINO-v2 [3] 是由 Meta 开发的用于生成计算机视觉中通用视觉特征的基础模型。作者将自监督方法应用于 ViT 架构,以了解图像和像素级别的图像特征;因此,DINO-v2 可以执行任何计算机视觉任务,例如分类或分割。在架构方面,DINO-v2 基于前身 DINO,即“无标签知识蒸馏”的缩写,如下所示。
DINO 有两个网络:学生网络和教师网络。它采用共蒸馏,学生网络和教师网络具有相同的架构,并且在两个方向(教师到学生和学生到教师)的训练过程中都应用蒸馏。请注意,学生到教师的蒸馏使用学生网络输出的平均值。
对于 DINO-v2,作者更新了训练方法,增加了一些损失和正则化。此外,他们还策划了一个高质量的数据集,以获得更高质量的图像特征。
在实验中,我们将使用类标记的输出,因为它们具有完整的图像语义信息,例如 ViT。
1.4 CLIP
CLIP 是 OpenAI [4] 开发的改变游戏规则的多模态模型之一。它被归类为弱监督学习,基于 Transformer 架构。得益于其独特的架构,它能够进行零样本图像分类。架构如下所示。
CLIP 架构包含文本和图像编码器。它通过对比损失对齐文本和图像特征并获得多模态能力。因此,它在文本和图像特征之间共享相同的特征空间,并且可以通过查找最相似的文本特征来实现零样本图像分类,如上图“(3) 用于零样本预测”。
CLIP 编码器基于 Transformer。因此,我们将在图像编码器中使用类标记的输出,同样 ViT 也是如此。
1.5 BLIP-2
BLIP-2 [5] 是 SalesForce 于 2023 年开发的开源多模态模型。它被归类为监督学习,基于 Transformer 架构。它专注于利用预训练的大型模型(例如 FlanT5 和 CLIP)来实现高效的训练(因为在典型的预算下从头开始训练大型模型很困难)。由于预训练的大型语言和视觉模型的训练方式不同,作者引入了 Q-Former 来对齐预训练模型之间的特征空间。
BLIP-2 包括两个阶段。第一阶段训练 Q-Former 以对齐来自预训练图像编码器的文本特征和图像特征,使用多种损失,例如图像-文本匹配、图像-文本对比损失和图像接地文本生成。第二阶段再次训练 Q-Former 以将其特征空间与大型语言模型(例如 FlanT5)对齐。因此,Q-Former 可以理解来自文本和图像源的特征。
顾名思义,Q-Former 架构基于 Transformer。我们将使用 Q-Former 的输出作为特征提取层。
2、比较
在本节中,我们将比较 EfficientNet、ViT、DINO-v2、CLIP 和 BLIP-2 的图像相似性搜索结果。这些模型具有不同的架构和训练损失。这会有什么不同?让我们从设置环境开始。
2.1 环境设置
我使用了带有 Python 3.10 的 conda 环境。我在 Ubuntu 20.04 上使用 cuda 11.0、16 GB GPU 和 16 GB RAM 进行了实验。
conda create -n transformers-env python=3.10 -y
conda activate transformers-env
接下来,我们需要通过 conda 和 pip 安装下面的库。
conda install pytorch torchvision torchaudio pytorch-cuda=11.8 -c pytorch -c nvidia
conda install -c pytorch faiss-cpu=1.8.0
conda install -c conda-forge pandas
pip install transformers
准备工作都完成了!现在,让我们实现代码。我们将使用 Faiss 库 [7] 来测量图像相似性以进行图像相似性搜索。Faiss 是一个基于近似最近邻搜索算法的高效相似性搜索库。此外,我们将使用 Flickr30k 数据集 [6] 进行实验。在直接深入研究图像相似性搜索之前,我们将探索如何从每个模型中提取嵌入(特征)。
2.2 提取特征
在这个实验中,我将使用 Huggingface transformer 库来提取嵌入。与简单的 Pytorch 实现相比,我们可以轻松提取隐藏状态。这一段代码检查了输入和输出的维度,所以我们会在CPU上运行它们。
- EfficientNet
EfficientNet的特征提取的提取代码如下所示。
import torch
from transformers import AutoImageProcessor, EfficientNetModel
# load pre-trained image processor for efficientnet-b7 and model weight
image_processor = AutoImageProcessor.from_pretrained("google/efficientnet-b7")
model = EfficientNetModel.from_pretrained("google/efficientnet-b7")
# prepare input image
inputs = image_processor(test_image, return_tensors='pt')
print('input shape: ', inputs['pixel_values'].shape)
with torch.no_grad():
outputs = model(**inputs, output_hidden_states=True)
embedding = outputs.hidden_states[-1]
print('embedding shape: ', embedding.shape)
embedding = torch.mean(embedding, dim=[2,3])
print('after reducing: ', embedding.shape)
### input shape: torch.Size([1, 3, 600, 600])
### embedding shape: torch.Size([1, 640, 19, 19])
### after reducing by taking mean: torch.Size([1, 640])
首先,我们需要准备一个输入。预定义的 EfficientNet 图像处理器会自动将输入形状处理为 (batch_size, 3, 600, 600)。经过模型处理后,我们可以得到一个带有隐藏状态的输出。最后一个隐藏状态的维度为 (batch_size, 640, 19, 19),因此我们将降均值过程应用于获得的嵌入。
- ViT
对于 ViT 的特征提取,提取代码如下所示。
# load pre-trained image processor for ViT-large and model weight
image_processor = AutoImageProcessor.from_pretrained("google/vit-large-patch16-224-in21k")
model = ViTModel.from_pretrained("google/vit-large-patch16-224-in21k")
# prepare input image
inputs = image_processor(test_image, return_tensors='pt')
print('input shape: ', inputs['pixel_values'].shape)
with torch.no_grad():
outputs = model(**inputs)
embedding = outputs.last_hidden_state
embedding = embedding[:, 0, :].squeeze(1)
print('embedding shape: ', embedding.shape)
### input shape: torch.Size([1, 3, 224, 224])
### embedding shape: torch.Size([1, 1024])
同样,预定义的 ViT 图像处理器会自动将输入形状处理为 (batch_size, 3, 224, 224)。最后一个隐藏状态有 (batch_size, 197, 1024) 个维度,我们只需要类标记,因此提取第二个维度 (197) 的第一个索引。
- DINO-v2
DINO-v2 基于 ViT,因此基本代码几乎相同。不同之处在于我们为 DINO-v2 加载图像处理器和模型。提取代码如下所示。
# load pre-trained image processor for DINO-v2 and model weight
image_processor = AutoImageProcessor.from_pretrained('facebook/dinov2-base')
model = AutoModel.from_pretrained('facebook/dinov2-base')
# prepare input image
inputs = image_processor(images=test_image, return_tensors='pt')
print('input shape: ', inputs['pixel_values'].shape)
with torch.no_grad():
outputs = model(**inputs)
embedding = outputs.last_hidden_state
embedding = embedding[:, 0, :].squeeze(1)
print('embedding shape: ', embedding.shape)
### input shape: torch.Size([1, 3, 224, 224])
### embedding shape: torch.Size([1, 1024])
基本上,我们使用相同的图像处理器。预定义的 ViT 图像处理器会自动将输入形状处理为 (batch_size, 3, 224, 224)。最后一个隐藏状态有 (batch_size, 197, 1024) 个维度,我们只需要类标记,因此提取第二个维度 (197) 的第一个索引。
- CLIP
CLIP 也是基于 ViT 的,因此流程相同。 huggingface transformers 库已经有 CLIP 的特征提取方法,因此实现更直接。
# load pre-trained image processor for CLIP and model weight
image_processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")
model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32")
# prepare input image
inputs = image_processor(images=test_image, return_tensors='pt', padding=True)
print('input shape: ', inputs['pixel_values'].shape)
with torch.no_grad():
outputs = model.get_image_features(**inputs)
print('embedding shape: ', outputs.shape)
### input shape: torch.Size([1, 3, 224, 224])
### embedding shape: torch.Size([1, 512])
我们使用相同的图像处理器。预定义的 ViT 图像处理器会自动将输入形状处理为 (batch_size, 3, 224, 224)。get_image_features 方法可以提取给定图像的嵌入,输出维度为 (batch_size, 512)。它与 ViT 和 DINO-v2 不同。
- BLIP-2
我们可以从 ViT 和 Q-Former 输出中提取图像嵌入。在这种情况下,Q-Former 输出可以包含来自图像和文本视角的语义,因此我们将使用它。
processor = Blip2Processor.from_pretrained("Salesforce/blip2-opt-2.7b")
model = Blip2Model.from_pretrained("Salesforce/blip2-opt-2.7b", torch_dtype=torch.float16)
# prepare input image
inputs = processor(images=test_image, return_tensors='pt', padding=True)
print('input shape: ', inputs['pixel_values'].shape)
with torch.no_grad():
outputs = model.get_qformer_features(**inputs)
print('embedding shape: ', outputs.shape)
我们使用可以处理图像和文本输入的 BLIP-2 处理器。它会自动将图像输入形状处理为 (batch_size, 3, 224, 224)。我们可以使用 get_qformer_features 提取 Q-Former 输出,输出维度为 (batch_size, 32, 768)。我们通过取平均值来减少输出,嵌入维度将为 (batch_size, 768)。
现在我们了解了如何从每个模型中提取嵌入。接下来,让我们检查使用 Faiss 实现的图像相似性搜索。
2.3 图像相似性搜索
我们只需几行代码即可使用 Faiss 接口轻松实现图像相似性搜索。我们假设有一个名为 features 的变量。过程如下。
- 将输入特征类型转换为 numpy.float32。
- 实例化 Faiss 向量存储并为其注册输入特征。
- 通过调用方法搜索来搜索向量。
我们可以选择如何测量向量之间的距离,例如欧几里得距离或余弦相似度。在本博客中,我们使用余弦相似度。伪代码可以写成如下。
# convert features type to np.float32
features = features.astype(np.float32)
# get embedding dimension
vector_dim = features.shape[1]
# register embedding to faiss vector store
index = faiss.IndexFlatIP(vector_dim)
faiss.normalize_L2(features)
index.add(features)
# For vector search, we just call search method.
top_k = 5
faiss.normalize_L2(embed)
distances, ann = index.search(embed, k=top_k)
现在,比较图像相似性搜索结果的所有先决条件都已完成。让我们从下一节开始查看具体结果。
2.4 图像相似性搜索结果比较
在本节中,我将使用五个模型比较图像相似性搜索结果。对于数据集,我使用从 Flickr30k 随机挑选的 10k 图像。我为每个模型实现了一个自定义管道,以实现批量特征提取。在本节后面,我将附上我用于此实验的笔记本。我选择了下面的图像来比较结果。
“3637013.jpg”的结果如下所示。
此案例相对其他图像而言比较简单,因此所有模型都可以拾取具有相似语义的图像。
“3662865.jpg”的结果如下所示。
在这种情况下,DINO-v2 和 CLIP 可以捕获“铲雪”的语义,但其他模型有时只能捕获“雪”。
“440375442.jpg”的结果如下所示。
EfficientNet 和 ViT 可能会将工作服误解为手术服,因此无法捕捉目标图像的语义。DINO-v2 可以理解“垃圾和穿工作服的人”的语义,CLIP 关注穿工作服的人,BLIP2 关注垃圾。我认为 DINO-v2、CLIP 和 BLIP2 可以捕捉语义。
“1377428277.jpg”的结果如下所示。
此图像的语义为:“街上很多人在享受节日或街头表演。” EfficientNet 和 ViT 专注于雨伞,因此无法捕捉语义。另一方面,DINO 专注于婴儿车,在性能方面落后一步。 CLIP 试图捕捉节日和街道部分,但也落后一步。 BLIP2 可以捕捉街头表演和婴儿车。
“57193495.jpg”的结果如下所示。
在本例中,EfficientNet、ViT 和 CLIP 有时可以捕捉到“女人穿着戏服并粉饰脸部”的语义。但是它们相对不足。相比之下,DINO-v2 和 BLIP2 可以捕捉到着装或戏服的语义。
“1393947190.jpg”的最后一个图像搜索结果如下所示。
结果不同,分别对应架构、CNN 和 Transformer。虽然 EfficientNet 可能专注于图像的白色和褐色,但其他模型可以捕捉“人正在缫丝”的语义。CLIP 可能专注于传统手工艺,但其他模型可以捕捉语义。
总之,我们有以下观察结果。
- EfficientNet(CNN 架构)不擅长捕捉像素信息以外的语义。
- Vision Transformer 比 CNN 更好,但仍然专注于像素信息而不是图像的含义。
- DINO-v2 可以捕捉图像的语义,并且倾向于关注正面物体。
- CLIP 可以捕捉语义,但有时可能会受到可以从图像中读取的语言信息的强烈影响。
- BLIP2 可以捕捉语义,这是其他模型中最优秀的结果。
我认为,为了获得更好的图像相似性搜索结果,我们基本上应该使用 DINO-v2 或 BLIP2。至于使用上的差异,当我们关注图像中的对象时,我们应该使用 DINO-v2。同时,当我们关注像素信息以外的语义时,我们应该使用 BLIP2,就像这种情况。
BimAnt翻译整理,转载请标明出处