圖像相似性搜索比較:EfficientNet vs. ViT vs. DINO-v2 vs. CLIP vs. BLIP-2
最近,我需要研究圖像相似性搜索,我想知道基于架構(gòu)訓(xùn)練方法的嵌入是否存在差異。在本文中,我將使用Flickr數(shù)據(jù)集[6]比較EfficientNet[1]、ViT[2]、DINO-v2[3]、CLIP[4]和BLIP-2[5]的視覺嵌入在圖像相似性搜索中的表現(xiàn)。我將主要使用Huggingface和Faiss庫進(jìn)行實現(xiàn)。首先,我將簡要介紹每個深度學(xué)習(xí)模型。接下來,我將展示代碼實現(xiàn)和比較結(jié)果。
一、EfficientNet、ViT、DINO-v2、CLIP和BLIP-2的簡要介紹
在本節(jié)中,我將介紹用于實驗的幾個深度學(xué)習(xí)模型。請注意,我將使用“嵌入”和“特征”等詞,它們的含義相同。我只是根據(jù)論文的描述來使用它們。讓我們深入了解它們!
1. EfficientNet
EfficientNet[1]是一種卷積神經(jīng)網(wǎng)絡(luò),專注于在保持計算效率的同時實現(xiàn)高精度。它屬于監(jiān)督學(xué)習(xí)。作者深入研究了通道數(shù)(寬度)、總層數(shù)(深度)和輸入分辨率,以解決模型大小、精度和計算效率之間的權(quán)衡問題。與已經(jīng)引入的計算機(jī)視覺模型(如ResNet)相比,它在2019年取得了最先進(jìn)的結(jié)果。
EfficientNet根據(jù)模型大小分為B0到B7幾個變體,如下所示。模型越大,精度越高。
在本文中,我將使用EfficientNet-B7進(jìn)行實驗。提取的嵌入是最后一個隱藏層的輸出,因為深層比淺層具有更多的語義信息。
2. Vision Transformer (ViT)
Vision Transformer[2]是由Google開發(fā)的第一篇成功將Transformer架構(gòu)應(yīng)用于計算機(jī)視覺領(lǐng)域的論文。它同樣屬于監(jiān)督學(xué)習(xí)。它將輸入圖像劃分為多個補(bǔ)丁,并將它們輸入到Transformer編碼器中。這些補(bǔ)丁相當(dāng)于自然語言處理中的標(biāo)記。對于分類任務(wù),ViT引入了一個稱為類標(biāo)記的標(biāo)記,它在最后一個注意力層的輸出中包含整個圖像的表示。
與NLP Transformer類似,它需要在大數(shù)據(jù)集上進(jìn)行預(yù)訓(xùn)練,并對下游任務(wù)進(jìn)行微調(diào)。與CNN相比,它的一個優(yōu)勢是可以通過自注意力機(jī)制利用圖像的全局信息。與EfficientNet一樣,模型越大,能力越強(qiáng)。
在本文中,我將使用ViT-Large。提取的嵌入是類標(biāo)記的輸出,因為它包含整個圖像的語義信息。
3. DINO-v2
DINO-v2[3]是由Meta開發(fā)的基礎(chǔ)模型,用于生成計算機(jī)視覺中的通用視覺特征。作者將自監(jiān)督方法應(yīng)用于ViT架構(gòu),以理解圖像和像素級別的特征;因此,DINO-v2可以執(zhí)行任何計算機(jī)視覺任務(wù),如分類或分割。在架構(gòu)方面,DINO-v2基于前身DINO,即“無標(biāo)簽知識蒸餾”的縮寫。
DINO有兩個網(wǎng)絡(luò):學(xué)生和教師。它利用協(xié)同蒸餾,其中學(xué)生和教師網(wǎng)絡(luò)具有相同的架構(gòu),并且在訓(xùn)練期間在兩個方向上進(jìn)行蒸餾,從教師到學(xué)生以及從學(xué)生到教師。注意,學(xué)生到教師的蒸餾使用學(xué)生網(wǎng)絡(luò)輸出的平均值。
對于DINO-v2,作者更新了訓(xùn)練方法,添加了一些損失和正則化。此外,他們還策劃了一個高質(zhì)量的數(shù)據(jù)集,以獲得更好的圖像特征。
在實驗中,我們將使用類標(biāo)記的輸出,因為它們像ViT一樣包含整個圖像的語義信息。
4. CLIP
CLIP是由OpenAI開發(fā)的改變游戲規(guī)則的多模態(tài)模型之一[4]。它屬于弱監(jiān)督學(xué)習(xí),基于Transformer架構(gòu)。由于其獨(dú)特的架構(gòu),它能夠進(jìn)行零樣本圖像分類。
CLIP架構(gòu)包含文本和圖像編碼器。它通過對比損失對齊文本和圖像特征,從而獲得多模態(tài)能力。因此,它在文本和圖像特征之間共享相同的特征空間,并且可以通過找到最相似的文本特征來實現(xiàn)零樣本圖像分類。
CLIP編碼器基于Transformer。因此,我們將使用圖像編碼器中類標(biāo)記的輸出,類似于ViT。
5. BLIP-2
BLIP-2[5]是由SalesForce在2023年開發(fā)的開源多模態(tài)模型。它屬于監(jiān)督學(xué)習(xí),基于Transformer架構(gòu)。它專注于利用預(yù)訓(xùn)練的大型模型(如FlanT5和CLIP)來實現(xiàn)高效的訓(xùn)練(因為在典型預(yù)算下從頭開始訓(xùn)練大型模型很困難)。由于預(yù)訓(xùn)練的大型語言和視覺模型的訓(xùn)練方式不同,作者引入了Q-Former來對齊預(yù)訓(xùn)練模型之間的特征空間。
BLIP-2包括兩個階段。第一階段訓(xùn)練Q-Former,以使用圖像-文本匹配、圖像-文本對比損失和基于圖像的文本生成等損失來對齊來自預(yù)訓(xùn)練圖像編碼器的文本特征和圖像特征。第二階段再次訓(xùn)練Q-Former,以將其特征空間與大型語言模型(如FlanT5)對齊。因此,Q-Former可以理解來自文本和圖像源的特征。
顧名思義,Q-Former架構(gòu)基于Transformer。我們將使用Q-Former的輸出作為特征提取層。
二、EfficientNet、ViT、DINO-v2、CLIP和BLIP-2在圖像相似性搜索中的嵌入比較
在本節(jié)中,我們將比較EfficientNet、ViT、DINO-v2、CLIP和BLIP-2在圖像相似性搜索中的結(jié)果。這些模型具有不同的架構(gòu)和訓(xùn)練損失。它們之間會有什么不同?讓我們從環(huán)境設(shè)置開始。
1. 環(huán)境設(shè)置
我使用了Python 3.10的conda環(huán)境。我在Ubuntu 20.04上進(jìn)行了實驗,使用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
準(zhǔn)備工作已經(jīng)完成!現(xiàn)在,讓我們實現(xiàn)代碼。我們將使用Faiss庫[7]來測量圖像相似性搜索中的圖像相似性。Faiss是一個基于近似最近鄰搜索算法的高效相似性搜索庫。此外,我們將使用Flickr30k數(shù)據(jù)集[6]進(jìn)行實驗。在直接進(jìn)入圖像相似性搜索之前,我們將探索如何從每個模型中提取嵌入(特征)。
2. 從每個模型中提取特征
在本實驗中,我將使用Huggingface的transformer庫來提取嵌入。與原始的Pytorch實現(xiàn)相比,我們可以輕松提取隱藏狀態(tài)。本節(jié)代碼檢查輸入和輸出維度,因此我們將在CPU上運(yùn)行它們。
(1) 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])
首先,我們需要準(zhǔn)備輸入。預(yù)定義的EfficientNet圖像處理器會自動將輸入形狀處理為(batch_size, 3, 600, 600)。經(jīng)過模型后,我們可以獲得帶有隱藏狀態(tài)的輸出。最后一個隱藏狀態(tài)的維度為(batch_size, 640, 19, 19),因此我們對獲得的嵌入應(yīng)用降維平均處理。
(2) 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])
同樣,預(yù)定義的ViT圖像處理器會自動將輸入形狀處理為(batch_size, 3, 224, 224)。最后一個隱藏狀態(tài)的維度為(batch_size, 197, 1024),我們只需要類標(biāo)記,因此提取第二個維度(197)的第一個索引。
(3) DINO-v2
DINO-v2基于ViT,因此基礎(chǔ)代碼幾乎相同。區(qū)別在于我們加載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])
基本上,我們使用相同的圖像處理器。預(yù)定義的ViT圖像處理器會自動將輸入形狀處理為(batch_size, 3, 224, 224)。最后一個隱藏狀態(tài)的維度為(batch_size, 197, 1024),我們只需要類標(biāo)記,因此提取第二個維度(197)的第一個索引。
(4) CLIP
CLIP也基于ViT,因此過程相同。Huggingface的transformers庫已經(jīng)為CLIP提供了特征提取方法,因此實現(xiàn)更加簡單。
# 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])
我們使用相同的圖像處理器。預(yù)定義的ViT圖像處理器會自動將輸入形狀處理為(batch_size, 3, 224, 224)。get_image_features方法可以提取給定圖像的嵌入,輸出維度為(batch_size, 512)。它與ViT和DINO-v2不同。
(5) 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)。
現(xiàn)在我們已經(jīng)了解了如何從每個模型中提取嵌入。接下來,讓我們檢查使用Faiss進(jìn)行圖像相似性搜索的實現(xiàn)。
3. 圖像相似性搜索
我們可以使用Faiss接口輕松實現(xiàn)圖像相似性搜索,只需幾行代碼。我們假設(shè)我們有一個名為features的變量。過程如下:
- 將輸入特征類型轉(zhuǎn)換為numpy.float32。
- 實例化Faiss向量存儲并為其注冊輸入特征。
- 通過調(diào)用search方法搜索向量。
我們可以選擇如何測量向量之間的距離,例如歐幾里得距離或余弦相似度。在本文中,我們使用余弦相似度。偽代碼如下所示。
# 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)
現(xiàn)在,比較圖像相似性搜索結(jié)果的所有先決條件已經(jīng)完成。讓我們從下一節(jié)開始檢查具體結(jié)果。
4. 圖像相似性搜索結(jié)果的比較
在本節(jié)中,我將比較使用五個模型進(jìn)行圖像相似性搜索的結(jié)果。對于數(shù)據(jù)集,我使用了從Flickr30k中隨機(jī)挑選的10k張圖像。我為每個模型實現(xiàn)了一個自定義管道,以實現(xiàn)批量特征提取。在本節(jié)末尾,我將附上我用于此實驗的筆記本。我選擇了以下圖像來比較結(jié)果。
從Flickr30k數(shù)據(jù)集中挑選的圖像
“3637013.jpg”的結(jié)果如下所示:
對“3637013.jpg”進(jìn)行的圖像相似性搜索
這個案例相對容易,因此所有模型都能挑選出語義相似的圖像?!?662865.jpg”的結(jié)果如下所示:
對“3662865.jpg”進(jìn)行的圖像相似性搜索
在這種情況下,DINO-v2和CLIP能夠捕捉到“鏟雪”的語義,但其他模型有時只能捕捉到“雪”。
“440375442.jpg”的結(jié)果如下所示:
對“440375442.jpg”進(jìn)行的圖像相似性搜索
EfficientNet和ViT可能將工作服誤解為手術(shù)服,因此它們無法捕捉目標(biāo)圖像的語義。DINO-v2能夠理解“垃圾和穿工作服的人”的語義,CLIP專注于穿工作服的人,而BLIP2專注于垃圾。我認(rèn)為DINO-v2、CLIP和BLIP2能夠捕捉語義。
“1377428277.jpg”的結(jié)果如下所示:
對“1377428277.jpg”進(jìn)行的圖像相似性搜索
這張圖像的語義是:“街上有很多人正在享受某個節(jié)日或街頭表演?!盓fficientNet和ViT專注于雨傘,因此它們無法捕捉語義。另一方面,DINO專注于嬰兒車,表現(xiàn)稍遜一籌。CLIP試圖捕捉節(jié)日和街頭的部分,但也稍遜一籌。BLIP2能夠捕捉街頭表演和嬰兒車。
“57193495.jpg”的結(jié)果如下所示:
對“57193495.jpg”進(jìn)行的圖像相似性搜索
在這種情況下,EfficientNet、ViT和CLIP有時能夠捕捉到“穿著戲服并涂白臉的女人”的語義。然而,它們相對不足。相比之下,DINO-v2和BLIP2能夠捕捉到服裝或角色扮演的語義。
最后一張圖像“1393947190.jpg”的搜索結(jié)果如下所示:
對“1393947190.jpg”進(jìn)行的圖像相似性搜索
結(jié)果因架構(gòu)(CNN和Transformer)而異。雖然EfficientNet可能專注于圖像的白色和棕色,但其他模型能夠捕捉到“正在卷絲的人”的語義。CLIP可能專注于傳統(tǒng)手工藝品,但其他模型能夠捕捉語義。
總結(jié)一下,我們有以下觀察結(jié)果:
- EfficientNet(CNN架構(gòu))不擅長捕捉超出像素信息的語義。
- Vision Transformer比CNN更好,但仍然更關(guān)注像素信息而不是圖像的含義。
- DINO-v2能夠捕捉圖像的語義,并且傾向于關(guān)注前景物體。
- CLIP能夠捕捉語義,但有時可能會受到圖像中可讀的語言信息的強(qiáng)烈影響。
- BLIP2能夠捕捉語義,是其他模型中表現(xiàn)最好的。
我認(rèn)為,為了獲得更好的圖像相似性搜索結(jié)果,我們基本上應(yīng)該使用DINO-v2或BLIP2。至于使用上的區(qū)別,當(dāng)我們專注于圖像中的物體時,應(yīng)該使用DINO-v2。而當(dāng)我們專注于超出像素信息的語義(如情境)時,應(yīng)該使用BLIP2。
完整代碼:https://gist.github.com/tanukon/00d689478ee3f7d2abd0366f1352cf9d#file-embedding_comparison-ipynb