橘智橘智
FakeOrange
预计阅读时间:10分钟18秒

图像相似度搜索中不同嵌入方式的比较:EfficientNet vs. ViT vs. VINO vs. CLIP vs. BLIP2

对于图像相似度搜索中不同模型会有什么差异

0
0

概述


最近,我需要研究图像相似性搜索。我想知道基于不同架构训练方法的嵌入是否存在差异。然而,很少有博客对几种模型的嵌入进行比较。因此,在这篇博客中,我将使用Flickr数据集对EfficientNet [1]、ViT [2]、DINO-v2 [3]、CLIP [4] 和 BLIP-2 [5]的视觉嵌入进行比较,以进行图像相似性搜索。我将主要使用Huggingface和Faiss库进行实现。首先,我将简要介绍每个深度学习模型。接下来,我将向您展示代码实现和比较结果。


目录


  1. EfficientNet、ViT、DINO-v2、CLIP 和 BLIP-2 的简要介绍
  2. EfficientNet、ViT、DINO-v2、CLIP 和 BLIP-2 之间的图像相似性搜索嵌入比较


1. EfficientNet、ViT、DINO-v2、CLIP 和 BLIP-2 的简要介绍


在这一部分,我将介绍用于实验的几种深度学习模型。请注意,我将“嵌入”和“特征”视为同义词,仅仅是为了适应论文的描述。让我们开始吧!


EfficientNet

EfficientNet [1] 是一种卷积神经网络,专注于在保持计算效率的同时实现准确性。它被归类为监督学习。作者详细研究了通道数(宽度)、总层数(深度)和输入分辨率,以解决模型大小、准确性和计算效率之间的权衡。与早期提出的计算机视觉模型(如ResNet)相比,它在2019年取得了最先进的结果。

data/78df2c1f-e442-415d-a382-fa7925af0c4b/87e0ba33-04c9-4a81-878f-fff6e3d9816bimage.png

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

data/78df2c1f-e442-415d-a382-fa7925af0c4b/7384ffed-aa28-4f1f-a533-b43154e7d833image.png

可以看出,它们在ImageNet上的准确性相当不错,但与最近的大型基础模型相比,模型大小相对紧凑。在本博客中,我将使用EfficientNet-B7进行实验。提取的嵌入是最后隐藏状态的输出,因为深层具有比浅层更多的语义信息。


Vision Transformer (ViT)

Vision Transformer [2] 是第一篇成功将Transformer架构应用于计算机视觉领域的论文,由Google开发。它也被归类为监督学习。它将输入图像分成若干补丁,并将其输入Transformer编码器。这些补丁相当于自然语言处理中的令牌。对于分类任务,ViT引入了一个称为类令牌的令牌,在最后的注意层输出中包含整个图像的表示。架构示意图如下。

data/78df2c1f-e442-415d-a382-fa7925af0c4b/aa0f1271-ded1-40d6-8c04-9ec92adf4667image.png

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

data/78df2c1f-e442-415d-a382-fa7925af0c4b/82346961-0401-499a-a144-93fdaf53f2a4image.png

可以看出,大模型的准确性高于EfficientNet。在本博客中,我将使用ViT-Large。提取的嵌入是类令牌的输出,因为它包含整个图像的语义信息。


DINO-v2

DINO-v2 [3] 是Meta开发的一种基础模型,用于在计算机视觉中生成通用视觉特征。作者在ViT架构上应用自监督方法,以理解图像特征,包括图像和像素级别。因此,DINO-v2能够执行任何计算机视觉任务,例如分类或分割。在架构方面,DINO-v2基于前身DINO,其缩写为“无标签的知识蒸馏”。

DINO有两个网络:学生网络和教师网络。它利用共蒸馏,学生和教师网络具有相同的架构,在训练期间进行双向蒸馏,教师向学生,学生向教师。需要注意的是,学生到教师的蒸馏使用的是学生网络输出的平均值。

data/78df2c1f-e442-415d-a382-fa7925af0c4b/973174cc-913e-4cbe-95f7-85c29e1aad1fimage.png

对于DINO-v2,作者更新了训练方法,增加了一些损失和正则化。此外,他们策划了一个高质量的数据集,以获得更高质量的图像特征。

在实验中,我们将使用类令牌的输出,因为它们具有完整的图像语义信息,类似于ViT。


CLIP

CLIP是OpenAI开发的改变游戏规则的多模态模型之一 [4]。它被归类为弱监督学习,基于Transformer架构。由于其独特的架构,它能够实现零样本图像分类。架构如下所示。

data/78df2c1f-e442-415d-a382-fa7925af0c4b/6ec5da05-fd31-4802-a149-203fbd0235bfimage.png

CLIP架构包含文本和图像编码器。它通过对比损失对齐文本和图像特征,获得多模态能力。因此,它在文本和图像特征之间共享相同的特征空间,并且可以通过查找最相似的文本特征来实现零样本图像分类,如上图“(3)用于零样本预测”所示。

CLIP编码器基于Transformers。因此,我们将使用图像编码器中的类令牌输出,方法与ViT相同。


BLIP-2

BLIP-2 [5] 是2023年由SalesForce开发的开源多模态模型。它被归类为监督学习,基于Transformer架构。它专注于利用预训练的大型模型(例如FlanT5和CLIP)实现高效的训练(因为在典型预算下很难从头开始训练大型模型)。由于预训练的大型语言模型和视觉模型的训练方式不同,作者引入了Q-Former,以对齐预训练模型之间的特征空间。

data/78df2c1f-e442-415d-a382-fa7925af0c4b/9ba2579b-ca9e-4c0a-a8de-ec144764b476image.png

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。程序步骤如下:

  1. 将输入特征类型转换为numpy.float32。
  2. 实例化Faiss向量存储并注册输入特征。
  3. 通过调用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张图像。我为每个模型实现了一个自定义管道,以进行批量特征提取。在本节末尾,我将附上我用于此实验的笔记本。我选择了以下图像进行比较。

data/78df2c1f-e442-415d-a382-fa7925af0c4b/edfc664f-a19a-456a-b88e-bc791d26d0edimage.png

“3637013.jpg”的结果如下:

data/78df2c1f-e442-415d-a382-fa7925af0c4b/1e65aac6-52a0-4646-81a4-a44f85f2b24aimage.png

这个案例相对较简单,因此所有模型都能选择到相似的语义图像。


“3662865.jpg”的结果如下:

data/78df2c1f-e442-415d-a382-fa7925af0c4b/d0432f59-284e-4ee2-9fc5-11c3c8c810bfimage.png

在这种情况下,DINO-v2和CLIP能够捕捉“铲雪”的语义,但其他模型有时只能捕捉到“雪”。


“440375442.jpg”的结果如下:

data/78df2c1f-e442-415d-a382-fa7925af0c4b/bf10dd8d-b66e-4299-ac79-5a63d9a1cbb6image.png

EfficientNet和ViT可能会将工作服误认为外科手术服,因此无法捕捉目标图像的语义。DINO-v2能够理解“垃圾和穿着工作服的人”的语义,CLIP关注穿着工作服的人,而BLIP2则关注垃圾。我认为DINO-v2、CLIP和BLIP2能够捕捉到语义。


“1377428277.jpg”的结果如下:

data/78df2c1f-e442-415d-a382-fa7925af0c4b/37477bb7-5ab4-48c9-8748-bbd1af0aca3eimage.png

该图像的语义是:“很多人在街上享受某种节日或街头表演。”EfficientNet和ViT关注的是伞,因此无法捕捉到语义。另一方面,DINO专注于婴儿车,表现稍逊一筹。CLIP试图捕捉节日和街道的部分,但也表现稍逊一筹。BLIP2能够捕捉街头表演和婴儿车。


“57193495.jpg”的结果如下:

data/78df2c1f-e442-415d-a382-fa7925af0c4b/35c6a3d8-b6bc-4748-a637-e15e2b87572cimage.png

在这种情况下,EfficientNet、ViT和CLIP有时能够捕捉“穿着服装的女人和白脸”的语义,但它们相对不足。相比之下,DINO-v2和BLIP2能够捕捉到“服装或角色扮演”的语义。


最后一张图像“1393947190.jpg”的搜索结果如下:

data/78df2c1f-e442-415d-a382-fa7925af0c4b/393a025a-e01e-45f6-a6d5-3636300df5e1image.png


结果各异,与架构、CNN和Transformer有关。尽管EfficientNet可能会关注图像的白色和棕色,但其他模型能够捕捉到“一个人在拉丝”的语义。CLIP可能关注传统手工艺,但其他模型能够捕捉到语义。


总结

在总结中,我们得出以下观察结果:

    • EfficientNet(CNN架构)不擅长捕捉超出像素信息的语义。
    • Vision Transformer (ViT) 比CNN更好,但仍然关注像素信息而非图像的意义。
    • DINO-v2 能够捕捉图像的语义,并倾向于关注正面物体。
    • CLIP 可以捕捉语义,但有时可能会受到图像中可读的语言信息的强烈影响。
    • BLIP2 能够捕捉语义,结果在所有模型中表现最为优越。

我认为,基本上我们应该使用DINO-v2或BLIP2,以获得更好的图像相似性搜索结果。至于使用的差异,当我们关注图像中的物体时,应该使用DINO-v2;而当我们关注超出像素信息的语义,例如场景时,应该使用BLIP2。

以下是我在这些实验中使用的代码。感谢您阅读我的文章!


原文链接:点击此处


评论