图像相似度搜索中不同嵌入方式的比较:EfficientNet vs. ViT vs. VINO vs. CLIP vs. BLIP2
对于图像相似度搜索中不同模型会有什么差异
概述
最近,我需要研究图像相似性搜索。我想知道基于不同架构训练方法的嵌入是否存在差异。然而,很少有博客对几种模型的嵌入进行比较。因此,在这篇博客中,我将使用Flickr数据集对EfficientNet [1]、ViT [2]、DINO-v2 [3]、CLIP [4] 和 BLIP-2 [5]的视觉嵌入进行比较,以进行图像相似性搜索。我将主要使用Huggingface和Faiss库进行实现。首先,我将简要介绍每个深度学习模型。接下来,我将向您展示代码实现和比较结果。
目录
- EfficientNet、ViT、DINO-v2、CLIP 和 BLIP-2 的简要介绍
- EfficientNet、ViT、DINO-v2、CLIP 和 BLIP-2 之间的图像相似性搜索嵌入比较
1. EfficientNet、ViT、DINO-v2、CLIP 和 BLIP-2 的简要介绍
在这一部分,我将介绍用于实验的几种深度学习模型。请注意,我将“嵌入”和“特征”视为同义词,仅仅是为了适应论文的描述。让我们开始吧!
EfficientNet
EfficientNet [1] 是一种卷积神经网络,专注于在保持计算效率的同时实现准确性。它被归类为监督学习。作者详细研究了通道数(宽度)、总层数(深度)和输入分辨率,以解决模型大小、准确性和计算效率之间的权衡。与早期提出的计算机视觉模型(如ResNet)相比,它在2019年取得了最先进的结果。

根据模型大小,EfficientNet有几个变体,标记为B0到B7。模型大小越大,准确性越好。

可以看出,它们在ImageNet上的准确性相当不错,但与最近的大型基础模型相比,模型大小相对紧凑。在本博客中,我将使用EfficientNet-B7进行实验。提取的嵌入是最后隐藏状态的输出,因为深层具有比浅层更多的语义信息。
Vision Transformer (ViT)
Vision Transformer [2] 是第一篇成功将Transformer架构应用于计算机视觉领域的论文,由Google开发。它也被归类为监督学习。它将输入图像分成若干补丁,并将其输入Transformer编码器。这些补丁相当于自然语言处理中的令牌。对于分类任务,ViT引入了一个称为类令牌的令牌,在最后的注意层输出中包含整个图像的表示。架构示意图如下。

与NLP Transformer类似,它需要使用大型数据集进行预训练,并对下游任务进行微调。与CNN相比,它的一个优势在于可以利用整个图像的信息,这要归功于自注意力机制。与EfficientNet一样,模型越大,能力越强。

可以看出,大模型的准确性高于EfficientNet。在本博客中,我将使用ViT-Large。提取的嵌入是类令牌的输出,因为它包含整个图像的语义信息。
DINO-v2
DINO-v2 [3] 是Meta开发的一种基础模型,用于在计算机视觉中生成通用视觉特征。作者在ViT架构上应用自监督方法,以理解图像特征,包括图像和像素级别。因此,DINO-v2能够执行任何计算机视觉任务,例如分类或分割。在架构方面,DINO-v2基于前身DINO,其缩写为“无标签的知识蒸馏”。
DINO有两个网络:学生网络和教师网络。它利用共蒸馏,学生和教师网络具有相同的架构,在训练期间进行双向蒸馏,教师向学生,学生向教师。需要注意的是,学生到教师的蒸馏使用的是学生网络输出的平均值。

对于DINO-v2,作者更新了训练方法,增加了一些损失和正则化。此外,他们策划了一个高质量的数据集,以获得更高质量的图像特征。
在实验中,我们将使用类令牌的输出,因为它们具有完整的图像语义信息,类似于ViT。
CLIP
CLIP是OpenAI开发的改变游戏规则的多模态模型之一 [4]。它被归类为弱监督学习,基于Transformer架构。由于其独特的架构,它能够实现零样本图像分类。架构如下所示。

CLIP架构包含文本和图像编码器。它通过对比损失对齐文本和图像特征,获得多模态能力。因此,它在文本和图像特征之间共享相同的特征空间,并且可以通过查找最相似的文本特征来实现零样本图像分类,如上图“(3)用于零样本预测”所示。
CLIP编码器基于Transformers。因此,我们将使用图像编码器中的类令牌输出,方法与ViT相同。
BLIP-2
BLIP-2 [5] 是2023年由SalesForce开发的开源多模态模型。它被归类为监督学习,基于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 之间的图像相似性搜索嵌入比较
在这一部分中,我们将比较EfficientNet、ViT、DINO-v2、CLIP和BLIP-2的图像相似性搜索结果。这些模型具有不同的架构和训练损失。这会带来什么区别呢?让我们先设置环境。
环境设置
我使用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] 进行实验。在直接进行图像相似性搜索之前,我们将探索如何从每个模型中提取嵌入(特征)。
从每个模型中提取特征
在本实验中,我将使用Huggingface变换器库来提取嵌入。与简单的Pytorch实现相比,我们可以更轻松地提取隐藏状态。此部分代码检查输入和输出维度,因此我们将在CPU上运行它们。
EfficientNet
EfficientNet的特征提取代码如下:
import torch
from transformers import AutoImageProcessor, EfficientNetModel
# 加载预训练的EfficientNet-B7图像处理器和模型权重
image_processor = AutoImageProcessor.from_pretrained("google/efficientnet-b7")
model = EfficientNetModel.from_pretrained("google/efficientnet-b7")
# 准备输入图像
inputs = image_processor(test_image, return_tensors='pt')
print('输入形状: ', inputs['pixel_values'].shape)
with torch.no_grad():
outputs = model(**inputs, output_hidden_states=True)
embedding = outputs.hidden_states[-1]
print('嵌入形状: ', embedding.shape)
embedding = torch.mean(embedding, dim=[2,3])
print('缩减后: ', embedding.shape)
### 输入形状: torch.Size([1, 3, 600, 600])
### 嵌入形状: torch.Size([1, 640, 19,
首先,我们需要准备一个输入。预定义的EfficientNet图像处理器会自动将输入形状处理为(batch_size, 3, 600, 600)。经过模型处理后,我们可以得到具有隐藏状态的输出。最后一个隐藏状态的维度为(batch_size, 640, 19, 19),因此我们通过取均值来缩减嵌入。
ViT
ViT的特征提取代码如下:
#加载预训练的ViT-Large图像处理器和模型权重
image_processor = AutoImageProcessor.from_pretrained("google/vit-large-patch16-224-in21k")
model = ViTModel.from_pretrained("google/vit-large-patch16-224-in21k")
# 准备输入图像
inputs = image_processor(test_image, return_tensors='pt')
print('输入形状: ', inputs['pixel_values'].shape)
with torch.no_grad():
outputs = model(**inputs)
embedding = outputs.last_hidden_state
embedding = embedding[:, 0, :].squeeze(1)
print('嵌入形状: ', embedding.shape)
### 输入形状: torch.Size([1, 3, 224, 224])
### 嵌入形状: torch.Size([1, 1024])
同样,预定义的ViT图像处理器会自动将输入形状处理为(batch_size, 3, 224, 224)。最后隐藏状态的维度为(batch_size, 197, 1024),我们只需要类令牌,因此提取第二维(197)的第一个索引。
DINO-v2
DINO-v2的特征提取代码如下:
# 加载预训练的DINO-v2图像处理器和模型权重
image_processor = AutoImageProcessor.from_pretrained('facebook/dinov2-base')
model = AutoModel.from_pretrained('facebook/dinov2-base')
# 准备输入图像
inputs = image_processor(images=test_image, return_tensors='pt')
print('输入形状: ', inputs['pixel_values'].shape)
with torch.no_grad():
outputs = model(**inputs)
embedding = outputs.last_hidden_state
embedding = embedding[:, 0, :].squeeze(1)
print('嵌入形状: ', embedding.shape)
### 输入形状: torch.Size([1, 3, 224, 224])
### 嵌入形状: torch.Size([1, 1024])
基本上,我们使用相同的图像处理器。预定义的ViT图像处理器会自动将输入形状处理为(batch_size, 3, 224, 224)。最后的隐藏状态具有(batch_size, 197, 1024)的维度,我们只需要类令牌,因此提取第二维(197)的第一个索引。
CLIP
CLIP的特征提取实现代码如下:
# 加载预训练的CLIP图像处理器和模型权重
image_processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")
model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32")
# 准备输入图像
inputs = image_processor(images=test_image, return_tensors='pt', padding=True)
print('输入形状: ', inputs['pixel_values'].shape)
with torch.no_grad():
outputs = model.get_image_features(**inputs)
print('嵌入形状: ', outputs.shape)
### 输入形状: torch.Size([1, 3, 224, 224])
### 嵌入形状: 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)
# 准备输入图像
inputs = processor(images=test_image, return_tensors='pt', padding=True)
print('输入形状: ', inputs['pixel_values'].shape)
with torch.no_grad():
outputs = model.get_qformer_features(**inputs)
print('嵌入形状: ', outputs.shape)
我们使用BLIP-2处理器,该处理器可以处理图像和文本输入。它会自动处理图像输入的形状为(batch_size, 3, 224, 224)。我们可以使用get_qformer_features提取Q-Former的输出,输出维度为(batch_size, 32, 768)。我们通过取均值来缩减输出,嵌入维度将为(batch_size, 768)。
现在我们了解了如何从每个模型中提取嵌入。接下来,让我们检查使用Faiss实现图像相似性搜索的代码。
图像相似性搜索
我们可以使用Faiss接口轻松实现图像相似性搜索,只需几行代码。我们假设我们有一个变量称为features。程序步骤如下:
- 将输入特征类型转换为numpy.float32。
- 实例化Faiss向量存储并注册输入特征。
- 通过调用
search方法进行向量搜索。
我们可以选择测量向量之间的距离,如欧几里得距离或余弦相似性。在本博客中,我们使用余弦相似性。伪代码如下所示:
# 将特征类型转换为np.float32
features = features.astype(np.float32)
# 获取嵌入维度
vector_dim = features.shape[1]
# 注册嵌入到Faiss向量存储
index = faiss.IndexFlatIP(vector_dim)
faiss.normalize_L2(features)
index.add(features)
# 对于向量搜索,我们只需调用search方法。
top_k = 5
faiss.normalize_L2(embed)
distances, ann = index.search(embed, k=top_k)
现在,所有比较图像相似性搜索结果的前提条件都已完成。接下来,让我们检查具体的结果。
图像相似性搜索结果的比较
在这一部分中,我将比较使用五种模型的图像相似性搜索结果。对于数据集,我使用了从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 (ViT) 比CNN更好,但仍然关注像素信息而非图像的意义。
- DINO-v2 能够捕捉图像的语义,并倾向于关注正面物体。
- CLIP 可以捕捉语义,但有时可能会受到图像中可读的语言信息的强烈影响。
- BLIP2 能够捕捉语义,结果在所有模型中表现最为优越。
我认为,基本上我们应该使用DINO-v2或BLIP2,以获得更好的图像相似性搜索结果。至于使用的差异,当我们关注图像中的物体时,应该使用DINO-v2;而当我们关注超出像素信息的语义,例如场景时,应该使用BLIP2。
以下是我在这些实验中使用的代码。感谢您阅读我的文章!