AI基于近邻图的向量搜索(一)

案例介绍

最近邻问题

SPTAG解决的问题是如何从大规模的向量中快速找出近似最近邻点(Approximate Nearest Neighbor),即实现最近邻搜索(Nearest Neighbor Search)

我们可以将问题定义为:

其中,q为查询向量,x为样本向量,我们可以计算它们的L2或余弦距离,获得两者距离最近的样本。

该问题最早是在1973年由高德纳在《计算机程序设计艺术》中提到,并称之为邮局问题,即居民寻找离自己家最近的邮局。

要实现这个目标,有以下几种常见的最近邻搜索算法:

  1. 线性查找

    线性查找通过遍历整个点集,计算它们和目标点之间的距离,从而找出距离最小的样本。该方法的时间复杂度为O(nd),n为样本数,d为样本的维度数,当数据量达到一定规模后,该方法的搜索时间将会变得难以接受。为了解决该问题,又有人提出了利用空间划分树来提高搜索效率。

  2. 空间划分树

    空间划分树常用的是KD-Tree(于1970s提出)。其算法是通过递归地随机选取或根据某种算法选取K维作为结点划分依据,将样本划分成左子树和右子树,最终生成一棵二叉树索引。这类方法的搜索效率通常对于低维度的数据效果比较好(如小于100维),但对于高维数据效果较差。而图片的向量表示通常能够达到1000维甚至更多。

由于KDTree在高维向量空间上的搜索效率不高,于是又有人提出了 近似最近邻搜索(Approximate Nearest Neighbor, ANN) 的算法,即不精确查找,而是利用算法查找出符合要求的近似最近邻。

当前有以下几种近似最近邻搜索算法:

  1. 基于哈希的最近邻搜索

    利用哈希算法(如2010年提出的LSH),在尽可能保留距离关系的情况下,将样本映射到不同的哈希桶(Bucket)中,这时只需比较同一哈希桶中的点即可。但是该方法的查询性能与哈希函数及样本分布有关,样本可能会聚集在某些哈希桶中,导致对于不同点的查询时延差距较大,稳定性不佳。

  2. 近邻图

    通过使样本中所有点连接其近邻点,我们可以构建一张近邻图。再通过使用特定的算法(如2016年提出的HNSW),我们可以在搜索时快速找到与查询点相连的近邻点。但是,我们无法确保我们构建的近邻图是连通图,因此有可能会陷入局部最优。

使用场景

最近邻搜索的应用场景十分广泛,包括计算机视觉、模式识别、基于内容的图像或视频检索等。

在这其中,SPTAG的一个重要应用场景便是搜索引擎了。随着深度学习的发展,我们可以利用深度神经网络将绝大部分的内容(常见的有文本、图片、音频、视频等)映射到高维空间,获取到对应的向量。利用向量,我们便可以更轻易地找出相关联的内容。

在搜索引擎中的应用

在过去,传统的搜索引擎往往是通过"关键词"对内容进行检索,但这就要求了用户需要将问题抽象成几个"关键词",且需要精确描述问题。然而,随着网络信息的爆炸式增长和信息技术的高速发展,工程师们发现用户习惯已经悄然改变:用户搜索的内容越来越长,他们往往期望通过输入一段自然语言甚至一张图片来搜索出他们想要的结果,而不只是通过关键词。网络搜索任务也因此变得更加复杂和多样化了。

为此,我们需要思考如何才能将自然语言或者图片与数据库中的内容匹配,找出最相关的内容呢?为了让计算机认识自然语言、图片等内容,我们需要将其编码成计算机认识的形式,其中,最常见和有效的就是向量了。向量可以是各种形式的内容(如关键词、图片、语音等)在高维空间的表示。我们可以利用现有的算法(如深度神经网络)将不同形式的内容转化的向量,获取到对应的向量。向量在高维空间的表示是有意义的,我们认为关联性越强的内容在空间上的距离越小或相似度越大。

利用向量的表示,我们可以将数据库的内容全部转换成向量,再将用户的搜索输入用同样的算法转换成在同一个空间的向量。此时,搜索引擎的匹配问题就变成了最近邻问题(Nearest Neighbor)。我们的任务就是从大规模的向量中找出与输入向量最相近的一组向量。

对于大规模数据,Brute-Force或线性查找的时间复杂度是难以接受的。因此,我们需要合适的算法来提高搜索速度。传统的关键词检索可以通过构建倒排索引(Inverted Index)来提高检索速度,但是我们无法为向量构建这样的索引。对于向量,我们通常是利用树或图(如KD-Tree, HNSW)来构建索引,从而帮助我们在大量的向量中找出最相似的向量。当然,鱼与熊掌不可兼得,速度的提升往往是以牺牲精度为前提的。于是,问题最终变成了近似最近邻(Approximate Nearest Neighbor, ANN)问题

但无论是使用树还是图,它们都有各自的问题,例如,KD-Tree在面对高维度数据时效果显著下降,而使用近邻图则容易陷入局部最优。因此,微软提出了SPTAG,通过结合树和图,在弥补各自的不足的基础上,还做出了一定的优化。SPTAG解决的核心问题其实是上述的ANN问题,因此,我们还可以将SPTAG应用到许多不同的场景,如计算机视觉、模式识别等领域。

核心知识点

  • KD-Tree(K维树)
  • BKTree(平衡 K 均值树)
  • KNN 图(K 最近邻图)
  • RNG(相对邻域图)

先修知识

  • C++
  • 数据结构(包含二叉树、哈希算法、图论等基础知识)

推荐学习时长

该案例推荐学习时长为:3小时

案例详解

SPTAG架构

可见,上述提到的算法都有各自的问题,适用于不同的场景,而SPTAG的核心思路是将树和图结合,从而弥补各自的缺陷,使场景更为通用。

其架构如图:

SPTAG分为了Tree部分和Graph部分。Tree部分利用KD-Tree或BKTree实现,Graph部分使用了基于KNN图改进的RNG。在进行搜索时,SPTAG首先会从Tree部分获取“种子”向量,将该种子向量作为Graph中的起始点进一步搜索近邻点。

Tree部分

Tree部分SPTAG使用了KDTree和BKTree实现。在调用时,可以根据需求选择任意一种。KD-Tree适合低维度(如小于100维)的向量,反之,BKTree适合高维度的向量。

KD-树

KD-Tree的本质是每个结点都以某一维划分的二叉树,是二叉搜索树(BST)的拓展。二叉搜索树的每个结点可以视为一个划分,小于等于该结点的归入左子树,大于该结点的归入右子树。

那么,如何构建二叉搜索树呢?

给定一组数据:[8, 3, 6, 10, 4, 1, 7, 14, 13]。我们选取数据中第一位作为划分结点,小于等于该值的归入左子树,大于该值的归入右子树,以此规则递归生成一棵二叉搜索树。

  1. 选取8作为划分结点,可以得到左子树l:[3, 6, 4, 1, 7],右子树r:[10, 14, 13]。

  2. 进一步划分左子树l,选3作为划分结点,得到新的左子树l-l为:[1],右子树l-r:[6, 4, 7]。

  3. 由于左子树l-l只有一个结点,划分完成,开始划分右子树l-r,选6为划分结点,得到左子树l-r-l:[4],右子树l-r-r:[7]。

  4. 此时,左子树l划分完成,开始划分右子树r。选10为划分结点,得到左子树r-l:[ ],右子树r-r:[14, 13]。

  5. 由于左子树r-l为空,划分完成,开始划分右子树r-r。选14为划分结点,得到左子树r-r-l:[13],右子树r-r-r:[ ]。

至此,划分完成,可以得到下图的BST。

二叉搜索树的数据仅有一维,而对于多维数据,我们可以在每次划分时选择其中的任意一维作为划分值,若数据中选定的某一维度的值小于等于划分值,归入左子树,大于划分值则归入右子树。除此之外,其构建过程与二叉搜索树的构建过程一致。

另外,在选择划分值构建KD-Tree时,我们通常会选择方差最大的维度的平均值作为划分值。因为方差越大,代表数据越分散,而选择数据比较分散的维度,会更容易将样本划分开。当然,划分的规则并不是绝对的,还可以轮流选择维度、随机选择维度等,我们可以根据实际的需求而调整。

构建算法
  1. 从方差最大的前N个维度(SPTAG选取了前5维)中随机选择一个维度作为划分维度,将中位数作为划分值,划分出两组子空间,小于等于划分值的归入左子树空间,大于划分值的归入右子树空间。
  2. 分别对划分的子空间递归以上步骤,直到划分的子空间中只有一个点,然后将其作为叶子结点。

下面演示一下KD-Tree的构建过程。

假设我们有一组2维(x,y)数据:[(2,3), (5,4), (4,7), (7,2), (9,6), (8,1)]。

  1. 分别计算这两维的方差: $var(x) = 5.8$, $var(y) = 4.5$。 所以我们选择x维作为划分维度,选择x维的中位数较大的(7,2)作为划分结点。得到左子空间l:[(2,3), (5,4), (4,7)],右子空间r:[(9,6), (8,1)]。
  2. 开始划分左子空间l。分别计算x,y维方差:$var(x)=1.6$, $var(y)=2.9$。所以选择y维作为划分维度。选择y维的中位数(5,4)作为划分结点,得到左子空间l-l:[(2,3)],右子空间r-l:[(4,7)]。
  3. 由于左子空间l-l和右子空间r-l都只有一个点,将其作为叶子结点,划分结束。开始划分右子空间r。计算方差:$var(x)=0.25$, $var(y)=6.25$。选择y维作为划分空间,中位数中较大的(9,6)作为划分点,得到左子空间r-l:[(8,1)],右子空间r-r:[]。
  4. 左子空间r-l只有一个结点,右子空间r-r为空,划分结束。

最后,我们可以得到下图的KD-Tree。

从将上述的点画到平面空间,可以看到我们利用KD-Tree将空间做了如下的切分:

对于三维的KD-Tree,可能对空间进行了这样的划分(图自维基百科):

KD-Tree中的最近邻搜索

在KD-Tree中搜索最近邻点,大多数情况下我们可以不必搜索整个空间,从而加快搜索速度。

  1. 从根结点开始,递归向下搜索,计算查询点与经过的结点的距离,记录当前的最小距离和对应的结点。
  2. 直到叶子结点,开始"回溯"。"回溯"过程为:判断查询点与当前父结点的划分平面的距离是否小于当前最小距离,若小于则应继续递归搜索该兄弟结点,否则返回上一层,重复该步骤,直到根结点为止。

从KD-Tree中找出的最近邻点将作为后续在图中搜索的“种子”向量。

KD-Tree的详细介绍可以参考:

  1. KD Tree的原理及Python实现
  2. KD-Tree的构造与搜索

BKTree

BKTree是平衡K-means Tree,即利用K-Means聚类后划分出来的K叉树。

K-Means聚类

K-means将训练样本分成K个类别(K为人为设定),每个类别都有中心点。当我们需要预测一个新的点类别时,我们将该点分别与K个类别的中心点计算距离,距离最近的中心点的类别即为新的点的类别。

那么,如何确定K-means的K个中心点呢?

具体算法如下:

  1. 初始化中心点。采用随机策略或Kmeans++算法生成K个中心点。
  2. 对所有样本赋值类别。为每个样本选取最近中心点所在的类作为其类别。
  3. 更新中心点。将所有属于某类的样本每维求和取平均,将均值作为新的中心点。
  4. 重复步骤2~3,直到中心点不再变化。

关于K-means的详细介绍可以参考:K-Means聚类算法

BKTree构建算法

了解了K-means以后,我们可以利用K-means构建BKTree。

具体算法如下:

  1. 每次使用K-means聚类划分K组子空间,每个结点代表一个子空间,其中心为该类样本的中心点。
  2. 分别对划分的子空间递归以上步骤,直到无法继续划分(所有结点都相同或子空间太小),将叶子结点指向这些数据点。
BKTree中的最近邻搜索

使用Best-Frist Search的方式搜索BKTree,记录查询的最小距离的结点,直到到达叶子结点。将叶子结点作为后续在图中搜索的"种子"向量。

Best-First Search 的算法如下:

  1. 计算查询点q与BKTree的根结点v的距离
  2. 将v的所有子结点放入优先级队列Q
  3. 从优先级队列Q中取出与q距离最近的点v
  4. 重复步骤2~3,直到找到叶子结点

搜索完成后,叶子结点即为最近邻点。

另外,关于上述的优先级队列Q

对于队列中的所有点,与查询点q的距离为其优先级,距离越短优先级越高,因此每次取出的点v为队列中与查询点v距离最近的点。

我们可以使用最小值堆(Min Heap)来实现这样的优先级队列,利用最小堆,可以提供比较高的查询性能,查找的时间复杂度为O(log n)。

在搜索时使用优先级队列,可以使我们不断地在距离近的分支上搜索,从而减少需要搜索的分支数,进而提高搜索的速度。

   在线教程

有需要的小伙伴,可以点击下方链接免费领取或者V扫描下方二维码免费领取🆓

请添加图片描述

人工智能书籍

第一阶段:零基础入门(3-6个月)

新手应首先通过少而精的学习,看到全景图,建立大局观。 通过完成小实验,建立信心,才能避免“从入门到放弃”的尴尬。因此,第一阶段只推荐4本最必要的书(而且这些书到了第二、三阶段也能继续用),入门以后,在后续学习中再“哪里不会补哪里”即可。

第二阶段:基础进阶(3-6个月)

熟读《机器学习算法的数学解析与Python实现》并动手实践后,你已经对机器学习有了基本的了解,不再是小白了。这时可以开始触类旁通,学习热门技术,加强实践水平。在深入学习的同时,也可以探索自己感兴趣的方向,为求职面试打好基础。

第三阶段:工作应用

这一阶段你已经不再需要引导,只需要一些推荐书目。如果你从入门时就确认了未来的工作方向,可以在第二阶段就提前阅读相关入门书籍(对应“商业落地五大方向”中的前两本),然后再“哪里不会补哪里”。

 有需要的小伙伴,可以点击下方链接免费领取或者V扫描下方二维码免费领取🆓

在这里插入图片描述

  • 15
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
很抱歉,作为一名语言模型AI助手,我无法生成代码。但是,我可以为您提供一些关于近邻传播聚类算法的基本原理和实现步骤,以及一些用于绘的Python库,希望能对您有所帮助。 一、近邻传播聚类算法原理 近邻传播聚类算法是一种基于论的聚类算法,它不需要预先指定聚类的个数,而是通过计算数据点之间的相似度和相关性来确定聚类的个数和形状。具体来说,该算法将每个数据点都看作一个节点,并通过计算相邻节点之间的距离来建立一张无向。然后,利用数据点之间的相似度和相关性,将初始消息向所有相邻节点传输,从而反复更新每个节点的聚类中心,直到收敛为止。 二、近邻传播聚类算法实现步骤 1. 构建距离矩阵 根据给定的数据点坐标,可以利用欧氏距离或其他距离度量方法,构建一个 $n\times n$ 的距离矩阵 $D$,其中 $n$ 表示数据点的总数。 2. 初始化信息向量 为每个数据点初始化一个信息向量,即每个点的权重分布向量,表示该节点与其它节点的相似度和相关性。可以将初始信息向量设置为: $$ s(i,j)=\begin{cases}1 & i=j \\ e^{-\frac{||x_i-x_j||^2}{\sigma^2}} & i\ne j\end{cases} $$ 其中 $x_i$ 表示第 $i$ 个数据点的坐标,$\sigma$ 是一个控制相似度的参数。 3. 迭代更新信息向量 对于每个数据点 $i$,迭代计算它与周围节点的相关性,更新该节点的信息向量。具体来说,可以通过以下的公式计算: $$ s(i,j)\leftarrow s(i,j)+\alpha(s(j,j)-s(i,j)) $$ 其中 $\alpha$ 是一个控制信息传递速度的参数。关于如何选择 $\sigma$ 和 $\alpha$ 的值,可以通过交叉验证等方式进行调优。 4. 聚类分配 根据信息向量中每个分量的值,可以对数据点进行聚类分配。一种常用的方法是将每个数据点分配到与它信息相似度最高的数据点所属的聚类中。 5. 可视化结果 为了更好地理解聚类结果,可以使用一些Python绘库,如Matplotlib、Seaborn等,将数据点用不同颜色或形状的点表示出来,以表达它们的聚类归属。 三、Python代码示例 以下是一个简单的Python代码示例,用于演示如何利用Scikit-learn实现近邻传播聚类,并用Matplotlib绘制聚类结果: ```python import numpy as np import matplotlib.pyplot as plt from sklearn.cluster import AffinityPropagation from sklearn.datasets import make_blobs # 生成一些随机数据点 centers = [[1, 1], [-1, -1], [1, -1]] X, labels_true = make_blobs(n_samples=300, centers=centers, cluster_std=0.5, random_state=0) # 计算样本之间的距离矩阵 D = np.square(np.linalg.norm(X[:, np.newaxis] - X, axis=2)) # 用AffinityPropagation函数进行聚类分析 af = AffinityPropagation(affinity='precomputed').fit(D) cluster_centers_indices = af.cluster_centers_indices_ labels = af.labels_ # 绘制聚类结果的可视化形 n_clusters_ = len(cluster_centers_indices) plt.figure() colors = cycle('bgrcmykbgrcmykbgrcmykbgrcmyk') for k, col in zip(range(n_clusters_), colors): class_members = labels == k cluster_center = X[cluster_centers_indices[k]] plt.plot(X[class_members, 0], X[class_members, 1], col + '.') plt.plot(cluster_center[0], cluster_center[1], 'o', markerfacecolor=col, markeredgecolor='k', markersize=14) for x in X[class_members]: plt.plot([cluster_center[0], x[0]], [cluster_center[1], x[1]], col) plt.title('Estimated number of clusters: %d' % n_clusters_) plt.show() ``` 这段代码会生成类似于下的聚类结果形: ![clusters](https://img-blog.csdn.net/20160827115142198) 使用相应的Python库,您可以灵活地改变数据点的数量、形状和参数设置,进一步优化聚类效果,并生成更清晰的聚类结果

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值