Annoy算法简单介绍

Annoy算法

与Faiss相比,Annoy搜索,速度更快一点,主要目的是建立一个数据结构快速找到任何查询点的最近点。通过牺牲查询准确率来换取查询速度,这个速度比faiss速度还要快。

是什么

Annoy:最近邻向量搜索,

原理/过程

算法原理:先构建索引,对于每个二叉树都建立索引,在这里二叉树是随机构造的

第一步:先随机找两个点,根据这两个点进行连线,找到垂直平分线,称为超平面。

 

第二步:在切分后的子空间,继续按照上面的方法,找一个点,然后,进行连线,找垂直平分超平面。

 

不断继续分割,直到子空间中数据量不超过k,在这里k是可以提前定义的,如k=10.

 

其实,从另一个角度出发,在不断分割的过程中,类似于二叉树,从根节点到子节点,然后不断的切割

 

通过二叉树来表示空间节点的分布,节点表示子空间,在点的分布空间中,接近的子空间在二叉树中表现为位置靠近的节点。

对应着一个假设条件,对于空间内距离非常近的两个点,任何切分方法都不能使之分开。

要想查找某个点,只需要从根节点出发,一路往下走,最终到达子节点,自然就找到匹配的数据了。

但是有时候可能找到的数据量比较少,

1、优先队列

把多棵树放到优先队列,挨个进行处理,然后设定阈值,如果查询的点,与二叉树中某个节点距离比较近,我们可以在根节点出发的时候,同样找旁边的相关节点,走两个分支。

 

 

或者还可以建立多个二叉树,

多个二叉树进行交叉,构成森林,基本上可以覆盖到查找匹配点所在的区域,然后通过计算所有点的距离,并进行排序,取TopK

 

 

如何查询:

把每个二叉树的根节点都放进优先队列

对每一个二叉树都进行搜索,每一个二叉树都可以得到TopK个候选集

删除重复候选集

计算候选集与查询点的距离,并进行排序

返回TopK

优缺点

优点:查询速度快,时间复杂度O(log(n))

缺点:对于存在边界附近的点,可能还是会无法查找出来,导致损失一部分查询精确率。

如何优化

应用场景

如:在搜索业务中,数据候选集dataset,需要对新来的一个或多个数据进行查询,返回数据集中与该查询最相似的TopK数据。

最笨的方法,每条数据都计算一次,当然,如果候选集比较小,也无所谓,对于海量数据呢,总不能依次计算吧。通过annoy算法可以大大降低查询时间,在牺牲少量查询精确率的情况下。

  

具体应用

安装包:pip install annoy

参数介绍:

重要设置:n_trees:树的个数,直接影响构建索引的时间,值越大表示最终的精度越高,但是会有更多的索引,主要影响索引时间

Search_k:衡量查询精度和速度,值越大表示搜索耗时越长,搜索的精度越高;如果不进行设定的情况下,默认为n_trees*n

from annoy import AnnoyIndex

import random

 

# f 表示向量的维度

f = 40

# 'angular' 是 annoy 支持的一种度量;

t = AnnoyIndex(f, 'angular')  # Length of item vector that will be indexed

# 插入数据

for i in range(1000):

    v = [random.gauss(0, 1) for z in range(f)]

    # i 是一个非负数,v 表示向量

    t.add_item(i, v)

 

# 树的数量

t.build(10) # 10 trees

 

# 存储索引成为文件

t.save('test.ann')

 

# 读取存储好的索引文件

u = AnnoyIndex(f, 'angular')

u.load('test.ann') # super fast, will just mmap the file

 

# 返回与第 0 个向量最相似的 Top 100 向量;

print(u.get_nns_by_item(0, 1000)) # will find the 1000 nearest neighbors

 

# 返回与该向量最相似的 Top 100 向量;

print(u.get_nns_by_vector([random.gauss(0, 1) for z in range(f)], 1000))

 

# 返回第 i 个向量与第 j 个向量的距离;

# 第 0 个向量与第 0 个向量的距离

print(u.get_distance(0, 0))

# 第 0 个向量与第 1 个向量的距离

print(u.get_distance(0, 1))

 

# 返回索引中的向量个数;

print(u.get_n_items())

# 返回索引中的树的棵数;

print(u.get_n_trees())

 

# 不再加载索引

print(u.unload())



不同距离应用:基于hamming距离

from annoy import AnnoyIndex

 

# Mentioned on the annoy-user list

bitstrings = [

 '0000000000011000001110000011111000101110111110000100000100000000',

    '0000000000011000001110000011111000101110111110000100000100000001',

    '0000000000011000001110000011111000101110111110000100000100000010',

    '0010010100011001001000010001100101011110000000110000011110001100',

    '1001011010000110100101101001111010001110100001101000111000001110',

    '0111100101111001011110010010001100010111000111100001101100011111',

    '0011000010011101000011010010111000101110100101111000011101001011',

    '0011000010011100000011010010111000101110100101111000011101001011',

    '1001100000111010001010000010110000111100100101001001010000000111',

    '0000000000111101010100010001000101101001000000011000001101000000',

    '1000101001010001011100010111001100110011001100110011001111001100',

    '1110011001001111100110010001100100001011000011010010111100100111',

]

 

# 将其转换成二维数组

vectors = [[int(bit) for bit in bitstring] for bitstring in bitstrings]

 

# 64 维度

f = 64

idx = AnnoyIndex(f, 'hamming')

for i, v in enumerate(vectors):

    idx.add_item(i, v)

 

# 构建索引

idx.build(10)

idx.save('idx.ann')

idx = AnnoyIndex(f, 'hamming')

idx.load('idx.ann')

js, ds = idx.get_nns_by_item(0, 5, include_distances=True)

 

# 输出索引和 hamming 距离

print(js, ds)

基于欧几里得距离的 annoy 使用案例:

from annoy import AnnoyIndex

import random

 

# f 表示向量的维度

f = 2

# 'euclidean' 是 annoy 支持的一种度量;

t = AnnoyIndex(f, 'euclidean')  # Length of item vector that will be indexed

# 插入数据

t.add_item(0, [0, 0])

t.add_item(1, [1, 0])

t.add_item(2, [1, 1])

t.add_item(3, [0, 1])

 

# 树的数量

t.build(n_trees=10) # 10 trees

t.save('test2.ann')

u = AnnoyIndex(f, 'euclidean')

u.load('test2.ann') # super fast, will just mmap the file

print(u.get_nns_by_item(1, 3)) # will find the 1000 nearest neighbors

print(u.get_nns_by_vector([0.1, 0], 3))

### 回答1: Annoy算法是一种快速的近似最近邻搜索算法,它使用可用的空间换取时间,以抗击高维空间中的维数灾难。它通过将搜索空间划分为多个维度,以构建一个索引树来加快搜索速度,利用预选定的属性来排序搜索结果。它通过计算欧式距离来查找最近邻,同时考虑多个预定义的属性,而不仅仅考虑空间坐标。 ### 回答2: Annoy算法是一种用于高效近似最近邻搜索的算法。该算法的设计目标是在大规模数据集上进行快速检索,以找到与给定查询向量最相似的K个数据点。该算法的效率来源于其对索引结构的优化以及使用了一种称为“近似k最近邻(approximate k nearest neighbors)”的策略。 在Annoy算法中,数据被组织成一个树状结构,每个节点都有一个分裂轴和一个分裂值。根节点是整个数据集的中心,而每个叶子节点都包含一个或多个数据点。通过不断递归地将数据集分割成两个子集,直到达到叶子节点的条件为止,就构建了这个树状结构。 当进行查询时,Annoy算法会从树的根节点开始,根据查询向量的特征值与节点的分裂轴和分裂值进行比较,确定下一步向哪个子节点搜索。这个过程将根据节点中数据点与查询向量的相似度进行排序,并保留与查询向量最相似的K个数据点。 在Annoy算法中,为了进一步提高搜索效率,使用了一种“随机近似”的策略。该策略包括在查询过程中仅搜索空间中的一部分数据点,而不是遍历整个数据集。这样一来,通过牺牲一定的搜索精度,大大减少了搜索的时间复杂度。 总的来说,Annoy算法通过构建树状结构和随机近似的策略,实现了在大规模数据集上进行高效的近似最近邻搜索。它被广泛应用于推荐系统、图像搜索、语音处理等领域,并取得了较好的效果。 ### 回答3: Annoy算法是一种用于近似最近邻搜索的高效算法。它通过将高维数据映射到低维空间中,然后使用一种快速的近似搜索技术来找到最近的数据点。 Annoy算法的核心思想是使用二叉树进行数据的划分。首先,选择一个向量作为根节点,并将其他向量分配到它的左右子节点中。然后,对每个节点递归地执行同样的操作,直到达到停止条件。在构建树的过程中,可以选择不同的划分策略,例如最大方差、ランダム划分等。 一旦树被构建完成,我们可以利用树的结构来进行近似搜索。给定一个查询向量,我们可以根据其与根节点的距离选择相应的子节点进行下一步的搜索。通过重复这个过程,直到达到叶子节点,我们可以得到一个候选的最近邻集合。最后,我们对候选集合进行进一步搜索,找到真正的最近邻。 Annoy算法在进行最近邻搜索时具有一定的误差,但是它的效率非常高。相比于准确的最近邻算法,它大大降低了计算复杂度,特别适用于大规模数据集。 总结起来,Annoy算法是一种基于二叉树的近似最近邻搜索方法。它通过将高维数据映射到低维空间中,并利用树的结构进行搜索,从而实现了高效的最近邻搜索。该算法的优势在于能够在大规模数据集上取得较好的近似结果,并且具有较低的计算复杂度。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hblg_bobo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值