3 min readfrom Machine Learning

Got scipy's KD-tree to handle inserts and deletes without rebuilding. Three things I learned [P]

Our take

For applications demanding precise nearest-neighbor search on evolving datasets, we introduce whitetree, a new library built on NumPy and SciPy. Addressing limitations in existing solutions, whitetree achieves remarkable speed – 40 to 300x faster than sklearn's BallTree(mahalanobis) and 7 to 60x faster than FAISS Flat at 500k points – while maintaining exact results even with frequent inserts and deletes. Explore the project and its performance benchmarks at [https://github.com/whitetree-dev/whitetree](https://github.com/whitetree-dev/whitetree),
Got scipy's KD-tree to handle inserts and deletes without rebuilding. Three things I learned [P]

The recent post detailing the development of "whitetree," a library for exact Mahalanobis nearest-neighbor search on streaming data, is a noteworthy contribution to the field of efficient data indexing, particularly for applications dealing with dynamic datasets. This work addresses a persistent challenge: maintaining performance with frequent insertions and deletions. The author's approach, cleverly leveraging multiple SciPy cKDTrees and a geometric size ratio, offers a compelling alternative to rebuilding indexes from scratch, a common bottleneck. This is particularly relevant given recent advancements in areas like medical image analysis, where rapid data acquisition and processing are crucial, as demonstrated in a recent project Reconstructing 3D bone geometry from 2 X-ray silhouettes using a statistical shape model + differentiable rendering. Similarly, the need for efficient feature engineering on rapidly evolving datasets resonates with the automated feature engineering approaches explored in py-evoFE: Automated Evolutionary Feature Engineering for Tabular ML in Python (Genetic Algorithms + Scikit-Learn + Polars), highlighting the broader demand for adaptable data structures.

The core innovation lies in the practical measurements the author presents, going beyond a simple pitch and focusing on concrete performance gains. The reported 40-300x speedup over sklearn's BallTree(mahalanobis) and 7-60x speedup over FAISS Flat for static data are impressive. Even more compelling is the ability to maintain exact nearest-neighbor search while handling continuous inserts and deletes at a rate of approximately 1,100 steps per second – a significant improvement over other methods like rebuilding a cKDTree per query, which struggles to keep pace. The author’s detailed analysis of FAISS’s whitening process, revealing a loss of recall at higher condition numbers, is a valuable observation that challenges assumptions about its efficacy and emphasizes the importance of careful benchmarking. The focus on practical considerations, such as the impact of interleaved updates and queries, underscores a grounded and user-centric approach to the development.

The reliance on standard NumPy and SciPy libraries contributes to the accessibility and ease of integration of whitetree, removing potential barriers to adoption. The decision to prioritize a single writer thread with multiple readers simplifies concurrency management, making it suitable for a wide range of applications. The author’s transparency in sharing code, tests, benchmark scripts, and a design note further enhances the value of this contribution, fostering collaboration and enabling others to build upon this work. The meticulous benchmarking process, adhering to established protocols like ann-benchmarks and big-ann-benchmarks, adds credibility to the performance claims and allows for fair comparisons with existing solutions. The acknowledgement of potential bugs in existing libraries, as highlighted in a recent discussion Catching bugs in scikit-learn, further demonstrates a commitment to rigorous evaluation and improvement.

Ultimately, the development of whitetree represents a significant step forward in providing practical and efficient solutions for dynamic nearest-neighbor search. The author’s insightful observations regarding the trade-offs between different approaches, particularly the interplay between update frequency and query performance, are crucial for practitioners seeking to optimize their data pipelines. As streaming data becomes increasingly ubiquitous across various domains, the demand for robust and scalable indexing techniques will only continue to grow. The question now is: will this approach spark further innovation in dynamic indexing strategies, and what new algorithmic breakthroughs will emerge to address the ever-evolving challenges of managing and querying data in motion?

Got scipy's KD-tree to handle inserts and deletes without rebuilding. Three things I learned [P]

I built a small library called whitetree for exact Mahalanobis nearest-neighbour search on low-dimensional sensor data that keeps arriving. The idea is old. Whiten with the Cholesky factor of the covariance so Mahalanobis becomes Euclidean, then keep several scipy cKDTrees instead of one so inserts and deletes never force a full rebuild. Three measurements came out of it that I haven't seen stated plainly anywhere, so I'm posting those rather than a pitch.

The short version first. On the static side it is 40 to 300x faster than sklearn's BallTree(mahalanobis) and 7 to 60x faster than FAISS Flat at 500k points, and on the interleaved side it is the only exact option I found that keeps up with one insert and one delete per query. It's numpy and scipy only, one writer thread with any number of readers, and results match a static cKDTree exactly (distance error 0.0) after any mix of inserts and deletes.

  1. Textbook Bentley-Saxe doesn't work on cKDTree. cKDTree.query has a fixed per-call cost (1.6 us on a 16-point tree, 3.2 us on a 50k-point tree), so what matters is how many trees a query visits, not how big they are. The binary decomposition keeps popcount(n) trees and queries dropped to 20 to 30% of static throughput. A geometric size ratio of 32 gives 3 or 4 trees at a million points and keeps 47 to 97% for batches, 20 to 80% for single queries.

  2. FAISS's native whitening loses recall, but its search doesn't. PCAMatrix estimates the covariance from a 1000*d subsample in float32. Measured against float64 brute force, recall@10 is 0.967 at condition number 1e4, 0.841 at 1e8, and NaN on data with a DC offset of 1e4. Hand the same whitened points to IndexFlatL2 and it scores 1.000. I'd hoped to find a float64 accuracy edge. There isn't one.

  3. Whether a dynamic index helps at all depends on how updates and queries interleave. On a 200k-point sliding window, one thread, with updates in batches of 20k and 2,000 queries in between, rebuilding a cKDTree per batch (2.2 s total) beats whitetree (14.9 s). With every step doing insert 1 / delete oldest / query 1, whitetree does ~1,100 steps/s, FAISS IDMap2 ~20 (remove_ids is O(n)), numpy 30 to 40, and rebuilding a cKDTree per query ~8.

Setup, briefly. Covariance in float64 with a scale-relative ridge and Ledoit-Wolf shrinkage only when n < 5d. Trees kept largest-first, each at least 32x the next, merged and rebuilt when a new one breaks that. The largest tree's k-th distance bounds the rest. Deletes are tombstones. Benchmarks follow the ann-benchmarks and big-ann-benchmarks streaming protocols, recall against float64 brute force.

Code, tests, benchmark scripts, and a design note with the numbers behind each decision are at https://github.com/whitetree-dev/whitetree

A question for people who run exact low-dimensional kNN on streams. Is there a dynamic exact index I should've benchmarked against and missed? I compared FAISS IndexFlatL2 with IDMap2, scipy cKDTree and sklearn BallTree rebuilt per query, and numpy brute force. If something beats ~1,100 insert/delete/query steps per second at 200k points on one core, I'd like to know.

submitted by /u/monononon34
[link] [comments]

Read on the original site

Open the publisher's page for the full experience

View original article