共计 6014 个字符,预计需要花费 16 分钟才能阅读完成。
基于 HuggingFace Datasets 和 Transformers 的图像相似性搜寻
通过本文,你将学习应用 🤗 Transformers 构建图像相似性搜寻零碎。找出查问图像和潜在候选图像之间的相似性是信息检索零碎的一个重要用例,例如反向图像搜寻 (即找出查问图像的原图)。此类零碎试图解答的问题是,给定一个 查问 图像和一组 候选 图像,找出候选图像中哪些图像与查问图像最类似。
咱们将应用 🤗 datasets
库,因为它无缝反对并行处理,这在构建零碎时会派上用场。
只管这篇文章应用了基于 ViT 的模型 (nateraw/vit-base-beans
) 和特定的 (Beans) 数据集,但它能够扩大到其余反对视觉模态的模型,也能够扩大到其余图像数据集。你能够尝试的一些驰名模型有:
- Swin Transformer
- ConvNeXT
- RegNet
此外,文章中介绍的办法也有可能扩大到其余模态。
要钻研残缺的图像类似度零碎,你能够参考 这个 Colab Notebook。
咱们如何定义相似性?
要构建这个零碎,咱们首先须要定义咱们想要如何计算两个图像之间的类似度。一种宽泛风行的做法是先计算给定图像的浓密表征 (即嵌入 (embedding)),而后应用 余弦相似性度量 (cosine similarity metric) 来确定两幅图像的类似水平。
在本文中,咱们将应用“嵌入”来示意向量空间中的图像。它为咱们提供了一种将图像从高维像素空间 (例如 224 × 224 × 3) 有意义地压缩到一个低得多的维度 (例如 768) 的好办法。这样做的次要长处是缩小了后续步骤中的计算工夫。
计算嵌入
为了计算图像的嵌入,咱们须要应用一个视觉模型,该模型晓得如何在向量空间中示意输出图像。这种类型的模型通常也称为图像编码器 (image encoder)。
咱们利用 AutoModel
类来加载模型。它为咱们提供了一个接口,能够从 HuggingFace Hub 加载任何兼容的模型 checkpoint。除了模型,咱们还会加载与模型关联的处理器 (processor) 以进行数据预处理。
from transformers import AutoFeatureExtractor, AutoModel
model_ckpt = "nateraw/vit-base-beans"
extractor = AutoFeatureExtractor.from_pretrained (model_ckpt)
model = AutoModel.from_pretrained (model_ckpt)
本例中应用的 checkpoint 是一个在 beans
数据集 上微调过的 ViT 模型。
这里可能你会问一些问题:
Q1: 为什么咱们不应用 AutoModelForImageClassification
?
这是因为咱们想要取得图像的浓密表征,而 AutoModelForImageClassification
只能输入离散类别。
Q2: 为什么应用这个特定的 checkpoint?
如前所述,咱们应用特定的数据集来构建零碎。因而,与其应用通用模型 (例如 在 ImageNet-1k 数据集上训练的模型),不如应用应用已针对所用数据集微调过的模型。这样,模型能更好地了解输出图像。
留神 你还能够应用通过自监督预训练取得的 checkpoint, 不用得由有监督学习训练而得。事实上,如果预训练切当,自监督模型能够 取得 令人印象粗浅的检索性能。
当初咱们有了一个用于计算嵌入的模型,咱们须要一些候选图像来被查问。
加载候选图像数据集
前面,咱们会构建将候选图像映射到哈希值的哈希表。在查问时,咱们会应用到这些哈希表,具体探讨的探讨稍后进行。当初,咱们先应用 beans
数据集 中的训练集来获取一组候选图像。
from datasets import load_dataset
dataset = load_dataset ("beans")
以下展现了训练集中的一个样本:
该数据集的三个 features
如下:
dataset ["train"].features
>>> {'image_file_path': Value (dtype='string', id=None),
'image': Image (decode=True, id=None),
'labels': ClassLabel (names=['angular_leaf_spot', 'bean_rust', 'healthy'], id=None)}
为了使图像相似性零碎可演示,零碎的总体运行工夫须要比拟短,因而咱们这里只应用候选图像数据集中的 100 张图像。
num_samples = 100
seed = 42
candidate_subset = dataset ["train"].shuffle (seed=seed).select (range (num_samples))
寻找类似图片的过程
下图展现了获取类似图像的根本过程。
略微拆解一下上图,咱们分为 4 步走:
- 从候选图像 (
candidate_subset
) 中提取嵌入,将它们存储在一个矩阵中。 - 获取查问图像并提取其嵌入。
- 遍历嵌入矩阵 (步骤 1 中失去的) 并计算查问嵌入和以后候选嵌入之间的类似度得分。咱们通常保护一个相似字典的映射,来保护候选图像的 ID 与相似性分数之间的对应关系。
- 依据类似度得分进行排序并返回相应的图像 ID。最初,应用这些 ID 来获取候选图像。
咱们能够编写一个简略的工具函数用于计算嵌入并应用 map()
办法将其作用于候选图像数据集的每张图像,以无效地计算嵌入。
import torch
def extract_embeddings (model: torch.nn.Module):
"""Utility to compute embeddings."""
device = model.device
def pp (batch):
images = batch ["image"]
# `transformation_chain` is a compostion of preprocessing
# transformations we apply to the input images to prepare them
# for the model. For more details, check out the accompanying Colab Notebook.
image_batch_transformed = torch.stack ([transformation_chain (image) for image in images]
)
new_batch = {"pixel_values": image_batch_transformed.to (device)}
with torch.no_grad ():
embeddings = model (**new_batch).last_hidden_state [:, 0].cpu ()
return {"embeddings": embeddings}
return pp
咱们能够像这样映射 extract_embeddings()
:
device = "cuda" if torch.cuda.is_available () else "cpu"
extract_fn = extract_embeddings (model.to (device))
candidate_subset_emb = candidate_subset.map (extract_fn, batched=True, batch_size=batch_size)
接下来,为不便起见,咱们创立一个候选图像 ID 的列表。
candidate_ids = []
for id in tqdm (range (len (candidate_subset_emb))):
label = candidate_subset_emb [id]["labels"]
# Create a unique indentifier.
entry = str (id) + "_" + str (label)
candidate_ids.append (entry)
咱们用蕴含所有候选图像的嵌入矩阵来计算与查问图像的类似度分数。咱们之前曾经计算了候选图像嵌入,在这里咱们只是将它们集中到一个矩阵中。
all_candidate_embeddings = np.array (candidate_subset_emb ["embeddings"])
all_candidate_embeddings = torch.from_numpy (all_candidate_embeddings)
咱们将应用 余弦类似度 来计算两个嵌入向量之间的类似度分数。而后,咱们用它来获取给定查问图像的类似候选图像。
def compute_scores (emb_one, emb_two):
"""Computes cosine similarity between two vectors."""
scores = torch.nn.functional.cosine_similarity (emb_one, emb_two)
return scores.numpy ().tolist ()
def fetch_similar (image, top_k=5):
"""Fetches the`top_k`similar images with`image`as the query."""
# Prepare the input query image for embedding computation.
image_transformed = transformation_chain (image).unsqueeze (0)
new_batch = {"pixel_values": image_transformed.to (device)}
# Comute the embedding.
with torch.no_grad ():
query_embeddings = model (**new_batch).last_hidden_state [:, 0].cpu ()
# Compute similarity scores with all the candidate images at one go.
# We also create a mapping between the candidate image identifiers
# and their similarity scores with the query image.
sim_scores = compute_scores (all_candidate_embeddings, query_embeddings)
similarity_mapping = dict (zip (candidate_ids, sim_scores))
# Sort the mapping dictionary and return `top_k` candidates.
similarity_mapping_sorted = dict (sorted (similarity_mapping.items (), key=lambda x: x [1], reverse=True)
)
id_entries = list (similarity_mapping_sorted.keys ())[:top_k]
ids = list (map (lambda x: int (x.split ("_")[0]), id_entries))
labels = list (map (lambda x: int (x.split ("_")[-1]), id_entries))
return ids, labels
执行查问
通过以上筹备,咱们能够进行相似性搜寻了。咱们从 beans
数据集的测试集中选取一张查问图像来搜寻:
test_idx = np.random.choice (len (dataset ["test"]))
test_sample = dataset ["test"][test_idx]["image"]
test_label = dataset ["test"][test_idx]["labels"]
sim_ids, sim_labels = fetch_similar (test_sample)
print (f"Query label: {test_label}")
print (f"Top 5 candidate labels: {sim_labels}")
后果为:
Query label: 0
Top 5 candidate labels: [0, 0, 0, 0, 0]
看起来咱们的零碎失去了一组正确的类似图像。将后果可视化,如下:
进一步扩大与论断
当初,咱们有了一个可用的图像类似度零碎。但理论零碎须要解决比这多得多的候选图像。思考到这一点,咱们目前的程序有不少毛病:
- 如果咱们按原样存储嵌入,内存需要会迅速减少,尤其是在解决数百万张候选图像时。在咱们的例子中嵌入是 768 维,这即便对大规模零碎而言可能也是绝对比拟高的维度。
- 高维的嵌入对检索局部波及的后续计算有间接影响。
如果咱们能以某种形式升高嵌入的维度而不影响它们的意义,咱们依然能够在速度和检索品质之间保持良好的折衷。本文 附带的 Colab Notebook 实现并演示了如何通过随机投影 (random projection) 和地位敏感哈希 (locality-sensitive hashing,LSH) 这两种办法来获得折衷。
🤗 Datasets 提供与 FAISS 的间接集成,进一步简化了构建相似性零碎的过程。假如你曾经提取了候选图像的嵌入 (beans
数据集) 并把他们存储在称为 embedding
的 feature
中。你当初能够轻松地应用 dataset
的 add_faiss_index()
办法来构建浓密索引:
dataset_with_embeddings.add_faiss_index (column="embeddings")
建设索引后,能够应用 dataset_with_embeddings
模块的 get_nearest_examples()
办法为给定查问嵌入检索最近邻:
scores, retrieved_examples = dataset_with_embeddings.get_nearest_examples ("embeddings", qi_embedding, k=top_k)
该办法返回检索分数及其对应的图像。要理解更多信息,你能够查看 官网文档 和 这个 notebook。
在本文中,咱们疾速入门并构建了一个图像类似度零碎。如果你感觉这篇文章很乏味,咱们强烈建议你基于咱们探讨的概念持续构建你的零碎,这样你就能够更加相熟外部工作原理。
还想理解更多吗?以下是一些可能对你有用的其余资源:
- Faiss: 高效相似性搜寻库
- ScaNN: 高效向量相似性搜寻
- 在挪动应用程序中集成图像搜索引擎
英文原文: https://hf.co/blog/image-simi…
译者: Matrix Yao (姚伟峰),英特尔深度学习工程师,工作方向为 transformer-family 模型在各模态数据上的利用及大规模模型的训练推理。
审校、排版: zhongdongy (阿东)