{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# How to use PyNNDescent\n", "\n", "PyNNDescent is a library that provides fast approximate nearest neighbor search. It is designed to be as flexible as possible for python users. That includes a wealth of pre-defined distance measures, the ability to use custom user-defined distance measures, as well as handling of sparse matrix inputs and more. Let's walk through how you can use PyNNDescent for approximate nearest neighbor search. First let's load the library, and some tools to get some suitable data." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import pynndescent\n", "import numpy as np\n", "import h5py\n", "from urllib.request import urlretrieve\n", "import os" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The [ann-benchmarks](http://ann-benchmarks.com) website, maintained by Erik Bernhardsson, Martin Aumueller and Alex Faitfull, provides a comprehensive suite of benchmarking for approximate nearest neighbor libraries and algorithms. For our purposes the important fact is that this includes keeping a variety of datasets, of varying levels of size and difficulty, for trying out nearest neighbor search on. To make things easy we'll write a short function that can fetch the (pre-prepared) dataset from ann-benchmarks and load it into train and test numpy arrays." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "def get_ann_benchmark_data(dataset_name):\n", " if not os.path.exists(f\"{dataset_name}.hdf5\"):\n", " print(f\"Dataset {dataset_name} is not cached; downloading now ...\")\n", " urlretrieve(f\"http://ann-benchmarks.com/{dataset_name}.hdf5\", f\"{dataset_name}.hdf5\")\n", " hdf5_file = h5py.File(f\"{dataset_name}.hdf5\", \"r\")\n", " return np.array(hdf5_file['train']), np.array(hdf5_file['test']), hdf5_file.attrs['distance']" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To start let's grab the [fashion-mnist](https://github.com/azlandoresearch/fashion-mnist) dataset for some initial experiments." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "fmnist_train, fmnist_test, _ = get_ann_benchmark_data('fashion-mnist-784-euclidean')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "With the dataset now loaded let's build a search index for the training set. This is done using the ``NNDescent`` class, which we simply hand the training data to (we'll look at other parameter options later). It will take a little time to build the index, so let's keep track using the ``%time`` magic in jupyter." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 48.5 s, sys: 1.07 s, total: 49.5 s\n", "Wall time: 27.7 s\n" ] } ], "source": [ "%%time\n", "index = pynndescent.NNDescent(fmnist_train)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Over ten seconds! That seems slow. How much data did we index, and how high dimensional is it?" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(60000, 784)" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "fmnist_train.shape" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Okay, so sixty-thousand samples living in a seven-hundred and eighty-four dimensional space -- that is a reasonable amount of data. We already had the data however, so why did we spend all that time building an index on top of it? It allows us to query that data to find the points closest to new, previously unseen data points. This is a surprisingly common problem -- \"find the other things that look like this\" -- that crops up in everything from recommendation systems (e.g. what are some other songs or artists that are like this song?), to classification and regression (using a [KNN-classifier](https://scikit-learn.org/stable/modules/neighbors.html#classification) and friends), to clustering (and density based clustering algorithm makes heavy use of near neighbor searches to determine density).\n", "\n", "So, given that we have built an index on the training data, we can use that index to find the nearest neighbors from the training set to each sample in the test set. Let's do that now for the first 10 samples of the test set, again, keeping track of the time:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 23.4 s, sys: 377 ms, total: 23.8 s\n", "Wall time: 18.3 s\n" ] } ], "source": [ "%%time\n", "neighbors = index.query(fmnist_test[:10])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "That was also slow! What happened? The first time the index is queried it does some book-keeping (that may not be required for some other tasks as we'll see below). There is also time for numba to JIT compile many routines in the background (you might find, for instance, that the very first run of index building might take longer than you expect, but subsequence runs are much faster). You can force this ahead of time by calling the ``prepare`` method. Let's rebuild the index from scratch including the ``prepare`` step to see how long that takes." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 47.3 s, sys: 743 ms, total: 48.1 s\n", "Wall time: 20 s\n" ] } ], "source": [ "%%time\n", "index = pynndescent.NNDescent(fmnist_train)\n", "index.prepare()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "That took longer, but how long do the queries take now? Let's query the entire test set." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 742 ms, sys: 17.6 ms, total: 759 ms\n", "Wall time: 758 ms\n" ] } ], "source": [ "%%time\n", "neighbors = index.query(fmnist_test)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "That's more like the sort of performance we might have been hoping for. And just to demonstrate that the prepare step is getting called on the first query, we'll start from scratch again." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 47.1 s, sys: 673 ms, total: 47.8 s\n", "Wall time: 19.4 s\n" ] } ], "source": [ "%%time\n", "index = pynndescent.NNDescent(fmnist_train)\n", "neighbors = index.query(fmnist_test[:10])" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 785 ms, sys: 21.3 ms, total: 806 ms\n", "Wall time: 821 ms\n" ] } ], "source": [ "%%time\n", "neighbors = index.query(fmnist_test)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Again, we get the sort of performance we might hope for by constructing a fast index over the data (and the numba JIT compiler is stating to warm up a little). There is a catch however -- at the start we said **approximate** nearest neighbor search. We might have gotten some neighbors data returned, but how good is it? Being fast doesn't matter if you return random results. To check we'll have to find the true nearest neighbors of the test set. Given that we have to search through sixty-thousand points for each of the ten-thousand test samples a pure brute force approach is going to be too expensive. Instead we can use [kd-trees](https://scikit-learn.org/stable/modules/generated/sklearn.neighbors.KDTree.html) from scikit-learn." ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "from sklearn.neighbors import KDTree" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The ``KDTree`` is scikit-learn has a very similar interface. You hand the tree constructor the training data, and then you can query it. In this case there is no prepare step required -- all the work is done at construction time since there aren't intermediate results that might be useful. We can time how long the ``KDTree`` takes to do its exact nearest neighbor computation to get a gauge on what PyNNDescent is buying us before we get to the issue of comparing the results of the exact and approximate algorithms." ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 15.2 s, sys: 177 ms, total: 15.3 s\n", "Wall time: 15.6 s\n" ] } ], "source": [ "%%time\n", "tree_index = KDTree(fmnist_train)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A faster build time for the tree itself. The question is how well the tree index works when querying new points..." ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 10min 19s, sys: 3.24 s, total: 10min 22s\n", "Wall time: 11min 12s\n" ] } ], "source": [ "%%time\n", "tree_neighbors = tree_index.query(fmnist_test, k=10)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Ouch! We can see now what PyNNDescent is buying us in terms of querying speed (though, to be fair, this is high dimensional data that is a worst case scenario for kd-trees). We did have to give up some accuracy to get that speed-up however. The real question is how much accuracy did we lose? How approximate are our nearest neighbors?\n", "\n", "We can write a short function to take some approximate neighbors and the true neighbors and output the proportion of neighbors that the apprxomation got correct for each and every query point (i.e. each point of the test set)." ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": [ "def accuracy_per_query_point(approx_neighbors, true_neighbors):\n", " approx_indices = approx_neighbors[0]\n", " true_indices = true_neighbors[1]\n", " result = np.zeros(approx_indices.shape[0])\n", " for i in range(approx_indices.shape[0]):\n", " n_correct = np.intersect1d(approx_indices[i], true_indices[i]).shape[0]\n", " result[i] = n_correct / true_indices.shape[1]\n", " return result" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [], "source": [ "accuracy_stats = accuracy_per_query_point(neighbors, tree_neighbors)" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [], "source": [ "import seaborn as sns\n", "import matplotlib.pyplot as plt\n", "\n", "sns.set(rc={\"figure.figsize\":(10,6)})" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Average accuracy of 0.9781\n" ] }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAl4AAAGECAYAAADnbC5SAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4yLjIsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy+WH4yJAAAgAElEQVR4nO3deXzU1b3/8fcwE4IYrAZnIKXIVStgEyUKaHGZAFoSlggGvGIQtK6gQgu3sRRSUlQUaRoUa7hdfNhWrRq1kBrTwYVKxahEXFga1NaAEDAZAtYsZEhmzu8Pf8wlskyizMlgXs/Hw0f8nu/5zvec+STO23NmEocxxggAAABR16WjBwAAANBZELwAAAAsIXgBAABYQvACAACwhOAFAABgCcELAADAEoIXEEU7duzQ2WefrfHjx2v8+PHKzMzU5MmTVVpaGu7z4IMPauXKlUd9nF//+td6+eWXD3vu4OsHDBigPXv2tGuMGzZs0IIFCyRJGzdu1KxZs9p1/VcRDAY1Y8YMpaen6/HHH4/6/RAb5s+fr7KysqP22b59u2bOnGlpRIB9ro4eAPBN161bNxUXF4ePq6qqdP3118vpdCo9PV0/+tGPIj7GW2+9pe9+97uHPdeW64/mX//6l6qrqyVJ55xzjpYtW/a1Hq8tqqurtXbtWr333ntyOp1Rvx9iw6JFiyL22blzpyorKy2MBugYBC/Asj59+mjWrFl65JFHlJ6errlz5+qss87SjTfeqGXLlumll15SXFycTjnlFN1333166aWXtGnTJi1ZskROp1OvvPKKPvvsM23fvl3Dhw9XbW1t+HpJeuCBB7Rx40aFQiH9+Mc/1ogRI/SXv/xFq1at0m9+8xtJCh//4he/0LJly1RXV6ef/exnmjBhgu6++26VlJSorq5OCxcu1JYtW+RwOHTppZdqzpw5crlcOuecc3TLLbfo9ddfV01NjW666SZlZ2cfMte3335bS5Ys0b59+xQXF6cf//jHOv/883XTTTeppaVFWVlZeuihh3TaaaeFr6msrNRdd92lhoYG+f1+DRw4UA888IDi4+P1/vvv65577gk/3p133qlhw4YdsX3AgAF64403lJiYKEnh448++kiLFi1S9+7d1dDQoOeee05LlizR+++/r4aGBhljdM8992jw4MFqaGjQPffco3feeUdOp1OXX365pk+frrS0NBUVFen000+XJF1//fW69tprdfnll4fn8tZbbyk/P1/f/va39fHHH6tbt25avHixzjzzTO3fv1/5+fkqLy9XMBjU9773PeXm5iohIUEjR47Uueeeqw8++EBz5szRD37wg/Bj1tXVKTc3V1u2bJHH41FSUpL69u2rmTNnauTIkXrwwQd1zjnnSFKr43feeUf5+fnat2+funTpojvuuCP8vfHss89q3759SkhIkMvl0ujRo/Xf//3fkqTCwkJ99tlnmjdvXqvafu9739PNN9+s1157TY2NjZozZ45GjRolSXr44Yf1wgsvyOl06vTTT9fPf/5zud1uTZ06VVOmTFFKSoquv/56paWl6f3339fnn3+unJwcjRw5Urm5uaqurtaNN96oRx555Ov9sAGxyACImu3bt5vU1NRD2j/88EMzaNAgY4wxP/3pT83vf/97s3PnTnP++eebQCBgjDHmkUceMS+99JIxxphrr73W/O1vfwv3v+6668KPdeB6Y4zp37+/+c1vfmOMMeaDDz4wF1xwgamtrTXPPfecueWWW8LXHHx88L+/+eabZuzYscYYY+68805z9913m1AoZAKBgLnhhhvCj92/f3/z2GOPGWOM2bhxo0lJSTFNTU2t5rhnzx4zbNgw895774XnfMEFF5hPPvnkiM+LMcYsXrzYrFy50hhjzP79+824ceOMz+cz+/fvNxdffLH5+9//Hr7vuHHjTCAQOGx7MBg0/fv3N7W1teHHPnD85ptvmoEDB5odO3YYY4x55513zMyZM00wGDTGGPOb3/zG3HrrrcYYY+69914ze/Zs09LSYgKBgJkyZYp58803zT333GPuv/9+Y4wx27ZtM2lpaaalpaXVXA7cp7y83BhjzJ///Gdz5ZVXGmOMeeihh8zixYtNKBQyxhjzq1/9yuTl5RljjBkxYoT59a9/fdjn5+677zY5OTkmFAoZv99vLr30UrNs2bLwdRs2bAj3PXD82WefmVGjRpnt27cbY4z59NNPjdfrNVVVVea5554zQ4cONXV1dcYYY1566SUzceJEY4wxwWDQjBgxwvz73/8+ZBz9+/c3y5cvN8YYU1FRYQYPHmxqa2vNs88+a66++mrT0NBgjDFm2bJl5oYbbjDG/N/38fbt203//v3N6tWrjTHG+Hw+M3z48PBzduB7EPgmYsUL6AAOh0PdunVr1darVy8NHDhQV155pbxer7xer4YNG3bY6wcPHnzEx77mmmskSf3799eZZ56pd9999yuN8R//+IeefPJJORwOde3aVZMnT9Yf//hH3XLLLZKkyy67TJKUnJys/fv3q7GxUfHx8eHrN2zYoNNOO02DBg2SJJ111lk6//zztW7dOl144YVHvG9OTo5ef/11/e53v9PWrVtVU1OjxsZGffjhh+rSpYuGDx8uSUpJSdHzzz+vzZs3H7Y9kqSkJPXp00eSdN555+lb3/qWnnrqKW3fvl1vvfWWTjzxRElSWVmZfvazn8npdMrpdIbfk+bxeHTttddq9uzZevrppzVp0qTDbpsOHDhQQ4YMkSRNnDhRd911l/bu3atXX31VdXV14fc8NTc3q2fPnuHrDlzzZW+++abmz58vh8OhU089Venp6RHn+t5778nv9+v2228PtzkcDn3wwQeSvlgJTEhIkCSNGDFCixYt0pYtW1RdXa3vfOc7OuOMMw77uNdee214jv3791d5ebn+8Y9/KCsrS927d5ckTZs2Tf/7v/+r/fv3t7o2Li5OaWlpkr5YPfvss88izgP4JiB4AR1g48aN6t+/f6u2Ll266PHHH9fGjRv1xhtv6N5779Wll16qO++885DrD7yoHU6XLv/3mZlQKCSXyyWHwyFz0J9lbW5ujjjGUCgkh8PR6rilpSV8fCBkHehjvvRnX4PBYKvrD/Q5+DEOZ86cOQoGgxo9erSGDx+uXbt2yRgjp9N5yON9+OGHR2z/clj48gv/wc/hq6++qkWLFumHP/yhLrvsMp1xxhn661//Kknh5++AXbt2qVu3bjr99NM1YMAAvfLKKyopKVFRUdFh53O4MOZ0OhUKhTRv3rxw+GhoaFAgEDjs+A4WHx/f6rmOi4trdf7gcwfmHAwGdeaZZ+qZZ54Jn6uurlZiYqKef/75VvdyOp26+uqr9eyzz6qmpkaTJ08+7Di+PLdQKBSe19G+bw4e94Hv1S/XD/gm41ONgGWVlZUqLCzUDTfc0Kp9y5YtGjdunM4880zdeuutuv7667Vx40ZJX7zARQosB6xYsUKStHnzZn3yyScaNGiQEhMT9dFHHykQCKi5uVmrVq0K9z/SY19yySV6/PHHZYzR/v37VVRUpIsuuqjN80xNTdXHH3+sDRs2SJI++ugjlZeX64ILLjjqdWvXrtXtt9+uMWPGSJLef/99BYNBnXHGGXI4HHr99dfD87vuuuuO2B4KhZSYmBh+DktKSo54z9dff10jRoxQdna2UlJS9PLLLysYDEqShg0bphUrVigUCmn//v2aNWuWysvLJUnZ2dlasmSJzj33XPXq1euwj71lyxZt2bJFkvT000/rvPPO00knnaRLLrlETzzxhPbv369QKKSf//znKigoiPi8Dh8+XEVFRQoGg6qrq9Mrr7wSPpeYmKhNmzZJ+uL9ZX6/X9IXtdi2bVt43BUVFUpPTw9/qOLLrrrqKr388svavHlzq/eXfdmBT9Nu3rxZlZWVGjp0qC699FI999xzamxslCQ99thjGjp0qLp27RpxbtIX349t+R8D4HjFihcQZU1NTRo/frykL1aj4uPjNWfOnPDW2AEDBw7U6NGjNXHiRHXv3l3dunVTbm6upC/eJF1QUNCmF6Tt27drwoQJcjgcKigo0Mknn6yLL75YQ4cO1ejRo+V2u3XhhReGt5lSU1P18MMP64477tDUqVPDj5Obm6t77rlHmZmZam5u1qWXXqrp06e3ed6JiYl68MEHdffdd6upqUkOh0P33XefTj/9dO3YseOI182ePVu33367unfvroSEBA0dOlSffPKJunbtqoceekj33nuvlixZori4OD300ENHbc/NzdVdd92lk046SRdddJHcbvdh7zl58mT9z//8jzIzM9XS0qKLL75YL774okKhkO644w4tWrRI48ePVzAY1JgxY8JvIh8xYoRyc3OPuip06qmn6oEHHlBVVZUSExO1ZMkSSdJtt92m+++/X1deeaWCwaDOPvtszZ07N+Lzesstt+jee+/VFVdcoZNOOqnVnH7yk5/oF7/4hZ5++mklJycrOTk5XItly5ZpyZIlCgQCMsZoyZIl+s53vqN169Ydco+ePXsqJSVFZ5555iEragd75513VFRUpFAopKVLl+pb3/qWJk2apF27dumqq65SKBRSv379lJ+fH3FeB3z3u99VfHy8Jk2apGeeeYbVMHzjOMyX9wcAAG3y7rvvKjc3VyUlJYcNCG+99Vb4U6LRctddd+mUU045pr/7as+ePZo0aZKeeOIJJSUlHbbPlz8xCqBtWPECgK/gpz/9qdatW6elS5d+o1ZlioqKVFBQoJkzZx4xdAH46ljxAgAAsIQ31wMAAFhC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L730kgYOHKhNmzbp3//+d0cPBwC+MoIXgJj35JNP6rLLLtOYMWP0xz/+Mdz+7LPPauzYscrMzNS0adO0a9euI7a/9dZbGjduXPjag48feugh3XjjjcrMzNRPfvIT7d69W7fddpuuvvpqjRw5UlOnTlVtba0kqbKyUlOnTg0/fmlpqdavX6/hw4crFApJkvbt26dhw4Zpz549tp4iAMcJgheAmPavf/1L7777rjIyMjRhwgQVFxdr79692rJli/Lz8/X73/9ezz//vEaOHKnly5cfsT2SqqoqrVixQvn5+XrhhReUmpqqp59+Wq+88oq6deum4uJiSdKcOXOUkZGhF154Qb/97W9VUFCgAQMG6Fvf+pZee+01SdILL7ygYcOGKTExMarPDYDjT6f6k0EAjj9PPvmkRowYoVNOOUWnnHKKvvOd76ioqEhdu3bVJZdcoqSkJEnS9ddfL0l69NFHD9v+1ltvHfU+qampcrm++E/iddddp7fffluPPvqotm7dqo8++kiDBg3SZ599pi1btuiqq66SJCUlJenll1+WJE2ZMkVFRUVKS0vT008/rTvvvPNYPxUAvgEIXgBiVmNjo4qLi9W1a1eNHDlSklRfX6/HH39cN910U6s/mN7U1KSqqio5nc7DtjscDh38F9Kam5tb3at79+7hf//lL3+pDRs2aOLEibrwwgvV0tIiY0w4mB38+B9//LG+/e1vKzMzUwUFBXrzzTfV2NiooUOHHtsnA8A3AluNAGLW888/r5NPPlmvvfaaVq9erdWrV+vll19WY2Oj6urq9MYbb6impkaS9NRTT+mXv/ylLrzwwsO2JyYmaufOnaqtrZUxRi+88MIR77t27Vpdd911mjBhgnr27KmysjIFg0ElJCQoOTlZK1eulCTt2rVL11xzjerq6nTCCSfoiiuu0Lx58zR58uToPzkAjkuseAGIWU8++aR++MMfyul0httOOukkTZ06VX//+9+Vk5MT/hUTbrdb9957r3r16nXE9smTJ2vixIlyu90aPny4Nm7ceNj73n777VqyZIkefPBBxcXF6fzzz9cnn3wiSfrVr36lhQsX6rHHHpPD4dCiRYvkdrslSVlZWSoqKtKECROi+bQAOI45zMFr7wCAr8QYo9/97neqqqrSwoULO3o4AGIUK14AcAxcdtll8ng8Kiws7OihAIhhrHgBAABYwpvrAQAALCF4AQAAWELwAgAAsITgBQAAYAnBCwAAwBKCFwAAgCX/DyXbdJ4USXrfAAAAAElFTkSuQmCC\n", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "sns.distplot(accuracy_stats, kde=False)\n", "plt.title(\"Distribution of accuracy per query point\")\n", "plt.xlabel(\"Accuracy\")\n", "print(f\"Average accuracy of {np.mean(accuracy_stats)}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So we are getting 100% accuracy for the vast majority of query points, but there are a thousand or so that get one neighbor wrong, and a very few that get two or three wrong. Even then we are likely getting the eleventh and twelfth nearest neighbors in those cases. So in general we have a very high accuracy given how much faster the query is.\n", "\n", "## Query parameters\n", "\n", "What if we want to be more accurate? There are a few ways to go about this. The most obvious is to simply ask for more neighbors, and then the top 10 will be more accurate. We can do this by specifying ``k`` in the query (just as we did for the KDTree -- it is just that PyNNDescent defaults to 10 neighbors)." ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 1.1 s, sys: 486 ms, total: 1.59 s\n", "Wall time: 1.99 s\n" ] } ], "source": [ "%%time\n", "more_neighbors = index.query(fmnist_test, k=15)" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Average accuracy of 0.99021\n" ] }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAl4AAAGECAYAAADnbC5SAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4yLjIsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy+WH4yJAAAgAElEQVR4nO3de1yUdd7/8fcIiLlYiQ3KmvkoSy0wrTTXVBBrBQ94QLvzkOaWa5qHTe80U1bXPGTEYmnhXVuP7r3NbaVMWZXFPJSb4YHs4CFNdz2GCuNpQ07CzPf3Rz9nxdTBki9jvJ7/2FxzzVzfaz4Sr64ZwmGMMQIAAEClq1HVCwAAAKguCC8AAABLCC8AAABLCC8AAABLCC8AAABLCC8AAABLCC+gEn377be688471atXL/Xq1Uvx8fHq37+/MjIyvPu88sorWrZs2WWf59VXX9WaNWsuet/5j2/WrJlOnjx5RWvctm2bpk6dKknavn27xo4de0WP/zHcbrdGjhyp2NhYvfPOO5V+PPiHKVOmKCsr67L7HD58WGPGjLG0IsC+wKpeAPBzV6tWLaWnp3tv5+TkaOjQoQoICFBsbKx+97vf+XyOzZs36/bbb7/ofRV5/OX885//VG5uriSpRYsWmjdv3k96vorIzc3Vhg0b9OWXXyogIKDSjwf/MGvWLJ/7HDlyRPv377ewGqBqEF6AZQ0bNtTYsWP11ltvKTY2VpMmTdIdd9yhJ554QvPmzdPq1asVFBSkunXr6oUXXtDq1au1Y8cOJSUlKSAgQGvXrtXp06d1+PBhderUSSdOnPA+XpJefvllbd++XR6PR08//bRiYmL0wQcfaNWqVXr99dclyXv7D3/4g+bNm6f8/Hw999xz6t27t2bMmKEVK1YoPz9f06dP1+7du+VwONSxY0eNHz9egYGBatGihYYPH65PP/1UeXl5GjZsmAYOHPiDc/3ss8+UlJSkoqIiBQUF6emnn9a9996rYcOGqaysTAkJCZo/f75uueUW72P279+v559/XgUFBXK5XGrevLlefvllBQcH66uvvtLMmTO9zzdx4kS1a9fuktubNWumjRs3KjQ0VJK8t/fu3atZs2apdu3aKigo0JIlS5SUlKSvvvpKBQUFMsZo5syZuu+++1RQUKCZM2fq888/V0BAgB566CGNGDFC0dHRSktL06233ipJGjp0qB599FE99NBD3nPZvHmzkpOT9ctf/lL79u1TrVq1NGfOHDVp0kRnz55VcnKysrOz5Xa7dddddykxMVEhISHq3Lmz7r77bn3zzTcaP368fv3rX3ufMz8/X4mJidq9e7fCwsIUHh6uRo0aacyYMercubNeeeUVtWjRQpLK3f7888+VnJysoqIi1ahRQ6NHj/b+3Xj//fdVVFSkkJAQBQYGqmvXrvqv//ovSVJqaqpOnz6tyZMnl5vtXXfdpd/+9rf65JNPVFhYqPHjx6tLly6SpNdee00rV65UQECAbr31Vv3+97+X0+nU4MGDNWjQIEVGRmro0KGKjo7WV199pe+++04TJkxQ586dlZiYqNzcXD3xxBN66623ftoXG+CPDIBKc/jwYdOqVasfbN+zZ49p2bKlMcaYZ5991rz55pvmyJEj5t577zUlJSXGGGPeeusts3r1amOMMY8++qj5+9//7t3/scce8z7XuccbY0zTpk3N66+/bowx5ptvvjH333+/OXHihFmyZIkZPny49zHn3z7/nzdt2mS6d+9ujDFm4sSJZsaMGcbj8ZiSkhLz+OOPe5+7adOmZuHChcYYY7Zv324iIyNNcXFxuXM8efKkadeunfnyyy+953z//febQ4cOXfJ1McaYOXPmmGXLlhljjDl79qzp0aOHyczMNGfPnjXt27c3H330kfe4PXr0MCUlJRfd7na7TdOmTc2JEye8z33u9qZNm0zz5s3Nt99+a4wx5vPPPzdjxowxbrfbGGPM66+/bp588kljjDGzZ88248aNM2VlZaakpMQMGjTIbNq0ycycOdO8+OKLxhhjDh48aKKjo01ZWVm5czl3nOzsbGOMMX/5y19Mnz59jDHGzJ8/38yZM8d4PB5jjDF//OMfzbRp04wxxsTExJhXX331oq/PjBkzzIQJE4zH4zEul8t07NjRzJs3z/u4bdu2efc9d/v06dOmS5cu5vDhw8YYY44dO2aioqJMTk6OWbJkiWnTpo3Jz883xhizevVq07dvX2OMMW6328TExJh//etfP1hH06ZNzYIFC4wxxuzatcvcd9995sSJE+b99983jzzyiCkoKDDGGDNv3jzz+OOPG2P+8/f48OHDpmnTpmbdunXGGGMyMzNNp06dvK/Zub+DwM8RV7yAKuBwOFSrVq1y2+rXr6/mzZurT58+ioqKUlRUlNq1a3fRx993332XfO4BAwZIkpo2baomTZroiy+++FFr/Mc//qF3331XDodDNWvWVP/+/fXnP/9Zw4cPlyQ9+OCDkqSIiAidPXtWhYWFCg4O9j5+27ZtuuWWW9SyZUtJ0h133KF7771XW7ZsUdu2bS953AkTJujTTz/Vn/70Jx04cEB5eXkqLCzUnj17VKNGDXXq1EmSFBkZqeXLl2vnzp0X3e5LeHi4GjZsKEm65557dMMNN+ivf/2rDh8+rM2bN+sXv/iFJCkrK0vPPfecAgICFBAQ4P1MWlhYmB599FGNGzdOixcvVr9+/S76tmnz5s3VunVrSVLfvn31/PPP69SpU/r444+Vn5/v/cxTaWmp6tWr533cucdcaNOmTZoyZYocDoduuukmxcbG+jzXL7/8Ui6XS6NGjfJuczgc+uabbyR9fyUwJCREkhQTE6NZs2Zp9+7dys3N1c0336zbbrvtos/76KOPes+xadOmys7O1j/+8Q8lJCSodu3akqQhQ4bof/7nf3T27Nlyjw0KClJ0dLSk76+enT592ud5AD8HhBdQBbZv366mTZuW21ajRg2988472r59uzZu3KjZs2erY8eOmjhx4g8ef+6b2sXUqPGfn5nxeDwKDAyUw+GQOe/XspaWlvpco8fjkcPhKHe7rKzMe/tcZJ3bx1zwa1/dbne5x5/b5/znuJjx48fL7Xara9eu6tSpk44ePSpjjAICAn7wfHv27Lnk9gtj4cJv/Oe/hh9//LFmzZql3/zmN3rwwQd122236W9/+5skeV+/c44ePapatWrp1ltvVbNmzbR27VqtWLFCaWlpFz2fi8VYQECAPB6PJk+e7I2PgoIClZSUXHR95wsODi73WgcFBZW7//z7zp2z2+1WkyZN9N5773nvy83NVWhoqJYvX17uWAEBAXrkkUf0/vvvKy8vT/3797/oOi48N4/H4z2vy/29OX/d5/6uXjg/4OeMn2oELNu/f79SU1P1+OOPl9u+e/du9ejRQ02aNNGTTz6poUOHavv27ZK+/wbnK1jOWbp0qSRp586dOnTokFq2bKnQ0FDt3btXJSUlKi0t1apVq7z7X+q5O3TooHfeeUfGGJ09e1ZpaWl64IEHKnyerVq10r59+7Rt2zZJ0t69e5Wdna3777//so/bsGGDRo0apW7dukmSvvrqK7ndbt12221yOBz69NNPvef32GOPXXK7x+NRaGio9zVcsWLFJY/56aefKiYmRgMHDlRkZKTWrFkjt9stSWrXrp2WLl0qj8ejs2fPauzYscrOzpYkDRw4UElJSbr77rtVv379iz737t27tXv3bknS4sWLdc899+j6669Xhw4dtGjRIp09e1Yej0e///3vlZKS4vN17dSpk9LS0uR2u5Wfn6+1a9d67wsNDdWOHTskff/5MpfLJen7WRw8eNC77l27dik2Ntb7QxUXevjhh7VmzRrt3Lmz3OfLLnTup2l37typ/fv3q02bNurYsaOWLFmiwsJCSdLChQvVpk0b1axZ0+e5Sd//fazIfxgA1yqueAGVrLi4WL169ZL0/dWo4OBgjR8/3vvW2DnNmzdX165d1bdvX9WuXVu1atVSYmKipO8/JJ2SklKhb0iHDx9W79695XA4lJKSohtvvFHt27dXmzZt1LVrVzmdTrVt29b7NlOrVq302muvafTo0Ro8eLD3eRITEzVz5kzFx8ertLRUHTt21IgRIyp83qGhoXrllVc0Y8YMFRcXy+Fw6IUXXtCtt96qb7/99pKPGzdunEaNGqXatWsrJCREbdq00aFDh1SzZk3Nnz9fs2fPVlJSkoKCgjR//vzLbk9MTNTzzz+v66+/Xg888ICcTudFj9m/f3/993//t+Lj41VWVqb27dvrww8/lMfj0ejRozVr1iz16tVLbrdb3bp1836IPCYmRomJiZe9KnTTTTfp5ZdfVk5OjkJDQ5WUlCRJeuqpp/Tiiy+qT58+crvduvPOOzVp0iSfr+vw4cM1e/Zs9ezZU9dff325c3rmmWf0hz/8QYsXL1ZERIQiIiK8s5g3b56SkpJUUlIiY4ySkpJ08803a8uWLT84Rr169RQZGakmTZr84Ira+T7//HOlpaXJ4/Fo7ty5uuGGG9SvXz8dPXpUDz/8sDwejxo3bqzk5GSf53XO7bffruDgYPXr10/vvfceV8Pws+MwF74/AACokC+++EKJiYlasWLFRQNh8+bN3p8SrSzPP/+86tate1X/31cnT55Uv379tGjRIoWHh190nwt/YhRAxXDFCwB+hGeffVZbtmzR3Llzf1ZXZdLS0pSSkqIxY8ZcMroA/Hhc8QIAALCED9cDAABYQngBAABYQngBAABYQngBAABYcs38VOOpUwXyeCr35wDq1QvRiRNnKvUYuDLMxP8wE//EXPwPM/FPlT2XGjUcqlv3F5e8/5oJL4/HVHp4nTsO/Asz8T/MxD8xF//DTPxTVc6FtxoBAAAsIbwAAAAsIbwAAAAsIbwAAAAsIbwAAAAsIbwAAAAsIbwAAAAsIbwAAAAsIbwAAAAsIbwAAAAsIbwAAAAsIbwAAAAsIbwAAAAsCazqBQAAAPxUZR6ppLTM5361Cs9aWM2lEV4AAOCaV1JapuxduT73i77vFjksrOdSeKsRAADAEsILAADAEsILAADAEsILAADAEsILAADAEsILAADAEsILAADAEsILAADAEsILAADAEsILAADAEsILAADAEsILAADAEsILAADAEsILAADAEsILAADAEsILAADAEsILAADAEsILAADAEsILAADAEsILAADAEsILAADAEsILAADAEsILAADAEsILAADAEsILAADAEsILAADAEsILAADAEsILAADAEsILAADAEsILAADAEsILAADAEsILAADAEsILAADAEsILAADAEsILAADAkgqFV3p6urp3767u3bvrxRdflCRlZWUpPj5eXbp00dy5c7377tq1SwkJCYqNjdWUKVNUVlYmSTpy5IgGDRqkuLg4jRw5UgUFBZVwOgAAAP7LZ3gVFRVp1qxZWrhwodLT0/XZZ59p3bp1mjx5slJTU5WRkaEdO3Zo/fr1kqQJEyZo6tSpWrVqlYwxSktLkyRNnz5dAwcOVGZmpiIjI5Wamlq5ZwYAAOBnfIaX2+2Wx+NRUVGRysrKVFZWppCQEDVu3FiNGjVSYGCg4uPjlZmZqZycHBUXF6tVq1aSpISEBGVmZqq0tFTZ2dmKjY0ttx0AAKA6CfS1Q0hIiH73u9+pa9euuu6669SmTRvl5eXJ6XR69wkLC1Nubu4PtjudTuXm5urUqVMKCQlRYGBgue1Xol69kCva/8dyOutYOQ4qjpn4H2bin5iL/2Em9piThaoTUqtC+1blXHyG1+7du7VkyRJ99NFHqlOnjp555hkdOHBADofDu48xRg6HQx6P56Lbz/15vgtv+3LixBl5POaKHnOlnM46crnyK/UYuDLMxP8wE//EXPwPM7GrsKRM+WeKK7RvZc6lRg3HZS8W+XyrccOGDWrXrp3q1aunmjVrKiEhQZs3b5bL5fLu43K5FBYWpgYNGpTbfvz4cYWFhSk0NFT5+flyu93l9gcAAKhOfIZX8+bNlZWVpcLCQhljtG7dOrVs2VL79+/XwYMH5Xa7tWLFCkVFRalhw4YKDg7W1q1bJX3/05BRUVEKCgpS69atlZGRIUlatmyZoqKiKvfMAAAA/IzPtxo7dOigr7/+WgkJCQoKClKLFi00ZswYtW/fXmPGjFFJSYmio6MVFxcnSUpOTlZiYqLOnDmjiIgIDRkyRJI0bdo0TZo0SQsWLFB4eLhSUlIq98wAAAD8jMMYU7kfnLpK+IxX9cRM/A8z8U/Mxf8wE7sKSsqUvcv3D+5F33eLHP//o0+V4Sd/xgsAAABXB+EFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgCeEFAABgSYXCa926dUpISFDXrl01c+ZMSVJWVpbi4+PVpUsXzZ0717vvrl27lJCQoNjYWE2ZMkVlZWWSpCNHjmjQoEGKi4vTyJEjVVBQUAmnAwAA4L98htfhw4c1bdo0paam6m9/+5u+/vprrV+/XpMnT1ZqaqoyMjK0Y8cOrV+/XpI0YcIETZ06VatWrZIxRmlpaZKk6dOna+DAgcrMzFRkZKRSU1Mr98wAAAD8jM/wWr16tbp166YGDRooKChIc+fO1XXXXafGjRurUaNGCgwMVHx8vDIzM5WTk6Pi4mK1atVKkpSQkKDMzEyVlpYqOztbsbGx5bYDAABUJ4G+djh48KCCgoI0YsQIHT16VJ06ddIdd9whp9Pp3ScsLEy5ubnKy8srt93pdCo3N1enTp1SSEiIAgMDy20HAACoTnyGl9vt1meffaaFCxeqdu3aGjlypGrVqiWHw+Hdxxgjh8Mhj8dz0e3n/jzfhbd9qVcv5Ir2/7GczjpWjoOKYyb+h5n4J+bif5iJPeZkoeqE1KrQvlU5F5/hddNNN6ldu3YKDQ2VJD300EPKzMxUQECAdx+Xy6WwsDA1aNBALpfLu/348eMKCwtTaGio8vPz5Xa7FRAQ4N3/Spw4cUYej7mix1wpp7OOXK78Sj0Grgwz8T/MxD8xF//DTOwqLClT/pniCu1bmXOpUcNx2YtFPj/jFRMTow0bNui7776T2+3WJ598ori4OO3fv18HDx6U2+3WihUrFBUVpYYNGyo4OFhbt26VJKWnpysqKkpBQUFq3bq1MjIyJEnLli1TVFTUVTpFAACAa4PPK14tW7bUsGHDNHDgQJWWlqp9+/YaMGCAbrvtNo0ZM0YlJSWKjo5WXFycJCk5OVmJiYk6c+aMIiIiNGTIEEnStGnTNGnSJC1YsEDh4eFKSUmp3DMDAADwMw5jTOW+f3eV8FZj9cRM/A8z8U/Mxf8wE7sKSsqUvcv3D+5F33eLHG53pa3jJ7/VCAAAgKuD8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCE8AIAALCkwuH14osvatKkSZKkrKwsxcfHq0uXLpo7d653n127dikhIUGxsbGaMmWKysrKJElHjhzRoEGDFBcXp5EjR6qgoOAqnwYAAID/q1B4bdy4UUuXLpUkFRcXa/LkyUpNTVVGRoZ27Nih9evXS5ImTJigqVOnatWqVTLGKC0tTZI0ffp0DRw4UJmZmYqMjFRqamolnQ4AAID/8hlep0+f1ty5czVixAhJ0rZt29S4cWM1atRIgYGBio+PV2ZmpnJyclRcXKxWrVpJkhISEpSZmanS0lJlZ2crNja23HYAAIDqxmd4TZ06VePGjdP1118vScrLy5PT6fTeHxYWptzc3B9sdzqdys3N1alTpxQSEqLAwMBy2wEAAKqbwMvd+d577yk8PFzt2rXTBx98IEnyeDxyOBzefYwxcjgcl9x+7s/zXXi7IurVC7nix/wYTmcdK8dBxTET/8NM/BNz8T/MxB5zslB1QmpVaN+qnMtlwysjI0Mul0u9evXSv//9bxUWFionJ0cBAQHefVwul8LCwtSgQQO5XC7v9uPHjyssLEyhoaHKz8+X2+1WQECAd/8rdeLEGXk85oofdyWczjpyufIr9Ri4MszE/zAT/8Rc/A8zsauwpEz5Z4ortG9lzqVGDcdlLxZd9q3Gt99+WytWrFB6errGjh2rzp07680339T+/ft18OBBud1urVixQlFRUWrYsKGCg4O1detWSVJ6erqioqIUFBSk1q1bKyMjQ5K0bNkyRUVFXcVTBAAAuDZc9orXxQQHB2vOnDkaM2aMSkpKFB0drbi4OElScnKyEhMTdebMGUVERGjIkCGSpGnTpmnSpElasGCBwsPDlZKScnXPAgAA4BrgMMZU7vt3VwlvNVZPzMT/MBP/xFz8DzOxq6CkTNm7fP/wXvR9t8jhdlfaOn7SW40AAAC4eggvAAAASwgvAAAASwgvAAAASwgvAAAASwgvAAAASwgvAAAASwgvAAAASwgvAAAASwgvAAAASwgvAAAASwgvAAAASwgvAAAASwgvAAAASwgvAAAASwgvAAAASwgvAAAASwgvAAAASwgvAAAASwgvAAAASwgvAAAASwgvAAAASwgvAAAASwgvAAAASwgvAAAASwgvAAAASwgvAAAASwgvAAAASwgvAAAASwgvAAAASwgvAAAASwgvAAAASwgvAAAASwgvAAAASwgvAAAASwgvAAAASwgvAAAASwgvAAAASwgvAAAASwgvAAAASwgvAAAASwgvAAAASwgvAAAASwgvAAAASwgvAAAASwgvAAAASwgvAAAASwgvAAAASwgvAAAASwgvAAAASwgvAAAASwgvAAAASwgvAAAASwgvAAAASwgvAAAASwgv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\n", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "more_accuracy_stats = accuracy_per_query_point(more_neighbors, tree_neighbors)\n", "sns.distplot(more_accuracy_stats, kde=False)\n", "plt.title(\"Distribution of accuracy per query point\")\n", "plt.xlabel(\"Accuracy\")\n", "print(f\"Average accuracy of {np.mean(more_accuracy_stats)}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "An alternative approach is to tolerate a little more backtracking in the approximate search. This is controlled by the parameter ``epsilon``. The larger the value the more backtracking the algorithm will tolerate, and the more accurate it will be (at the cost of greater search time). The default value is ``0.1`` and for euclidean distance going to ``0.2`` or even ``0.3`` might make sense. You can, of course, also turn it down to ``0.0`` and do *no* backtracking for even faster search (but reduced accuracy). Let's try both options; greater accuracy first." ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 1.35 s, sys: 18.2 ms, total: 1.37 s\n", "Wall time: 1.39 s\n" ] } ], "source": [ "%%time\n", "better_neighbors = index.query(fmnist_test, epsilon=0.2)" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Average accuracy of 0.9965\n" ] }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAmQAAAGECAYAAACYvTyjAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4yLjIsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy+WH4yJAAAgAElEQVR4nO3de1yUdaLH8e8ICBlaYTNJZp6yzMKSSitLQawVFMnCOnlJa81TWWmrm0XKylpaZiyWtbjdXu2ectsoU5II0yzLMCW7eFktd/NCaDB4KS6CMPM7f7jOES+BOPBz8vP+R5/f/J55fs9XxK/PM8M4jDFGAAAAsKaF7QUAAACc7ChkAAAAllHIAAAALKOQAQAAWEYhAwAAsIxCBgAAYBmFDLDghx9+0MUXX6xBgwZp0KBBSkpK0pAhQ5Sbm+ub8+yzz2rBggW/+DzPP/+8lixZcsTHDt7/oosu0q5du45pjWvWrNGUKVMkSWvXrtW4ceOOaf/G8Hg8GjNmjOLj4/X66683+fFwYpg8ebLy8/N/cU5hYaHGjh3bTCsCml+w7QUAJ6uwsDBlZ2f7touKinTnnXcqKChI8fHxevDBB+t9jpUrV+qCCy444mMN2f+X/Otf/1JxcbEk6dJLL9Xs2bOP6/kaori4WMuXL9fXX3+toKCgJj8eTgzTp0+vd8727du1efPmZlgNYAeFDDhBtG/fXuPGjdMrr7yi+Ph4paSk6MILL9Rdd92l2bNna/HixQoJCdEZZ5yhJ598UosXL9a6des0c+ZMBQUF6cMPP9SePXtUWFioPn36aOfOnb79JemZZ57R2rVr5fV69bvf/U5xcXF65513tGjRIr3wwguS5Nv+4x//qNmzZ6usrEyPPvqobrrpJj3++OPKyclRWVmZpk6dqo0bN8rhcKh3796aMGGCgoODdemll+ruu+/WZ599ppKSEo0ePVrDhg077Fy/+OILzZw5U3v37lVISIh+97vf6YorrtDo0aNVW1ur5ORkPffcczr33HN9+2zevFmPPfaYKioq5Ha71aVLFz3zzDMKDQ3VN998o2nTpvme7+GHH1bPnj2POn7RRRdpxYoVioiIkCTf9qZNmzR9+nS1atVKFRUVmjdvnmbOnKlvvvlGFRUVMsZo2rRpuvLKK1VRUaFp06bpyy+/VFBQkG644Qbde++9io2NVVZWls477zxJ0p133qnbb79dN9xwg+9cVq5cqfT0dJ199tn6/vvvFRYWphkzZqhTp07at2+f0tPTVVBQII/Ho0suuUSpqakKDw9X3759ddlll+nbb7/VhAkT9Jvf/Mb3nGVlZUpNTdXGjRvlcrkUGRmpDh06aOzYserbt6+effZZXXrppZJUZ/vLL79Uenq69u7dqxYtWuiBBx7wfW28/fbb2rt3r8LDwxUcHKz+/fvrv//7vyVJmZmZ2rNnjyZNmlTnz/aSSy7R//zP/+jTTz9VZWWlJkyYoH79+kmS/vznP+u9995TUFCQzjvvPP3hD3+Q0+nUiBEjNHz4cHXt2lV33nmnYmNj9c033+jnn3/WxIkT1bdvX6Wmpqq4uFh33XWXXnnlleP7ywaciAyAZldYWGiio6MPG//uu+9Mt27djDHGPPLII+bll18227dvN1dccYWprq42xhjzyiuvmMWLFxtjjLn99tvN+++/75t/xx13+J7rwP7GGNO5c2fzwgsvGGOM+fbbb81VV11ldu7caebNm2fuvvtu3z4Hbx/8+88//9wkJiYaY4x5+OGHzeOPP268Xq+prq42o0aN8j13586dzWuvvWaMMWbt2rWma9eupqqqqs457tq1y/Ts2dN8/fXXvnO+6qqrzLZt246aizHGzJgxwyxYsMAYY8y+ffvMwIEDTV5entm3b5+57rrrzEcffeQ77sCBA011dfURxz0ej+ncubPZuXOn77kPbH/++eemS5cu5ocffjDGGPPll1+asWPHGo/HY4wx5oUXXjD33HOPMcaYJ554wowfP97U1taa6upqM3z4cPP555+badOmmaeeesoYY8zWrVtNbGysqa2trXMuB45TUFBgjDHm73//u7n55puNMcY899xzZsaMGcbr9RpjjPnTn/5k0tLSjDHGxMXFmeeff/6I+Tz++ONm4sSJxuv1GrfbbXr37m1mz57t22/NmjW+uQe29+zZY/r162cKCwuNMcb8+OOPJiYmxhQVFZl58+aZHj16mLKyMmOMMYsXLzaDBw82xhjj8XhMXFyc+fe//33YOjp37mzmzJljjDFmw4YN5sorrzQ7d+40b7/9trnttttMRUWFMcaY2bNnm1GjRhlj/v/ruLCw0HTu3NksXbrUGGNMXl6e6dOnjy+zA1+DwK8RV8iAE4jD4VBYWFidsbPOOktdunTRzTffrJiYGMXExKhnz55H3P/KK6886nMPHTpUktS5c2d16tRJX331VaPW+Mknn+iNN96Qw+FQy5YtNWTIEP3tb3/T3XffLUm6/vrrJUlRUVHat2+fKisrFRoa6tt/zZo1Ovfcc9WtWzdJ0oUXXqgrrrhCq1at0tVXX33U406cOFGfffaZXnrpJW3ZskUlJSWqrKzUd999pxYtWqhPnz6SpK5du2rhwoVav379EcfrExkZqfbt20uSLr/8cp122mn6xz/+ocLCQq1cuVKnnnqqJCk/P1+PPvqogoKCFBQU5HvNm8vl0u23367x48frzTff1C233HLE269dunRR9+7dJUmDBw/WY489pt27d+vjjz9WWVmZ7zVVNTU1atu2rW+/A/sc6vPPP9fkyZPlcDh05plnKj4+vt5z/frrr+V2u3X//ff7xhwOh7799ltJ+68choeHS5Li4uI0ffp0bdy4UcXFxTrnnHN0/vnnH/F5b7/9dt85du7cWQUFBfrkk0+UnJysVq1aSZJGjhypv/zlL9q3b1+dfUNCQhQbGytp/9W2PXv21HsewK8BhQw4gaxdu1adO3euM9aiRQu9/vrrWrt2rVasWKEnnnhCvXv31sMPP3zY/gf+sTuSFi3+/z08Xq9XwcHBcjgcMgd9nG1NTU29a/R6vXI4HHW2a2trfdsHyteBOeaQj8v1eDx19j8w5+DnOJIJEybI4/Gof//+6tOnj3bs2CFjjIKCgg57vu++++6o44eWiEMLwcEZfvzxx5o+fbp++9vf6vrrr9f555+vd999V5J8+R2wY8cOhYWF6bzzztNFF12kDz/8UDk5OcrKyjri+RyppAUFBcnr9WrSpEm+UlJRUaHq6uojru9goaGhdbIOCQmp8/jBjx04Z4/Ho06dOumtt97yPVZcXKyIiAgtXLiwzrGCgoJ022236e2331ZJSYmGDBlyxHUcem5er9d3Xr/0dXPwug98rR765wf8mvEuS+AEsXnzZmVmZmrUqFF1xjdu3KiBAweqU6dOuueee3TnnXdq7dq1kvb/w1dfkTlg/vz5kqT169dr27Zt6tatmyIiIrRp0yZVV1erpqZGixYt8s0/2nP36tVLr7/+uowx2rdvn7KysnTttdc2+Dyjo6P1/fffa82aNZKkTZs2qaCgQFddddUv7rd8+XLdf//9GjBggCTpm2++kcfj0fnnny+Hw6HPPvvMd3533HHHUce9Xq8iIiJ8Gebk5Bz1mJ999pni4uI0bNgwde3aVUuWLJHH45Ek9ezZU/Pnz5fX69W+ffs0btw4FRQUSJKGDRummTNn6rLLLtNZZ511xOfeuHGjNm7cKEl68803dfnll6tNmzbq1auX5s6dq3379snr9eoPf/iDMjIy6s21T58+ysrKksfjUVlZmT788EPfYxEREVq3bp2k/a9fc7vdkvb/WWzdutW37g0bNig+Pt73Zo5D3XrrrVqyZInWr19f5/Vrhzrw7t7169dr8+bN6tGjh3r37q158+apsrJSkvTaa6+pR48eatmyZb3nJu3/emzIfxiAQMUVMsCSqqoqDRo0SNL+q1ehoaGaMGGC7xbbAV26dFH//v01ePBgtWrVSmFhYUpNTZW0/8XZGRkZDfqHqrCwUDfddJMcDocyMjJ0+umn67rrrlOPHj3Uv39/OZ1OXX311b7bVdHR0frzn/+sBx54QCNGjPA9T2pqqqZNm6akpCTV1NSod+/euvfeext83hEREXr22Wf1+OOPq6qqSg6HQ08++aTOO+88/fDDD0fdb/z48br//vvVqlUrhYeHq0ePHtq2bZtatmyp5557Tk888YRmzpypkJAQPffcc784npqaqscee0xt2rTRtddeK6fTecRjDhkyRL///e+VlJSk2tpaXXfddfrggw/k9Xr1wAMPaPr06Ro0aJA8Ho8GDBjge/F6XFycUlNTf/Eq0plnnqlnnnlGRUVFioiI0MyZMyVJ9913n5566indfPPN8ng8uvjii5WSklJvrnfffbeeeOIJ3XjjjWrTpk2dc3rooYf0xz/+UW+++aaioqIUFRXl+7OYPXu2Zs6cqerqahljNHPmTJ1zzjlatWrVYcdo27atunbtqk6dOh12Be5gX375pbKysuT1ejVr1iyddtppuuWWW7Rjxw7deuut8nq96tixo9LT0+s9rwMuuOAChYaG6pZbbtFbb73F1TP86jjMofcTAADH5auvvlJqaqpycnKOWBxWrlzpe9dqU3nsscd0xhln+PVnd+3atUu33HKL5s6dq8jIyCPOOfQdrAAahitkAOBHjzzyiFatWqVZs2b9qq7iZGVlKSMjQ2PHjj1qGQPQeFwhAwAAsIwX9QMAAFhGIQMAALCMQgYAAGAZhQwAAMCy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\n", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "better_accuracy_stats = accuracy_per_query_point(better_neighbors, tree_neighbors)\n", "sns.distplot(better_accuracy_stats, kde=False)\n", "plt.title(\"Distribution of accuracy per query point\")\n", "plt.xlabel(\"Accuracy\")\n", "print(f\"Average accuracy of {np.mean(better_accuracy_stats)}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "With ``epsilon=0.2`` we see a great improvement in accuracy. Everything but a very small number of query points get all 10 neighbors correct! We took a little more time, but still vastly less than the kd-tree. We also were significantly more accurate than simply taking more neighbors for very little extra cost in search time. What if we go the other way and turn ``epsilon`` down to zero?" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 456 ms, sys: 6.8 ms, total: 462 ms\n", "Wall time: 471 ms\n" ] } ], "source": [ "%%time\n", "worse_neighbors = index.query(fmnist_test, epsilon=0.0)" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Average accuracy of 0.88776\n" ] }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAl4AAAGECAYAAADnbC5SAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4yLjIsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy+WH4yJAAAgAElEQVR4nO3deXxU1f3/8fcwE0LTuBCcQIrIQ1FAg4IKWlQSQJsEQwQDfkWQpS4IKnyFb1GEFIoIYkyDgoZq9eG3VWuNC0QwDQpWKoYlRWX7EqUKCAGTIUDNQpaZOb8//DElbBOQnAzk9fwn3HPPvfec+8kw79w7i8MYYwQAAIAG16yxBwAAANBUELwAAAAsIXgBAABYQvACAACwhOAFAABgCcELAADAEoIX0IB27dqlyy+/XAMGDNCAAQOUkpKiIUOGKDc3N9Dnueee06JFi064n+eff17Lli075rrDt+/UqZP27dt3UmPcsGGDpk2bJknauHGjxo8ff1Lbnwqfz6exY8cqMTFRr7/+eoMfD6Fh6tSpys/PP2GfnTt3aty4cZZGBNjnauwBAGe7Fi1aKCcnJ7BcVFSkUaNGyel0KjExUf/93/8ddB9r1qzRpZdeesx19dn+RP71r3+puLhYknTllVdq3rx5P2l/9VFcXKyVK1fqyy+/lNPpbPDjITTMmjUraJ/du3dr27ZtFkYDNA6CF2BZ27ZtNX78eL3yyitKTEzU5MmTddlll+nee+/VvHnz9NFHHyksLEwtW7bUU089pY8++kibNm1Senq6nE6nli9frgMHDmjnzp3q3bu3SktLA9tL0rPPPquNGzfK7/frkUceUZ8+ffTee+9p6dKlevHFFyUpsPy73/1O8+bNU1lZmR5//HENHDhQM2fO1JIlS1RWVqYZM2aosLBQDodDvXr10sSJE+VyuXTllVdq9OjR+uyzz1RSUqL77rtPQ4cOPWqu//znP5Wenq6DBw8qLCxMjzzyiK655hrdd9998nq9Sk1N1fz583XRRRcFttm2bZueeOIJVVRUyOPxqHPnznr22WcVHh6u9evX68knnwzs79FHH1XPnj2P296pUyetWrVKUVFRkhRY3rp1q2bNmqWIiAhVVFTo3XffVXp6utavX6+KigoZY/Tkk0/q2muvVUVFhZ588kl9/vnncjqduuWWWzRmzBjFx8crOztbF198sSRp1KhRuvvuu3XLLbcE5rJmzRplZGToF7/4hb799lu1aNFCc+bMUYcOHVRTU6OMjAwVFBTI5/PpiiuuUFpamiIjI9W3b19dddVV+uqrrzRx4kT96le/CuyzrKxMaWlpKiwsVHR0tGJiYtSuXTuNGzdOffv21XPPPacrr7xSkuosf/7558rIyNDBgwfVrFkzPfzww4HfjXfeeUcHDx5UZGSkXC6X+vXrp//6r/+SJGVlZenAgQOaMmVKndpeccUVuv/++/Xpp5+qsrJSEydOVEJCgiTphRde0AcffCCn06mLL75Yv/3tb+V2uzV8+HANGzZMXbp00ahRoxQfH6/169frhx9+0KRJk9S3b1+lpaWpuLhY9957r1555ZWf9mADQpEB0GB27txpunXrdlT7119/bbp27WqMMeaxxx4zL7/8stm9e7e55pprTHV1tTHGmFdeecV89NFHxhhj7r77bvO3v/0t0H/kyJGBfR3a3hhjOnbsaF588UVjjDFfffWVue6660xpaal59913zejRowPbHL58+L9Xr15tkpOTjTHGPProo2bmzJnG7/eb6upqc8899wT23bFjR/Paa68ZY4zZuHGj6dKli6mqqqozx3379pmePXuaL7/8MjDn6667znz33XfHPS/GGDNnzhyzaNEiY4wxNTU1pn///iYvL8/U1NSYG2+80fz9738PHLd///6murr6mO0+n8907NjRlJaWBvZ9aHn16tWmc+fOZteuXcYYYz7//HMzbtw44/P5jDHGvPjii+aBBx4wxhgze/ZsM2HCBOP1ek11dbUZNmyYWb16tXnyySfN008/bYwxZseOHSY+Pt54vd46czl0nIKCAmOMMX/5y1/M7bffbowxZv78+WbOnDnG7/cbY4z5/e9/b6ZPn26MMaZPnz7m+eefP+b5mTlzppk0aZLx+/3G4/GYXr16mXnz5gW227BhQ6DvoeUDBw6YhIQEs3PnTmOMMd9//72Ji4szRUVF5t133zU9evQwZWVlxhhjPvroIzNo0CBjjDE+n8/06dPHfPPNN0eNo2PHjmbBggXGGGO2bNlirr32WlNaWmreeecdc+edd5qKigpjjDHz5s0z99xzjzHmP7/HO3fuNB07djQff/yxMcaYvLw807t378A5O/Q7CJyNuOIFNAKHw6EWLVrUaWvdurU6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+fJAMEbjaOznepe1I4WA1q1ba8KECRoxYoRqa2s1ePBgXXXVVbr//vs1fvx4XXnllcrIyFBaWprKy8sVGxurESNGNPawz3rB6vL999/r//7v/+Tz+bR06VJJUpcuXbjy1YDq81iBffWpywsvvKC0tDQdPHhQbdq0UXp6emMP+6xWn5o89dRTeuSRR2SMUatWrTR79uzGHnaTFCrP9Q5jjLF2NAAAgCasSd1qBAAAaEwELwAAAEsIXgAAAJYQvAAAACwheAEAAFhC8AIQ8mpra3XTTTfpvvvua+yhAMBPQvACEPI++ugjde7cWZs2bdI333zT2MMBgFNG8AIQ8t58803dfPPNuvXWW/WnP/0p0P7OO+8oOTlZKSkpGjFihPbs2XPc9jVr1qh///6BbQ9fnj9/vu69916lpKToN7/5jfbu3asHH3xQd955p/r27avhw4ertLRUkrRt2zYNHz48sP/c3FytW7dOvXv3lt/vlyQdPHhQPXv21L59+2ydIgBnCIIXgJD2r3/9S1988YWSkpI0cOBA5eTkaP/+/SosLFRGRoZefvllLV68WH379tWCBQuO2x5MUVGRFi5cqIyMDH3wwQfq1q2b3nrrLS1fvlwtWrRQTk6OJGnixIlKSkrSBx98oJdeekmZmZnq1KmTzjvvPH366aeSpA8++EA9e/ZUVFRUg54bAGeeJvWVQQDOPG+++ab69Omjli1bqmXLlrrwwguVnZ2t5s2b66abblJMTIwkadSoUZKkV1999Zjta9asOeFxunXrJpfrx/8SR44cqX/+85969dVXtX37dm3dulVdu3bVgQMHVFhYqDvuuEOSFBMTo2XLlkmShg0bpuzsbMXHx+utt97So48+erpPBYCzAMELQMiqrKxUTk6Omjdvrr59+0qSysvL9frrr+u+++6r84XpVVVVKioqktPpPGa7w+HQ4d+QVltbW+dYERERgX8/88wz2rBhgwYNGqTrr79eXq9XxphAMDt8/99++61+8YtfKCUlRZmZmVq9erUqKyvVo0eP03syAJwVuNUIIGQtXrxY559/vj799FN9/PHH+vjjj7Vs2TJVVlaqrKxMq1atUklJiSTpr3/9q5555hldf/31x2yPiorS7t27VVpaKmOMPvjgg+Med+XKlRo5cqQGDhyoVq1aKT8/Xz6fT5GRkYqNjdWiRYskSXv27NFdd92lsrIy/exnP9Ntt92mKVOmaMiQIQ1/cgCckbjiBSBkvfnmm/r1r38tp9MZaDv33HM1fPhw/f3vf9ekSZMCHzHhdrs1e/ZstW7d+rjtQ4YM0aBBg+R2u9W7d29t3LjxmMd96KGHlJ6erueee05hYWG65ppr9N1330mSfv/732vGjBl67bXX5HA4NGvWLLndbklSamqqsrOzNXDgwIY8LQDOYA5z+LV3AMApMcboj3/8o4qKijRjxozGHg6AEMUVLwA4DW6++WZFR0crKyursYcCIIRxxQsAAMASXlwPAABgCcELAADAEoIXAACAJQQvAAAASwheAAAAlhC8AAAALPl/RngMs1H66aUAAAAASUVORK5CYII=\n", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "worse_accuracy_stats = accuracy_per_query_point(worse_neighbors, tree_neighbors)\n", "sns.distplot(worse_accuracy_stats, kde=False)\n", "plt.title(\"Distribution of accuracy per query point\")\n", "plt.xlabel(\"Accuracy\")\n", "print(f\"Average accuracy of {np.mean(worse_accuracy_stats)}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The accuracy has dropped quite a bit now, but if we only need to be \"close\" then this might be good enough, and it is definitely faster. This provides an easy trade-off between accuracy and query time.\n", "\n", "## Index parameters\n", "\n", "It is often the case that we know what we really need is fast queries -- as fast as possible -- and we are willing to sacrifice accuracy to get there. We know we are going to do a lot of queries, so it would be better if we could set up the index itself to target faster querying rather than just hoping the ``epsilon`` parameter will get things fast enough.\n", "\n", "Alternatively we might really want very accurate queries -- ideally exact queries, but they are too expensive -- and be prepared to put in more work on the index to make queries more accurate but still fast.\n", "\n", "These options can be dealt with via various parameters that are passed to the index constructor. There are many parameters that can be tweaked (check the docstring for full details), but in practice there are only a few we need to concern ourselves with. The primary ones of interest are\n", "\n", " 1. ``n_neighbors``;\n", " 2. ``diversify_prob``; and\n", " 3. ``pruning_degree_multiplier``.\n", " \n", "We'll look at each in turn to get a quick understanding of what they do. First up is ``n_neighbors``. By default this is ``30``; more neighbors leads to a more accurate (albeit slower) index, fewer neighbors leads to a faster index at the cost of accuracy. In general for a high accuracy (on the order or 90%+) index you might want an ``n_neighbors`` value in the range of fifty to one-hundred (a lot will depend on other issues, such as the dataset itself, and the metric used -- angular metrics generally need higher ``n_neighbors`` values). For fast indexes you can set it more in the range of five to twenty (again, dependent on the dataset itself and the metric; a little experimentation may be required). Let's try a couple of values with the fashion-mnist dataset and see how it works." ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 1min 21s, sys: 1.77 s, total: 1min 23s\n", "Wall time: 35.1 s\n" ] } ], "source": [ "%%time\n", "accurate_index = pynndescent.NNDescent(fmnist_train, n_neighbors=50)\n", "accurate_index.prepare()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "It takes a little longer to construct the index. Querying will also be a little slower." ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 1.39 s, sys: 20.2 ms, total: 1.41 s\n", "Wall time: 1.42 s\n" ] } ], "source": [ "%%time\n", "accurate_neighbors = accurate_index.query(fmnist_test, epsilon=0.2)" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Average accuracy of 0.9980899999999999\n" ] }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAmQAAAGECAYAAACYvTyjAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4yLjIsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy+WH4yJAAAgAElEQVR4nO3de1zUdb7H8fcICLlohc0kmXnKMgtLK60sBbEWUCAL66SY2prHstI2N4uUdC0tIxbLWjzdHu2eatsoU5II00zLMCW7eFksd/MWGoy34iIIM9/zR+useAnEwa+Tr+c/+PvO9/f7fb4fEN78fjOMwxhjBAAAAGta2C4AAADgZEcgAwAAsIxABgAAYBmBDAAAwDICGQAAgGUEMgAAAMsIZIAF33//vS666CINHDhQAwcOVHJysgYPHqz8/HzfnGeeeUbz5s37xeM899xzWrRo0WEfO3D/Cy+8ULt27TqqGlevXq3JkydLktasWaNx48Yd1f5N4fF4NGbMGMXHx+u1115r9vPhxDBp0iQVFhb+4pytW7dq7Nixx6ki4PgLtl0AcLIKCwtTbm6ub7ukpES33367goKCFB8fr/vuu6/BY6xYsULnn3/+YR9rzP6/5J///KdKS0slSZdccolmzZp1TMdrjNLSUi1btkxfffWVgoKCmv18ODFMnz69wTnbtm3Txo0bj0M1gB0EMuAE0b59e40bN04vv/yy4uPjlZaWpgsuuEB33HGHZs2apYULFyokJESnn366nnjiCS1cuFBr165VRkaGgoKC9OGHH2rPnj3aunWr+vbtq507d/r2l6Snn35aa9askdfr1e9//3vFxsbqnXfe0YIFC/T8889Lkm/7j3/8o2bNmqXy8nI9/PDDuvHGG/XYY48pLy9P5eXlmjp1qtavXy+Hw6E+ffpo/PjxCg4O1iWXXKLRo0fr008/VVlZmUaNGqXU1NRD1vr5558rIyNDe/fuVUhIiH7/+9/r8ssv16hRo1RXV6eUlBQ9++yzOuecc3z7bNy4UY8++qgqKyvldrvVpUsXPf300woNDdXXX3+tadOm+Y734IMPqlevXkccv/DCC7V8+XJFRERIkm97w4YNmj59ulq1aqXKykrNmTNHGRkZ+vrrr1VZWSljjKZNm6YrrrhClZWVmjZtmr744gsFBQXp+uuv11133aWYmBjl5OTo3HPPlSTdfvvtuu2223T99df71rJixQplZmbqrLPO0nfffaewsDDNmDFDnTp10r59+5SZmamioiJ5PB5dfPHFSk9PV3h4uPr166dLL71U33zzjcaPH6/f/va3vmOWl5crPT1d69evl8vlUmRkpDp06KCxY8eqX79+euaZZ3TJJZdIUr3tL774QpmZmdq7d69atGihe++91/e18fbbb2vv3r0KDw9XcHCw+vfvr//+7/+WJGVnZ2vPnj2aOHFivc/txRdfrP/5n//RJ598oqqqKo0fP15xcXGSpD//+c967733FBQUpHPPPVePPPKInE6nhg0bpqFDh6pr1666/fbbFRMTo6+//lo//fSTJkyYoH79+ik9PV2lpaW644479PLLLx/bfzbgRGQAHHdbt2413bt3P2T822+/Nd26dTPGGPPQQw+Zl156yWzbts1cfvnlpqamxhhjzMsvv2wWLlxojDHmtttuM++//75v/ogRI3zH2r+/McZ07tzZPP/888YYY7755htz5ZVXmp07d5o5c+aY0aNH+/Y5cPvAf3/22WcmMTHRGGPMgw8+aB577DHj9XpNTU2NGTlypO/YnTt3Nq+++qoxxpg1a9aYrl27murq6npr3LVrl+nVq5f56quvfGu+8sorzZYtW47YF2OMmTFjhpk3b54xxph9+/aZpKQkU1BQYPbt22euvfZa89FHH/nOm5SUZGpqag477vF4TOfOnc3OnTt9x96//dlnn5kuXbqY77//3hhjzBdffGHGjh1rPB6PMcaY559/3tx5553GGGMef/xxc//995u6ujpTU1Njhg4daj777DMzbdo08+STTxpjjNm8ebOJiYkxdXV19day/zxFRUXGGGP+9re/mZtuuskYY8yzzz5rZsyYYbxerzHGmD/96U9mypQpxhhjYmNjzXPPPXfY/jz22GNmwoQJxuv1Grfbbfr06WNmzZrl22/16tW+ufu39+zZY+Li4szWrVuNMcb88MMPJjo62pSUlJg5c+aYnj17mvLycmOMMQsXLjSDBg0yxhjj8XhMbGys+de//nVIHZ07dzazZ882xhhTXFxsrrjiCrNz507z9ttvm1tvvdVUVlYaY4yZNWuWGTlypDHmP1/HW7duNZ07dzaLFy82xhhTUFBg+vbt6+vZ/q9B4NeIK2TACcThcCgsLKze2JlnnqkuXbropptuUnR0tKKjo9WrV6/D7n/FFVcc8dhDhgyRJHXu3FmdOnXSl19+2aQaP/74Y73xxhtyOBxq2bKlBg8erL/+9a8aPXq0JOm6666TJEVFRWnfvn2qqqpSaGiob//Vq1frnHPOUbdu3SRJF1xwgS6//HKtXLlSV1111RHPO2HCBH366ad68cUXtWnTJpWVlamqqkrffvutWrRoob59+0qSunbtqvnz52vdunWHHW9IZGSk2rdvL0m67LLLdOqpp+rvf/+7tm7dqhUrVug3v/mNJKmwsFAPP/ywgoKCFBQU5HvOm8vl0m233ab7779fb775pm6++ebD3n7t0qWLevToIUkaNGiQHn30Ue3evVtLlixReXm57zlVtbW1atu2rW+//fsc7LPPPtOkSZPkcDh0xhlnKD4+vsG1fvXVV3K73brnnnt8Yw6HQ998842kn68choeHS5JiY2M1ffp0rV+/XqWlpTr77LN13nnnHfa4t912m2+NnTt3VlFRkT7++GOlpKSoVatWkqThw4frf//3f7Vv3756+4aEhCgmJkbSz1fb9uzZ0+A6gF8DAhlwAlmzZo06d+5cb6xFixZ67bXXtGbNGi1fvlyPP/64+vTpowcffPCQ/ff/sDucFi3+8xoer9er4OBgORwOmQPezra2trbBGr1erxwOR73turo63/b+8LV/jjno7XI9Hk+9/ffPOfAYhzN+/Hh5PB71799fffv21fbt22WMUVBQ0CHH+/bbb484fnCIODgQHNjDJUuWaPr06frd736n6667Tuedd57effddSfL1b7/t27crLCxM5557ri688EJ9+OGHysvLU05OzmHXc7iQFhQUJK/Xq4kTJ/pCSWVlpWpqag5b34FCQ0Pr9TokJKTe4wc+tn/NHo9HnTp10ltvveV7rLS0VBEREZo/f369cwUFBenWW2/V22+/rbKyMg0ePPiwdRy8Nq/X61vXL33dHFj3/q/Vgz9/wK8Zr7IEThAbN25Udna2Ro4cWW98/fr1SkpKUqdOnXTnnXfq9ttv15o1ayT9/IOvoSCz39y5cyVJ69at05YtW9StWzdFRERow4YNqqmpUW1trRYsWOCbf6Rj9+7dW6+99pqMMdq3b59ycnJ0zTXXNHqd3bt313fffafVq1dLkjZs2KCioiJdeeWVv7jfsmXLdM8992jAgAGSpK+//loej0fnnXeeHA6HPv30U9/6RowYccRxr9eriIgIXw/z8vKOeM5PP/1UsbGxSk1NVdeuXbVo0SJ5PB5JUq9evTR37lx5vV7t27dP48aNU1FRkSQpNTVVGRkZuvTSS3XmmWce9tjr16/X+vXrJUlvvvmmLrvsMrVp00a9e/fW66+/rn379snr9eqRRx5RVlZWg33t27evcnJy5PF4VF5erg8//ND3WEREhNauXSvp5+evud1uST9/LjZv3uyru7i4WPHx8b4Xcxzslltu0aJFi7Ru3bp6z1872P5X965bt04bN25Uz5491adPH82ZM0dVVVWSpFdffVU9e/ZUy5YtG1yb9PPXY2N+YQACFVfIAEuqq6s1cOBAST9fvQoNDdX48eN9t9j269Kli/r3769BgwapVatWCgsLU3p6uqSfn5ydlZXVqB9UW7du1Y033iiHw6GsrCyddtppuvbaa9WzZ0/1799fTqdTV111le92Vffu3fXnP/9Z9957r4YNG+Y7Tnp6uqZNm6bk5GTV1taqT58+uuuuuxq97oiICD3zzDN67LHHVF1dLYfDoSeeeELnnnuuvv/++yPud//99+uee+5Rq1atFB4erp49e2rLli1q2bKlnn32WT3++OPKyMhQSEiInn322V8cT09P16OPPqo2bdrommuukdPpPOw5Bw8erD/84Q9KTk5WXV2drr32Wn3wwQfyer269957NX36dA0cOFAej0cDBgzwPXk9NjZW6enpv3gV6YwzztDTTz+tkpISRUREKCMjQ5J0991368knn9RNN90kj8ejiy66SGlpaQ32dfTo0Xr88cd1ww03qE2bNvXW9MADD+iPf/yj3nzzTUVFRSkqKsr3uZg1a5YyMjJUU1MjY4wyMjJ09tlna+XKlYeco23bturatas6dep0yBW4A33xxRfKycmR1+vVzJkzdeqpp+rmm2/W9u3bdcstt8jr9apjx47KzMxscF37nX/++QoNDdXNN9+st956i6tn+NVxmIPvJwAAjsmXX36p9PR05eXlHTY4rFixwveq1eby6KOP6vTTT/fr3+7atWuXbr75Zr3++uuKjIw87JyDX8EKoHG4QgYAfvTQQw9p5cqVmjlz5q/qKk5OTo6ysrI0duzYI4YxAE3HFTIAAADLeFI/AACAZQQyAAAAywhkAAAAlhHIAAAALAv4V1nu3l0pr7d5X5fQtm24du6saNZznEzop//RU/+in/5HT/2Lfvpfc/e0RQuHTj/9N0d8POADmddrmj2Q7T8P/Id++h899S/66X/01L/op//Z7Cm3LAEAACwjkAEAAFhGIAMAALCsUYGsoqJCSUlJvjf+LSwsVHJysuLi4jRz5kzfvOLiYqWkpCg+Pl6TJk1SXV2dJGnbtm0aOnSoEhISNGbMGFVWVkqSfvrpJ40ePVr9+/fX0KFD5Xa7/b0+AACAE16Dgezrr7/WkCFDtGnTJklSdXW1Jk6cqOzsbOXn52vt2rVaunSpJGnChAmaPHmyFixYIGOMcnJyJElTp05VamqqCgoK1LVrV2VnZ0uSnn76afXo0UPvv/++brnlFk2fPr2ZlgkAAHDiajCQ5eTkaMqUKXK5XJKk1atXq2PHjurQoYOCg4OVnJysgoIClZSUqLq6Wt27d5ckpaSkqKCgQLW1tSoqKlJ8fHy9cUlasmSJkpOTJUlJSUn6+OOPVVtb2ywLBQAAOFE1+GcvDr5qVVZWJqfT6dt2uVwqLS09ZNzpdKq0tFS7d+9WeHi4goOD640ffKzg4GCFh4dr165dOvPMM499ZQAAAAHiqP8OmdfrlcPh8G0bY+RwOI44vv/jgQ7ePnCfFi2O7nUGbduGH9X8pnI6Wx+X85ws6Kf/0VP/op/+R0/9i376n82eHnUga9euXb0n37vdbrlcrkPGd+zYIZfLpYiICJWXl8vj8SgoKMg3X/r56tqOHTvUrl071dXVqbKyUqeddtpR1bNzZ0Wz/yE3p7O13O7yZj3HyYR++h899S/66X/01L/op/81d09btHD84kWko/6zF926ddPGjRu1efNmeTwe5eXlKTo6Wu3bt1doaKhWrVolScrNzVV0dLRCQkLUo0cP5efnS5LmzZun6OhoSVJMTIzmzZsnScrPz1ePHj0UEhJy1IsEAAAIZEd9hSw0NFQzZszQ2LFjVVNTo5iYGCUkJEiSMjMzlZ6eroqKCkVFRWn48OGSpClTpigtLU2zZ89WZGSksrKyJEn33Xef0tLSlJiYqNatWyszM9OPSwMAAAgMDmNMQL8ZFrcsAw/99D966l/00//oqX/RT/8LuFuWAAAA8K+jvmUJAAAQKOq8Uk1tXYPzwqr2HYdqjoxABgAAfrVqautUVFza4LyYK87R4f8o1/HBLUsAAADLCGQAAACWEcgAAAAsI5ABAABYRiADAACwjEAGAABgGYEMAADAMgIZAACAZQQyAAAAywhkAAAAlhHIAAAALCOQAQAAWEYgAwAAsIxABgAAYBmBDAAAwDICGQAAgGUEMgAAAMsIZAAAAJYRyAAAACwjkAEAAFhGIAMAALCMQAYAAGAZgQwAAMAyAhkAAIBlBDIAAADLCGQAAACWEcgAAAAsI5ABAABYRiADAACwjEAGAABgGYEMAADAMgIZAACAZQQyAAAAywhkAAAAlhHIAAAALCOQAQAAWEYgAwAAsIxABgAAYBmBDAAAwDICGQAAgGUEMgAAAMsIZAAAAJYRyAAAACwjkAEAAFhGIAMAALCMQAYAAGAZgQwAAMAyAhkAAIBlBDIAAADLCGQAAACWEcgAAAAsO6ZAlpubq8TERCUmJurJJ5+UJBUWFio5OVlxcXGaOXOmb25xcbFSUlIUHx+vSZMmqa6uTpK0bds2DR06VAkJCRozZowqKyuPpSQAAICA0+RAtnfvXk2fPl2vvvqqcnNz9fnnn2vx4sWaOHGisrOzlZ+fr7Vr12rp0qWSpAkTJmjy5MlasGCBjDHKycmRJE2dOlWpqakqKChQ165dlZ2d7Z+VAQAABIgmBzKPxyOv16u9e/eqrq5OdXV1Cg8PV8eOHdWhQwcFBwcrOTlZBQUFKikpUXV1tbp37y5JSklJUUFBgWpra1VUVKT4+Ph64wAAACeT4KbuGB4ervvuu0/9+/fXKaecop49e6qsrExOp9M3x+VyqbS09JBxp9Op0tJS7d69W+Hh4QoODq43DgAAcDJpciBbv3695syZo48++kitW7fWAw88oE2bNsnhcPjmGGPkcDjk9XoPO77/44EO3m5I27bhTV3CUXE6Wx+X85ws6Kf/0VP/op/+R0/9i342jtlVpdbhYY2aa7OnTQ5ky5YtU69evdS2bVtJP99ufPnllxUUFOSb43a75XK51K5dO7ndbt/4jh075HK5FBERofLycnk8HgUFBfnmH42dOyvk9ZqmLqNRnM7WcrvLm/UcJxP66X/01L/op//RU/+in41XVVOn8orqRs1tzp62aOH4xYtITX4OWZcuXVRYWKiqqioZY7R48WJ169ZNGzdu1ObNm+XxeJSXl6fo6Gi1b99eoaGhWrVqlaSfX50ZHR2tkJAQ9ejRQ/n5+ZKkefPmKTo6uqklAQAABKQmXyHr3bu3/vGPfyglJUUhISG65JJLNHbsWF177bUaO3asampqFBMTo4SEBElSZmam0tPTVVFRoaioKA0fPlySNGXKFKWlpWn27NmKjIxUVlaWf1YGAAAQIBzGmOa939fMuGUZeOin/9FT/6Kf/kdP/Yt+Nl5lTZ2Kiht+wWDMFefI4fE0Wx3NdssSAAAA/kEgAwAAsIxABgAAYBmBDAAAwDICGQAAgGUEMgAAAMsIZAAAAJYRyAAAACwjkAEAAFhGIAMAALCMQAYAAGAZgQwAAMAyAhkAAIBlBDIAAADLCGQAAACWEcgAAAAsI5ABAABYRiADAACwjEAGAABgGYEMAADAMgIZAACAZQQyAAAAywhkAAAAlhHIAAAALCOQAQAAWEYgAwAAsIxABgAAYBmBDAAAwDICGQAAgGUEMgAAAMsIZAAAAJYRyAAAACwjkAEAAFhGIAMAALCMQAYAAGAZgQwAAMAyAhkAAIBlBDIAAADLCGQAAACWEcgAAAAsI5ABAABYRiADAACwjEAGAABgGYEMAADAMgIZAACAZQQyAAAAywhkAAAAlhHIAAAALCOQAQAAWEYgAwAAsIxABgAAYBmBDAAAwDICGQAAgGUEMgAAAMsIZAAAAJYdUyBbvHixUlJS1L9/f02bNk2SVFhYqOTkZMXFxWnmzJm+ucXFxUpJSVF8fLwmTZqkuro6SdK2bds0dOhQJSQkaMyYMaqsrDyWkgAAAAJOkwPZ1q1bNWXKFGVnZ+vdd9/VP/7xDy1dulQTJ05Udna28vPztXbtWi1dulSSNGHCBE2ePFkLFiyQMUY5OTmSpKlTpyo1NVUFBQXq2rWrsrOz/bMyAACAANHkQLZw4UINGDBA7dq1U0hIiGbOnKlTTjlFHTt2VIcOHRQcHKzk5GQVFBSopKRE1dXV6t69uyQpJSVFBQUFqq2tVVFRkeLj4+uNAwAAnEyCm7rj5s2bFRISorvuukvbt29X3759dcEFF8jpdPrmuFwulZaWqqysrN640+lUaWmpdu/erfDwcAUHB9cbBwAAOJk0OZB5PB59/vnnevXVV9WqVSuNGTNGYWFhcjgcvjnGGDkcDnm93sOO7/94oIO3G9K2bXhTl3BUnM7Wx+U8Jwv66X/01L/op//RU/+in41jdlWpdXhYo+ba7GmTA9kZZ5yhXr16KSIiQpJ0/fXXq6CgQEFBQb45brdbLpdL7dq1k9vt9o3v2LFDLpdLERERKi8vl8fjUVBQkG/+0di5s0Jer2nqMhrF6Wwtt7u8Wc9xMqGf/kdP/Yt++h899S/62XhVNXUqr6hu1Nzm7GmLFo5fvIjU5OeQxcbGatmyZfrpp5/k8Xj0ySefKCEhQRs3btTmzZvl8XiUl5en6OhotW/fXqGhoVq1apUkKTc3V9HR0QoJCVGPHj2Un58vSZo3b56io6ObWhIAAEBAavIVsm7dumnUqFFKTU1VbW2trr32Wg0ZMkTnnXeexo4dq5qaGsXExCghIUGSlJmZqfT0dFVUVCgqKkrDhw+XJE2ZMkVpaWmaPXu2IiMjlZWV5Z+VAQAABAiHMaZ57/c1M25ZBh766X/01L/op//RU/+in41XWVOnouKGXzAYc8U5cng8zVZHs92yBAAAgH8QyAAAACwjkAEAAFhGIAMAALCMQAYAAGAZgQwAAMAyAhkAAIBlBDIAAADLCGQAAACWEcgAAAAsI5ABAABYRiADAACwjEAGAABgGYEMAADAMgIZAACAZQQyAAAAywhkAAAAlhHIAAAALCOQAQAAWEYgAwAAsIxABgAAYBmBDAAAwDICGQAAgGUEMgAAAMsIZAAAAJYRyAAAACwjkAEAAFhGIAMAALCMQAYAAGAZgQwAAMAyAhkAAIBlBDIAAADLCGQAAACWEcgAAAAsI5ABAABYRiADAACwjEAGAABgGYEMAADAMgIZAACAZQQyAAAAywhkAAAAlhHIAAAALCOQAQAAWEYgAwAAsIxABgAAYBmBDAAAwDICGQAAgGUEMgAAAMsIZAAAAJYRyAAAACwjkAEAAFhGIAMAALCMQAYAAGAZgQwAAMAyvwSyJ598UmlpaZKkwsJCJScnKy4uTjNnzvTNKS4uVkpKiuLj4zVp0iTV1dVJkrZt26ahQ4cqISFBY8aMUWVlpT9KAgAACBjHHMiWL1+uuXPnSpKqq6s1ceJEZWdnKz8/X2vXrtXSpUslSRMmTNDkyZO1YMECGWOUk5MjSZo6dapSU1NVUFCgrl27Kjs7+1hLAgAACCjHFMj27NmjmTNn6q677pIkrV69Wh07dlSHDh0UHBys5ORkFRQUqKSkRNXV1erevbskKSUlRQUFBaqtrVVRUZHi4+PrjQMAAJxMjimQTZ48Wffff7/atGkjSSorK5PT6fQ97nK5VFpaesi40+lUaWmpdu/erfDwcAUHB9cbBwAAOJkEN3XHt956S5GRkerVq5feeecdSZLX65XD4fDNMcbI4XAccXz/xwMdvN2Qtm3Dm7qEo+J0tj4u5zlZ0E//o6f+RT/9j576F/1sHLOrSq3Dwxo112ZPmxzI8vPz5Xa7NXDgQP3444+qqqpSSUmJgoKCfHPcbrdcLpfatWsnt9vtG9+xY4dcLpciIiJUXl4uj8ejoKAg3/yjsXNnhbxe09RlNIrT2Vpud3mznuNkQj/9j576F/30P3rqX/Sz8apq6lReUd2ouc3Z0xYtHL94EanJtyxfeeUV5eXlKTc3V+PGjVO/fv300ksvaePGjdq8ebM8Ho/y8vIUHR2t9u3bKzQ0VKtWrZIk5ebmKjo6WiEhIerRo4fy8/MlSfPmzVN0dHRTSwIAAAhITb5CdjihoaGaMWOGxo4dq5qaGsXExCghIUGSlJmZqfT0dFVUVCgqKkrDhw+XJE2ZMkVpaWmaPXu2IiMjlZWV5c+SAAAATngOY0zz3u9rZtyyDDz00//oqX/RT/+jp/5FPxuvsqZORcUNv2Aw5opz5PB4mq2OZrtlCQAAAP8gkAEAAFhGIAMAALCMQAYAAGAZgQwAAMAyAhkAAIBlBDIAAADLCGQAAACWEcgAAAAsI5ABAABYRiADAACwjEAGAABgGYEMAADAMgIZAACAZQQyAAAAywhkAAAAlhHIAAAALCOQAQAAWEYgAwAAsIxABgAAYBmBDAAAwDICGQAAgGUEMgAAAMsIZAAAAJYRyAAAACwjkAEAAFhGIAMAALCMQAYAAGAZgQwAAMAyAhkAAIBlBDIAAADLCGQAAACWEcgAAAAsI5ABAABYRiADAACwjEAGAABgGYEMAADAMgIZAACAZQQyAAAAywhkAAAAlhHIAAAALCOQAQAAWEYgAwAAsIxABgAAYBmBDAAAwDICGQAAgGUEMgAAAMsIZAAAAJYRyAAAACwjkAEAAFhGIAMAALCMQAYAAGAZgQwAAMAyAhkAAIBlxxTInnvuOSUmJioxMVEZGRmSpMLCQiUnJysuLk4zZ870zS0uLlZKSori4+M1adIk1dXVSZK2bdumoUOHKiEhQWPGjFFlZeWxlAQAABBwmhzICgsLtWzZMs2dO1fz5s3TunXrlJeXp4kTJyo7O1v5+flau3atli5dKkmaMGGCJk+erAULFsgYo5ycHEnS1KlTlZqaqoKCAnXt2lXZ2dn+WRkAAECAaHIgczqdSktLU8uWLRUSEqJOnTpp06ZN6tixozp06KDg4GAlJyeroKBAJSUlqq6uVvfu3SVJKSkpKigoUG1trYqKihQfH19vHAAA4GTS5EB2wQUX+ALWpk2b9P7778vhcMjpdPrmuFwulZaWqqysrN640+lUaWmpdu/erfDwcAUHB9cbBwAAOJkEH+sBNmzYoDvvvFMPPviggoKCtGnTJt9jxhg5HA55vV45HI5Dxvd/PNDB2w1p2zb8mOpvLKez9bPozrcAAA33SURBVHE5z8mCfvofPfUv+ul/9NS/6GfjmF1Vah0e1qi5Nnt6TIFs1apVGjdunCZOnKjExEStXLlSbrfb97jb7ZbL5VK7du3qje/YsUMul0sREREqLy+Xx+NRUFCQb/7R2LmzQl6vOZZlNMjpbC23u7xZz3EyoZ/+R0/9i376Hz31L/rZeFU1dSqvqG7U3ObsaYsWjl+8iNTkW5bbt2/XPffco8zMTCUmJkqSunXrpo0bN2rz5s3yeDzKy8tTdHS02rdvr9DQUK1atUqSlJubq+joaIWEhKhHjx7Kz8+XJM2bN0/R0dFNLQkAACAgNfkK2csvv6yamhrNmDHDNzZ48GDNmDFDY8eOVU1NjWJiYpSQkCBJyszMVHp6uioqKhQVFaXhw4dLkqZMmaK0tDTNnj1bkZGRysrKOsYlAQAABBaHMaZ57/c1M25ZBh766X/01L/op//RU/+in41XWVOnouKGXzAYc8U5cng8zVZHs92yBAAAgH8QyAAAACwjkAEAAFhGIAMAALCMQAYAAGAZgQwAAMAyAhkAAIBlBDIAAADLCGQAAACWEcgAAAAsI5ABAABYRiADAACwjEAGAABgGYEMAADAMgIZAACAZQQyAAAAywhkAAAAlhHIAAAALCOQAQAAWEYgAwAAsIxABgAAYBmBDAAAwDICGQAAgGUEMgAAAMsIZAAAAJYRyAAAACwjkAEAAFhGIAMAALCMQAYAAGAZgQwAAMAyAhkAAIBlBDIAAADLCGQAAACWEcgAAAAsI5ABAABYRiADAACwjEAGAABgGYEMAADAMgIZAACAZQQyAAAAywhkAAAAlhHIAAAALCOQAQAAWEYgAwAAsIxABgAAYBmBDAAAwDICGQAAgGUEMgAAAMsIZAAAAJYRyAAAACwjkAEAAFhGIAMAALCMQAYAAGBZsO0CAADA4dV5pZraukPGza4qVdX8Zzw0JFjBXGIJaCdEIJs/f75mz56turo6jRgxQkOHDrVdEgAA1tXU1qmouPSQ8dbhYSqvqPZt97zoTAWHnhA/0tFE1j97paWlmjlzpt555x21bNlSgwcP1lVXXaXzzz/fdmkAAADHhfULnIWFhbr66qt12mmnqVWrVoqPj1dBQYHtsgAAAI4b61fIysrK5HQ6fdsul0urV69u9P4tWjiaoyxr5zlZ0E//o6f+RT/9j54eveCgFmoVFnLI+CmhwfLUhdSbR38P70g9PFiLFg45TPP1sKHPj/VA5vV65XD8p0hjTL3thpx++m+ao6xDtG0bflzOc7Kgn/5HT/2LfvofPW2asyNPtV1CwAuEHlq/ZdmuXTu53W7fttvtlsvlslgRAADA8WU9kF1zzTVavny5du3apb179+qDDz5QdHS07bIAAACOG+u3LM8880zdf//9Gj58uGpra3XzzTfr0ksvtV0WAADAceMwxhjbRQAAAJzMrN+yBAAAONkRyAAAACwjkAEAAFhGIAMAALCMQAYAAGAZgewA8+fP14ABAxQXF6fXX3/9iPOWLFmifv36HcfKAlND/XzuuecUGxurgQMHauDAgb/Yc/ysoZ5+9913GjZsmG644Qbdcccd+vHHHy1UGTh+qZ/FxcW+r82BAweqT58+SkpKslRp4Gjoa3TdunUaNGiQbrjhBt1555366aefLFQZOBrq59KlS5WcnKzk5GT94Q9/UGVlpYUqA0tFRYWSkpL0/fffH/JYcXGxUlJSFB8fr0mTJqmuru74FWZgjDHmhx9+MLGxsWb37t2msrLSJCcnmw0bNhwyz+12m4SEBBMbG2uhysDRmH7eeeed5osvvrBUYeBpqKder9fExcWZpUuXGmOMeeqpp0xGRoatck94jf0/b4wxVVVVJjEx0RQVFR3nKgNLY3o6ZMgQs2TJEmOMMU888YTJysqyUWpAaKifP/74o7n66qt9Yy+88IJ57LHHbJUbEL766iuTlJRkoqKizNatWw95PDEx0Xz55ZfGGGMefvhh8/rrrx+32rhC9m+FhYW6+uqrddppp6lVq1aKj49XQUHBIfPS09N17733WqgwsDSmn2vXrtXzzz+v5ORkPfroo6qpqbFUbWBoqKfr1q1Tq1atfO90cdddd2no0KG2yj3hNfb/vCQ9//zz6tmzp3r06HGcqwwsjemp1+v1XcXZu3evwsLCbJQaEBrq56ZNm3TWWWfp/PPPlyTFxsZq0aJFtsoNCDk5OZoyZcph36KxpKRE1dXV6t69uyQpJSXliN8TmgOB7N/KysrkdDp92y6XS6WlpfXm/N///Z8uvvhidevW7XiXF3Aa6mdlZaUuuugiTZgwQXPnztVPP/2k7OxsG6UGjIZ6umXLFp1xxhmaOHGibrrpJk2ZMkWtWrWyUWpAaMz/eUkqLy9XTk4Ov4g1QmN6mpaWpvT0dPXu3VuFhYUaPHjw8S4zYDTUz//6r//SDz/8oPXr10uS3n//fe3YseO41xlIpk+ffsRfrA7ut9PpPOz3hOZCIPs3r9crh8Ph2zbG1Nv+9ttv9cEHH+juu++2UV7Aaaifv/nNb/Tiiy+qU6dOCg4O1siRI7V06VIbpQaMhnpaV1enlStXasiQIZo7d646dOigGTNm2Cg1IDTUz/3effddXX/99Wrbtu3xLC8gNdTT6upqTZo0SX/5y1+0bNkypaam6qGHHrJRakBoqJ9t2rTRk08+qUceeUSDBg2Sy+VSSEiIjVJ/FRr7PaG5EMj+rV27dnK73b5tt9td75JmQUGB3G63Bg0apNGjR6usrEypqak2Sg0IDfVz27Ztevvtt33bxhgFB1t/a9UTWkM9dTqd6tixoy655BJJUlJSklavXn3c6wwUDfVzv0WLFmnAgAHHs7SA1VBPv/32W4WGhvrer/jWW2/VypUrj3udgaKhfno8HrVr105vvfWW5syZo4suukgdOnSwUeqvwsH93rFjx2G/JzQXAtm/XXPNNVq+fLl27dqlvXv36oMPPvA9F0eSxo0bpwULFig3N1cvvPCCXC6X/va3v1ms+MTWUD/DwsL01FNPaevWrTLG6PXXX9dvf/tbixWf+Brq6WWXXaZdu3b5bl8sXrxYUVFRtso94TXUT+nnXxTWrVunyy67zFKVgaWhnnbs2FE//PCDvvvuO0nShx9+6PsFAodqqJ8Oh0MjR45UaWmpjDH6y1/+wi8Px6B9+/YKDQ3VqlWrJEm5ubmHfE9oVsft5QMB4N133zWJiYkmLi7OvPDCC8YYY0aNGmVWr15db97WrVt5lWUjNNTPgoIC3+NpaWmmpqbGZrkBoaGefvXVV2bQoEFmwIABZuTIkWbHjh02yz3hNdTPHTt2mGuuucZmiQGnoZ4uWbLEJCcnm6SkJDNixAizZcsWm+We8Brq50cffWSSkpJMXFycmTJlitm3b5/NcgNGbGys71WWB/azuLjYDBo0yMTHx5vx48cf159LDmOMOX7xDwAAAAfjliUAAIBlBDIAAADLCGQAAACWEcgAAAAsI5ABAABYRiADENBqa2vVu3dvjRo1ynYpANBkBDIAAW3hwoXq0qWL1q5dq3/961+2ywGAJiGQAQhob7zxhq677joNGDBAf/3rX33jb7/9thITE5WcnKzhw4dr+/btRxxfsWKFkpKSfPseuP3ss8/qjjvuUHJysh544AHt2LFDd999t2699Vb169dPw4YN086dOyVJGzdu1LBhw3zHz8/P16pVq9S3b195vV5J0t69e9WrVy/t2rXreLUIQAAgkAEIWP/85z/15ZdfKiEhQTfeeKNyc3O1e/durV+/XpmZmXrppZc0f/589evXT7Nnzz7ieENKSko0d+5cZWZm6r333lP37t315ptv6sMPP1RYWJhyc3MlSePHj1dCQoLee+89vfDCC8rKytKFF16oU089VZ988okk6b333lOvXr0UERHRrL0BEFh4N2cAAeuNN95QbGysTj/9dJ1++uk6++yzlZOTo5YtW6p3796KjIyUJN1+++2SpFdeeeWw4ytWrPjF83Tv3l3BwT9/uxwxYoQ+//xzvfLKK9q0aZM2bNigbt26ac+ePVq/fr1uueUWSVJkZKQWLVokSRo6dKhycnIUExOjN998Uw8++KC/WwEgwBHIAASkqqoq5ebmqmXLlurXr58kqaKiQq+99ppGjRolh8Phm1tdXa2SkhIFBQUddtzhcOjAd5Grra2td65WrVr5/v3UU09p9erVGjRokK666irV1dXJGOMLbAce/7vvvtNZZ52l5ORkZWVl6bPPPlNVVZV69uzp32YACHjcsgQQkObPn6/TTjtNn3zyiRYvXqzFixdr0aJFqqqqUnl5uZYvX66ysjJJ0t///nc99dRTuuqqqw47HhERoW3btmnnzp0yxui999474nmXLVumESNG6MYbb1Tbtm1VWFgoj8ej8PBwRUVFad68eZKk7du3a8iQISovL9cpp5yiG264QRMnTtTgwYObvzkAAg5XyAAEpDfeeEO/+93vFBQU5Btr06aNhg0bpo8++kgTJkzw/SkMp9Opxx9/XGeeeeYRxwcPHqxBgwbJ6XSqb9++WrNmzWHPe8899ygjI0PPPPOMQkJCdPnll2vLli2SpD/96U+aOnWqXn31VTkcDk2fPl1Op1OSlJKSopycHN14443N2RYAAcphDrxODwDwO2OMXnzxRZWUlGjq1Km2ywFwAuIKGQA0s+uuu04ul0vZ2dm2SwFwguIKGQAAgGU8qR8AAMAyAhkAAIBlBDIAAADLCGQAAACWEcgAAAAsI5ABAABY9v80nFU3q/ej9wAAAABJRU5ErkJggg==\n", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "acc_index_accuracy_stats = accuracy_per_query_point(accurate_neighbors, tree_neighbors)\n", "sns.distplot(acc_index_accuracy_stats, kde=False)\n", "plt.title(\"Distribution of accuracy per query point\")\n", "plt.xlabel(\"Accuracy\")\n", "print(f\"Average accuracy of {np.mean(acc_index_accuracy_stats)}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "But we see that we are now essentially just about perfect with our accuracy -- only a few query points have any neighbors at all that aren't true nearest neighbors (and they are almost certainly very close).\n", "\n", "Now we can go the other way, and try to make a very very fast index (supposing we want to make an awful lot of queries at a very high throughput rate). We can decrease ``n_neighbors``." ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/Users/leland/anaconda3/envs/numba51/lib/python3.8/site-packages/scipy/sparse/_index.py:124: SparseEfficiencyWarning: Changing the sparsity structure of a csr_matrix is expensive. lil_matrix is more efficient.\n", " self._set_arrayXarray(i, j, x)\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 23.5 s, sys: 974 ms, total: 24.5 s\n", "Wall time: 9.15 s\n" ] } ], "source": [ "%%time\n", "fast_index = pynndescent.NNDescent(fmnist_train, n_neighbors=5)\n", "fast_index.prepare()" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 290 ms, sys: 13.8 ms, total: 304 ms\n", "Wall time: 305 ms\n" ] } ], "source": [ "%%time\n", "fast_neighbors = fast_index.query(fmnist_test, epsilon=0.0)" ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Average accuracy of 0.6279\n" ] }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAl4AAAGECAYAAADnbC5SAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4yLjIsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy+WH4yJAAAgAElEQVR4nO3de3RU5b3/8c+QhIk5wUtwBmikLEUxNiipSmkUw0VLwiWiA61cJFKliBeocBpFkpKiRpCmoGjjqa3L04PWkkYliDR4QakYBUTlZqi0AkLAZLjY5kKGzMzz+8Mf0wQCyQzJToa8X2u5ZD+zn9nfZ76TzCd7bjZjjBEAAABaXae2LgAAAKCjIHgBAABYhOAFAABgEYIXAACARQheAAAAFiF4AQAAWITgBbSiffv26YorrtDo0aM1evRopaena9y4cVq1alVgn6eeekrLly8/7fU888wzevvttxu9rP78yy+/XIcPHw6qxi1btmju3LmSpK1bt2rGjBlBzQ+Fz+fTPffco9TUVL344outfjy0D1lZWSopKTntPnv37tX06dMtqgiwXmRbFwCc7aKjo1VUVBTYLisr0+TJkxUREaHU1FT9/Oc/b/I61q9fr0svvbTRy5oz/3T+8Y9/qLy8XJJ05ZVXasmSJWd0fc1RXl6udevW6bPPPlNERESrHw/tQ25ubpP77N+/X7t27bKgGqBtELwAi8XHx2vGjBl6/vnnlZqaqtmzZ+uyyy7TXXfdpSVLluitt95SVFSULrjgAs2fP19vvfWWtm3bpoULFyoiIkLvvPOOvvnmG+3du1eDBw/WoUOHAvMl6cknn9TWrVvl9/v1wAMPaMiQIXr11Ve1evVq/e53v5OkwPavfvUrLVmyRJWVlXr44Yd1yy236NFHH9XKlStVWVmpefPmaceOHbLZbLrhhhs0a9YsRUZG6sorr9TUqVP1wQcfqKKiQlOmTNGECRNOWuvHH3+shQsX6ujRo4qKitIDDzygq6++WlOmTJHX65XL5dLTTz+t7373u4E5u3bt0iOPPKLq6mq53W4lJCToySeflN1u1+bNm/XYY48Fru/BBx9UcnLyKccvv/xyffjhh4qLi5OkwPbOnTuVm5urmJgYVVdX65VXXtHChQu1efNmVVdXyxijxx57TNdcc42qq6v12GOP6ZNPPlFERIRuuukmTZs2TYMGDVJBQYEuvvhiSdLkyZN1++2366abbgqsZf369crLy9N3vvMdffnll4qOjtaCBQvUu3dvHTt2THl5edq4caN8Pp++973vKTs7W7GxsRo6dKiuuuoq/f3vf9esWbP0ox/9KHCdlZWVys7O1o4dO+R0OtWjRw/17NlT06dP19ChQ/XUU0/pyiuvlKQG25988ony8vJ09OhRderUSffff3/gvlFYWKijR48qNjZWkZGRGj58uH7yk59IkvLz8/XNN99ozpw5DXr7ve99Tz/72c/0/vvvq6amRrNmzdKwYcMkSb/97W/1xhtvKCIiQhdffLF++ctfyuFwaNKkSZo4caL69u2ryZMna9CgQdq8ebP+/e9/KzMzU0OHDlV2drbKy8t111136fnnnz+zHzagPTIAWs3evXtNUlLSSeNffPGF6devnzHGmIceesj84Q9/MPv37zdXX3218Xg8xhhjnn/+efPWW28ZY4y5/fbbzV//+tfA/nfccUfguo7PN8aYPn36mN/97nfGGGP+/ve/mx/84Afm0KFD5pVXXjFTp04NzKm/Xf/fH330kRk5cqQxxpgHH3zQPProo8bv9xuPx2PuvPPOwHX36dPHLF261BhjzNatW03fvn1NbW1tgzUePnzYJCcnm88++yyw5h/84Afmq6++OuXtYowxCxYsMMuXLzfGGHPs2DEzatQoU1xcbI4dO2auv/568+677waOO2rUKOPxeBod9/l8pk+fPubQoUOB6z6+/dFHH5mEhASzb98+Y4wxn3zyiZk+fbrx+XzGGGN+97vfmbvvvtsYY8zjjz9uZs6cabxer/F4PGbixInmo48+Mo899ph54oknjDHG7NmzxwwaNMh4vd4Gazl+nI0bNxpjjPnTn/5kbr31VmOMMU8//bRZsGCB8fv9xhhjfvOb35icnBxjjDFDhgwxzzzzTKO3z6OPPmoyMzON3+83brfb3HDDDWbJkiWBeVu2bAnse3z7m2++McOGDTN79+41xhjz9ddfm5SUFFNWVmZeeeUV079/f1NZWWmMMeatt94yY8aMMcYY4/P5zJAhQ8w///nPk+ro06ePefbZZ40xxpSWlpprrrnGHDp0yBQWFprbbrvNVFdXG2OMWbJkibnzzjuNMf+5H+/du9f06dPHrFmzxhhjTHFxsRk8eHDgNjt+HwTORpzxAtqAzWZTdHR0g7Fu3bopISFBt956q1JSUpSSkqLk5ORG519zzTWnvO7x48dLkvr06aPevXvr008/DanGv/3tb3r55Zdls9nUuXNnjRs3Tn/84x81depUSdKNN94oSUpMTNSxY8dUU1Mju90emL9lyxZ997vfVb9+/SRJl112ma6++mpt2LBBAwYMOOVxMzMz9cEHH+j3v/+9du/erYqKCtXU1OiLL75Qp06dNHjwYElS37599frrr2v79u2NjjelR48eio+PlyR9//vf13nnnac///nP2rt3r9avX6//+q//kiSVlJTo4YcfVkREhCIiIgKvSXM6nbr99ts1c+ZMLVu2TGPHjm30adOEhARde+21kqQxY8bokUce0ZEjR/Tee++psrIy8Jqnuro6de3aNTDv+JwTffTRR8rKypLNZtOFF16o1NTUJtf62Wefye1267777guM2Ww2/f3vf5f07ZnA2NhYSdKQIUOUm5urHTt2qLy8XBdddJEuueSSRq/39ttvD6yxT58+2rhxo/72t7/J5XIpJiZGkpSRkaH/+Z//0bFjxxrMjYqK0qBBgyR9e/bsm2++aXIdwNmA4AW0ga1bt6pPnz4Nxjp16qQXX3xRW7du1YcffqjHH39cN9xwgx588MGT5h9/UGtMp07/ec+M3+9XZGSkbDabTL2vZa2rq2uyRr/fL5vN1mDb6/UGto+HrOP7mBO+9tXn8zWYf3yf+tfRmFmzZsnn82n48OEaPHiwDhw4IGOMIiIiTrq+L7744pTjJ4aFEx/469+G7733nnJzc/XTn/5UN954oy655BKtWLFCkgK333EHDhxQdHS0Lr74Yl1++eV65513tHLlShUUFDS6nsbCWEREhPx+v+bMmRMIH9XV1fJ4PI3WV5/dbm9wW0dFRTW4vP5lx9fs8/nUu3dv/eUvfwlcVl5erri4OL3++usNjhUREaHbbrtNhYWFqqio0Lhx4xqt48S1+f3+wLpOd7+pX/fx++qJ/QPOZryrEbDYrl27lJ+frzvvvLPB+I4dOzRq1Cj17t1bd999tyZPnqytW7dK+vYBrqnActxrr70mSdq+fbu++uor9evXT3Fxcdq5c6c8Ho/q6uq0evXqwP6nuu6BAwfqxRdflDFGx44dU0FBga677rpmrzMpKUlffvmltmzZIknauXOnNm7cqB/84Aennbdu3Trdd999GjFihCRp8+bN8vl8uuSSS2Sz2fTBBx8E1nfHHXecctzv9ysuLi5wG65cufKUx/zggw80ZMgQTZgwQX379tXbb78tn88nSUpOTtZrr70mv9+vY8eOacaMGdq4caMkacKECVq4cKGuuuoqdevWrdHr3rFjh3bs2CFJWrZsmb7//e/r3HPP1cCBA/XSSy/p2LFj8vv9+uUvf6lFixY1ebsOHjxYBQUF8vl8qqys1DvvvBO4LC4uTtu2bZP07evL3G63pG97sWfPnkDdpaWlSk1NDbyp4kQ//vGP9fbbb2v79u0NXl92ouPvpt2+fbt27dql/v3764YbbtArr7yimpoaSdLSpUvVv39/de7cucm1Sd/eH5vzhwEQrjjjBbSy2tpajR49WtK3Z6PsdrtmzZoVeGrsuISEBA0fPlxjxoxRTEyMoqOjlZ2dLenbF0kvWrSoWQ9Ie/fu1S233CKbzaZFixbp/PPP1/XXX6/+/ftr+PDhcjgcGjBgQOBppqSkJP32t7/V/fffr0mTJgWuJzs7W4899pjS09NVV1enG264QdOmTWv2uuPi4vTUU0/p0UcfVW1trWw2m+bPn6+LL75Y+/btO+W8mTNn6r777lNMTIxiY2PVv39/ffXVV+rcubOefvppPf7441q4cKGioqL09NNPn3Y8OztbjzzyiM4991xdd911cjgcjR5z3Lhx+u///m+lp6fL6/Xq+uuv15tvvim/36/7779fubm5Gj16tHw+n0aMGBF4EfmQIUOUnZ192rNCF154oZ588kmVlZUpLi5OCxculCTde++9euKJJ3TrrbfK5/Ppiiuu0OzZs5u8XadOnarHH39cN998s84999wGa/rFL36hX/3qV1q2bJkSExOVmJgY6MWSJUu0cOFCeTweGWO0cOFCXXTRRdqwYcNJx+jatav69u2r3r17n3RGrb5PPvlEBQUF8vv9Wrx4sc477zyNHTtWBw4c0I9//GP5/X716tVLeXl5Ta7ruEsvvVR2u11jx47VX/7yF86G4axjMyc+PwAAaJZPP/1U2dnZWrlyZaMBYf369YF3ibaWRx55RBdccEGLfvbV4cOHNXbsWL300kvq0aNHo/uc+I5RAM3DGS8ACMFDDz2kDRs2aPHixWfVWZmCggItWrRI06dPP2XoAhA6zngBAABYhBfXAwAAWITgBQAAYBGCFwAA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wh6fXA8AAGARghcAAIBFCF4AAAAWIXgBAABYhOAFAABgEYIXAACARQheAAAAFiF4AQAAWITgBQAAYBGCFwAAgEUIXgAAABYheAEAAFiE4AUAAGARghcAAIBFCF4AAAAWIXgBAABYhOAFAABgEYIXAACARQheAAAAFiF4AQAAWITgBQAAYBGCFwAAgEUIXgAAABYheAEAAFiE4AUAAGARghcAAIBFCF4AAAAWIXgBAABYhOAFAABgEYIXAACARSwNXq+//rpGjBihYcOG6aWXXrLy0AAAAG0u0qoDlZeXa/HixXr11VfVuXNnjRs3TgMGDNCll15qVQkAAABtyrIzXiUlJfrhD3+o888/XzExMUpNTVVxcbFVhwcAAGhzlp3xqqiokMPhCGw7nU5t2bKl2fM7dbK1RlkNmE42xURHBT0vMqKTJfXVP1441Bmq+us7xx4pn7d5aw2XPhyfG269OK45PQnn9TV3Xnu7r52qL/SiZQVTZ/2enO19OD63vfaivk6dbLKZ1quzqdvAsuDl9/tls/2nGGNMg+2mXHDBf7VGWScZeUNvS45zpi7qcV5bl9CqwmV94VLnmTjb1xgu6wuXOs9EuKwxXOoMVTitL5xqPc6ypxq7d+8ut9sd2Ha73XI6nVYdHgAAoM1ZFryuu+46ffjhhzp8+LCOHj2qN998UykpKVYdHgAAoM1Z9lRjt27dNHPmTGVkZKiurk5jx47VVVddZdXhAQAA2pzNGGPauggAAICOgE+uBwAAsAjBCwAAwCIELwAAAIsQvAAAACxC8AIAALBIhwter7/+ukaMGKFhw4bppZdeOuny0tJSuVwupaamKisrS16vtw2q7Hia6svbb7+t0aNH6+abb9a9996rf/3rX21QZcfSVE+Oe++99zR06FALK+vYmurLl19+qUmTJunmm2/WXXfdxc+KBZrqyfbt2zVmzBjdfPPNuvvuu/Xvf/+7DarseKqqqjRq1Cjt27fvpMva9LHedCBff/21GTJkiDly5Iiprq426enpZufOnQ32GTlypPn000+NMcY8/PDD5qWXXmqLUjuUpvpSWVlprr/+evP1118bY4x58sknzaOPPtpW5XYIzflZMcYYt9tt0tLSzJAhQ9qgyo6nqb74/X4zbNgws3btWmOMMb/+9a/NwoUL26rcDqE5Pyvjx4837733njHGmPnz55tFixa1RakdymeffWZGjRplEhMTzd69e0+6vC0f6zvUGa+SkhL98Ic/1Pnnn6+YmBilpqaquLg4cHlZWZlqa2uVlJQkSXK5XA0uR+toqi91dXXKyclRt27dJEmXX365Dhw40FbldghN9eS47Oxs3X///W1QYcfUVF+2b9+umJiYwLeCTJs2TRMnTmyrcjuE5vys+P1+VVdXS5KOHj2q6Ojotii1QykoKFBOTk6jX03Y1o/1HSp4VVRUyOFwBLadTqfKy8tPebnD4WhwOVpHU3254IIL9KMf/UiSVFtbq+eee0433XST5XV2JE31RJL+7//+T9/73vfUr18/q8vrsJrqy1dffaULL7xQc+bM0a233qqcnBzFxMS0RakdRnN+VmbPnq3s7GwNHDhQJSUlGjdunNVldji5ubm69tprG72srR/rO1Tw8vv9stlsgW1jTIPtpi5H62ju7V5ZWampU6cqISFBt956q5UldjhN9eSLL77Qm2++qXvvvbctyuuwmuqL1+vVhg0bNH78eL322mvq2bOnFixY0BaldhhN9aS2tlZZWVn63//9X61bt04TJkzQQw891Bal4v9r68f6DhW8unfvLrfbHdh2u90NTkOeePnBgwcbPU2JltVUX6Rv/0KZMGGCLr/8cuXm5lpdYofTVE+Ki4vldrs1ZswYTZ06NdAftK6m+uJwONSrVy9deeWVkqRRo0Zpy5YtltfZkTTVky+++EJ2uz3w3cS33XabNmzYYHmd+I+2fqzvUMHruuuu04cffqjDhw/r6NGjevPNNwOvhZCk+Ph42e12bdq0SZJUVFTU4HK0jqb64vP5NG3aNA0fPlxZWVmchbRAUz2ZMWOGVq9eraKiIj333HNyOp3605/+1IYVdwxN9eX73/++Dh8+rB07dkiS1qxZo8TExLYqt0Noqie9evXS119/rS+//FKS9M477wSCMdpGWz/WR1p2pHagW7dumjlzpjIyMlRXV6exY8fqqquu0s9+9jPNmDFDV155pfLy8pSdna2qqiolJiYqIyOjrcs+6zXVl6+//lqff/65fD6fVq9eLUnq27cvZ75aUXN+VmC95vTlt7/9rbKzs3X06FF1795dCxcubOuyz2rN6cn8+fP1wAMPyBijrl276vHHH2/rsjuk9vJYbzPGGMuOBgAA0IF1qKcaAQAA2hLBCwAAwCIELwAAAIsQvAAAACxC8AIAALAIwQtAu1dXV6eBAwdqypQpbV0KAJwRgheAdu+tt95SQkKCtm3bpn/+859tXQ4AhIzgBaDde/nll3XjjTdqxIgR+uMf/xgYLyws1MiRI5Wenq6MjAwdOHDglOPr16/XqFGjAnPrbz/99NO66667lJ6erl/84hc6ePCg7r33Xt12220aOnSoJk2apEOHDkmSdu3apUmTJgWuf9WqVdq0aZMGDx4sv98vSTp69KiSk5N1+PBhq24iAGGC4AWgXfvHP/6hTz/9VGlpabrllltUVFSkI0eOaMeOHcrLy9Mf/vAHvf766xo6dKieffbZU443paysTK+99pry8vL0xhtvKCkpScuWLdM777yj6OhoFRUVSZJmzZqltLQ0vfHGG3ruuee0aNEiXX755TrvvPP0/vvvS5LeeOMNJScnKy4urlVvGwDhp0N9ZRCA8PPyyy9ryJAhuuCCC3TBBRfooosuUkFBgTp37qyBAweqR48ekqTJkydLkl544YVGx9evX3/a4yQlJSky8ttfiXfccYc+/vhjvfDCC9q9e7d27typfv366ZtvvtGOHTv04x//WJLUo0cPvf3225KkiRMnqqCgQIMGDdKyZcv04IMPtvRNAeAsQPAC0G7V1NSoqKhInTt31tChQyVJVVVVevHFFzVlypQGX5heW1ursrIyRURENDpus9lU/xvS6urqGhwrJiYm8O9f//rX2rJli8aMGaMBAwbI6/XKGBMIZvWv/8svv9R3vvMdpaena9GiRfroo49UU1Oj/v37t+yNAeCswFONANqt119/Xeeff77ef/99rVmzRmvWrNHbb7+tmpoaVVZW6sMPP1RFRYUk6c9//rN+/etfa8CAAY2Ox8XFaf/+/Tp06JCMMXrjjTdOedx169bpjjvu0C233KKuXbuqpKREPp9PsbGxSkxM1PLlyyVJBw4c0Pjx41VZWalzzjlHN998s+bMmaNx48a1/o0DICxxxgtAu/Xyyy/rpz/9qSIiIgJj5557riZNmqR3331XmZmZgY+YcDgcevzxx9WtW7dTjo8bN05jxoyRw+HQ4MGDtXXr1kaPe99992nhwoV66qmnFBUVpauvvlpfffWVJOk3v/mN5s2bp6VLl8pmsyk3N1cOh0OS5HK5VFBQoFtuuaU1bxYAYcxm6p97BwCExBij3//+9yorK9O8efPauhwA7RRnvACgBdx4441yOp3Kz89v61IAtGOc8QIAALAIL64HAACwCMELAADAIgQvAAAAixC8AAAALELwAgAAsAjBCwAAwCL/D44c/4610xPGAAAAAElFTkSuQmCC\n", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "fast_index_accuracy_stats = accuracy_per_query_point(fast_neighbors, tree_neighbors)\n", "sns.distplot(fast_index_accuracy_stats, kde=False)\n", "plt.title(\"Distribution of accuracy per query point\")\n", "plt.xlabel(\"Accuracy\")\n", "print(f\"Average accuracy of {np.mean(fast_index_accuracy_stats)}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The results are not that accurate -- there are some query points that got no neighbors right (although it likely still got close-by points). However is speed is of the essence we have gotten under the time we were previously capable of with an ``epsilon`` of ``0.0``. Depending on your needs, and the nature of your dataset you can play with ``n_neighbors`` to find the trade-off between speed and accuracy that best suits you.\n", "\n", "The next step is ``diversify_prob``. When PyNNDescent is constructing it's graph based index it tries to \"diversify\" the edges, pruning away some that are largely redundant. The value of ``diversify_prob`` is the probability that an edge identified as redundant will get pruned. The more edges that get pruned away the less accurate the index is, but with fewer edges searching becomes much faster. If you want as accurate an index as you can get then turn ``diversify_prob`` to ``0.0``. For speed you should obviously use ``1.0`` and prune away as many edges as you can. The default value is, in fact, ``1.0`` since it is usually worth it to edges identified by the diversify step. Let's try setting it to ``0.0`` and see how that effects speed and accuracy." ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 1min 47s, sys: 1.07 s, total: 1min 48s\n", "Wall time: 40.6 s\n" ] } ], "source": [ "%%time\n", "accurate_index = pynndescent.NNDescent(fmnist_train, n_neighbors=50, diversify_prob=0.0)\n", "accurate_index.prepare()" ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 3.04 s, sys: 37.6 ms, total: 3.07 s\n", "Wall time: 3.12 s\n" ] } ], "source": [ "%%time\n", "accurate_neighbors = accurate_index.query(fmnist_test, epsilon=0.2)" ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Average accuracy of 0.9999299999999999\n" ] }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAmQAAAGECAYAAACYvTyjAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4yLjIsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy+WH4yJAAAgAElEQVR4nO3df1zUVb7H8fcIiBlaYjNBZt6yzBZLtrSyEsS6gAJpaDcSf7TlWm6pZVmkpGtpayyLZS3e2rrt3n5tlAVJhGmWZZiRlb/CcgtN0WD8tfFDEGbO/aOHc8UfqSicUV/Pf/B75ny/33PmA8zb850v4zDGGAEAAMCaVrYHAAAAcKojkAEAAFhGIAMAALCMQAYAAGAZgQwAAMAyAhkAAIBlBDLAgs2bN+uSSy7RoEGDNGjQICUlJSklJUUFBQW+Pk899ZRyc3N/9TjPPPOMFi1adNDH9t3/4osv1o4dO45qjKtWrdLUqVMlSatXr9b48eOPav+m8Hg8Gjt2rOLi4vTyyy83+/ngH6ZMmaKioqJf7bNp0yaNGzeuhUYEtLxA2wMATlVt2rRRXl6eb7usrEy33XabAgICFBcXpwkTJhz2GMuXL9eFF1540MeOZP9f869//Uvl5eWSpEsvvVRz5sw5puMdifLyci1dulRff/21AgICmv188A8zZ848bJ8tW7aotLS0BUYD2EEgA/xEp06dNH78eL3wwguKi4tTWlqaLrroIt1xxx2aM2eOFi5cqKCgIHXo0EF/+tOftHDhQq1Zs0YZGRkKCAjQBx98oF27dmnTpk3q16+ftm/f7ttfkp588kmtXr1aXq9X9957r2JiYvTWW29pwYIFevbZZyXJt/3HP/5Rc+bMUWVlpR5++GENHjxYjz32mPLz81VZWanp06dr3bp1cjgc6tu3ryZOnKjAwEBdeumlGjNmjD799FNVVFRo9OjRGjZs2AFz/eKLL5SRkaHdu3crKChI9957ry6//HKNHj1aDQ0NSk5O1tNPP63zzjvPt09paakeffRRVVdXy+12q3v37nryyScVHByslStXasaMGb7jPfjgg+rTp88h2y+++GItW7ZMoaGhkuTbXr9+vWbOnKm2bduqurpa8+bNU0ZGhlauXKnq6moZYzRjxgxdccUVqq6u1owZM/Tll18qICBAN9xwg+666y5FR0crJydH559/viTptttu0/Dhw3XDDTf45rJ8+XJlZmbqnHPO0Q8//KA2bdpo1qxZ6tq1q/bs2aPMzEwVFxfL4/HoN7/5jdLT0xUSEqL+/fvrsssu07fffquJEyfqP//zP33HrKysVHp6utatWyeXy6Xw8HB17txZ48aNU//+/fXUU0/p0ksvlaRG219++aUyMzO1e/dutWrVSvfcc4/ve+PNN9/U7t27FRISosDAQA0YMED/9V//JUnKzs7Wrl27NHny5Ea1/c1vfqPf//73+uSTT1RTU6OJEycqNjZWkvTXv/5V7777rgICAnT++efrkUcekdPp1IgRI5SamqoePXrotttuU3R0tFauXKmff/5ZkyZNUv/+/ZWenq7y8nLdcccdeuGFF47thw3wRwZAi9u0aZOJjIw8oP27774zPXv2NMYY89BDD5nnn3/ebNmyxVx++eWmrq7OGGPMCy+8YBYuXGiMMWb48OHmvffe8/UfNWqU71h79zfGmG7duplnn33WGGPMt99+a6688kqzfft2M2/ePDNmzBjfPvtu7/vvzz77zCQkJBhjjHnwwQfNY489Zrxer6mrqzO3336779jdunUzL730kjHGmNWrV5sePXqY2traRnPcsWOH6dOnj/n66699c77yyivNjz/+eMjnxRhjZs2aZXJzc40xxuzZs8ckJiaawsJCs2fPHnPttdeaDz/80HfexMREU1dXd9B2j8djunXrZrZv3+479t7tzz77zHTv3t1s3rzZGGPMl19+acaNG2c8Ho8xxphnn33W3HnnncYYYx5//HFz3333mYaGBlNXV2dSU1PNZ599ZmbMmGGeeOIJY4wxGzduNNHR0aahoaHRXPaep7i42BhjzKuvvmpuuukmY4wxTz/9tJk1a5bxer3GGGP+8pe/mGnTphljjImJiTHPPPPMQZ+fxx57zEyaNMl4vV7jdrtN3759zZw5c3z7rVq1ytd37/auXbtMbGys2bRpkzHGmJ9++slERUWZsrIyM2/ePNO7d29TWVlpjDFm4cKFZsiQIcYYYzwej4mJiTHff//9AePo1q2bmTt3rjHGmJKSEnPFFVeY7du3mzfffNPccsstprq62hhjzJw5c8ztt99ujPn/7+NNmzaZbt26mcWLFxtjjCksLDT9+vXzPWd7vweBkxErZIAfcTgcatOmTaO2s88+W927d9dNN92kqKgoRUVFqU+fPgfd/4orrjjksW+99VZJUrdu3dS1a1d99dVXTRrjxx9/rNdee00Oh0OtW7dWSkqK/vGPf2jMmDGSpOuvv16SFBERoT179qimpkbBwcG+/VetWqXzzjtPPXv2lCRddNFFuvzyy/X555/rqquuOuR5J02apE8//VR/+9vftGHDBlVUVKimpkbfffedWrVqpX79+kmSevToofnz52vt2rUHbT+c8PBwderUSZL029/+VmeccYb++c9/atOmTVq+fLlOP/10SVJRUZEefvhhBQQEKCAgwPeeN5fLpeHDh+u+++7T66+/rqFDhx708mv37t3Vq1cvSdKQIUP06KOPaufOnfroo49UWVnpe09VfX29Onbs6Ntv7z77++yzzzRlyhQ5HA6dddZZiouLO+xcv/76a7ndbt19992+NofDoW+//VbSLyuHISEhkqSYmBjNnDlT69atU3l5uc4991xdcMEFBz3u8OHDfXPs1q2biouL9fHHHys5OVlt27aVJI0cOVL//d//rT179jTaNygoSNHR0ZJ+WW3btWvXYecBnAwIZIAfWb16tbp169aorVWrVnr55Ze1evVqLVu2TI8//rj69u2rBx988ID9977YHUyrVv9/D4/X61VgYKAcDofMPh9nW19ff9gxer1eORyORtsNDQ2+7b3ha28fs9/H5Xo8nkb77+2z7zEOZuLEifJ4PBowYID69eunrVu3yhijgICAA4733XffHbJ9/xCxfyDY9zn86KOPNHPmTP3ud7/T9ddfrwsuuEDvvPOOJPmev722bt2qNm3a6Pzzz9fFF1+sDz74QPn5+crJyTnofA4W0gICAuT1ejV58mRfKKmurlZdXd1Bx7ev4ODgRs91UFBQo8f3fWzvnD0ej7p27ao33njD91h5eblCQ0M1f/78RucKCAjQLbfcojfffFMVFRVKSUk56Dj2n5vX6/XN69e+b/Yd997v1f3rB5zMuMsS8BOlpaXKzs7W7bff3qh93bp1SkxMVNeuXXXnnXfqtttu0+rVqyX98sJ3uCCz19tvvy1JWrt2rX788Uf17NlToaGhWr9+verq6lRfX68FCxb4+h/q2Nddd51efvllGWO0Z88e5eTk6JprrjnieUZGRuqHH37QqlWrJEnr169XcXGxrrzyyl/db+nSpbr77rs1cOBASdLKlSvl8Xh0wQUXyOFw6NNPP/XNb9SoUYds93q9Cg0N9T2H+fn5hzznp59+qpiYGA0bNkw9evTQokWL5PF4JEl9+vTR22+/La/Xqz179mj8+PEqLi6WJA0bNkwZGRm67LLLdPbZZx/02OvWrdO6deskSa+//rp++9vfqn379rruuuv0yiuvaM+ePfJ6vXrkkUeUlZV12Oe1X79+ysnJkcfjUWVlpT744APfY6GhoVqzZo2kX96/5na7Jf1Si40bN/rGXVJSori4ON/NHPu7+eabtWjRIq1du7bR+9f2t/fu3rVr16q0tFS9e/dW3759NW/ePNXU1EiSXnrpJfXu3VutW7c+7NykX74fj+Q/DMCJihUywJLa2loNGjRI0i+rV8HBwZo4caLvEtte3bt314ABAzRkyBC1bdtWbdq0UXp6uqRf3pydlZV1RC9UmzZt0uDBg+VwOJSVlaUzzzxT1157rXr37q0BAwbI6XTqqquu8l2uioyM1F//+lfdc889GjFihO846enpmjFjhpKSklRfX6++ffvqrrvuOuJ5h4aG6qmnntJjjz2m2tpaORwO/elPf9L555+vzZs3H3K/++67T3fffbfatm2rkJAQ9e7dWz/++KNat26tp59+Wo8//rgyMjIUFBSkp59++lfb09PT9eijj6p9+/a65ppr5HQ6D3rOlJQU3X///UpKSlJDQ4OuvfZavf/++/J6vbrnnns0c+ZMDRo0SB6PRwMHDvS9eT0mJkbp6em/uop01lln6cknn1RZWZlCQ0OVkZEhSfrDH/6gJ554QjfddJM8Ho8uueQSpaWlHfZ5HTNmjB5//HHdeOONat++faM5PfDAA/rjH/+o119/XREREYqIiPDVYs6cOcrIyFBdXZ2MMcrIyNC5556rzz///IBzdOzYUT169FDXrl0PWIHb15dffqmcnBx5vV7Nnj1bZ5xxhoYOHaqtW7fq5ptvltfrVZcuXZSZmXnYee114YUXKjg4WEOHDtUbb7zB6hlOOg6z//UEAMAx+eqrr5Senq78/PyDBofly5f77lptLo8++qg6dOhwXP92144dOzR06FC98sorCg8PP2if/e9gBXBkWCEDgOPooYce0ueff67Zs2efVKs4OTk5ysrK0rhx4w4ZxgA0HStkAAAAlvGmfgAAAMsIZAAAAJYRyAAAACwjkAEAAFh2wt9luXNntbze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a63N4WQVExPju8ty35qUlJSYIUOGmLi4ODNx4kRTV1dnjGn513qHMVykBgAAsOmUvWQJAADgLwhkAAAAlhHIAAAALCOQAQAAWEYgAwAAsIxABuCEVl9fr+uuu06jR4+2PRQAaDICGYAT2sKFC9W9e3etWbNG33//ve3hAECTEMgAnNBee+01XX/99Ro4cKD+8Y9/+NrffPNNJSQkKCkpSSNHjtTWrVsP2b58+XIlJib69t13++mnn9Ydd9yhpKQkPfDAA9q2bZv+8Ic/6JZbblH//v01YsQIbd++XZJUWlqqESNG+I5fUFCgFStWqF+/fvJ6vZKk3bt3q0+fPtqxY0dLPUUATgAEMgAnrH/961/66quvFB8fr8GDBysvL087d+7UunXrlJmZqeeff17z589X//79NXfu3EO2H05ZWZnefvttZWZm6t1331VkZKRef/11ffDBB2rTpo3vo6EmTpyo+Ph4vfvuu3ruueeUlZWliy++WGeccYY++eQTSdK7776rPn368LE4ABoJtD0AAGiq1157TTExMerQoYM6dOigc889Vzk5OWrdurWuu+46hYeHS5Juu+02SdKLL7540Pbly5f/6nkiIyMVGPjLr8tRo0bpiy++0IsvvqgNGzZo/fr16tmzp3bt2qV169bp5ptvliSFh4dr0aJFkqTU1FTl5OQoOjpar7/+uh588MHj/VQAOMERyACckGpqapSXl6fWrVurf//+kqSqqiq9/PLLGj16tBwOh69vbW2tysrKFBAQcNB2h8OhfT9Frr6+vtG52rZt6/v3n//8Z61atUpDhgzRVVddpYaGBhljfIFt3+P/8MMPOuecc5SUlKSsrCx99tlnqqmpUe/evY/vkwHghMclSwAnpPnz5+vMM8/UJ598osWLF2vx4sVatGiRampqVFlZqWXLlqmiokKS9M9//lN//vOfddVVVx20PTQ0VFu2bNH27dtljNG77757yPMuXbpUo0aN0uDBg9WxY0cVFRXJ4/EoJCREERERys3NlSRt3bpVt956qyorK3Xaaafpxhtv1OTJk5WSktL8Tw6AEw4rZABOSK+99pp+97vfKSAgwNfWvn17jRgxQh9++KEmTZrk+1MYTqdTjz/+uM4+++xDtqekpGjIkCFyOp3q16+fVq9efdDz3n333crIyNBTTz2loKAgXX755frxxx8lSX/5y180ffp0vfTSS3I4HJo5c6acTqckKTk5WTk5ORo8eHBzPi0ATlAOs+86PQDguDPG6G9/+5vKyso0ffp028MB4IdYIQOAZnb99dfL5XIpOzvb9lAA+ClWyAAAACzjTf0AAACWEcgAAAAsI5ABAABYRiADAACwjEAGAABgGYEMAADAsv8DXnum3ylSZPkAAAAASUVORK5CYII=\n", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "acc_index_accuracy_stats = accuracy_per_query_point(accurate_neighbors, tree_neighbors)\n", "sns.distplot(acc_index_accuracy_stats, kde=False)\n", "plt.title(\"Distribution of accuracy per query point\")\n", "plt.xlabel(\"Accuracy\")\n", "print(f\"Average accuracy of {np.mean(acc_index_accuracy_stats)}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "At this point out of ten-thousand query points, each with 10 neighbors, we are getting a grand total of eight wrong -- that's eight out of a hundred-thousand! Of course our query time is up to several seconds now, but that still beats the many minutes of the kd-trees by a wide margin.\n", "\n", "We still have one parameter left however -- the ``pruning_degree_multplier``. This gives the multiple of the number of neighbors many edges that any given vertex in the search graph is allowed to have. So if ``n_neighbors`` is 30 and the ``pruning_degree_multiplier`` is the default ``1.5`` then any vertex in the graph can have at most forty-five edges connected to it. This prevents hubs (which form in high dimensional spaces) causing the search stage to blow out in complexity. It can be tuned, however, to trade off between speed and accuracy. Higher multiples result in more accurate graphs with more edges that take longer to search. Low multiples (including less than one!) result in graphs with many fewer edges that are fast to search, but may get stuck in local minima or fail to find the true nearest neighbors. Let's play with that dial for a moment..." ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 1min 47s, sys: 1.35 s, total: 1min 48s\n", "Wall time: 41.2 s\n" ] } ], "source": [ "%%time\n", "accurate_index = pynndescent.NNDescent(\n", " fmnist_train, \n", " n_neighbors=50, \n", " diversify_prob=0.0, \n", " pruning_degree_multiplier=3.0\n", ")\n", "accurate_index.prepare()" ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 3.65 s, sys: 69.9 ms, total: 3.72 s\n", "Wall time: 3.71 s\n" ] } ], "source": [ "%%time\n", "accurate_neighbors = accurate_index.query(fmnist_test, epsilon=0.2)" ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Average accuracy of 1.0\n" ] }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAmQAAAGECAYAAACYvTyjAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4yLjIsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy+WH4yJAAAgAElEQVR4nO3de1yUZcL/8e84IEZYic0oa66vstQCk0prrQS1VvBAFtqTYlrbwbTS0ieLVVaz1IxlobTwqc1Xzz6dVjoISYRplmWYmpWntMPmKTQYTxsHQZi5fn/0c1YURZG4QD7vf+i+5p6Z656LyY/3PYjDGGMEAAAAa5rZngAAAEBTR5ABAABYRpABAABYRpABAABYRpABAABYRpABAABYRpABFvz000+69NJLNXjwYA0ePFhxcXEaNmyYcnJy/Ps8++yzyszMPOHjPPfcc1q6dGm1tx15/86dO2vfvn2nNMf169dr6tSpkqQNGzZo/Pjxp3T/2vB6vRo7dqxiYmL06quv/ubPh4ZhypQpysvLO+E+O3fu1Lhx4+ppRkD9C7A9AaCpatGihbKysvzb+fn5uvPOO+V0OhUTE6OHHnqoxsdYtWqVLr744mpvO5n7n8gPP/yggoICSVLXrl01Z86c03q8k1FQUKAVK1bo66+/ltPp/M2fDw3DzJkza9xn165d2rp1az3MBrCDIAMaiHbt2mn8+PGaP3++YmJilJiYqEsuuUR333235syZoyVLligwMFCtWrXSU089pSVLlmjjxo1KTk6W0+nUhx9+qAMHDmjnzp3q3bu39u7d67+/JD3zzDPasGGDfD6fHn74YfXp00fvvPOOFi9erBdeeEGS/NuPP/645syZo6KiIv35z3/WzTffrCeffFLZ2dkqKirS9OnTtWXLFjkcDvXq1UsTJ05UQECAunbtqtGjR+uzzz5TYWGh7rnnHiUkJBxzrF988YWSk5N18OBBBQYG6uGHH9aVV16pe+65R5WVlYqPj9fcuXP1+9//3n+frVu36oknnlBJSYk8Ho+6dOmiZ555RkFBQVq3bp1mzJjhf7xHH31UPXv2PO54586dtXLlSoWGhkqSf/v777/XzJkzFRwcrJKSEr399ttKTk7WunXrVFJSImOMZsyYoauuukolJSWaMWOGvvzySzmdTt14440aM2aMoqOjlZGRoQsvvFCSdOedd+r222/XjTfe6D+WVatWKSUlRb/73e/0448/qkWLFpo9e7Y6duyoQ4cOKSUlRWvWrJHX69Vll12mpKQkhYSEqG/fvrr88sv17bffauLEifrjH//of8yioiIlJSVpy5YtcrvdCgsLU/v27TVu3Dj17dtXzz77rLp27SpJVba//PJLpaSk6ODBg2rWrJkefPBB//fGW2+9pYMHDyokJEQBAQHq37+//uu//kuSlJ6ergMHDmjy5MlV1vayyy7Tvffeq08//VSlpaWaOHGi+vXrJ0l6/vnn9d5778npdOrCCy/UX/7yF7lcLo0cOVIjRoxQRESE7rzzTkVHR2vdunX65ZdfNGnSJPXt21dJSUkqKCjQ3Xffrfnz55/emw1oiAyAerdz504TGRl5zPh3331nunXrZowx5rHHHjMvvfSS2bVrl7nyyitNeXm5McaY+fPnmyVLlhhjjLn99tvN+++/79//jjvu8D/W4fsbY0ynTp3MCy+8YIwx5ttvvzVXX3212bt3r3n77bfN6NGj/fc5cvvI//7888/NwIEDjTHGPProo+bJJ580Pp/PlJeXm7vuusv/2J06dTKvvPKKMcaYDRs2mIiICFNWVlblGPft22d69uxpvv76a/8xX3311WbHjh3HfV2MMWb27NkmMzPTGGPMoUOHzKBBg0xubq45dOiQue6668xHH33kf95BgwaZ8vLyase9Xq/p1KmT2bt3r/+xD29//vnnpkuXLuann34yxhjz5ZdfmnHjxhmv12uMMeaFF14w9913nzHGmFmzZpkJEyaYyspKU15ebkaMGGE+//xzM2PGDPP0008bY4zZvn27iY6ONpWVlVWO5fDzrFmzxhhjzOuvv25uueUWY4wxc+fONbNnzzY+n88YY8zf/vY3M23aNGOMMX369DHPPfdcta/Pk08+aSZNmmR8Pp/xeDymV69eZs6cOf77rV+/3r/v4e0DBw6Yfv36mZ07dxpjjPn5559NVFSUyc/PN2+//bbp0aOHKSoqMsYYs2TJEjNkyBBjjDFer9f06dPH/Otf/zpmHp06dTLz5s0zxhizefNmc9VVV5m9e/eat956y9x2222mpKTEGGPMnDlzzF133WWM+c/38c6dO02nTp3MsmXLjDHG5Obmmt69e/tfs8Pfg8CZiDNkQAPicDjUokWLKmNt2rRRly5ddMsttygqKkpRUVHq2bNntfe/6qqrjvvYw4cPlyR16tRJHTt21FdffVWrOX7yySd644035HA41Lx5cw0bNkz/+Mc/NHr0aEnSDTfcIEkKDw/XoUOHVFpaqqCgIP/9169fr9///vfq1q2bJOmSSy7RlVdeqdWrV+uaa6457vNOmjRJn332mf7+979r27ZtKiwsVGlpqb777js1a9ZMvXv3liRFRERo0aJF2rRpU7XjNQkLC1O7du0kSVdccYXOPfdc/fOf/9TOnTu1atUqnX322ZKkvLw8/fnPf5bT6ZTT6fR/5s3tduv222/XhAkTtGDBAg0dOrTay69dunRR9+7dJUlDhgzRE088of379+vjjz9WUVGR/zNVFRUVat26tf9+h+9ztM8//1xTpkyRw+HQ+eefr5iYmBqP9euvv5bH49EDDzzgH3M4HPr2228l/XrmMCQkRJLUp08fzZw5U1u2bFFBQYEuuOACXXTRRdU+7u233+4/xk6dOmnNmjX65JNPFB8fr+DgYEnSqFGj9D//8z86dOhQlfsGBgYqOjpa0q9n2w4cOFDjcQBnAoIMaEA2bNigTp06VRlr1qyZXn31VW3YsEErV67UrFmz1KtXLz366KPH3P/wH3bVadbsPz/D4/P5FBAQIIfDIXPEr7OtqKiocY4+n08Oh6PKdmVlpX/7cHwd3scc9etyvV5vlfsf3ufIx6jOxIkT5fV61b9/f/Xu3Vu7d++WMUZOp/OYx/vuu++OO350RBwdBEe+hh9//LFmzpypP/3pT7rhhht00UUX6d1335Uk/+t32O7du9WiRQtdeOGF6ty5sz788ENlZ2crIyOj2uOpLtKcTqd8Pp8mT57sj5KSkhKVl5dXO78jBQUFVXmtAwMDq9x+5G2Hj9nr9apjx4568803/bcVFBQoNDRUixYtqvJcTqdTt912m9566y0VFhZq2LBh1c7j6GPz+Xz+4zrR982R8z78vXr0+gFnMn7KEmggtm7dqvT0dN11111Vxrds2aJBgwapY8eOuu+++3TnnXdqw4YNkn79g6+mkDls4cKFkqRNmzZpx44d6tatm0JDQ/X999+rvLxcFRUVWrx4sX//4z329ddfr1dffVXGGB06dEgZGRm69tprT/o4IyMj9eOPP2r9+vWSpO+//15r1qzR1VdffcL7rVixQg888IAGDBggSVq3bp28Xq8uuugiORwOffbZZ/7ju+OOO4477vP5FBoa6n8Ns7Ozj/ucn332mfr06aOEhARFRERo6dKl8nq9kqSePXtq4cKF8vl8OnTokMaPH681a9ZIkhISEpScnKzLL79cbdq0qfaxt2zZoi1btkiSFixYoCuuuELnnHOOrr/+er322ms6dOiQfD6f/vKXvyg1NbXG17V3797KyMiQ1+tVUVGRPvzwQ/9toaGh2rhxo6RfP7/m8Xgk/boW27dv98978+bNiomJ8f8wx9FuvfVWLV26VJs2bary+bWjHf7p3k2bNmnr1q3q0aOHevXqpbffflulpaWSpFdeeUU9evRQ8+bNazw26dfvx5P5CwPQWHGGDLCkrKxMgwcPlvTr2augoCBNnDjRf4ntsC5duqh///4aMmSIgoOD1aJFCyUlJUn69cPZqampJ/UH1c6dO3XzzTfL4XAoNTVV5513nq677jr16NFD/fv3l8vl0jXXXOO/XBUZGannn39eDz74oEaOHOl/nKSkJM2YMUNxcXGqqKhQr169NGbMmJM+7tDQUD377LN68sknVVZWJofDoaeeekoXXnihfvrpp+Peb8KECXrggQcUHByskJAQ9ejRQzt27FDz5s01d+5czZo1S8nJyQoMDNTcuXNPOJ6UlKQnnnhC55xzjq699lq5XK5qn3PYsGH67//+b8XFxamyslLXXXedPvjgA/l8Pj344IOaOXOmBg8eLK/XqwEDBvg/vN6nTx8lJSWd8CzS+eefr2eeeUb5+fkKDQ1VcnKyJOn+++/X008/rVtuuUVer1eXXnqpEhMTa3xdR48erVmzZummm27SOeecU+WYHnnkET3++ONasGCBwsPDFR4e7l+LOXPmKDk5WeXl5TLGKDk5WRdccIFWr159zHO0bt1aERER6tix4zFn4I705ZdfKiMjQz6fT2lpaTr33HM1dOhQ7d69W7feeqt8Pp86dOiglJSUGo/rsIsvvlhBQUEaOnSo3nzzTc6e4YzjMEdfTwAAnJavvvpKSUlJys7OrjYcVq1a5f+p1d/KE088oVatWtXpv921b98+DR06VK+99prCwsKq3efon2AFcHI4QwYAdeixxx7T6tWrlZaWdkadxcnIyFBqaqrGjRt33BgDUHucIQMAALCMD/UDAABYRpABAABYRpABAABYRpABAABY1uh/ynL//hL5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QyVJN954o3Jzc+V0Ov37eDweud1utW3bVh6Pxz++Z88eud1uhYaGqqioSF6vV06n07//qdi7t1g+n6ntYTQpLldLeTxFtqeBo7AuDZDTqaLishp3Ky0tl8frrYcJQeK90hCxJievWTPHCU8i1fozZH369NGKFSv0yy+/yOv16tNPP1VsbKy2bt2q7du3y+v1Kjs7W1FRUWrXrp2CgoK0du1aSVJWVpaioqIUGBio7t27KycnR5KUmZmpqKio2k4JAACgUar1GbJu3brpnnvuUUJCgioqKnTddddp+PDhuuiiizRu3DiVl5crOjpasbGxkqSUlBQlJSWpuLhY4eHhGjVqlCRp2rRpSkxM1Lx58xQWFqbU1NS6OTIAAIBGwmGMadTX+7hkefI4tdwwsS4Nj3E6tXztjhr363FpG50dVOu/1+IU8V5peFiTk/ebXbIEAABA3SDIAAAALCPIAAAALCPIAAAALCPIAAAALCPIAAAALCPIAAAALCPIAAAALCPIAAAALCPIAAAALCPIAAAALCPIAAAALCPIAAAALCPIAAAALCPIAAAALCPIAAAALCPIAAAALCPIAAAALCPIAAAALCPIAAAALCPIAAAALCPIAAAALCPIAAAALCPIAAAALCPIAAAALCPIAAAALCPIAAAALCPIAAAALCPIAAAALCPIAAAALCPIAAAALCPIAAAALCPIAAAALCPIAAAALCPIAAAALCPIAAAALCPIAAAALCPIAAAALCPIAAAALCPIAAAALCPIAAAALCPIAAAALCPIAAAALCPIAAAALCPIAAAALCPIAAAALCPIAAAALCPIAAAALCPIAAAALCPIAAAALCPIAAAALCPIAAAALCPIAAAALKuTIHv66aeVmJgoScrLy1NcXJz69euntLQ0/z6bN29WfHy8YmJiNGXKFFVWVkqSdu3apREjRig2NlZjx45VSUlJXUwJAACg0TjtIFu5cqUWLlwoSSorK9PkyZOVnp6unJwcbdy4UcuXL5ckTZo0SVOnTtXixYtljFFGRoYkafr06UpISFBubq4iIiKUnp5+ulMCAABoVE4ryA4cOKC0tDSNGTNGkrR+/Xp16NBB7du3V0BAgOLi4pSbm6v8/HyVlZUpMjJSkhQfH6/c3FxVVFRozZo1iomJqTIOAADQlJxWkE2dOlUTJkzQOeecI0kqLCyUy+Xy3+52u1VQUHDMuMvlUkFBgfbv36+QkBAFBARUGQcAAGhKAmp7xzfffFNhYWHq2bOn3nnnHUmSz+eTw+Hw72OMkcPhOO744a9HOnq7Jq1bh9T2EJokl6ul7SmgGqxLw1K4r1QtQ1rUuF9wcJBcocH1MCMcxnul4WFN6katgywnJ0cej0eDBw/Wv//9b5WWlio/P19Op9O/j8fjkdvtVtu2beXxePzje/bskdvtVmhoqIqKiuT1euV0Ov37n4q9e4vl85naHkaT4nK1lMdTZHsaOArr0gA5nSoqLqtxt9LScnm83nqYECTeKw0Ra3LymjVznPAkUq0vWb788svKzs5WVlaWxo8fr759++qll17S1q1btX37dnm9XmVnZysqKkrt2rVTUFCQ1q5dK0nKyspSVFSUAgMD1b17d+Xk5EiSMjMzFRUVVdspAQAANEq1PkNWnaCgIM2ePVvjxo1TeXm5oqOjFRsbK0lKSUlRUlKSiouLFR4erlGjRkmSpk2bpsTERM2bN09hYWFKTU2tyykBAAA0eA5jTKO+3scly5PHqeWGiXVpeIzTqeVrd9S4X49L2+jsoDr9ey1OgPdKw8OanLzf7JIlAAAA6gZBBgAAYBlBBgAAYBlBBgAAYBlBBgAAYBlBBgAAYBlBBgAAYBlBBgAAYBlBBgAAYBlBBgAAYBlBBgAAYBlBBgAAYBlBBgAAYBlBBgAAYBlBBgAAYBlBBgAAYBlBBgAAYBlBBgAAYBlBBgAAYBlBBgAAYBlBBgAAYBlBBgAAYBlBBgAAYBlBBgAAYBlBBgAAYBlBBgAAYBlBBgAAYBlBBgAAYBlBBgAAYBlBBgAAYBlBBgAAYBlBBgAAYBlBBgAAYBlBBgAAYBlBBgAAYBlBBgAAYBlBBgAAYBlBBgAAYBlBBgAAYBlBBgAAYBlBBgAAYBlBBgAAYBlBBgAAYBlBBgAAYBlBBgAAYBlBBgAAYBlBBgAAYBlBBgAAYBlBBgAAYBlBBgAAYBlBBgAAYBlBBgAAYBlBBgAAYNlpBdlzzz2ngQMHauDAgUpOTpYk5eXlKS4uTv369VNaWpp/382bNys+Pl4xMTGaMmWKKisrJUm7du3SiBEjFBsbq7Fjx6qkpOR0pgQAANDo1DrI8vLytGLFCi1cuFCZmZnatGmTsrOzNXnyZKWnpysnJ0cbN27U8uXLJUmTJk3S1KlTtXjxYhljlJGRIUmaPn26EhISlJubq4iICKWnp9fNkQEAADQStQ4yl8ulxMRENW/eXIGBgerYsaO2bdumDh06qH379goICFBcXJxyc3OVn5+vsrIyRUZGSpLi4+OVm5uriooKrVmzRjExMVXGAQAAmpJaB9kll1ziD6xt27bp/fffl8PhkMvl8u/jdrtVUFCgwsLCKuMul0sFBQXav3+/QkJCFBAQUGUcAACgKQk43Qf4/vvvdd999+nRRx+V0+nUtm3b/LcZY+RwOOTz+eRwOI4ZP/z1SEdv16R165DTmn9T43K1tD0FVIN1aVgK95WqZRk3DI4AAAwoSURBVEiLGvcLDg6SKzS4HmaEw3ivNDysSd04rSBbu3atxo8fr8mTJ2vgwIFavXq1PB6P/3aPxyO32622bdtWGd+zZ4/cbrdCQ0NVVFQkr9crp9Pp3/9U7N1bLJ/PnM5hNBkuV0t5PEW2p4GjsC4NkNOpouKyGncrLS2Xx+uthwlB4r3SELEmJ69ZM8cJTyLV+pLl7t279cADDyglJUUDBw6UJHXr1k1bt27V9u3b5fV6lZ2draioKLVr105BQUFau3atJCkrK0tRUVEKDAxU9+7dlZOTI0nKzMxUVFRUbacEAADQKNX6DNn8+fNVXl6u2bNn+8eGDRum2bNna9y4cSovL1d0dLRiY2MlSSkpKUpKSlJxcbHCw8M1atQoSdK0adOUmJioefPmKSwsTKmpqad5SAAAAI2LwxjTqK/3ccny5HFquWFiXRoe43Rq+dodNe7X49I2OjvotD+Ki5PEe6XhYU1O3m92yRIAAAB1gyADAACwjCADAACwjCADAACwjCADAACwjCADAACwjCADAACwjCADAACwjCADAACwjCADAACwjCADAACwjCADAACwjCADAACwjCADAACwjCADAACwjCADAACwjCADAACwjCADAACwjCADAACwjCADAACwjCADAACwjCADAACwjCADAACwjCADAACwjCADAACwjCADAACwjCADAACwjCADAACwjCADAACwjCADAACwjCADAACwjCADAACwjCADAACwjCADAACwjCADAACwjCADAACwjCADAACwjCADAACwjCADAACwjCADAACwjCADAACwjCADAACwjCADAACwjCADAACwjCADAACwjCADAACwjCADAACwjCADAACwjCADAACwjCADAACwjCADAACwjCADAACwjCADAACwrEEE2aJFizRgwAD169dPr732mu3pAAAA1KsA2xMoKChQWlqa3nnnHTVv3lzDhg3TNddco4svvtj21AAAAOqF9TNkeXl5+sMf/qDzzjtPwcHBiomJUW5uru1pAQAA1BvrZ8gKCwvlcrn82263W+vXrz/p+zdr5vgtpnXG4vVqmFiXhsU0cyi4RWCN+wU4m7F29YzXu+FhTU5OTa+T9SDz+XxyOP4zSWNMle2atGp19m8xrTNW69YhtqeAarAuDc/AXh1tTwHV4L3S8LAmdcP6Jcu2bdvK4/H4tz0ej9xut8UZAQAA1C/rQXbttddq5cqV2rdvnw4ePKgPPvhAUVFRtqcFAABQb6xfsmzTpo0mTJigUaNGqaKiQkOHDtXll19ue1oAAAD1xmGMMbYnAQAA0JRZv2QJAADQ1BFkAAAAlhFkAAAAlhFkAAAAlhFkAAAAlhFkZ6BFixZpwIAB6tevn1577bVjbv/xxx81cuRI3XTTTbr77rv173//28Ism56a1mXTpk0aMmSIbrrpJt1333365ZdfLMyy6SkuLtagQYP0008/HXPb5s2bFR8fr5iYGE2ZMkWVlZUWZtj0nGhNli5dqsGDB+umm27S/fffz/+/6tGJ1uWwjz/+WH379q3HWZ05CLIzTEFBgdLS0vT6668rMzNTCxYs0A8//OC/3RijsWPH6t5779W7776rSy+9VC+++KLFGTcNNa2LJM2cOVPjx4/Xu+++qwsvvFDz58+3NNumY926dRo+fLi2bdtW7e2TJk3S1KlTtXjxYhljlJGRUb8TbIJOtCbFxcV6/PHH9eKLL+rdd99V586dNXfu3PqfZBNU03tFkvbs2aOnn366/iZ1hiHIzjB5eXn6wx/+oPPOO0/BwcGKiYlRbm6u//ZNmzYpODjY/9sQxowZoxEjRtiabpNR07pIv/5e15KSEknSwYMH1aJFCxtTbVIyMjI0bdq0an9dW35+vsrKyhQZGSlJio+PP2bNUPdOtCYVFRWaNm2a2rRpI0nq3Lmzdu/eXd9TbJJOtC6HJSUl6cEHH6zHWZ1ZrP9L/ahbhYWFcrlc/m23263169f7t3fs2KHzzz9fkydP1ubNm3XRRRfpL3/5i42pNik1rYskJSYm6q677tKsWbN01llncTamHsycOfO4tx29Zi6XSwUFBfUxrSbtRGvSqlUr/fGPf5QklZWV6cUXX9TIkSPra2pN2onWRZL+7//+T5dddpm6detWTzM683CG7Azj8/nkcDj828aYKtuVlZVavXq1hg8froULF6p9+/aaPXu2jak2KTWtS1lZmaZMmaL//d//1YoVK5SQkKDHHnvMxlTx/9W0ZrCnqKhIo0ePVpcuXXTLLbfYnk6T99133+mDDz7Q/fffb3sqjRpBdoZp27atPB6Pf9vj8VQ5xexyudShQwd17dpVkjRo0KBjztSg7tW0Lt99952CgoL8v8f1tttu0+rVq+t9nviPo9dsz549J7xcg/pRWFiohIQEde7cucazNqgfubm58ng8GjJkiEaPHu1fI5waguwMc+2112rlypXat2+fDh48qA8++MD/eTFJuuKKK7Rv3z5t2bJFkrRs2TKFh4fbmm6TUdO6dOjQQT///LN+/PFHSdKHH37oj2bY0a5dOwUFBWnt2rWSpKysrCprhvrn9Xo1ZswY9e/fX1OmTOGMZQMxfvx4LV68WFlZWXrxxRfldrv1+uuv255Wo8NnyM4wbdq00YQJEzRq1ChVVFRo6NChuvzyy3Xvvfdq/Pjx6tq1q55//nklJSXp4MGDatu2rZKTk21P+4x3Muvy1FNP6eGHH5YxRq1bt9asWbNsT7tJOnJNUlJSlJSUpOLiYoWHh2vUqFG2p9ckHV6Tn3/+Wd988428Xq8WL14sSYqIiOBMmSVHvldw+hzGGGN7EgAAAE0ZlywBAAAsI8gAAAAsI8gAAAAsI8gAAAAsI8gAAAAsI8gANGoVFRW6/vrrdc8999ieCgDUGkEGoFFbsmSJunTpoo0bN+pf//qX7ekAQK0QZAAatTfeeEM33HCDBgwYoH/84x/+8bfeeksDBw5UXFycRo0apd27dx93fNWqVRo0aJD/vkduz507V3fffbfi4uL0yCOPaM+ePbr//vt12223qW/fvho5cqT27t0rSdq6datGjhzpf/ycnBytXbtWvXv3ls/nkyQdPHhQPXv21L59++rrJQLQCBBkABqtH374QV999ZViY2N18803KysrS/v379eWLVuUkpKil156SYsWLVLfvn01b968447XJD8/XwsXLlRKSoree+89RUZGasGCBfrwww/VokULZWVlSZImTpyo2NhYvffee3rxxReVmpqqzp0769xzz9Wnn34qSXrvvffUs2dPhYaG/qavDYDGhV+dBKDReuONN9SnTx+1atVKrVq10gUXXKCMjAw1b95c119/vcLCwiRJd955pyTp5ZdfrnZ81apVJ3yeyMhIBQT8+r/LO+64Q1988YVefvllbdu2Td9//726deumAwcOaMuWLbr11lslSWFhYVq6dKkkacSIEcrIyFB0dLQWLFigRx99tK5fCgCNHEEGoFEqLS1VVlaWmjdvrr59+0qSiouL9eqrr+qee+6p8ouny8rKlJ+fL6fTWe24w+HQkb9FrqKiospzBQcH+//7r3/9q9avX68hQ4bommuuUWVlpYwx/mA78vF//PFH/e53v1NcXJxSU1P1+eefq7S0VD169KjbFwNAo8clSwCN0qJFi3Teeefp008/1bJly7Rs2TItXbpUpaWlKioq0sqVK1VYWChJ+uc//6m//vWvuuaaa6odDw0N1a5du7R3714ZY/Tee+8d93lXrFihO+64QzfffLNat26tvLw8eb1ehYSEKDw8XJmZmZKk3bt3a/jw4SoqKtJZZ52lm266SZMnT9awYcN++xcHQKPDGTIAjdIbb7yhP/3pT3I6nf6xc845RyNHjtRHH32kSZMm+f8pDJfLpVmzZqlNmzbHHR82bJiGDBkil8ul3r17a8OGDdU+7wMPPKDk5GQ9++yzCgwM1JVXXqkdO3ZIkv72t79p+vTpeuWVV+RwODRz5ky5XC5JUnx8vDIyMnTzzTf/li8LgEbKYY48Tw8AqHPGGP39739Xfn6+pk+fbns6ABogzpABwG/shhtukNvtVnp6uu2pAGigOEMGAABgGR/qBwAAsIwgAwAAsIwgAwAAsIwgAwAAsIwgAwAAsIwgAwAAsOz/ASWOJjG9Kd+6AAAAAElFTkSuQmCC\n", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "acc_index_accuracy_stats = accuracy_per_query_point(accurate_neighbors, tree_neighbors)\n", "sns.distplot(acc_index_accuracy_stats, kde=False)\n", "plt.title(\"Distribution of accuracy per query point\")\n", "plt.xlabel(\"Accuracy\")\n", "print(f\"Average accuracy of {np.mean(acc_index_accuracy_stats)}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "By turning up the ``pruning_degree_multiplier`` we have achieved perfect accuracy -- and still at a very tiny fraction of the query time required for the exact kd-tree query. Of course on a different more complex dataset these same settings may not suffice. Ultimately what we lose is the *guarantee* that we will get the true nearest neighbors.\n", "\n", "We can turn the dial the other way, however, and get very fast searching that is potentially still fairly accurate (compared to the very low ``n_neighbors`` version)." ] }, { "cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/Users/leland/anaconda3/envs/numba51/lib/python3.8/site-packages/scipy/sparse/_index.py:124: SparseEfficiencyWarning: Changing the sparsity structure of a csr_matrix is expensive. lil_matrix is more efficient.\n", " self._set_arrayXarray(i, j, x)\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 27.2 s, sys: 1.14 s, total: 28.3 s\n", "Wall time: 11.3 s\n" ] } ], "source": [ "%%time\n", "fast_index = pynndescent.NNDescent(\n", " fmnist_train, \n", " n_neighbors=10, \n", " diversify_prob=1.0,\n", " pruning_degree_multiplier=0.5,\n", ")\n", "fast_index.prepare()" ] }, { "cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 302 ms, sys: 16 ms, total: 318 ms\n", "Wall time: 317 ms\n" ] } ], "source": [ "%%time\n", "fast_neighbors = fast_index.query(fmnist_test, epsilon=0.0)" ] }, { "cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Average accuracy of 0.65031\n" ] }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAl4AAAGECAYAAADnbC5SAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4yLjIsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy+WH4yJAAAgAElEQVR4nO3deXzU1b3/8feQCYlpsBqcARopD7VibFBSldIohkVLwhLRQCuLRKoUcYEKt1EkKSlqBGkKbo23tj68vWgtaVSCSIMLSsUoICqbodIKCAGTYbHNQobMzPn9wY+5BAJZSE4y5PX8B75nznfO58wnybzznWTiMMYYAQAAoNV1ausCAAAAOgqCFwAAgCUELwAAAEsIXgAAAJYQvAAAACwheAEAAFhC8AJa0Z49e3T55Zdr1KhRGjVqlFJTUzV27FitWLEiOOfJJ5/U0qVLT3s/zzzzjN5+++16bzv+/Msuu0wHDx5sUo2bNm3SnDlzJEmbN2/W9OnTm3R+c/j9ft19991KTk7Wiy++2OrroX3IzMxUcXHxaefs3r1b06ZNs1QRYJ+zrQsAznaRkZEqLCwMHpeWlmrSpEkKCwtTcnKyfvGLXzR4H2vXrtX3vve9em9rzPmn889//lNlZWWSpCuuuEJPPfXUGd1fY5SVlWnNmjX67LPPFBYW1urroX3IyclpcM7evXu1Y8cOC9UAbYPgBVgWGxur6dOn6/nnn1dycrJmzZqlSy+9VHfeeaeeeuopvfXWWwoPD9f555+vefPm6a233tKWLVu0YMEChYWF6Z133tE333yj3bt3a9CgQTpw4EDwfEl64okntHnzZgUCAd1///0aPHiwXn31Va1cuVK///3vJSl4/Otf/1pPPfWUKioq9NBDD+nmm2/WI488ouXLl6uiokJz587Vtm3b5HA4dP3112vmzJlyOp264oorNGXKFH3wwQcqLy/X5MmTNX78+JP2+vHHH2vBggU6fPiwwsPDdf/99+uqq67S5MmT5fP5lJaWpqefflrf/e53g+fs2LFDDz/8sKqqquTxeBQXF6cnnnhCERER2rhxox599NHg/T3wwANKTEw85fhll12mDz/8UDExMZIUPN6+fbtycnIUFRWlqqoqvfLKK1qwYIE2btyoqqoqGWP06KOP6uqrr1ZVVZUeffRRffLJJwoLC9ONN96oqVOnauDAgcrPz9dFF10kSZo0aZJuu+023XjjjcG9rF27Vrm5ufrOd76jL7/8UpGRkZo/f74uueQSHTlyRLm5uVq/fr38fr++//3vKysrS9HR0RoyZIiuvPJK/eMf/9DMmTP14x//OHifFRUVysrK0rZt2+R2u9WjRw/17NlT06ZN05AhQ/Tkk0/qiiuukKQ6x5988olyc3N1+PBhderUSffdd1/wY6OgoECHDx9WdHS0nE6nhg0bpp/+9KeSpLy8PH3zzTeaPXt2nd5+//vf189//nO9//77qq6u1syZMzV06FBJ0u9+9zu98cYbCgsL00UXXaRf/epXcrlcmjhxoiZMmKA+ffpo0qRJGjhwoDZu3Kj//Oc/ysjI0JAhQ5SVlaWysjLdeeedev7558/skw1ojwyAVrN7926TkJBw0vgXX3xh+vbta4wx5sEHHzR//OMfzd69e81VV11lvF6vMcaY559/3rz11lvGGGNuu+0287e//S04//bbbw/e17HzjTGmd+/e5ve//70xxph//OMf5oc//KE5cOCAeeWVV8yUKVOC5xx/fPz/P/roIzNixAhjjDEPPPCAeeSRR0wgEDBer9fccccdwfvu3bu3Wbx4sTHGmM2bN5s+ffqYmpqaOns8ePCgSUxMNJ999llwzz/84Q/NV199dcrHxRhj5s+fb5YuXWqMMebIkSNm5MiRpqioyBw5csRcd9115t133w2uO3LkSOP1eusd9/v9pnfv3ubAgQPB+z52/NFHH5m4uDizZ88eY4wxn3zyiZk2bZrx+/3GGGN+//vfm7vuussYY8xjjz1mZsyYYXw+n/F6vWbChAnmo48+Mo8++qh5/PHHjTHG7Nq1ywwcOND4fL46ezm2zvr1640xxvz5z382t9xyizHGmKefftrMnz/fBAIBY4wxv/3tb012drYxxpjBgwebZ555pt7H55FHHjEZGRkmEAgYj8djrr/+evPUU08Fz9u0aVNw7rHjb775xgwdOtTs3r3bGGPM119/bZKSkkxpaal55ZVXTL9+/UxFRYUxxpi33nrLjB492hhjjN/vN4MHDzb/+te/Tqqjd+/e5tlnnzXGGFNSUmKuvvpqc+DAAVNQUGBuvfVWU1VVZYwx5qmnnjJ33HGHMeb/Po53795tevfubVatWmWMMaaoqMgMGjQo+Jgd+xgEzkZc8QLagMPhUGRkZJ2xbt26KS4uTrfccouSkpKUlJSkxMTEes+/+uqrT3nf48aNkyT17t1bl1xyiT799NNm1fj3v/9dL7/8shwOhzp37qyxY8fqT3/6k6ZMmSJJuuGGGyRJ8fHxOnLkiKqrqxURERE8f9OmTfrud7+rvn37SpIuvfRSXXXVVVq3bp369+9/ynUzMjL0wQcf6A9/+IN27typ8vJyVVdX64svvlCnTp00aNAgSVKfPn30+uuva+vWrfWON6RHjx6KjY2VJP3gBz/Qt7/9bf3lL3/R7t27tXbtWn3rW9+SJBUXF+uhhx5SWFiYwsLCgj+T5na7ddttt2nGjBlasmSJxowZU+/LpnFxcbrmmmskSaNHj9bDDz+sQ4cO6b333lNFRUXwZ55qa2vVtWvX4HnHzjnRRx99pMzMTDkcDl1wwQVKTk5ucK+fffaZPB6P7r333uCYw+HQP/7xD0lHrwRGR0dLkgYPHqycnBxt27ZNZWVluvDCC3XxxRfXe7+33XZbcI+9e/fW+vXr9fe//11paWmKioqSJKWnp+u///u/deTIkTrnhoeHa+DAgZKOXj375ptvGtwHcDYgeAFtYPPmzerdu3edsU6dOunFF1/U5s2b9eGHH+qxxx7T9ddfrwceeOCk8489qdWnU6f/+52ZQCAgp9Mph8Mhc9yfZa2trW2wxkAgIIfDUefY5/MFj4+FrGNzzAl/9tXv99c5/9ic4++jPjNnzpTf79ewYcM0aNAg7du3T8YYhYWFnXR/X3zxxSnHTwwLJz7xH/8Yvvfee8rJydHPfvYz3XDDDbr44ou1bNkySQo+fsfs27dPkZGRuuiii3TZZZfpnXfe0fLly5Wfn1/vfuoLY2FhYQoEApo9e3YwfFRVVcnr9dZb3/EiIiLqPNbh4eF1bj/+tmN79vv9uuSSS/TXv/41eFtZWZliYmL0+uuv11krLCxMt956qwoKClReXq6xY8fWW8eJewsEAsF9ne7j5vi6j32sntg/4GzGbzUClu3YsUN5eXm644476oxv27ZNI0eO1CWXXKK77rpLkyZN0ubNmyUdfYJrKLAc89prr0mStm7dqq+++kp9+/ZVTEyMtm/fLq/Xq9raWq1cuTI4/1T3PWDAAL344osyxujIkSPKz8/Xtdde2+h9JiQk6Msvv9SmTZskSdu3b9f69ev1wx/+8LTnrVmzRvfee6+GDx8uSdq4caP8fr8uvvhiORwOffDBB8H93X777accDwQCiomJCT6Gy5cvP+WaH3zwgQYPHqzx48erT58+evvtt+X3+yVJiYmJeu211xQIBHTkyBFNnz5d69evlySNHz9eCxYs0JVXXqlu3brVe9/btm3Ttm3bJElLlizRD37wA5177rkaMGCAXnrpJR05ckSBQEC/+tWvtHDhwgYf10GDBik/P19+v18VFRV65513grfFxMRoy5Ytko7+fJnH45F0tBe7du0K1l1SUqLk5OTgL1Wc6Cc/+Ynefvttbd26tc7Pl53o2G/Tbt26VTt27FC/fv10/fXX65VXXlF1dbUkafHixerXr586d+7c4N6kox+PjfnGAAhVXPECWllNTY1GjRol6ejVqIiICM2cOTP40tgxcXFxGjZsmEaPHq2oqChFRkYqKytL0tEfkl64cGGjnpB2796tm2++WQ6HQwsXLtR5552n6667Tv369dOwYcPkcrnUv3//4MtMCQkJ+t3vfqf77rtPEydODN5PVlaWHn30UaWmpqq2tlbXX3+9pk6d2uh9x8TE6Mknn9QjjzyimpoaORwOzZs3TxdddJH27NlzyvNmzJihe++9V1FRUYqOjla/fv301VdfqXPnznr66af12GOPacGCBQoPD9fTTz992vGsrCw9/PDDOvfcc3XttdfK5XLVu+bYsWP1X//1X0pNTZXP59N1112nN998U4FAQPfdd59ycnI0atQo+f1+DR8+PPhD5IMHD1ZWVtZprwpdcMEFeuKJJ1RaWqqYmBgtWLBAknTPPffo8ccf1y233CK/36/LL79cs2bNavBxnTJlih577DHddNNNOvfcc+vs6Ze//KV+/etfa8mSJYqPj1d8fHywF0899ZQWLFggr9crY4wWLFigCy+8UOvWrTtpja5du6pPnz665JJLTrqidrxPPvlE+fn5CgQCWrRokb797W9rzJgx2rdvn37yk58oEAioV69eys3NbXBfx3zve99TRESExowZo7/+9a9cDcNZx2FOfH0AANAon376qbKysrR8+fJ6A8LatWuDvyXaWh5++GGdf/75LfreVwcPHtSYMWP00ksvqUePHvXOOfE3RgE0Dle8AKAZHnzwQa1bt06LFi06q67K5Ofna+HChZo2bdopQxeA5uOKFwAAgCX8cD0AAIAlBC8AAABLCF4AAACWELwAAAAsCZnfajx0qEqBQOv+HkDXrtE6cKCyVddA09CT9oeetE/0pf2hJ+1Ta/elUyeHzj//W6e8PWSCVyBgWj14HVsH7Qs9aX/oSftEX9ofetI+tWVfeKkRAADAEoIXAACAJQQvAAAASwheAAAAlhC8AAAALCF4AQAAWELwAgAAsITgBQAAYAnBCwAAwBKCFwAAgCUELwAAAEsIXgAAAJYQvAAAACxxtnUBAACECl9A8tb6GjXXHKxWtffo3Ihwp5xc6oAIXgAANJq31qf1JWWNmtslOlIVlTWSpH6Xd5Mzgqdc8FIjAACANQQvAAAASwheAAAAlhC8AAAALCF4AQAAWELwAgAAsITgBQAAYAnBCwAAwBKCFwAAgCUELwAAAEsIXgAAAJYQvAAAACwheAEAAFhC8AIAALCkUcGrsrJSI0eO1J49eyRJn376qX76059qxIgRmjlzpo4cOSJJKikpUVpampKTk5WZmSmfzydJ2rt3ryZMmKCUlBTdfffdqqqqaqXtAAAAtF8NBq+NGzdq3Lhx2rlzp6SjIWzatGl6+OGH9cYbb0iSCgoKJEkZGRmaM2eOVq5cKWOM8vPzJUlz587V+PHjVVRUpD59+igvL6+VtgMAANB+NRi88vPzlZ2dLbfbLUn64IMPlJCQoLi4OElSVlaWfvzjH6u0tFQ1NTVKSEiQJKWlpamoqEi1tbVav369kpOT64wDAAB0NM6GJuTk5NQ53rVrl6KiojRjxgx9+eWXuuqqqzRr1ix9/vnncrlcwXkul0tlZWU6dOiQoqOj5XQ664wDAAB0NA0GrxP5/X6tWbNGS5Ys0Xe+8x1lZmbqueee07XXXiuHwxGcZ4yRw+EI/nu8E48bo2vX6Caf0xwuVxcr66Dx6En7Q0/aJ/rS+szBanWJjmz0/GNzo6Ii5IqJaq2y0ERt+bnS5OB1wQUXqG/fvurZs6ckadiwYXrxxReVlpYmj8cTnLd//3653W7FxMSooqJCfr9fYWFh8ng8wZctm+LAgUoFAqbJ5zWFy9VFHk9Fq66BpqEn7Q89aZ/oix3VXp8qKmsaNbdLdGRwbnW1Vx6/vzVLQyO19udKp06O014savLbSQwYMEBbt27Vvn37JEnvvvuu4uPjFRsbq4iICG3YsEGSVFhYqKSkJIWHh+uaa67RihUrJElLly5VUlJSc/YCAAAQ0pp8xatHjx56+OGHNXXqVHm9Xl1++eV68MEHJUm5ubnKyspSZWWl4uPjlZ6eLknKzs7WrFmz9Oyzz6pHjx5auHBhy+4CAAAgBDiMMa37+l0L4aXGjometD/0pH0K9b74ApK31tfk8yLCnXJafCvwKq9P60sa9wtix7/U2O/ybvpWRJOvdaAVtPVLjXwUAADanLe28YHmeP0u7yYngQYhhD8ZBAAAYAnBCwAAwBKCFwAAgCUELwAAAEsIXgAAAJYQvAAAACwheAEAAFhC8AIAALCE4AUAAGAJwQsAAMASghcAAIAlBC8AAABLCF4AAACWELwAAAAsIXgBAABYQvACAACwhOAFAABgibOtCwAAAO2DLyB5a33NOjci3Cknl3MaRPACAACSjoau9SVlzTq33+Xd5IwgVjSEbAoAAGAJwQsAAMASghcAAIAlBC8AAABLCF4AAACWELwAAAAsIXgBAABY0qjgVVlZqZEjR2rPnj11xl988UVNnDgxeFxSUqK0tDQlJycrMzNTPt/RN2Hbu3evJkyYoJSUFN19992qqqpqwS0AAACEhgaD18aNGzVu3Djt3Lmzzvg///lPPffcc3XGMjIyNGfOHK1cuVLGGOXn50uS5s6dq/Hjx6uoqEh9+vRRXl5ey+0AAAAgRDQYvPLz85WdnS232x0cO3LkiObMmaPp06cHx0pLS1VTU6OEhARJUlpamoqKilRbW6v169crOTm5zjgAAEBH0+B7++fk5Jw09tvf/lajR4/WhRdeGBwrLy+Xy+UKHrtcLpWVlenQoUOKjo6W0+msMw4AANDRNPmPKn3wwQfat2+fHnroIa1duzY4HggE5HA4gsfGGDkcjuC/xzvxuDG6do1u8jnN4XJ1sbIOGo+etD/0pH0K5b6Yg9XqEh3Z5POioiLkiolqhYrq19Q6j821XWdzNbcPUujsUWrbz5UmB6/ly5dr+/btGjVqlKqrq7V//37df//9ysjIkMfjCc7bv3+/3G63YmJiVFFRIb/fr7CwMHk8njovWzbWgQOVCgRMk89rCperizyeilZdA01DT9ofetI+hXpfqr0+VVTWNP28aq88fn8rVHSK9ZpQZ5foyOBc23U2V3P7IIXOHlv7c6VTJ8dpLxY1OXjNmzcv+P+1a9fqmWee0RNPPCFJioiI0IYNG3T11VersLBQSUlJCg8P1zXXXKMVK1YoNTVVS5cuVVJSUjO2AgAAENpa9H28cnNzNW/ePKWkpKi6ulrp6emSpOzsbOXn52v48OH6+OOPdf/997fksgAAACGh0Ve8Vq1addJY//791b9//+BxXFycCgoKTpoXGxurxYsXN7NEAACAswPvXA8AAGAJwQsAAMASghcAAIAlBC8AAABLCF4AAACWELwAAAAsIXgBAABYQvACAACwhOAFAABgCcELAADAEoIXAACAJQQvAAAASwheAAAAlhC8AAAALCF4AQAAWELwAgAAsITgBQAAYAnBCwAAwBKCFwAAgCUELwAAAEsIXgAAAJYQvAAAACwheAEAAFjibOsCAACn5wtI3lrfaeeYg9Wq9p48JyLcKSffYgPtBsELANo5b61P60vKTjunS3SkKiprThrvd3k3OSP4Ug+0F3wfBAAAYAnBCwAAwJJGBa/KykqNHDlSe/bskSQtWbJEI0eOVGpqqh566CEdOXJEklRSUqK0tDQlJycrMzNTPt/RnzfYu3evJkyYoJSUFN19992qqqpqpe0AAAC0Xw0Gr40bN2rcuHHauXOnJGnHjh16/vnn9Ze//EXLli1TIBDQn//8Z0lSRkaG5syZo5UrV8oYo/z8fEnS3LlzNX78eBUVFalPnz7Ky8trvR0BAAC0Uw0Gr/z8fGVnZ8vtdkuSOnfurOzsbEVHR8vhcKh3797au3evSktLVVNTo4SEBElSWlqaioqKVFtbq/Xr1ys5ObnOOAAAQEfT4K+65OTk1DmOjY1VbGysJOngwYN66aWXNG/ePJWXl8vlcgXnuVwulZWV6dChQ4qOjpbT6awzDgAA0NE0+3eMy8rKNHnyZI0ePVr9+/fXhg0b5HA4grcbY+RwOIL/Hu/E48bo2jW6uaU2icvVxco6aDx60v7QE7vMwWp1iY5scF59c6KiIuSKiWqNslpUY/d4Itv7a2qdx+ae7X2QQmePUtt+DWtW8PrXv/6lyZMna+LEibrjjjskSd27d5fH4wnO2b9/v9xut2JiYlRRUSG/36+wsDB5PJ7gy5ZNceBApQIB05xyG83l6iKPp6JV10DT0JP2h57YV+311fseXcc71ft4VVd75fH7W6u0FtOYPdZ7nuX9NaXO43tytvdBCp09tvbXsE6dHKe9WNTkt5OorKzUnXfeqV/84hfB0CUdfQkyIiJCGzZskCQVFhYqKSlJ4eHhuuaaa7RixQpJ0tKlS5WUlNTUZQEAAEJek4NXQUGB9u/frxdeeEGjRo3SqFGj9OSTT0qScnNzNW/ePKWkpKi6ulrp6emSpOzsbOXn52v48OH6+OOPdf/997fsLgAAAEJAo19qXLVqlSRp0qRJmjRpUr1z4uLiVFBQcNJ4bGysFi9e3LwKAQAAzhK8cz0AAIAlBC8AAABLCF4AAACWELwAAAAsIXgBAABYQvACAACwhOAFAABgCcELAADAEoIXAACAJQQvAAAASwheAAAAlhC8AAAALCF4AQAAWELwAgAAsITgBQAAYAnBCwAAwBKCFwAAgCUELwAAAEsIXgAAAJYQvAAAACwheAEAAFhC8AIAALCE4AUAAGAJwQsAAMASghcAAIAlBC8AAABLCF4AAACWELwAAAAsaVTwqqys1MiRI7Vnzx5JUnFxsVJTUzV06FAtWrQoOK+kpERpaWlKTk5WZmamfD6fJGnv3r2aMGGCUlJSdPfdd6uqqqoVtgIAANC+NRi8Nm7cqHHjxmnnzp2SpJqaGs2ePVt5eXlasWKFtmzZotWrV0uSMjIyNGfOHK1cuVLGGOXn50uS5s6dq/Hjx6uoqEh9+vRRXl5e6+0IAACgnWoweOXn5ys7O1tut1uStGnTJvXq1Us9e/aU0+lUamqqioqKVFpaqpqaGiUkJEiS0tLSVFRUpNraWq1fv17Jycl1xgEAADoaZ0MTcnJy6hyXl5fL5XIFj91ut8rKyk4ad7lcKisr06FDhxQdHS2n01lnHAAAoKNpMHidKBAIyOFwBI+NMXI4HKccP/bv8U48boyuXaObfE5zuFxdrKyDxqMn7c/Z0pOK6iM6XONr8nnnRDrVJapzK1RUP3OwWl2iIxucV9+cqKgIuWKiWqOsFtXYPZ7I9v6aWuexuWd7H6TQ2aPUtl/Dmhy8unfvLo/HEzz2eDxyu90nje/fv19ut1sxMTGqqKiQ3+9XWFhYcH5THThQqUDANPm8pnC5usjjqWjVNdA09KT9OZt6UuX1aX1J06/A97u8m2qqvK1QUf2qvT5VVNacdk6X6Mh651RXe+Xx+1urtBbTmD3We57l/TWlzuN7crb3QQqdPbb217BOnRynvVjU5LeT6Nu3r3bs2KFdu3bJ7/dr+fLlSkpKUmxsrCIiIrRhwwZJUmFhoZKSkhQeHq5rrrlGK1askCQtXbpUSUlJzdwOAABA6GryFa+IiAjNnz9f06ZNk9fr1cCBA5WSkiJJys3NVVZWliorKxUfH6/09HRJUnZ2tmbNmqVnn31WPXr00MKFC1t2FwAAACGg0cFr1apVwf8nJiZq2bJlJ82Ji4tTQUHBSeOxsbFavHhxM0sEAAA4O/DO9QAAAJYQvAAAACwheAEAAFhC8AIAALCE4AUAAGAJwQsAAMASghcAAIAlTX4DVQAAgLbmC0je2qb/rdXI6iOtUE3jEbwAAEDI8dY272+tDrz6u3K0Qj2NxUuNAAAAlhC8AAAALCF4AQAAWELwAgAAsITgBQAAYAnBCwAAwBKCFwAAgCUELwAAAEsIXgAAAJYQvAAAACwheAEAAFhC8AIAALCE4AUAAGAJwQsAAMASghcAAIAlBC8AAABLCF4AAACWELwAAAAsOaPgVVhYqBEjRmjEiBF6/PHHJUnFxcVKTU3V0KFDtWjRouDckpISpaWlKTk5WZmZmfL5fGdWOQAAQIhpdvA6fPiwcnJytHjxYhUWFurjjz/WqlWrNHv2bOXl5WnFihXasmWLVq9eLUnKyMjQnDlztHLlShljlJ+f32KbAAAACAXNDl5+v1+BQECHDx+Wz+eTz+dTdHS0evXqpZ49e8rpdCo1NVVFRUUqLS1VTU2NEhISJElpaWkqKipqsU0AAACEAmdzT4yOjtYvfvELDRs2TOecc4769eun8vJyuVyu4By3262ysrKTxl0ul8rKys6scgAAgBDT7OC1bds2vfLKK3r33XfVpUsX/fKXv9TOnTvlcDiCc4wxcjgcCgQC9Y43Rdeu0c0ttUlcri5W1kHj0ZP252zpiTlYrS7RkU0+LyoqQq6YqFaoqH6NrbO+ObZrba6zrRfHHJt7tvdBav+9OF5bfg1rdvBas2aNEhMT1bVrV0lHXz58/vnnFRYWFpzj8XjkdrvVvXt3eTye4Pj+/fvldrubtN6BA5UKBExzy20Ul6uLPJ6KVl0DTUNP2p+zqSfVXp8qKmuafl61Vx6/vxUqOsV6jaizS3RkvXNs19pcZ1Mvjjm+J2d7H6T23YsTtebXsE6dHKe9WNTsn/GKi4tTcXGxqqurZYzRqlWr1LdvX+3YsUO7du2S3+/X8uXLlZSUpNjYWEVERGjDhg2Sjv42ZFJSUnOXBgAACEnNvuI1YMAAff7550pLS1N4eLiuuOIKTZs2Tdddd52mTZsmr9ergQMHKiUlRZKUm5urrKwsVVZWKj4+Xunp6S22CQAAgFDQ7OAlSVOmTNGUKVPqjCUmJmrZsmUnzY2Li1NBQcGZLAcAABDSeOd6AAAASwheAAAAlhC8AAAALCF4AQAAWELwAgAAsITgBQAAYAnBCwAAwBKCFwAAgCUELwAAAEsIXgAAAJYQvAAAACwheAEAAFhyRn8kG8DZzReQvLW+OmPmYLWqvb5TnHFURLhTTr6tA4CTELwAnJK31qf1JWV1xrpER6qisua05/W7vJucEXx5AYAT8T0pAJ7I1eAAABSlSURBVACAJQQvAAAASwheAAAAlhC8AAAALCF4AQAAWELwAgAAsITgBQAAYAnBCwAAwBKCFwAAgCUELwAAAEsIXgAAAJYQvAAAACwheAEAAFhyRsFr1apVSktL07Bhw/Too49KkoqLi5WamqqhQ4dq0aJFwbklJSVKS0tTcnKyMjMz5fP5zqxyAACAENPs4LV7925lZ2crLy9Py5Yt0+eff67Vq1dr9uzZysvL04oVK7RlyxatXr1akpSRkaE5c+Zo5cqVMsYoPz+/xTYBAAAQCpodvN566y0NHz5c3bt3V3h4uBYtWqRzzjlHvXr1Us+ePeV0OpWamqqioiKVlpaqpqZGCQkJkqS0tDQVFRW12CYAAABCgbO5J+7atUvh4eGaOnWq9u3bp0GDBunSSy+Vy+UKznG73SorK1N5eXmdcZfLpbKysjOrHAAAIMQ0O3j5/X59/PHHWrx4saKionT33XcrMjJSDocjOMcYI4fDoUAgUO94U3TtGt3cUpvE5epiZR00Hj1pO+ZgtbpER540Xt/Y8aKiIuSKiWqtslrMqfbXENv7a2yd9c2hFy2rqXUem3u290Fq/704Xls+rzQ7eF1wwQVKTExUTEyMJOnGG29UUVGRwsLCgnM8Ho/cbre6d+8uj8cTHN+/f7/cbneT1jtwoFKBgGluuY3icnWRx1PRqmugaehJ26r2+lRRWVNnrEt05EljJ51X7ZXH72/N0lpEfftr1HmW99eYOk/VF3rRsppS5/E9Odv7ILXvXpyoNZ9XOnVynPZiUbN/xmvw4MFas2aN/vOf/8jv9+v9999XSkqKduzYoV27dsnv92v58uVKSkpSbGysIiIitGHDBklSYWGhkpKSmrs0AABASGr2Fa++fftq8uTJGj9+vGpra3Xddddp3LhxuvjiizVt2jR5vV4NHDhQKSkpkqTc3FxlZWWpsrJS8fHxSk9Pb7FNAAAAhIJmBy9JGjNmjMaMGVNnLDExUcuWLTtpblxcnAoKCs5kOQAAgJDGO9cDAABYQvACAACwhOAFAABgCcELAADAEoIXAACAJQQvAAAASwheAAAAlhC8AAAALCF4AQAAWELwAgAAsITgBQAAYAnBCwAAwBKCFwAAgCXOti4ACFW+gOSt9TXr3Ihwp5x82wMAHQ7BC2gmb61P60vKmnVuv8u7yRnBpx8AdDR8zw0AAGAJwQsAAMASghcAAIAlBC8AAABLCF4AAACWELwAAAAsIXgBAABYQvACAACwhOAFAABgCcELAADAEoIXAACAJQQvAAAASwheAAAAlrRI8Hr88cc1a9YsSVJxcbFSU1M1dOhQLVq0KDinpKREaWlpSk5OVmZmpnw+X0ssDQAAEDLOOHh9+OGHeu211yRJNTU1mj17tvLy8rRixQpt2bJFq1evliRlZGRozpw5WrlypYwxys/PP9OlAQAAQsoZBa9vvvlGixYt0tSpUyVJmzZtUq9evdSzZ085nU6lpqaqqKhIpaWlqqmpUUJCgiQpLS1NRUVFZ149AABACDmj4DVnzhzNmDFD5557riSpvLxcLpcreLvb7VZZWdlJ4y6XS2VlZWeyNAAAQMhxNvfEv/71r+rRo4cSExP16quvSpICgYAcDkdwjjFGDofjlONN0bVrdHNLbRKXq4uVddB47bUn5mC1ukRHNuvcqKgIuWKiWriilneqPTa071DfX0Ns76+xddY3h160rKbWeWzu2d4Hqf334nht+bzS7OC1YsUKeTwejRo1Sv/+979VXV2t0tJShYWFBed4PB653W51795dHo8nOL5//3653e4mrXfgQKUCAdPcchvF5eoij6eiVddA07TnnlR7faqorGneudVeefz+Fq6o5dW3xy7RkQ3uO5T316jzLO+vMXWeqi/0omU1pc7je3K290Fq3704UWs+r3Tq5DjtxaJmB68XXngh+P9XX31V69at09y5czV06FDt2rVLF154oZYvX67Ro0crNjZWERER2rBhg66++moVFhYqKSmpuUsDAACEpGYHr/pERERo/vz5mjZtmrxerwYOHKiUlBRJUm5urrKyslRZWan4+Hil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zJs3T/fff7+MMeratasee+yxti67Q2ovz/UOY4yxthoAAEAH1qFeagQAAGhLBC8AAABLCF4AAACWELwAAAAsIXgBAABYQvAC0O7V1tZqwIABmjx5cluXAgBnhOAFoN176623FBcXpy1btuhf//pXW5cDAM1G8ALQ7r388su64YYbNHz4cP3pT38KjhcUFGjEiBFKTU1Venq69u3bd8rxtWvXauTIkcFzjz9++umndeeddyo1NVW//OUvtX//ft1zzz269dZbNWTIEE2cOFEHDhyQJO3YsUMTJ04M3v+KFSu0YcMGDRo0SIFAQJJ0+PBhJSYm6uDBg7YeIgAhguAFoF375z//qU8//VQpKSm6+eabVVhYqEOHDmnbtm3Kzc3VH//4R73++usaMmSInn322VOON6S0tFSvvfaacnNz9cYbbyghIUFLlizRO++8o8jISBUWFkqSZs6cqZSUFL3xxht67rnntHDhQl122WX69re/rffff1+S9MYbbygxMVExMTGt+tgACD0d6k8GAQg9L7/8sgYPHqzzzz9f559/vi688ELl5+erc+fOGjBggHr06CFJmjRpkiTphRdeqHd87dq1p10nISFBTufRL4m33367Pv74Y73wwgvauXOntm/frr59++qbb77Rtm3b9JOf/ESS1KNHD7399tuSpAkTJig/P18DBw7UkiVL9MADD7T0QwHgLEDwAtBuVVdXq7CwUJ07d9aQIUMkSZWVlXrxxRc1efLkOn8wvaamRqWlpQoLC6t33OFw6Pi/kFZbW1tnraioqOD/f/Ob32jTpk0aPXq0+vfvL5/PJ2NMMJgdf/9ffvmlvvOd7yg1NVULFy7URx99pOrqavXr169lHwwAZwVeagTQbr3++us677zz9P7772vVqlVatWqV3n77bVVXV6uiokIffvihysvLJUl/+ctf9Jvf/Eb9+/evdzwmJkZ79+7VgQMHZIzRG2+8ccp116xZo9tvv10333yzunbtquLiYvn9fkVHRys+Pl5Lly6VJO3bt0/jxo1TRUWFzjnnHN10002aPXu2xo4d2/oPDoCQxBUvAO3Wyy+/rJ/97GcKCwsLjp177rmaOHGi3n33XWVkZATfYsLlcumxxx5Tt27dTjk+duxYjR49Wi6XS4MGDdLmzZvrXffee+/VggUL9OSTTyo8PFxXXXWVvvrqK0nSb3/7W82dO1eLFy+Ww+FQTk6OXC6XJCktLU35+fm6+eabW/NhARDCHOb4a+8AgGYxxugPf/iDSktLNXfu3LYuB0A7xRUvAGgBN9xwg9xut/Ly8tq6FADtGFe8AAAALOGH6wEAACwheAEAAFhC8AIAALCE4AUAAGAJwQsAAMASghcAAIAl/w/NAeah9AiIdQAAAABJRU5ErkJggg==\n", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "fast_index_accuracy_stats = accuracy_per_query_point(fast_neighbors, tree_neighbors)\n", "sns.distplot(fast_index_accuracy_stats, kde=False)\n", "plt.title(\"Distribution of accuracy per query point\")\n", "plt.xlabel(\"Accuracy\")\n", "print(f\"Average accuracy of {np.mean(fast_index_accuracy_stats)}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In general this isn't a good idea, but it gives you an idea of the trade-offs that can be made." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Nearest neighbors of the training set\n", "\n", "While querying for nearest neighbors of new points is a common use case, for many tasks, such as manifold learning or density based clustering, you want the nearest neighbors of all the points in the training set. You could build the index and then query for all the training points -- but because PyNNDescent builds a nearest neighbor graph as part of its index you can actually get there faster by just building the index and extracting the results directly. This is, for example, what libraries like [UMAP](https://github.com/lmcinnes/umap) and [OpenTSNE](https://github.com/pavlin-policar/openTSNE) do.\n", "\n", "This is why the ``prepare`` step is kept separate. It is extra overhead, preparing the index to be queried for new unseen data, that isn't required for the use case of simply extracting the nearest neighbors of the full training set. Thus if we just build, but don't prepare, the index:" ] }, { "cell_type": "code", "execution_count": 39, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 36.5 s, sys: 375 ms, total: 36.9 s\n", "Wall time: 14.8 s\n" ] } ], "source": [ "%%time\n", "index = pynndescent.NNDescent(fmnist_train)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can then extract the nearest neighbors of each training sample by using the ``neighbor_graph`` attribute." ] }, { "cell_type": "code", "execution_count": 40, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(array([[ 0, 25719, 27655, ..., 38300, 4643, 7353],\n", " [ 1, 42564, 37550, ..., 12169, 50358, 49552],\n", " [ 2, 53513, 35424, ..., 16891, 29542, 40182],\n", " ...,\n", " [59997, 45348, 22272, ..., 58896, 53292, 11657],\n", " [59998, 17378, 8495, ..., 4389, 23426, 34912],\n", " [59999, 11912, 40600, ..., 31966, 45245, 58489]]),\n", " array([[ 0. , 1188.7826 , 1215.344 , ..., 1417.4388 , 1422.567 ,\n", " 1424.7806 ],\n", " [ 0. , 1048.0482 , 1068.395 , ..., 1219.923 , 1222.1665 ,\n", " 1225.5464 ],\n", " [ 0. , 532.61993, 632.16534, ..., 885.45245, 886.3972 ,\n", " 887.8125 ],\n", " ...,\n", " [ 0. , 1086.2596 , 1118.3738 , ..., 1303.3699 , 1306.9954 ,\n", " 1308.5958 ],\n", " [ 0. , 685.18243, 690.4209 , ..., 795.6589 , 796.894 ,\n", " 797.1349 ],\n", " [ 0. , 884.41394, 886.4903 , ..., 1048.9224 , 1049.3502 ,\n", " 1051.806 ]], dtype=float32))" ] }, "execution_count": 40, "metadata": {}, "output_type": "execute_result" } ], "source": [ "index.neighbor_graph" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The first array is ``n_samples`` by ``n_neighbors`` such that the ``i``th row contains the ``n_neighbors`` nearest neighbors of the ``i``th data point. The second array is the associated distances to each of those neighboring points. From the distance array you can see that each row is sorted, with the closest neighbors first, and the furthest last.\n", "\n", "If you are simply wanting to extract this sort of data for further processing this is the fastest way to do it, and since you only need to run the index build and not actually query for any data, this can often be faster than some other approximate nearest neighbor techniques that need to be queried after being constructed." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.0" } }, "nbformat": 4, "nbformat_minor": 4 }