Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
PyNNDescent: Fast Approximate Nearest Neighbors...
Search
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
Leland McInnes
July 16, 2021
Programming
1k
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
PyNNDescent: Fast Approximate Nearest Neighbors with Numba
A PDF version of slides for my SciPy 2021 talk on PyNNDescent.
Leland McInnes
July 16, 2021
More Decks by Leland McInnes
See All by Leland McInnes
Word and Document Embeddings
lmcinnes
0
180
Topological Data Analysis
lmcinnes
1
370
Ensemble Topic Modelling
lmcinnes
1
500
Learning Topology: topological methods for unsupervised learning
lmcinnes
2
3.6k
A Guide to Dimension Reduction
lmcinnes
3
1.4k
UMAP: Uniform Manifold Approximation and Projection for Dimension Reduction
lmcinnes
2
2.8k
Other Decks in Programming
See All in Programming
ADKを使って簡単にAIエージェントを作ってみよう
k1mu21
0
280
はてなアカウント基盤 State of the Union
cockscomb
1
890
OSもどきOS
arkw
0
590
コンテキストの使い捨てをやめる — ビジネスルール駆動開発と miko —
ioki
0
240
jQueryをバージョンアップする前に使いたいjQuery Migrate
matsuo_atsushi
0
600
Skillsは効率化、Agentsは"自分の拡張"——Builder時代のエージェント編成(CC Night 2026)
wemra
1
170
Strategic Design in the Frontend: Moduliths & Micro Frontends @DDDEurope
manfredsteyer
PRO
0
130
ランチタイムLT会3周年!ランチタイムLT会を3年間続けられたお話
y0hgi
1
110
その問い、本当に正しいですか?AI時代のエンジニアに必要な哲学と認知科学 / ai-philosophy-cognitive-science
minodriven
13
6.3k
Mujeres en SEO Summit 2026 - Greatest Disaster Hits en Web Performance
guaca
0
200
気づいたらRubyで100作品 ー クリエイティブコーディングが生活の一部になるまで / 100 Ruby Sketches Later: How Creative Coding Became Part of My Life
chobishiba
3
610
才能?センス?知らん、 続けたもん勝ちだ。-- 結婚・出産・癌を越えてなお、私がプロダクトを創り続ける理由
16bitidol
1
460
Featured
See All Featured
Visualizing Your Data: Incorporating Mongo into Loggly Infrastructure
mongodb
49
10k
Claude Code どこまでも/ Claude Code Everywhere
nwiizo
65
56k
So, you think you're a good person
axbom
PRO
2
2.1k
Self-Hosted WebAssembly Runtime for Runtime-Neutral Checkpoint/Restore in Edge–Cloud Continuum
chikuwait
0
620
How People are Using Generative and Agentic AI to Supercharge Their Products, Projects, Services and Value Streams Today
helenjbeal
1
220
My Coaching Mixtape
mlcsv
0
160
Bootstrapping a Software Product
garrettdimon
PRO
307
120k
Design in an AI World
tapps
1
250
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
35
3.5k
Context Engineering - Making Every Token Count
addyosmani
9
990
Connecting the Dots Between Site Speed, User Experience & Your Business [WebExpo 2025]
tammyeverts
11
950
Navigating Team Friction
lara
192
16k
Transcript
Fast Approximate Nearest Neighbour Search with Numba
What are Nearest Neighbours?
Given a set of points with A distance measure between
them…
… and a new “query point” …
Find the closest points to the query point
Why Nearest Neighbors?
Nearest Neighbour computations are at the heart of many machine
learning algorithms
KNN-Classi fi ers KNN-Regressors
Clustering https://commons.wikimedia.org/wiki/File:DBSCAN-Illustration.svg by Chire https://www. fl ickr.com/photos/trevorpatt/41875889652/in/photostream/ by Trevor Patt
HDBSCAN DBSCAN Single Linkage Clustering Spectral Clustering
Dimension Reduction http://lvdmaaten.github.io/tsne/ http://www-clmc.usc.edu/publications/T/tenenbaum-Science2000.pdf t-SNE Isomap Spectral Embedding UMAP
Recommender Systems Query Expansion
Why Approximate Nearest Neighbours?
Finding exact nearest neighbours is hard
Approximate nearest neighbour search trades accuracy for performance
How Do You Find Nearest Neighbors?
Using Trees
Hierarchically divide up the space into a tree
Bound the search using the tree structure (And the triangle
inequality)
KD-Tree
Ball Tree
Random Projection Tree
Using Graphs
How do you search for nearest neighbours of a query
using a graph? Malkov and Yashunin, 2018 Dong, Moses and Li, 2011 Iwasaki and Miyazaki, 2018
Start with a nearest neighbour graph of the training data
Assume we now want to fi nd neighbours of a query point
Choose a starting node in the graph (potentially randomly) as
a candidate node
None
Look at all nodes connected by an edge to the
best untried candidate node in the graph Add all these nodes to our potential candidate pool
None
Sort the candidate pool by closeness to the query point
Truncate the pool to the k best candidates
None
Return to the Expansion step unless we have already tried
all the candidates in the pool
Stop when there are no untried candidates in the pool
None
None
None
None
Looks inef fi cient Scales up well
None
Graph adapts to intrinsic dimension of the data
But how do we build the graph?!
The algorithm works (badly) even on a bad graph
Run one iteration of search for every node Update the
graph with new better neighbours Search is better on the improved graph
None
None
None
None
None
Perfect accuracy of neighbours is not assured We can get
an approximate knn-graph quickly
How Do You Make it Fast?
Algorithm tricks
Query node Expansion node Current neighbour
Neighbour A Neighbour B Common node
Hubs have a lot of neighbours!
None
None
Sample neighbours when constructing the graph Prune away edges before
performing searches
Necessary to fi nd green’s nearest neighbour Necessary to fi
nd blue’s nearest neighbour Not required since we can traverse through blue
For search remove the longest edges of any triangles in
the graph
Initialize with Random Projection Trees
Implementation tricks
None
Pro fi le and inspect llvm code for innermost functions
Type declarations and code choices can help the compiler a lot!
@numba.jit def euclidean(x, y): return np.sqrt(np.sum((x - y)**2)) Query benchmark
took 12s
@numba.jit(fastmath=True) def euclidean(x, y): result = 0.0 for i in
range(x.shape[0]): result += (x[i] - y[i])**2 return np.sqrt(result) Query benchmark took 8.5s
@numba.njit( numba.types.float32( numba.types.Array( numba.types.float32, 1, "C", readonly=True ), numba.types.Array( numba.types.float32,
1, "C", readonly=True ), ), fastmath=True, locals={ "result": numba.types.float32, "diff": numba.types.float32, "i": numba.types.uint16, }, ) def squared_euclidean(x, y): result = 0.0 dim = x.shape[0] for i in range(dim): diff = x[i] - y[i] result += diff * diff return result Query benchmark took 7.6s
Custom data structure implementations to help numba for often called
code
@numba.njit( "i4(f4[ :: 1],i4[ :: 1],f4,i4)", ) def simple_heap_push(priorities, indices,
p, n): ...
Numba has signi fi cant function call overhead with large
parameters Use closures over static data instead
@numba.njit() def frequently_called_function(param, large_readonly_data): ... val = access(large_readonly_data, param) ...
def create_frequently_called_function(large_readonly_data): @numba.njit() def closure(param): ... val = access(large_readonly_data, param) ... return closure
How Does it Compare?
Performance
We can test query performance using ann-benchmarks https://github.com/erikbern/ann-benchmarks
Consider the whole accuracy / performance trade-off space
vs
None
None
None
None
Caveats: •Newer algorithms and implementations •Hardware can makes a big
difference •No GPU support for pynndescent
Features
Out of the box support for a wide variety of
distance measures: Euclidean Cosine Hamming Manhattan Minkowski Chebyshev Jaccard Haversine Dice Wasserstein Hellinger Spearman Correlation Mahalanobis Canberra Bray-Curtis Angular TSSS +20 more measures https://towardsdatascience.com/9-distance-measures-in-data-science-918109d069fa By Maarten Grootendorst
Custom metrics in Python (using numba)
Support for sparse data
Drop-in replacement for sklearn KNeighborsTransformer
Summary
pip install pynndescent conda install pynndescent https://github.com/lmcinnes/pynndescent
[email protected]
@leland_mcinnes
Questions?
[email protected]
@leland_mcinnes