├── .gitignore ├── LICENSE ├── Neighborhood Components Analysis.ipynb ├── README.md └── nca.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | venv/ 83 | ENV/ 84 | 85 | # Spyder project settings 86 | .spyderproject 87 | 88 | # Rope project settings 89 | .ropeproject 90 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Erlend Davidson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Neighborhood Components Analysis.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Neighborhood Components Analysis in Python\n", 8 | "\n", 9 | "I wrote a simple implementation implemention of the neighborhood components analysis method (NCA) for learning a Mahalanobis distance measure that attempts to optimize the performance of a k-nearest-neighbor classifier. \n", 10 | "\n", 11 | "Basically the method introduces a transformation matrix $A$, which maps from the (raw) input space for the covariates ($X$) to a transformed space, i.e. $x_i^{trans} = Ax_i$. The transformation matrix $A$ is optimized using a differentiable cost function based on the stochastic assignment of neighbors, similar to the t-SNE method [1,2] (note that Geoff Hinton is a common author to both methods).\n", 12 | "\n", 13 | "$p_{ij} = \\frac{exp(-\\|Ax_i - Ax_j\\|)^2}{\\sum_{k\\neq i}exp(-\\|Ax_i - Ax_k\\|)^2}$, where $p_{ij}$ is the probability of sample $i$ choosing $j$ as its neighbor in the transformed space.\n", 14 | "\n", 15 | "Now we can specify a cost function that pulls points from the same class closer together:\n", 16 | "\n", 17 | "$f(A) = \\sum_i \\sum_{j\\in C_i} p_{ij}. $, where $C_i$ is the class label of sample $i$.\n", 18 | "\n", 19 | "Since the cost function is differentiable with respect to $A$ we can optimize it using gradient descent. For more details see references [1-3].\n", 20 | "\n", 21 | "\n", 22 | "### References\n", 23 | "[1] J. Goldberger, S. Roweis, G. Hinton, and R. Salakhutdinov. Neighbourhood components analysis.\n", 24 | "In L. K. Saul, Y. Weiss, and L. Bottou, editors, Advances in Neural Information Processing\n", 25 | "Systems 17, pages 513–520, Cambridge, MA, 2005. MIT Press.
\n", 26 | "[2] L. van der Maaten and G. E. Hinton, \"Visualizing data using t-Sne,\" Journal of Machine Learning Research, vol. 9, pages 2579-2605, November 2008.
\n", 27 | "[3] http://www.wikicoursenote.com/wiki/Neighbourhood_Components_Analysis\n", 28 | "\n", 29 | "\n", 30 | "## A simple example\n", 31 | "I wanted to test my NCA implemention (and also just see what the algorithm does), so I tried it on a toy problem of just two four sample points and just two classes. I placed the sample point in rectangle, so that the nearest neighbor would lead to a misclassification (i.e. the nearest neighbor to each point is a point from the opposite class). While this is a simple problem for a classifier like logistic regression, k-nearest-neighbors would typically get an accuracy of zero here." 32 | ] 33 | }, 34 | { 35 | "cell_type": "code", 36 | "execution_count": 1, 37 | "metadata": { 38 | "collapsed": false 39 | }, 40 | "outputs": [], 41 | "source": [ 42 | "from sklearn.model_selection import cross_val_score, LeaveOneOut\n", 43 | "from sklearn.metrics import log_loss, accuracy_score\n", 44 | "from nca import *\n", 45 | "import pandas as pd\n", 46 | "import numpy as np\n", 47 | "from sklearn.neighbors import KNeighborsClassifier as kNN\n", 48 | "import matplotlib\n", 49 | "%matplotlib inline\n", 50 | "import seaborn as sns; sns.set()\n", 51 | "from matplotlib import animation, rc\n", 52 | "from IPython.display import HTML" 53 | ] 54 | }, 55 | { 56 | "cell_type": "code", 57 | "execution_count": 2, 58 | "metadata": { 59 | "collapsed": false 60 | }, 61 | "outputs": [ 62 | { 63 | "data": { 64 | "text/plain": [ 65 | "(-0.5, 0.5)" 66 | ] 67 | }, 68 | "execution_count": 2, 69 | "metadata": {}, 70 | "output_type": "execute_result" 71 | }, 72 | { 73 | "data": { 74 | "image/png": "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\nxsbGsn79+hw+fDj1ej39/f05//zzJ5zz3HPP5bHHHku9Xk9PT0/uuuuuSRkYAGa6hu6cn3322cyb\nNy+PP/54brvttgwMDEw4/ve//z0DAwP59a9/nW3btmX37t15++23J2VgAJjpGorz7t2709fXlyS5\n4oorsnfv3gnHzz777DzzzDM555xzkiTz58/PRx99dIajAsDs0FCcR0ZG0tXVlSSp1Wo566yzMjY2\nNuGc9vb2JMmbb76Zw4cP57LLLjvDUQFgdjjlZ85PPfVUtm/fnlqtliSpqir79++fcM7x48dP+LPv\nvPNO7rnnngwMDKRer0/CuAAw89Wqqqq+6Q9t2LAhK1asSG9vb8bGxnL11Vfn5ZdfnnDO+++/n1tu\nuSUPPvhgLrnkklP+zrGxf6StTcABoKFva/f29ub5559Pb29vXnzxxfT09HzlnPvuuy+bNm06rTAn\nyejop42M0lTd3R0ZHj7a7DFmNDueenY8Pex56rXajru7O772WENxXr58eXbu3JnVq1dnzpw52bJl\nS5Lk0UcfTU9PT+bNm5e9e/fm4YcfTlVVqdVqWbNmTa666qrG/gQAMIs09Lb2VGilVztfaLVXaa3I\njqeeHU8Pe556rbbjk905+xvCAKAw4gwAhRFnACiMOANAYcQZAAojzgBQGHEGgMKIMwAURpwBoDDi\nDACFEWcAKIw4A0BhxBkACiPOAFAYcQaAwogzABRGnAGgMOIMAIURZwAojDgDQGHEGQAKI84AUBhx\nBoDCiDMAFEacAaAw4gwAhRFnACiMOANAYcQZAAojzgBQGHEGgMKIMwAURpwBoDDiDACFEWcAKIw4\nA0BhxBkACiPOAFAYcQaAwogzABRGnAGgMOIMAIURZwAojDgDQGHEGQAKI84AUBhxBoDCiDMAFEac\nAaAw4gwAhRFnACiMOANAYdoa+aGxsbGsX78+hw8fTr1eT39/f84///wTnrtu3brMmTMn/f39ZzQo\nAMwWDd05P/vss5k3b14ef/zx3HbbbRkYGDjheTt37sxf/vKXMxoQAGabhuK8e/fu9PX1JUmuuOKK\n7N279yvnfP7553nkkUeydu3aM5sQAGaZhuI8MjKSrq6uJEmtVstZZ52VsbGxCec8+uijuf766zN3\n7twznxIAZpFTfub81FNPZfv27anVakmSqqqyf//+CeccP358wuODBw/mjTfeyB133JFXX311EscF\ngJmvVlVV9U1/aMOGDVmxYkV6e3szNjaWq6++Oi+//PL48V/96lfZsWNHzjnnnBw9ejSjo6O5+eab\nc/PNN3/t7xwb+0fa2uqN/SkAYAZp6Nvavb29ef7559Pb25sXX3wxPT09E47fdNNNuemmm5Ikr732\nWgYHB08a5iQZHf20kVGaqru7I8PDR5s9xoxmx1PPjqeHPU+9Vttxd3fH1x5r6DPn5cuXZ2xsLKtX\nr84TTzyRu+++O8k/P2fet29fY1MCAEkafFt7KrTSq50vtNqrtFZkx1PPjqeHPU+9VtvxpN85AwBT\nR5wBoDDiDACFEWcAKIw4A0BhxBkACiPOAFAYcQaAwogzABRGnAGgMOIMAIURZwAojDgDQGHEGQAK\nI84AUBhxBoDCiDMAFEacAaAw4gwAhRFnACiMOANAYcQZAAojzgBQGHEGgMKIMwAURpwBoDDiDACF\nqVVVVTV7CADgf7lzBoDCiDMAFEacAaAw4gwAhRFnACiMOANAYdqaPUCrGBsby/r163P48OHU6/X0\n9/fn/PPPP+G569aty5w5c9Lf3z/NU7a+09nzc889l8ceeyz1ej09PT256667mjRt6+nv78++fftS\nq9Vy77335tJLLx0/tmvXrjz00EOp1+u58sorc/vttzdx0tZ1sh3v2bNnfMcXXnhhNm/e3MRJW9fJ\ndvyFgYGB/OEPf8hvfvObJkw4CSpOy+DgYPWTn/ykqqqqeuWVV6o777zzhOe98sor1cqVK6v169dP\n53gzxqn2/Le//a367ne/W3366adVVVXVypUrq7feemva52xFr732WnXrrbdWVVVVb731VvWDH/xg\nwvHly5dX77//fnX8+PFq9erV9tqAU+34e9/7XvXBBx9UVVVVP/rRj6qXX3552mdsdafa8RfPr1q1\nqrrxxhune7xJ423t07R79+709fUlSa644ors3bv3K+d8/vnneeSRR7J27drpHm/GONWezz777Dzz\nzDM555xzkiTz58/PRx99NO1ztqIv73bRokU5cuRIjh07liQ5dOhQ5s+fn4ULF6ZWq2XZsmXZs2dP\nM8dtSSfbcZLs2LEjCxYsSJJ0dXW5dhtwqh0nyZYtW7Ju3bpmjDdpxPk0jYyMpKurK0lSq9Vy1lln\nZWxsbMI5jz76aK6//vrMnTu3GSPOCKez5/b29iTJm2++mcOHD+eyyy6b9jlb0Zd3mySdnZ0ZGRk5\n4bGurq4MDQ1N+4yt7mQ7TjL+3w1DQ0PZtWtXli1bNu0ztrpT7XhwcDA9PT0599xzmzHepPGZ8wk8\n9dRT2b59e2q1WpKkqqrs379/wjnHjx+f8PjgwYN54403cscdd+TVV1+dtllbWSN7/sI777yTe+65\nJwMDA6nX61M+60xUneRv7j3ZMU7fifb44YcfZu3atbn//vszb968Jkw1s3x5xx9//HF27NiRX/7y\nl3nvvfda+joW5xNYuXJlVq5cOeG5DRs2ZGRkJBdffPH4nVxb2/+u76WXXsp7772XVatW5ejRoxkd\nHc0vfvGL3HzzzdM6eytpZM9J8v777+eHP/xhHnzwwVx88cXTNm+rW7BgwYQ7jKGhoXR3d48fGx4e\nHj/2wQcfjL/9yuk72Y6T5JNPPsktt9ySu+++O5dffnkzRmx5J9vxnj17Mjo6mhtuuCGfffZZDh06\nlC1btmT9+vXNGrdh3tY+Tb29vXn++eeTJC+++GJ6enomHL/pppvy29/+Ntu2bcumTZuybNkyYW7A\nqfacJPfdd182bdqUSy65ZLrHa2m9vb154YUXkiQHDhzIwoULxz8iOO+883Ls2LEcPnw4Y2Njeeml\nl7J06dJmjtuSTrbj5J+fha5Zsya9vb3NGrHlnWzH11xzTZ599tls27YtW7duzbe//e2WDHPizvm0\nLV++PDt37szq1aszZ86cbNmyJck/P2fu6enJd77znSZPODOcas/z5s3L3r178/DDD6eqqtRqtaxZ\nsyZXXXVVkycv35IlS7J48eKsWrUq9Xo9GzduzODgYDo6OtLX15dNmzaNf4lmxYoVueCCC5o8ces5\n2Y6XLl2ap59+Ou+++26efPLJ1Gq1XHvttV9594iTO9V1PFP4v4wEgMJ4WxsACiPOAFAYcQaAwogz\nABRGnAGgMOIMAIURZwAojDgDQGH+G4ye3c9y7e0ZAAAAAElFTkSuQmCC\n", 75 | "text/plain": [ 76 | "" 77 | ] 78 | }, 79 | "metadata": {}, 80 | "output_type": "display_data" 81 | } 82 | ], 83 | "source": [ 84 | "# place four points in a rectangle\n", 85 | "# and then view those points...\n", 86 | "X = np.array( [[0,0], [0.,0.1], [0.3,0.1], [0.3, 0.0]] )\n", 87 | "y = np.array( [0, 1, 1, 0] )\n", 88 | "\n", 89 | "Xcl1 = X[y == 0]\n", 90 | "Xcl2 = X[y == 1]\n", 91 | "cl1, = sns.plt.plot(Xcl1[:,0], Xcl1[:,1], linestyle='', marker='o', color='b')\n", 92 | "cl2, = sns.plt.plot(Xcl2[:,0], Xcl2[:,1], linestyle='', marker='o', color='r')\n", 93 | "sns.plt.xlim((-0.5, 0.5))\n", 94 | "sns.plt.ylim((-0.5, 0.5))" 95 | ] 96 | }, 97 | { 98 | "cell_type": "code", 99 | "execution_count": 3, 100 | "metadata": { 101 | "collapsed": false 102 | }, 103 | "outputs": [ 104 | { 105 | "name": "stdout", 106 | "output_type": "stream", 107 | "text": [ 108 | "(0.0, 0.0)\n" 109 | ] 110 | } 111 | ], 112 | "source": [ 113 | "# This is a method to get the accuracy (using leave-one-out validation) of the data\n", 114 | "def assess_perf(clf, X, y, scoring='accuracy', cv=None):\n", 115 | " if cv is None: cv = LeaveOneOut()\n", 116 | " scores = cross_val_score(clf, X, y, scoring=scoring, cv=cv)\n", 117 | " return np.mean(scores), np.std(scores)\n", 118 | "\n", 119 | "knn = kNN(n_neighbors=1, weights='distance', algorithm='brute', n_jobs=-1)\n", 120 | "print assess_perf(knn, X, y, scoring='accuracy')" 121 | ] 122 | }, 123 | { 124 | "cell_type": "code", 125 | "execution_count": 4, 126 | "metadata": { 127 | "collapsed": false, 128 | "scrolled": false 129 | }, 130 | "outputs": [ 131 | { 132 | "name": "stdout", 133 | "output_type": "stream", 134 | "text": [ 135 | "NCA progress: 0% 10% 20% 30% 40% 50% 60% 70% 80% 90% Done.\n" 136 | ] 137 | } 138 | ], 139 | "source": [ 140 | "# We can initialise the transformation matrix to \n", 141 | "# anything we like, here we choose a diagonal matrix.\n", 142 | "A = np.eye(X.shape[1])\n", 143 | "# Store the transformed data, accuracies and the delta_A at each iteration\n", 144 | "Xtrfmed = []\n", 145 | "accs = []\n", 146 | "diffs = []\n", 147 | "n_elements_in_A = A.shape[0]*A.shape[1]\n", 148 | "\n", 149 | "print 'NCA progress:',\n", 150 | "for i in range(200): # 200 iterations\n", 151 | " if i%20 == 0: print '{:d}%'.format(int(i*100/200.)),\n", 152 | " A_prev = np.copy(A)\n", 153 | " # do a simple step of the NCA method, matrix A will be updated\n", 154 | " nca(A, X, y, lr=0.5)\n", 155 | " Adiff = np.sum(np.abs(A_prev - A))/n_elements_in_A \n", 156 | " Xtransformed = transform(A, X)\n", 157 | " Xtrfmed.append(Xtransformed)\n", 158 | " acc, _ = assess_perf(knn, Xtransformed, y, scoring='accuracy')\n", 159 | " accs.append(acc)\n", 160 | " diffs.append(Adiff)\n", 161 | " \n", 162 | "print 'Done.'" 163 | ] 164 | }, 165 | { 166 | "cell_type": "markdown", 167 | "metadata": {}, 168 | "source": [ 169 | "The NCA ran for 200 iterations, let's plot the accuracy of the transformed data, and the change in $A$ with each iteration." 170 | ] 171 | }, 172 | { 173 | "cell_type": "code", 174 | "execution_count": 5, 175 | "metadata": { 176 | "collapsed": false 177 | }, 178 | "outputs": [ 179 | { 180 | "data": { 181 | "text/plain": [ 182 | "" 183 | ] 184 | }, 185 | "execution_count": 5, 186 | "metadata": {}, 187 | "output_type": "execute_result" 188 | }, 189 | { 190 | "data": { 191 | "image/png": "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\n", 192 | "text/plain": [ 193 | "" 194 | ] 195 | }, 196 | "metadata": {}, 197 | "output_type": "display_data" 198 | } 199 | ], 200 | "source": [ 201 | "fig, (ax1, ax2) = sns.plt.subplots(1, 2, figsize=(14,4))\n", 202 | "ax1.plot(accs)\n", 203 | "ax2.plot(diffs)\n", 204 | "ax1.set_xlabel('Epochs (iterations)')\n", 205 | "ax2.set_xlabel('Epochs (iterations)')\n", 206 | "ax1.set_ylabel('Accuracy (on tranformed data)')\n", 207 | "ax2.set_ylabel('Per-element change in A')" 208 | ] 209 | }, 210 | { 211 | "cell_type": "markdown", 212 | "metadata": { 213 | "collapsed": true 214 | }, 215 | "source": [ 216 | "We could actually stop after just $58$ iterations, as the transformationmatrix has already learned a good mapping with $100%$ classification accuracy by this point. The nice thing about this toy problem in this example is that we could solve it by hand: you want to either squeeze the feature-space in the x-direction (horizontal) or strength in the y-direction. By animating the learned representation at every iteration we can see what NCA actually did to the feature-space..." 217 | ] 218 | }, 219 | { 220 | "cell_type": "code", 221 | "execution_count": 6, 222 | "metadata": { 223 | "collapsed": false, 224 | "scrolled": false 225 | }, 226 | "outputs": [ 227 | { 228 | "data": { 229 | "text/html": [ 230 | "" 509 | ], 510 | "text/plain": [ 511 | "" 512 | ] 513 | }, 514 | "execution_count": 6, 515 | "metadata": {}, 516 | "output_type": "execute_result" 517 | }, 518 | { 519 | "data": { 520 | "image/png": "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\neeWVV8aP/+pXv8qOHTty7rnn5tixYxkdHc0tt9ySW2655Z9+z7Gxv6elpd7YnwIAZpGGPq3d09OT\nF154IT09PXnppZfS3d094fjNN9+cm2++OUny+uuvZ2ho6JRhTpLR0c8aGWVGdXa25ciRYzM9xqxm\nx1PPjqeHPU+9ZttxZ2fbPz3W0HvOK1asyNjYWNasWZMnn3wy99xzT5J/vM+8b9++xqYEAJI0+LL2\nVGimZztfarZnac3IjqeeHU8Pe556zbbjSb9yBgCmjjgDQGHEGQAKI84AUBhxBoDCiDMAFEacAaAw\n4gwAhRFnACiMOANAYcQZAAojzgBQGHEGgMKIMwAURpwBoDDiDACFEWcAKIw4A0BhxBkACiPOAFAY\ncQaAwogzABRGnAGgMOIMAIURZwAojDgDQGHEGQAKU6uqqprpIQCA/+HKGQAKI84AUBhxBoDCiDMA\nFEacAaAw4gwAhWmZ6QGaxdjYWDZs2JDDhw+nXq+nv78/F1xwwUkfu379+syfPz/9/f3TPGXzO5M9\nP//883n88cdTr9fT3d2du+++e4ambT79/f3Zt29farVa7rvvvlx22WXjx3bt2pWHH3449Xo9V111\nVe64444ZnLR5nWrHe/bsGd/xRRddlC1btszgpM3rVDv+0uDgYH7/+9/nN7/5zQxMOAkqzsjQ0FD1\nk5/8pKqqqnr11Veru+6666SPe/XVV6tVq1ZVGzZsmM7xZo3T7fmvf/1r9d3vfrf67LPPqqqqqlWr\nVlVvv/32tM/ZjF5//fXqtttuq6qqqt5+++3qBz/4wYTjK1asqD744IPqxIkT1Zo1a+y1Aafb8fe+\n973qww8/rKqqqn70ox9Vr7zyyrTP2OxOt+Mv71+9enV10003Tfd4k8bL2mdo9+7d6evrS5JceeWV\n2bt379ce88UXX+TRRx/NunXrpnu8WeN0ez7nnHPy7LPP5txzz02SLFq0KB9//PG0z9mMvrrbJUuW\n5OjRozl+/HiS5NChQ1m0aFEWL16cWq2W3t7e7NmzZybHbUqn2nGS7NixI11dXUmSjo4Of3cbcLod\nJ8nAwEDWr18/E+NNGnE+QyMjI+no6EiS1Gq1zJs3L2NjYxMe89hjj+WGG27IggULZmLEWeFM9tza\n2pokeeutt3L48OFcfvnl0z5nM/rqbpOkvb09IyMjJz3W0dGR4eHhaZ+x2Z1qx0nG/9swPDycXbt2\npbe3d9pnbHan2/HQ0FC6u7tz3nnnzcR4k8Z7zifx9NNPZ/v27anVakmSqqqyf//+CY85ceLEhNsH\nDx7Mm2++mTvvvDOvvfbatM3azBrZ85fefffd3HvvvRkcHEy9Xp/yWWej6hQ/ufdUxzhzJ9vjRx99\nlHXr1uWBBx7IwoULZ2Cq2eWrO/7kk0+yY8eO/PKXv8z777/f1H+PxfkkVq1alVWrVk24b+PGjRkZ\nGckll1wyfiXX0vI/63v55Zfz/vvvZ/Xq1Tl27FhGR0fzi1/8Irfccsu0zt5MGtlzknzwwQf54Q9/\nmIceeiiXXHLJtM3b7Lq6uiZcYQwPD6ezs3P82JEjR8aPffjhh+Mvv3LmTrXjJPn0009z66235p57\n7skVV1wxEyM2vVPteM+ePRkdHc2NN96Yzz//PIcOHcrAwEA2bNgwU+M2zMvaZ6inpycvvPBCkuSl\nl15Kd3f3hOM333xz/vu//zvbtm3L5s2b09vbK8wNON2ek+T+++/P5s2bc+mll073eE2tp6cnL774\nYpLkwIEDWbx48fhbBOeff36OHz+ew4cPZ2xsLC+//HKWL18+k+M2pVPtOPnHe6Fr165NT0/PTI3Y\n9E6142uvvTbPPfdctm3blq1bt+bb3/52U4Y5ceV8xlasWJGdO3dmzZo1mT9/fgYGBpL8433m7u7u\nfOc735nhCWeH0+154cKF2bt3bx555JFUVZVarZa1a9fm6quvnuHJy7ds2bIsXbo0q1evTr1ez6ZN\nmzI0NJS2trb09fVl8+bN4x+iWblyZS688MIZnrj5nGrHy5cvzzPPPJP33nsvTz31VGq1Wq677rqv\nvXrEqZ3u7/Fs4VdGAkBhvKwNAIURZwAojDgDQGHEGQAKI84AUBhxBoDCiDMAFEacAaAw/w9VJd3P\ntf9BhwAAAABJRU5ErkJggg==\n", 521 | "text/plain": [ 522 | "" 523 | ] 524 | }, 525 | "metadata": {}, 526 | "output_type": "display_data" 527 | } 528 | ], 529 | "source": [ 530 | "# First set up the figure, the axis, and the plot element we want to animate\n", 531 | "fig = sns.plt.figure()\n", 532 | "ax = sns.plt.axes(xlim=(-0.5, 0.5), ylim=(-0.5, 0.5))\n", 533 | "\n", 534 | "#line, = ax.plot([], [], lw=2, color='b')\n", 535 | "cl1, = ax.plot([],[], linestyle='', marker='o', color='b')\n", 536 | "cl2, = ax.plot([],[], linestyle='', marker='o', color='r')\n", 537 | "# initialization function: plot the background of each frame\n", 538 | "def init():\n", 539 | " cl1.set_data([], [])\n", 540 | " cl2.set_data([], [])\n", 541 | " return [cl1, cl2,]\n", 542 | "\n", 543 | "# animation function. This is called sequentially\n", 544 | "def animate(t):\n", 545 | " X_transformed_at_t = Xtrfmed[t]\n", 546 | " Xtcl1 = X_transformed_at_t[y == 0]\n", 547 | " Xtcl2 = X_transformed_at_t[y == 1]\n", 548 | " cl1.set_data(Xtcl1[:,0], Xtcl1[:,1])\n", 549 | " cl2.set_data(Xtcl2[:,0], Xtcl2[:,1])\n", 550 | " return [cl1, cl2, ]\n", 551 | "\n", 552 | "# call the animator. \n", 553 | "anim = animation.FuncAnimation(fig, animate, init_func=init, \n", 554 | " frames=range(len(Xtrfmed)), \n", 555 | " interval=50, blit=True)\n", 556 | "sns.plt.grid(True)\n", 557 | "HTML(anim.to_html5_video())" 558 | ] 559 | } 560 | ], 561 | "metadata": { 562 | "anaconda-cloud": {}, 563 | "kernelspec": { 564 | "display_name": "Python 2", 565 | "language": "python", 566 | "name": "python2" 567 | }, 568 | "language_info": { 569 | "codemirror_mode": { 570 | "name": "ipython", 571 | "version": 2 572 | }, 573 | "file_extension": ".py", 574 | "mimetype": "text/x-python", 575 | "name": "python", 576 | "nbconvert_exporter": "python", 577 | "pygments_lexer": "ipython2", 578 | "version": "2.7.12" 579 | } 580 | }, 581 | "nbformat": 4, 582 | "nbformat_minor": 2 583 | } 584 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Neighborhood-Components-Analysis-in-Python 2 | A Python gradient-descent implementation of the Neighborhood Components Analysis (NCA) method for metric learning. 3 | 4 | For a description and example of usage, see the [Jupyter notebook here](https://github.com/erlendd/Neighborhood-Components-Analysis-in-Python/blob/master/Neighborhood%20Components%20Analysis.ipynb) 5 | 6 | -------------------------------------------------------------------------------- /nca.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | import seaborn as sns; sns.set() 4 | 5 | def applyA(A, vec): 6 | #return np.matmul(A, vec) 7 | result = np.zeros(vec.shape) 8 | for i in range(A.shape[0]): 9 | result[i] = A[i, i] * vec[i] 10 | return result 11 | 12 | def compute_softmax_norm_i(A, inp, i): 13 | softmax_norm = 0. 14 | for k in range(inp.shape[0]): 15 | if i == k: continue 16 | exponent = applyA(A, inp[i]) - applyA(A, inp[k]) 17 | exponent = np.dot(exponent, exponent) 18 | softmax_norm += np.exp(-exponent) 19 | return softmax_norm 20 | 21 | 22 | def compute_pij(A, inp, i, j): 23 | if i == j: return 0 # since pij == 0 24 | exponent = applyA(A, inp[i]) - applyA(A, inp[j]) 25 | exponent = np.dot(exponent, exponent) 26 | pij = np.exp(-exponent) / compute_softmax_norm_i(A, inp, i) 27 | return pij 28 | 29 | def nca(A, inp, label, lr=0.5): 30 | inp = transform(A, inp) 31 | for i in range(inp.shape[0]): 32 | p = 0. 33 | for j in range(inp.shape[0]): 34 | if label[i] == label[j]: 35 | p += compute_pij(A, inp, i, j) 36 | 37 | #print 'p=',p 38 | first_term = np.zeros( (inp.shape[1], inp.shape[1]) ) 39 | second_term = np.zeros( (inp.shape[1], inp.shape[1]) ) 40 | for k in range(inp.shape[0]): 41 | if i == k: continue 42 | xik = inp[i] - inp[k] 43 | pik = compute_pij(A, inp, i, k) 44 | term = pik * np.outer(xik, xik) 45 | #print 'term=',term 46 | first_term += term 47 | if label[k] == label[i]: 48 | second_term += term 49 | first_term *= p 50 | #print 'i,1st,2nd:',i, first_term, second_term 51 | A += lr * (first_term - second_term) 52 | return A 53 | 54 | def transform(A, inp): 55 | out = np.zeros(inp.shape) 56 | for i in range(len(out)): 57 | out[i] = applyA(A, inp[i]) 58 | return out 59 | 60 | 61 | 62 | if __name__ == "__main__": 63 | 64 | X = np.array( [ [0], [0.1], [0.9] ] ) 65 | y = np.array( [0, 1, 1] ) 66 | A = np.eye(X.shape[1]) 67 | 68 | #print compute_pij(A, X, 1, 2) 69 | nca(A, X, y) 70 | print 71 | print 'X:',X 72 | print 73 | print 'A:',A 74 | print 75 | #Ascale = [] 76 | #for iter in range(1000): 77 | # print nca(A, X, y) 78 | # Ascale.append( np.sum(A) ) 79 | # 80 | #sns.plt.plot(Ascale) 81 | #sns.plt.show() 82 | # 83 | from sklearn.datasets import make_classification 84 | from sklearn.neighbors import KNeighborsClassifier as kNN 85 | from sklearn.model_selection import cross_val_score 86 | 87 | X, y = make_classification(n_samples=100, n_features=2, n_redundant=0) 88 | sns.plt.scatter(X[:, 0], X[:, 1], c=y) 89 | sns.plt.show() 90 | 91 | clf = kNN(weights='distance') 92 | scores = cross_val_score(clf, X, y, scoring='neg_log_loss', cv=25) 93 | print np.mean(scores) 94 | 95 | A = np.eye(X.shape[1]) 96 | Xt = transform(A, X) 97 | print Xt.shape 98 | 99 | Ascale = [] 100 | for iter in range(20): 101 | if iter % 5 == 0: 102 | print 'Iteration',iter, 103 | nca(A, X, y) 104 | #print 'A',A 105 | flattenedA = np.sum(A) 106 | Ascale.append( np.sum(A) ) 107 | if iter % 5 == 0: 108 | print flattenedA, 109 | Xt = transform(A, X) 110 | scores = cross_val_score(clf, Xt, y, scoring='neg_log_loss', cv=25) 111 | print np.mean(scores) 112 | 113 | 114 | #if iter % 20 == 0: 115 | # sns.plt.scatter(Xt[:, 0], Xt[:, 1], c=y) 116 | # sns.plt.show() 117 | 118 | sns.plt.plot(Ascale) 119 | sns.plt.show() 120 | 121 | 122 | --------------------------------------------------------------------------------