从0到1手写向量索引(四):关键技术

73 阅读7分钟

1. 向量的特点

  • 一组向量具有相同的格式,但是可以表示非结构化长度格式不一的数据,例如图片,声音,视频,行为,甚至可以表示一个用户,这些数据如何转换为向量呢,需要通过一些特定的算法,这个后面再介绍,我们假定已经有了一些向量。如果不关心具体含义,甚至可以用一些随机数表示一个任意向量。它可以表示为多维空间中的一个点
  • 向量检索具有泛化性。“泛”,类似编程语言里的泛型,使得代码灵活可扩展。这里的泛化体现在三个方面
    • 普适性:训练过程中所学到的特征和模式,能够应用于不同分布或不同类型的数据,而不仅限于训练数据,这意味着在面对从未见过的数据,甚至是新产生的数据,都能够按照既定的模式提取有效信息并正确表示。传统的倒排索引不能做到这一点,它的比对方法是有限的,只能比较是不是,或者范围查找是否在某个范围内,对于新格式的数据束手无策。
    • 鲁棒性:含有噪声或部分不完整的信息时,依然能够进行有效的向量检索。例如,当输入数据缺失某些特征或包含一些意料之外的干扰信息时,检索系统仍然能找到与查询向量相似的结果。
    • 适应性:向量的生成模式是可以改变的,也就是对应的生成算法可以进化,能够适应变化的环境或需求,在不同的场景中保持检索性能的一致性。
  • 检索的方式多样。向量本身是灵活的,检索方式也是灵活的,从“是不是”进化到“像不像”,甚至可以做到对查找到纠偏。例如询问大模型一些问题时,有时候对问题的描述是错误的,它会回答猜你想问的是另一个问题,是因为有一个和当前问题很像的问题被问过多次了。

2. 检索算法概述

  • 向量的精准检索,就是从向量库中找出与目标向量最接近的k个向量,即k近邻算法(KNN,全称K Nearest Neighbors)。通常的实现是暴力计算,依次计算目标向量与向量库中所有向量的距离,排序后取top k个向量。显然,每次查询所有向量都要参与计算,其时间复杂度达到了O(n) + O(logn),对于具有数千万甚至更多向量库是不可接受的,需要速度更快的算法。
  • 精准检索的计算复杂度不可避免,为了使得向量检索在具备可用的性能,需要适当牺牲部分准确度。 提升检索性能有几个方向:
    • 减少计算单个向量与目标向量距离的计算量
    • 减少需要计算向量对个数,即减少检索时的候选向量集合大小
    • 分批并行计算 压缩单个向量的维度可以减少单个向量的计算量,提前对整个向量集合按照一定的规则划分为几个子空间,在子空间中检索,可以减少候选集合大小,并且多个子空间可以并行检索,于是有了ANN搜索算法(Approximate Nearest Neighbor)。

3. ANN算法简析

ANN 的基本原理

  • 使用索引和高级数据结构:例如 KD 树、局部敏感哈希或基于图形的方法为数据增加索引,加速检索。
  • 距离计算:不是计算与每个向量的确切距离,而是提前将整个向量库分成多个区域,试探法快速确定可能包含最近邻域的区域,然后进一步检索
  • 查找邻域:确定一组可能接近查询点的数据点,邻域不能保证是确切的最近点,对于一般的需求达到既定的准确度就足够,可以按需平衡检索速度与准确度

ANN的方法分为四大类

1. 基于树的方法

如果将精确检索看作是从数组中找出目标值,基于树的方法类似从C++的std::map(红黑树)中查找。经典的实现如KD树,构建过程就是迭代二分空间,每次找出方差最大的维度,计算中位数点作为划分点,分为左右子树,然后递归执行上面的过程,直到每个子空间的向量个数小于设定的阈值。按照方差最大的维度划分,使得每次划分的数据分布更加均匀,平衡左右子树的结构,降低树的高度,从而提高查询效率。

image.png

经典KD树在大部分情况下表现良好,就像快速排序面对特定数据可能退化到 O(n2)O(n^2) 的时间复杂度一样,经典KD树也可能面对特定的数据也可能出现最坏情况,可以考虑使用随机KD树算法,构造出多个KD树取其中最好的,或者同时使用多个随机KD树

spotify的开源库annoy中使用了另一种树方法: 在构建树时用选取的两个质心的法平面对空间切分,最终将子空间的向量个数控制在K以内,对于待插入样本,从根节点ROOT使用法向量与X做内积运算判断在左子树还是右子树。构建多个树提高查询召回率

image.png (图片来自annoy

2. 哈希方法

如果基于树的方法类似从C++的std::map(红黑树)中查找,那么哈希方法则类似于从C++的std::unordered_map(哈希)中查找。先是根据特定的哈希函数给每个向量算出哈希值,期望是距离相近的向量具有相同的哈希值,构建出一个hash table,此时的重点就在于如何选取哈希函数。

一个经典的方法是LSH(local sensitive hash,局部敏感哈希)。由于向量具备一个特性,原始数据空间中的两个相邻数据点通过相同的映射或投影变换(projection)后,这两个数据点在新的数据空间中仍然相邻的概率很大,而不相邻的数据点被映射到同一个桶的概率很小,可以看作是将一个高维度的精确向量分段归一化成了一个低维度的模糊向量,相似的精确向量可以转换成同一个模糊向量,我们期望hash函数可以将距离相近的向量映射到同一个桶里,可以使用这个方法

image.png

3. 矢量量化方法

对应减少计算单个向量与目标向量距离的计算量,具体的做法顾名思义,就是把精确值转换成一个计算量更小更快的模糊值,例如double转float,float转integer。 经典实现如乘积量化(Product Quantization),标量量化(Scalar Quantization)。

4. 基于图的方法

图方法的思想是,需要找 top k 个和目标向量距离最近的向量作为结果输出,那么检索结果的向量之间在整个向量库中也是距离最近的,又因为向量库是已知的,可以提前计算好向量库中每个向量之间的距离保存起来,在检索时无需重复计算,只需要找到和目标向量距离最近的一个向量,然后找到和这个向量最近的 k - 1 个向量,省去了大量的向量距离计算量。

找到和目标向量距离最近的一个向量,是找到top k个向量的特殊情况,在距离是内积时,这类特殊问题称作最大内积搜索。

image.png 将向量库中所有向量之间的距离提前算好,用一条边连接起来,边的长度表示距离,就构成了图。