Annoy是高维空间求近似最近邻的一个开源库。

Annoy构建一棵二叉树,查询时间为O(logn)。

Annoy通过随机挑选两个点,并使用垂直于这个点的等距离超平面将集合划分为两部分。

如图所示,图中灰色线是连接两个点,超平面是加粗的黑线。按照这个方法在每个子集上迭代进行划分。

依此类推,直到每个集合最多剩余k个点,下图是一个k = 10 的情况。

相应的完整二叉树结构:

随机投影森林。

一个思想依据是:在原空间中相邻的点,在树结构上也表现出相互靠近的特点,也就是说,如果两个点在空间上相互靠近,那么他们很可能被树结构划分到一起。

如果要在空间中查找临近点,我们可以在这个二叉树中搜索。上图中每个节点用超平面来定义,所以我们可以计算出该节点往哪个方向遍历,搜索时间 log n

如上图,我们找到了七个最近邻,但是假如我们想找到更多的最近邻怎么办?有些最近邻是在我们遍历的叶子节点的外边的。

技巧1:使用优先队列

如果一个划分的两边“靠得足够近”(量化方式在后面介绍),我们就两边都遍历。这样就不只是遍历一个节点的一边,我们将遍历更多的点

我们可以设置一个阈值,用来表示是否愿意搜索划分“错”的一遍。如果设置为0,我们将总是遍历“对”的一片。但是如果设置成0.5,就按照上面的搜索路径。

这个技巧实际上是利用优先级队列,依据两边的最大距离。好处是我们能够设置比0大的阈值,逐渐增加搜索范围。

技巧2:构建一个森林

我们能够用一个优先级队列,同时搜索所有的树。这样有另外一个好处,搜索会聚焦到那些与已知点靠得最近的那些树——能够把距离最远的空间划分出去

每棵树都包含所有的点,所以当我们搜索多棵树的时候,将找到多棵树上的多个点。如果我们把所有的搜索结果的叶子节点都合在一起,那么得到的最近邻就非常符合要求。

依照上述方法,我们找到一个近邻的集合,接下来就是计算所有的距离和对这些点进行排序,找到最近的k个点。

很明显,我们会丢掉一些最近的点,这也是为什么叫近似最近邻的原因。

Annoy在实际使用的时候,提供了一种机制可以调整(搜索k),你能够根据它来权衡性能(时间)和准确度(质量)。

tips:

1.距离计算,采用归一化的欧氏距离:vectors = sqrt(2-2*cos(u, v))

2.向量维度较小(<100),即使维度到达1000变现也不错

3.内存占用小

4.索引创建与查找分离(特别是一旦树已经创建,就不能添加更多项)

5.有两个参数可以用来调节Annoy 树的数量n_trees和搜索期间检查的节点数量search_k

  n_trees在构建时提供,并影响构建时间和索引大小。 较大的值将给出更准确的结果,但更大的索引。

  search_k在运行时提供,并影响搜索性能。 较大的值将给出更准确的结果,但将需要更长的时间返回。

如果不提供search_k,它将默认为n * n_trees,其中n是近似最近邻的数目。 否则,search_k和n_tree大致是独立的,即如果search_k保持不变,n_tree的值不会影响搜索时间,反之亦然。 基本上,建议在可用负载量的情况下尽可能大地设置n_trees,并且考虑到查询的时间限制,建议将search_k设置为尽可能大。

Python demo:

from annoy import AnnoyIndex
import random f = 40 #维度
t = AnnoyIndex(f) # Length of item vector that will be indexed
for i in xrange(1000):
v = [random.gauss(0, 1) for z in xrange(f)]
t.add_item(i, v) #添加向量 t.build(10) # 10 trees
t.save('test.ann') # ... u = AnnoyIndex(f)
u.load('test.ann') # super fast, will just mmap the file
print(u.get_nns_by_item(0, 1000)) # will find the 1000 nearest neighbors of the first(0) vec

python API:

AnnoyIndex(f, metric='angular') returns a new index that's read-write and stores vector of f dimensions. Metric can be either "angular" or "euclidean".
a.add_item(i, v) adds item i (any nonnegative integer) with vector v. Note that it will allocate memory for max(i)+1 items.
a.build(n_trees) builds a forest of n_trees trees. More trees gives higher precision when querying. After calling build, no more items can be added.
a.save(fn) saves the index to disk.
a.load(fn) loads (mmaps) an index from disk.
a.unload() unloads.
a.get_nns_by_item(i, n, search_k=-1, include_distances=False) returns the n closest items. During the query it will inspect up to search_k nodes which defaults to n_trees * n if not provided. search_k gives you a run-time tradeoff between better accuracy and speed. If you set include_distances to True, it will return a 2 element tuple with two lists in it: the second one containing all corresponding distances.
a.get_nns_by_vector(v, n, search_k=-1, include_distances=False) same but query by vector v.
a.get_item_vector(i) returns the vector for item i that was previously added.
a.get_distance(i, j) returns the distance between items i and j. NOTE: this used to returned the squared distance, but has been changed as of Aug 2016.
a.get_n_items() returns the number of items in the index.

类似可以做这个工作的RPForest和sklearn.neighbors中的LSHForest,但Annoy的效果要比他们好很多。

查询树上所有节点都共用这个数据结构:

  1. n_descendants为该节点及其子孙节点包含的向量个数。
  2. children为左右子树指针。因为搜索树在物理上其实是这个结构体的数组,按照16字节对齐。搜索时把索引及数据文件mmap到内存,然后通过数组下标进行随机定位。所以指针就是数组下标

Annoy解析的更多相关文章

  1. 近似最近邻算法-annoy解析

    转自https://www.cnblogs.com/futurehau/p/6524396.html Annoy是高维空间求近似最近邻的一个开源库. Annoy构建一棵二叉树,查询时间为O(logn) ...

  2. 【原】Android热更新开源项目Tinker源码解析系列之三:so热更新

    本系列将从以下三个方面对Tinker进行源码解析: Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Android热更新开源项目Tinker源码解析系列之二:资源文件热更新 A ...

  3. .NET Core中的认证管理解析

    .NET Core中的认证管理解析 0x00 问题来源 在新建.NET Core的Web项目时选择“使用个人用户账户”就可以创建一个带有用户和权限管理的项目,已经准备好了用户注册.登录等很多页面,也可 ...

  4. Html Agility Pack 解析Html

    Hello 好久不见 哈哈,今天给大家分享一个解析Html的类库 Html Agility Pack.这个适用于想获取某网页里面的部分内容.今天就拿我的Csdn的博客列表来举例. 打开页面  用Fir ...

  5. 【原】Android热更新开源项目Tinker源码解析系列之一:Dex热更新

    [原]Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Tinker是微信的第一个开源项目,主要用于安卓应用bug的热修复和功能的迭代. Tinker github地址:http ...

  6. 【原】Android热更新开源项目Tinker源码解析系列之二:资源文件热更新

    上一篇文章介绍了Dex文件的热更新流程,本文将会分析Tinker中对资源文件的热更新流程. 同Dex,资源文件的热更新同样包括三个部分:资源补丁生成,资源补丁合成及资源补丁加载. 本系列将从以下三个方 ...

  7. 多线程爬坑之路-Thread和Runable源码解析之基本方法的运用实例

    前面的文章:多线程爬坑之路-学习多线程需要来了解哪些东西?(concurrent并发包的数据结构和线程池,Locks锁,Atomic原子类) 多线程爬坑之路-Thread和Runable源码解析 前面 ...

  8. Asp.Net WebApi核心对象解析(下篇)

    在接着写Asp.Net WebApi核心对象解析(下篇)之前,还是一如既往的扯扯淡,元旦刚过,整个人还是处于晕的状态,一大早就来处理系统BUG,简直是坑爹(好在没让我元旦赶过来该BUG),队友挖的坑, ...

  9. 【知识必备】内存泄漏全解析,从此拒绝ANR,让OOM远离你的身边,跟内存泄漏say byebye

    一.写在前面 对于C++来说,内存泄漏就是new出来的对象没有delete,俗称野指针:而对于java来说,就是new出来的Object放在Heap上无法被GC回收:而这里就把我之前的一篇内存泄漏的总 ...

随机推荐

  1. Android学习笔记_53_Android界面的基本属性

    很好很全面http://www.eoeandroid.com/forum.php?mod=viewthread&tid=46859 布局: 在 android 中我们常用的布局方式有这么几种: ...

  2. EntityFramework Code-First-------领域类配置之DataAnnotations

    EF Code-First提供了一个可以用在领域类或其属性上的DataAnnotation特性集合,DataAnnotation特性会覆盖默认的EF约定. DataAnnotation存在于两个命名空 ...

  3. 使用带有字符串的data-ng-bind

    <!DOCTYPE html><html><head><meta http-equiv="Content-Type" content=&q ...

  4. js标准对象——Date

    在JavaScript中,Date对象用来表示日期和时间. 要获取系统当前的时间: var now = new Date(); alert(now); now;//Mon Oct 23 2017 11 ...

  5. PL/SQL12的安装与使用

    楼主比较懒,直接放一个别人的链接吧,比较全面. 大致过程就是,下载pl/sql    下载oracle instance client    然后配置oci.dll     添加 一个连接具体数据库的 ...

  6. Angularjs基础(四)

    AngularJS过滤器 过滤器可以使用一个管道符(|)添加到表达式和指令中. AngularJS过滤器可用于转换数据: currency 格式化数字为货币格式 filter 从数组中选着应子集. l ...

  7. qt项目:员工信息管理系统

    开发一个员工信息管理系统 一.项目具体要求: 1.用qt开发界面,数据库用QSqlite 数据库文件名:demostudent.db 2.通过界面能够查看到数据库中员工信息表中内容,包括员工姓名.年龄 ...

  8. 用HANA STADIO 开发ABAP程序

    Help-->Install New Software-->ADD NAME: hana_on_mars Location: https://tools.hana.ondemand.com ...

  9. mysqldump备份与基于bin-log实现完全恢复

    MySQL数据库备份是一项非常重要的工作,mysql的备份主要分为逻辑备份和物理备份,同时,不同的生产环境要备份的策略也不会不同.下面先说一说备份时要考虑到的一些因素,然后再实际操作进行不同方式的数据 ...

  10. Docker运行Nginx服务器

    一.获取Docker容器的Nginx镜像 二.创建Docker容器宿主机挂载目录 # 创建挂载目录,-v 显示创建的目录名 [root@idclooknet ~]# mkdir -vp /opt/do ...