annoy README

The project's README from github.com/spotify/annoy. As one markdown file: README.md.

Annoy

.. figure:: https://raw.github.com/spotify/annoy/master/ann.png :alt: Annoy example :align: center

.. image:: https://github.com/spotify/annoy/actions/workflows/ci.yml/badge.svg :target: https://github.com/spotify/annoy/actions

Annoy (Approximate Nearest Neighbors <http://en.wikipedia.org/wiki/Nearest_neighbor_search#Approximate_nearest_neighbor>__ Oh Yeah) is a C++ library with Python bindings to search for points in space that are close to a given query point. It also creates large read-only file-based data structures that are mmapped <https://en.wikipedia.org/wiki/Mmap>__ into memory so that many processes may share the same data.

Install

To install, simply do pip install --user annoy to pull down the latest version from PyPI <https://pypi.python.org/pypi/annoy>_.

For the C++ version, just clone the repo and #include "annoylib.h".

Background

There are some other libraries to do nearest neighbor search. Annoy is almost as fast as the fastest libraries, (see below), but there is actually another feature that really sets Annoy apart: it has the ability to use static files as indexes. In particular, this means you can share index across processes. Annoy also decouples creating indexes from loading them, so you can pass around indexes as files and map them into memory quickly. Another nice thing of Annoy is that it tries to minimize memory footprint so the indexes are quite small.

Why is this useful? If you want to find nearest neighbors and you have many CPU's, you only need to build the index once. You can also pass around and distribute static files to use in production environment, in Hadoop jobs, etc. Any process will be able to load (mmap) the index into memory and will be able to do lookups immediately.

We use it at Spotify <http://www.spotify.com/>__ for music recommendations. After running matrix factorization algorithms, every user/item can be represented as a vector in f-dimensional space. This library helps us search for similar users/items. We have many millions of tracks in a high-dimensional space, so memory usage is a prime concern.

Annoy was built by Erik Bernhardsson <http://www.erikbern.com>__ in a couple of afternoons during Hack Week <http://labs.spotify.com/2013/02/15/organizing-a-hack-week/>__.

Summary of features

Python code example

.. code-block:: python

from annoy import AnnoyIndex import random

f = 40 # Length of item vector that will be indexed

t = AnnoyIndex(f, 'angular') for i in range(1000): v = [random.gauss(0, 1) for z in range(f)] 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 print(u.get_nns_by_item(0, 1000)) # will find the 1000 nearest neighbors

Right now it only accepts integers as identifiers for items. Note that it will allocate memory for max(id)+1 items because it assumes your items are numbered 0 … n-1. If you need other id's, you will have to keep track of a map yourself.

Full Python API

Notes:

The C++ API is very similar: just #include "annoylib.h" to get access to it.

Tradeoffs

There are just two main parameters needed to tune Annoy: the number of trees n_trees and the number of nodes to inspect during searching search_k.

If search_k is not provided, it will default to n * n_trees where n is the number of approximate nearest neighbors. Otherwise, search_k and n_trees are roughly independent, i.e. the value of n_trees will not affect search time if search_k is held constant and vice versa. Basically it's recommended to set n_trees as large as possible given the amount of memory you can afford, and it's recommended to set search_k as large as possible given the time constraints you have for the queries.

You can also accept slower search times in favour of reduced loading times, memory usage, and disk IO. On supported platforms the index is prefaulted during load and save, causing the file to be pre-emptively read from disk into memory. If you set prefault to False, pages of the mmapped index are instead read from disk and cached in memory on-demand, as necessary for a search to complete. This can significantly increase early search times but may be better suited for systems with low memory compared to index size, when few queries are executed against a loaded index, and/or when large areas of the index are unlikely to be relevant to search queries.

How does it work

Using random projections <http://en.wikipedia.org/wiki/Locality-sensitive_hashing#Random_projection>__ and by building up a tree. At every intermediate node in the tree, a random hyperplane is chosen, which divides the space into two subspaces. This hyperplane is chosen by sampling two points from the subset and taking the hyperplane equidistant from them.

We do this k times so that we get a forest of trees. k has to be tuned to your need, by looking at what tradeoff you have between precision and performance.

Hamming distance (contributed by Martin Aumüller <https://github.com/maumueller>__) packs the data into 64-bit integers under the hood and uses built-in bit count primitives so it could be quite fast. All splits are axis-aligned.

Dot Product distance (contributed by Peter Sobot <https://github.com/psobot>__ and Pavel Korobov <https://github.com/pkorobov>) reduces the provided vectors from dot (or "inner-product") space to a more query-friendly cosine space using a method by Bachrach et al., at Microsoft Research, published in 2014 <https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/XboxInnerProduct.pdf>.

More info

.. figure:: https://raw.githubusercontent.com/erikbern/ann-benchmarks/main/results/glove-100-angular.png :alt: ANN benchmarks :align: center :target: https://github.com/erikbern/ann-benchmarks

Source code

It's all written in C++ with a handful of ugly optimizations for performance and memory usage. You have been warned :)

The code should support Windows, thanks to Qiang Kou <https://github.com/thirdwing>__ and Timothy Riley <https://github.com/tjrileywisc>__.

To run the tests, execute python setup.py nosetests. The test suite includes a big real world dataset that is downloaded from the internet, so it will take a few minutes to execute.

Discuss

Feel free to post any questions or comments to the annoy-user <https://groups.google.com/group/annoy-user>__ group. I'm @fulhack <https://twitter.com/fulhack>__ on Twitter.

annoy (spotify/annoy)

Searching Database…

14.3K ·GitHub
这是什么东西?Annoy(Approximate Nearest Neighbors Oh Yeah)是一个带 Python 绑定的 C++ 库,用于找出空间中离给定查询点最近的那些点。它针对内存占用以及索引的保存和加载到磁盘做了优化。
这是一个我直接运行的应用,还是链接到我项目里的代码?它是一个库,不是应用:在 Python 中你导入 annoy 这个包;在 C++ 中你克隆仓库后 #include annoylib.h。社区还提供了 R、Java、Scala、Ruby、Go、Lua、Node、Rust、dotnet 和 dart 的绑定。
我怎么把它加到我的项目里?运行 pip install --user annoy 即可从 PyPI 获取最新版本,也可以安装 conda 包(Linux、OS X 和 Windows 上都有)。C++ 方面则无需安装任何东西——只要克隆仓库并包含头文件即可。
开始使用之前还需要装别的东西吗?不需要服务器、数据库或 GPU——它在进程内运行。由于有 PyPI 和 conda 包,从 Python 使用时不需要编译器;README 列出了已测试支持的 Python 2.7、3.6 和 3.7。
建好索引之后有什么限制?索引一旦建好就是只读的——构建或保存之后就不能再添加数据,而且索引的创建和查询是完全解耦的。id 必须是非负整数,并且会为 max(id)+1 分配内存,所以如果 id 稀疏或不是整数,你需要自己维护一个映射表。
有哪些公司在生产环境用它?Spotify 用它做音乐推荐:经过矩阵分解后,用户和歌曲都变成向量,Annoy 在内存非常宝贵的高维空间里,从数百万首歌曲中找出相似的向量。
多个进程可以共用同一个索引吗?可以——这正是它的亮点。索引是静态文件,通过内存映射(mmap)加载进内存,所以你只需构建一次,多个进程就能共享同一份数据。对于装不进内存的数据集,还支持直接在磁盘上构建索引。
在 Python 里典型的使用流程是怎样的?先创建 AnnoyIndex(f, 'angular') 来处理 f 维向量,用 additem 逐个添加向量,调用 build(10) 构建 10 棵树,然后保存到类似 test.ann 的文件。在其他地方加载这个文件——它只是做内存映射——再调用 get_nns_by_item 或 get_nns_by_vector。
我该怎么在精度和速度之间做取舍?只有两个旋钮:构建时的 n_trees(树越多精度越高,但索引越大)和查询时的 search_k(检查的节点越多,结果越准但越慢)。默认情况下 search_k 是 n_trees 乘以 n,其中 n 是请求的近邻个数。
可以选择哪些距离度量?'angular'(余弦距离)、'euclidean'、'manhattan'、'hamming' 和 'dot'(内积)。angular 指的是归一化向量之后的欧氏距离。它在维度低于 100 时效果最好,但到 1,000 维时表现也出奇地好。
What are similar projects?faiss, pgvector, weaviate, lancedb, qdrant, zvec, dlib, flatbuffers
Creator
spotify
Size
2 MB
Stars
14.3K
Age
13y
Commits · 90 Days