以图搜图到底搜了什么?用可视化打开embedding与近似最近邻搜索~

2,512 阅读11分钟

Fig.1 - 以图搜图示例

以图搜图,也叫反向图像搜索,输入一张图片,从数据库中返回最相似的几张图片。 在信息社会高速发展的今天,图片、视频等非结构化数据逐渐占据主流,这种新的信息搜索方式应运而生。 用户有时惊叹图片何其相似,有时吐槽结果离谱至极。 那么这个结果是怎么来的呢?

计算机如何理解“相似” - 嵌入向量(embedding)

众所周知,视觉感知是人类的独特天赋,而计算机存储的图片不过是一串数字代码,那么计算机要如何判断图片的“相似性”呢?直接对比位图的每个像素点显然不是那么有效。 那我们可不可以预先对图片进行打标分类,把相似图片的搜索问题转化为标签的搜索和匹配问题呢? 比如识别到输入的目标是一只橘猫,那么就在数据库中返回所有带有橘猫标签的图片。 这是可行的,早期一些应用也是这么做的。 但是我们很容易发现其中的问题——搜索结果的准确性高度依赖于标签的精细程度。然而为每张图片打上足够标签的成本实在是太高了,尤其是在数据爆炸的今天。

那有没有什么有效的方法,能在没有标签的情况下,比较两张图片的相似度呢? 现在流行的嵌入向量(embedding)方法很好的解决了这个问题。

那什么是嵌入向量呢? 举个例子,一个苹果,在我们脑海中,可以抽象为[红色,球形,甜,芳香,长在树上, ...]等多个特征,任何与这些特征相似(不一定完全相同)的我们都可以叫做苹果。 如果设置一套规则,用数字来对应这些特征概念,即可获取其向量表达,例如将其编码为[1.324, 2.3434, 4.7619, ...]。而转化成数字以后进行计算恰恰是计算机的拿手绝活。

只要存在某种确定的映射规则,将图片尽可能多的特征提取出来,并将这些特征以数字的形式展示出来,计算机就可以利用它强大的计算能力去获取这些图片向量间的距离,比如欧氏距离,余弦距离等。

这种确定的映射规则就是训练好的机器学习模型,提取尽可能多的特征就是模型在学习和训练过程中获取的能力,以数字向量的形式展示指的是图片特征嵌入到向量空间来获取数字表示。计算机最后通过计算向量间的距离判断图片之间的相似性

你也许会问,同一张图片,不同模型得到的嵌入向量是一样的吗?答案是否定的。就像不同的人看同一张图片也会有不同的感知一样。 但只要是一个好的模型,那么红苹果与青苹果的嵌入向量之间的距离一定比和一只猫要近得多。单个嵌入向量的绝对数值是不具有实际意义的,而这种相对距离才是机器学习模型对图片的“理解”。

目前学术界提出了非常多的机器学习方法,公开了很多已经预训练好的模型。这里推荐一个可以一行代码获取所有图片嵌入向量的便捷工具towhee,内部集成了多个前沿的模型。 在本篇博客中,涉及到的案例所使用的数据都是我们使用towhee获取的。

import numpy as np
import towhee

dc = (towhee.glob('train/*/*.JPEG')
          .image_decode()
          .image_embedding.timm(model_name='resnet50')
          .to_list()
     )
vectors = np.array(dc, dtype="float32")

如何高效地搜索距离最近的向量 - 近似最近邻搜索

现在我们已经知道计算机是如何计算图片相似度了,回过头来看以图搜图的整个流程:

  • Step 0. 预处理:图片全部编码为向量储存
  • Step 1. 获取目标图片的嵌入向量
  • Step 2. 在向量数据库中找到距离最近的向量
  • Step 3. 根据搜索的结果,返回对应id的图片

pipeline.png

Fig.2 - 以图搜图的流程

看起来一切似乎已经水到渠成。但是作为一个程序员,得出正确结果只是第一步,高效地执行也相当重要。step.0虽然看起来非常耗时,但好在只需要初始化一次。step.1只需要处理一张目标图片,step.3只需要根据id去找对应的结果,值得关注的是step.2,如何找到距离最近的向量呢?这个问题的时间复杂度是多少呢?

最直接的方式当然是暴力遍历一遍,但需要注意的是,每次计算两张图片相似度的计算量(比如欧式距离)都与向量的维度直接相关,而embedding维度的数量常常为512,1024,甚至更多,这意味单次计算量已经比较大了,如果在数据量很大的情况下,继续遍历整个数据库的话,单次查询的负载将会非常高。

接下来的问题是,要如何提高效率呢?可以尝试换个思路,既然单次向量对比的计算量很大,那么有没有可能减少向量对比的次数呢? 比如kd-tree就是一个非常流行的最近邻查找算法,感兴趣的同学可以自行了解。我们今天将介绍另外一种思路,在计算机领域,对时空复杂度很高的算法,常常会用近似来平衡准确率和效率。通过牺牲一些精度换取效率的巨大提升是一笔非常划算的买卖。

今天我们将介绍最为经典的一种近似最近邻搜索算法 - IVF_Flat。熟悉数据库的同学肯定知道IVF即倒排索引。通过对标量数据构建索引来加速搜索是关系型数据库的核心功能,那么,对向量数据而言,要如何构建索引呢? 让我们借助可视化工具Feder,来打开近似最近邻搜索的新世界。

索引构建

我们使用towhee获取VOC_2012数据集全部17,000+图片的嵌入向量作为数据集,使用开源索引库faiss构建了一个ivf_flat索引,设置索引构建参数nlist=256(下面我们会解释nlist的作用)。

import faiss

num_elements, dim = vectors.shape
nlist = 256

index = faiss.index_factory(dim, 'IVF%s,Flat' % nlist)
index.train(vectors)
index.add(vectors)

faiss.write_index(index, 'faiss_ivf_flat_voc.index')

ivf_flat索引在构建时,会将所有向量根据k-means分为256(nlist)个聚类。k-means是一种无监督的聚类方式,可以让距离相近的点尽可能分属于同一个聚类。其中每个聚类都有一个几何中心,任意向量都将分属于距离最近的中心所在的聚类。这种分类方式将整个向量空间划分成了256份。

ivf_flat_overview.png

Fig.3 - ivf_flat在构建索引时,将所有向量进行聚类。

粗略查询

在查询过程中,ivf_flat利用已经建立好的索引,先后执行了两个步骤:粗略查询与精细查询,可以设置的查询参数有两个:nprobek

首先,在粗略查询过程中,将目标向量与全部256(nlist=256)个聚类的中心向量对比,找出最近的8个中心向量(设置参数nprobe=8)。ivf_flat在这里做了一个近似,认为最终需要的top-k结果都存在于这8个聚类之中,于是在精细查询中将不再搜索这其余的聚类。

ivf_flat_coarse.png

Fig.4 - ivf_flat粗略查询

精细查询

接下来ivf_flat将会计算目标向量和这8(nprobe=8)个聚类内所有向量的距离,并从中找出距离目标向量最近的9(设置参数为k=9)个向量作为最终结果。

1.gif

Fig.5 - ivf_flat精细查询

近似最近邻效率分析

遍历整个数据库,这至少需要计算17,000次,而在使用了ivf_flat之后(当前参数设置下),只计算了1,000次左右(粗略查询256次 + 精细查询800次左右),这大大加速了搜索结果的获取。

通过调整nprobe,可以平衡召回率和搜索效率。增大nprobe,可以搜索更多的向量,更大的容错空间带来更精确的结果,减小nprobe,可以减少搜索的范围,缩短计算时间已获取更加高效的查询。

重新审视以图搜图

在了解了近似最近邻算法后,我们回过头来看整个以图搜图的流程(Fig. 2),可以发现,最终搜索结果的好坏取决于两个点:

  1. step.0与step.1 - 机器学习模型是否能正确提取图片的特征?提取了多少特征?嵌入后的向量空间保留多少原始空间的信息?
  2. step.2 - 由于近似最近邻搜索本身算法上不够精确,是不是实际有更近的向量但是索引没有搜到?调整索引的参数能不能改善搜索结果?这些索引参数到底是如何影响搜索过程的?

这两个问题都非常不简单。第二个问题需要用户对近似最近邻算法相当熟悉,才能了解各个参数实际发挥的作用。第一个问题就更难了,机器学习模型的可解释性是学术界一直在试图攻克的难题。

目前评价这两个过程,主要是通过准备测试集来从宏观上计算不同模型/不同近似最近邻算法的准确率和召回率。

有了Feder这个可视化工具之后,我们尝试从单次查询出发,从微观上来深入观察这个过程。 我们以Fig. 1的第三个case为例。

image.png

从视觉感知出发,我们可以这样描述这张图片:晴空万里,一个人骑着,在场馆草地上,正腾飞在空中,跨越栏杆。那模型又是如何理解这张图片呢?在粗略查询中,找到了距离目标最近的几个cluster。Feder支持用户交互地查看cluster的信息,同时从中随机挑选9张图片作为代表,帮助我们更好地观察和理解。

骑马跳_demo_clusters.png

Fig.6 - 案例分析:ivf_flat粗略查询的搜索结果。

我们可以发现,cluster-54(右上)中的图片大多描绘了人的跳跃过程,腾飞在空中,而cluster-103(右下)更多是以为主题,而cluster-217与113咋眼看上去与目标图片相去甚远,实际上暗合了目标图片的栏杆场馆建筑特征。

骑马跳_demo_900.gif

Fig.7 - 案例分析:ivf_flat精细查询的搜索结果。

在精细查询过程中,我们可以从聚类的层次进一步深入到具体的向量粒度进行观察,去对比和思考嵌入后的信息空间与真实感知的视觉空间,你也许会疑惑为什么图片A比图片B更近?为什么最符合视觉的图片C反而被错过?

现在或许我们还不能很好地回答这些疑问,但恰恰是这些“为什么”在带领着我们去进一步去认识模型,优化模型。神经网络绝不只是冷冰冰的参数堆砌的多层结构,也许有一天,研究者们能够训练出一个和人类感知一模一样的模型,从而反观人脑的认知神经,揭开更多我们大脑的奥秘。

探索永不停歇

如果看到这里你还意犹未尽,想亲自上手体验,我们为你准备了一个可交互的以图搜图分析网页,你可以自由地挑选感兴趣的图片进行搜索,并结合可视化工具Feder去观察模型和近似最近邻的搜索过程。欢迎向大家分享你的所思所感~

如果你是一个进阶的模型训练家,有着自己的图片集和向量数据,欢迎使用我们的开源可视化工具Feder,包括了JavaScript和Python两个版本。我们在github上为你提供了详细的教程,如果能收到你宝贵的star,对我们将是莫大的鼓励~

如果你对近似最近邻算法很感兴趣,那么请保持关注,我们之后将介绍另一种高效的,但与ivf_flat思路完全不同的索引算法-HNSW~

如果你对算法可视化略有研究,想了解Feder的布局策略以及技术实现,也请保持关注,我们之后将详细介绍ivf_flat的可视化设计,到时欢迎大家一起探讨~