.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "auto_examples/neighbors/approximate_nearest_neighbors.py" .. LINE NUMBERS ARE GIVEN BELOW. .. only:: html .. note:: :class: sphx-glr-download-link-note Click :ref:`here <sphx_glr_download_auto_examples_neighbors_approximate_nearest_neighbors.py>` to download the full example code or to run this example in your browser via Binder .. rst-class:: sphx-glr-example-title .. _sphx_glr_auto_examples_neighbors_approximate_nearest_neighbors.py: ===================================== Approximate nearest neighbors in TSNE ===================================== This example presents how to chain KNeighborsTransformer and TSNE in a pipeline. It also shows how to wrap the packages `annoy` and `nmslib` to replace KNeighborsTransformer and perform approximate nearest neighbors. These packages can be installed with `pip install annoy nmslib`. Note: In KNeighborsTransformer we use the definition which includes each training point as its own neighbor in the count of `n_neighbors`, and for compatibility reasons, one extra neighbor is computed when `mode == 'distance'`. Please note that we do the same in the proposed wrappers. Sample output:: Benchmarking on MNIST_2000: --------------------------- AnnoyTransformer: 0.305 sec NMSlibTransformer: 0.144 sec KNeighborsTransformer: 0.090 sec TSNE with AnnoyTransformer: 2.818 sec TSNE with NMSlibTransformer: 2.592 sec TSNE with KNeighborsTransformer: 2.338 sec TSNE with internal NearestNeighbors: 2.364 sec Benchmarking on MNIST_10000: ---------------------------- AnnoyTransformer: 2.874 sec NMSlibTransformer: 1.098 sec KNeighborsTransformer: 1.264 sec TSNE with AnnoyTransformer: 16.118 sec TSNE with NMSlibTransformer: 15.281 sec TSNE with KNeighborsTransformer: 15.400 sec TSNE with internal NearestNeighbors: 15.573 sec Note that the prediction speed KNeighborsTransformer was optimized in scikit-learn 1.1 and therefore approximate methods are not necessarily faster because computing the index takes time and can nullify the gains obtained at prediction time. .. GENERATED FROM PYTHON SOURCE LINES 45-312 .. code-block:: default # Author: Tom Dupre la Tour # # License: BSD 3 clause import time import sys try: import annoy except ImportError: print("The package 'annoy' is required to run this example.") sys.exit() try: import nmslib except ImportError: print("The package 'nmslib' is required to run this example.") sys.exit() import numpy as np import matplotlib.pyplot as plt from matplotlib.ticker import NullFormatter from scipy.sparse import csr_matrix from sklearn.base import BaseEstimator, TransformerMixin from sklearn.neighbors import KNeighborsTransformer from sklearn.utils._testing import assert_array_almost_equal from sklearn.datasets import fetch_openml from sklearn.pipeline import make_pipeline from sklearn.manifold import TSNE from sklearn.utils import shuffle class NMSlibTransformer(TransformerMixin, BaseEstimator): """Wrapper for using nmslib as sklearn's KNeighborsTransformer""" def __init__(self, n_neighbors=5, metric="euclidean", method="sw-graph", n_jobs=1): self.n_neighbors = n_neighbors self.method = method self.metric = metric self.n_jobs = n_jobs def fit(self, X): self.n_samples_fit_ = X.shape[0] # see more metric in the manual # https://github.com/nmslib/nmslib/tree/master/manual space = { "euclidean": "l2", "cosine": "cosinesimil", "l1": "l1", "l2": "l2", }[self.metric] self.nmslib_ = nmslib.init(method=self.method, space=space) self.nmslib_.addDataPointBatch(X) self.nmslib_.createIndex() return self def transform(self, X): n_samples_transform = X.shape[0] # For compatibility reasons, as each sample is considered as its own # neighbor, one extra neighbor will be computed. n_neighbors = self.n_neighbors + 1 results = self.nmslib_.knnQueryBatch(X, k=n_neighbors, num_threads=self.n_jobs) indices, distances = zip(*results) indices, distances = np.vstack(indices), np.vstack(distances) indptr = np.arange(0, n_samples_transform * n_neighbors + 1, n_neighbors) kneighbors_graph = csr_matrix( (distances.ravel(), indices.ravel(), indptr), shape=(n_samples_transform, self.n_samples_fit_), ) return kneighbors_graph class AnnoyTransformer(TransformerMixin, BaseEstimator): """Wrapper for using annoy.AnnoyIndex as sklearn's KNeighborsTransformer""" def __init__(self, n_neighbors=5, metric="euclidean", n_trees=10, search_k=-1): self.n_neighbors = n_neighbors self.n_trees = n_trees self.search_k = search_k self.metric = metric def fit(self, X): self.n_samples_fit_ = X.shape[0] self.annoy_ = annoy.AnnoyIndex(X.shape[1], metric=self.metric) for i, x in enumerate(X): self.annoy_.add_item(i, x.tolist()) self.annoy_.build(self.n_trees) return self def transform(self, X): return self._transform(X) def fit_transform(self, X, y=None): return self.fit(X)._transform(X=None) def _transform(self, X): """As `transform`, but handles X is None for faster `fit_transform`.""" n_samples_transform = self.n_samples_fit_ if X is None else X.shape[0] # For compatibility reasons, as each sample is considered as its own # neighbor, one extra neighbor will be computed. n_neighbors = self.n_neighbors + 1 indices = np.empty((n_samples_transform, n_neighbors), dtype=int) distances = np.empty((n_samples_transform, n_neighbors)) if X is None: for i in range(self.annoy_.get_n_items()): ind, dist = self.annoy_.get_nns_by_item( i, n_neighbors, self.search_k, include_distances=True ) indices[i], distances[i] = ind, dist else: for i, x in enumerate(X): indices[i], distances[i] = self.annoy_.get_nns_by_vector( x.tolist(), n_neighbors, self.search_k, include_distances=True ) indptr = np.arange(0, n_samples_transform * n_neighbors + 1, n_neighbors) kneighbors_graph = csr_matrix( (distances.ravel(), indices.ravel(), indptr), shape=(n_samples_transform, self.n_samples_fit_), ) return kneighbors_graph def test_transformers(): """Test that AnnoyTransformer and KNeighborsTransformer give same results""" X = np.random.RandomState(42).randn(10, 2) knn = KNeighborsTransformer() Xt0 = knn.fit_transform(X) ann = AnnoyTransformer() Xt1 = ann.fit_transform(X) nms = NMSlibTransformer() Xt2 = nms.fit_transform(X) assert_array_almost_equal(Xt0.toarray(), Xt1.toarray(), decimal=5) assert_array_almost_equal(Xt0.toarray(), Xt2.toarray(), decimal=5) def load_mnist(n_samples): """Load MNIST, shuffle the data, and return only n_samples.""" mnist = fetch_openml("mnist_784", as_frame=False) X, y = shuffle(mnist.data, mnist.target, random_state=2) return X[:n_samples] / 255, y[:n_samples] def run_benchmark(): datasets = [ ("MNIST_2000", load_mnist(n_samples=2000)), ("MNIST_10000", load_mnist(n_samples=10000)), ] n_iter = 500 perplexity = 30 metric = "euclidean" # TSNE requires a certain number of neighbors which depends on the # perplexity parameter. # Add one since we include each sample as its own neighbor. n_neighbors = int(3.0 * perplexity + 1) + 1 tsne_params = dict( init="random", # pca not supported for sparse matrices perplexity=perplexity, method="barnes_hut", random_state=42, n_iter=n_iter, learning_rate="auto", ) transformers = [ ("AnnoyTransformer", AnnoyTransformer(n_neighbors=n_neighbors, metric=metric)), ( "NMSlibTransformer", NMSlibTransformer(n_neighbors=n_neighbors, metric=metric), ), ( "KNeighborsTransformer", KNeighborsTransformer( n_neighbors=n_neighbors, mode="distance", metric=metric ), ), ( "TSNE with AnnoyTransformer", make_pipeline( AnnoyTransformer(n_neighbors=n_neighbors, metric=metric), TSNE(metric="precomputed", **tsne_params), ), ), ( "TSNE with NMSlibTransformer", make_pipeline( NMSlibTransformer(n_neighbors=n_neighbors, metric=metric), TSNE(metric="precomputed", **tsne_params), ), ), ( "TSNE with KNeighborsTransformer", make_pipeline( KNeighborsTransformer( n_neighbors=n_neighbors, mode="distance", metric=metric ), TSNE(metric="precomputed", **tsne_params), ), ), ("TSNE with internal NearestNeighbors", TSNE(metric=metric, **tsne_params)), ] # init the plot nrows = len(datasets) ncols = np.sum([1 for name, model in transformers if "TSNE" in name]) fig, axes = plt.subplots( nrows=nrows, ncols=ncols, squeeze=False, figsize=(5 * ncols, 4 * nrows) ) axes = axes.ravel() i_ax = 0 for dataset_name, (X, y) in datasets: msg = "Benchmarking on %s:" % dataset_name print("\n%s\n%s" % (msg, "-" * len(msg))) for transformer_name, transformer in transformers: start = time.time() Xt = transformer.fit_transform(X) duration = time.time() - start # print the duration report longest = np.max([len(name) for name, model in transformers]) whitespaces = " " * (longest - len(transformer_name)) print("%s: %s%.3f sec" % (transformer_name, whitespaces, duration)) # plot TSNE embedding which should be very similar across methods if "TSNE" in transformer_name: axes[i_ax].set_title(transformer_name + "\non " + dataset_name) axes[i_ax].scatter( Xt[:, 0], Xt[:, 1], c=y.astype(np.int32), alpha=0.2, cmap=plt.cm.viridis, ) axes[i_ax].xaxis.set_major_formatter(NullFormatter()) axes[i_ax].yaxis.set_major_formatter(NullFormatter()) axes[i_ax].axis("tight") i_ax += 1 fig.tight_layout() plt.show() if __name__ == "__main__": test_transformers() run_benchmark() .. rst-class:: sphx-glr-timing **Total running time of the script:** ( 0 minutes 0.000 seconds) .. _sphx_glr_download_auto_examples_neighbors_approximate_nearest_neighbors.py: .. only:: html .. container:: sphx-glr-footer sphx-glr-footer-example .. container:: binder-badge .. image:: images/binder_badge_logo.svg :target: https://mybinder.org/v2/gh/scikit-learn/scikit-learn/1.1.X?urlpath=lab/tree/notebooks/auto_examples/neighbors/approximate_nearest_neighbors.ipynb :alt: Launch binder :width: 150 px .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: approximate_nearest_neighbors.py <approximate_nearest_neighbors.py>` .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: approximate_nearest_neighbors.ipynb <approximate_nearest_neighbors.ipynb>` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery <https://sphinx-gallery.github.io>`_