这篇文章发表在2015年的ICCV会议上,详细文章以及源码见作者主页PQTable。《
PQTable: Non-exhaustive Fast Search for Product-quantized Codes using Hash Tables
》首先介绍一下最近邻检索,最近邻检索就是寻找与给定的点最近的点。由于现在的大数据量与高维度,最近邻检索从最初的蛮力发展到基于树结构,基于哈希,到目前的采用空间量化的方法。
对于像这个有10亿个,每个都是128维的数据集,如果采用矢量量化的方法要将这10亿个数据聚类产生200万个码字,无论是聚类训练产生这么大的码本的内存占用,还是进行查找时的距离计算都是特别复杂的,而乘积量化采用空间分解的办法解决了这个问题,在这个例子中,128维的空间被划分成4个部分,每个子空间都是32维的,如果我们在每个子空间聚类生成有100个码字的码本,那么做笛卡尔积就相当于产生了拥有一亿个码字的码本,这就直接解决了之前的内存占用问题,另外对32维的数据的距离计算比128维的计算也简单了很多。通过这样的方法,我们可以将原本数据库中128维的数据根据它们在每个子空间的所属类别构建索引,在查询的时候,将查询向量也划分为4个部分,在每个部分寻找离它最近的码字,将4个码字进行组合就能找到相应的索引。
上面是基于乘积量化的一些检索结构,第一个采用线性扫描的办法,查询向量与数据库中所有的向量进行计算比较,这种办法非常低效;第二个是基于倒排索引的检索结构,它是采用分桶的思想,将数据库中所有向量进行简单的粗糙聚类划分到多个桶中,在查询的时候,先找到查询向量所属的桶,再在桶中寻找与它最近的码,由于划分桶的粗糙性,所以通常查询向量会被分配到多个桶中进行检索,这种方法是采用牺牲空间和时间的办法来提高准确率,为了找到平衡点需要大量的训练选取最优的参数(粗糙类数和搜索范围);第三种是本文提出的乘积量化table,它将原本进行PQ量化后的码进行哈希,并连同它们的标识一起存储到哈希表中,在查询的时候只需将查询向量也进行PQ编码,再通过哈希查表的操作就能找到与它最近的向量。
上述PQTable存在一定的问题,第一个就是空项问题:
将查询向量编码后进行哈希有可能在哈希表中找不到对应的项,比如将这个查询向量进行PQ编码后为{13,192,3,43},而这个码在哈希表中是找不到对应的项,因此通过一个key生成器不断生成距离查询向量从小到大的候选码,在这个例子中到生成的第2个候选码进行哈希仍然没有在哈希表中找到项,一直到第8个候选码{76,160,202,23}经过哈希在表中找到了项87,所以数据库中87号标识向量就是与查询向量最近的元素。
第二个是长码问题:
如果我们划分8个子空间,在每个子空间生成256个码字,就相当于使用64位的长码来编码每一个向量,这样的话会使得哈希表的长度非常长,约等于1.8×10^19,而这个数远大于数据库中向量的个数10^9,所以这就使得编码后哈希表变得特别的稀疏。解决的办法就是将哈希表划分成T个小表,这里的T为2,在构建哈希表时,比如对一个标识为65的PQ码,可以将它的前两个子码哈希进第一个表,并标识为65,同样将它的后两个子码哈希进第二个表,也标识为65,对数据库中的所有向量都做这样的操作。在查询阶段,查询向量被划分为两个部分,在每个部分执行与单个哈希表相同的操作,编码,哈希,寻找近邻元素。每个表生成的结果都按照它们与查询向量的距离从小到大排序,589就是对于第一个表查询的最近元素,24就是对于第二个表查询的最近元素。在合并的时候选取某个标识出现T次的位置,作者证明所有距离查询向量比标识456小的都已经包含在这个灰色区域,然后计算全维的查询向量与这些灰色标识的距离,将小于等于456对应距离的保留,再将它们进行排序生成查询向量的K最近邻,如果当前不足K个,继续按照相同的操作向后查找直到找够K个。
当然有一点非常重要,要将原表划分为T个小表,这个T取多少才合适。T取太大会导致合并阶段的效率特别低下,T取太小又没有有效地解决长码问题。本文中作者在前人多表索引的基础上进行实验归纳,得出T的取值为![](https://i-blog.csdnimg.cn/blog_migrate/9562fedfc6086ae08fc3bd01b8ca7053.png)