diff --git a/examples/test.ipynb b/examples/test.ipynb
index a969eae..833366a 100644
--- a/examples/test.ipynb
+++ b/examples/test.ipynb
@@ -4,14 +4,14 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "# Using FIt-SNE\n",
+ "# A brief guide to FIt-SNE. Visualizing MNIST\n",
"\n",
"Author: Dmitry Kobak"
]
},
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
@@ -21,25 +21,16 @@
"import pylab as plt\n",
"import seaborn as sns; sns.set()\n",
"\n",
- "# change the path!\n",
- "import sys; sys.path.append('/home/dmitry/github/kerind/FIt-SNE')\n",
+ "# the path should point to the FIt-SNE directory\n",
+ "import sys; sys.path.append('../')\n",
"from fast_tsne import fast_tsne"
]
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": 17,
"metadata": {},
"outputs": [
- {
- "name": "stderr",
- "output_type": "stream",
- "text": [
- "/Users/george/anaconda3/lib/python3.6/site-packages/h5py/__init__.py:36: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.\n",
- " from ._conv import register_converters as _register_converters\n",
- "Using TensorFlow backend.\n"
- ]
- },
{
"name": "stdout",
"output_type": "stream",
@@ -52,8 +43,8 @@
"# Load MNIST data\n",
"from keras.datasets import mnist\n",
"(x_train, y_train), (x_test, y_test) = mnist.load_data()\n",
- "x_train = x_train.reshape(60000, 784).astype('float64') / 255\n",
- "x_test = x_test.reshape(10000, 784).astype('float64') / 255\n",
+ "x_train = x_train.reshape(60000, 784).astype('float') / 255\n",
+ "x_test = x_test.reshape(10000, 784).astype('float') / 255\n",
"X = np.concatenate((x_train, x_test))\n",
"y = np.concatenate((y_train, y_test))\n",
"print(X.shape)\n",
@@ -63,9 +54,6 @@
"U, s, V = np.linalg.svd(X, full_matrices=False)\n",
"X50 = np.dot(U, np.diag(s))[:,:50]\n",
"\n",
- "# We will use PCA initialization later on\n",
- "PCAinit = X50[:,:2] / np.std(X50[:,0]) * 0.0001\n",
- "\n",
"# 10 nice colors\n",
"col = np.array(['#a6cee3','#1f78b4','#b2df8a','#33a02c','#fb9a99',\n",
" '#e31a1c','#fdbf6f','#ff7f00','#cab2d6','#6a3d9a'])"
@@ -73,9 +61,17 @@
},
{
"cell_type": "code",
- "execution_count": 3,
+ "execution_count": 15,
"metadata": {},
"outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "CPU times: user 685 ms, sys: 84.2 ms, total: 769 ms\n",
+ "Wall time: 58.7 s\n"
+ ]
+ },
{
"data": {
"application/javascript": [
@@ -89,7 +85,7 @@
" } else if (typeof(MozWebSocket) !== 'undefined') {\n",
" return MozWebSocket;\n",
" } else {\n",
- " alert('Your browser does not have WebSocket support.' +\n",
+ " alert('Your browser does not have WebSocket support. ' +\n",
" 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
" 'Firefox 4 and 5 are also supported but you ' +\n",
" 'have to enable WebSockets in about:config.');\n",
@@ -306,7 +302,7 @@
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
- " var nav_element = $('
')\n",
+ " var nav_element = $('');\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
@@ -363,7 +359,7 @@
" var fmt = mpl.extensions[ind];\n",
" var option = $(\n",
" '', {selected: fmt === mpl.default_extension}).html(fmt);\n",
- " fmt_picker.append(option)\n",
+ " fmt_picker.append(option);\n",
" }\n",
"\n",
" // Add hover states to the ui-buttons\n",
@@ -427,7 +423,7 @@
" var height = Math.abs(y1 - y0);\n",
"\n",
" fig.rubberband_context.clearRect(\n",
- " 0, 0, fig.canvas.width, fig.canvas.height);\n",
+ " 0, 0, fig.canvas.width / mpl.ratio, fig.canvas.height / mpl.ratio);\n",
"\n",
" fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
"}\n",
@@ -633,7 +629,7 @@
"mpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) {\n",
" this.message.textContent = tooltip;\n",
"};\n",
- "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
+ "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
"\n",
"mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
"\n",
@@ -732,7 +728,7 @@
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
- " var nav_element = $('')\n",
+ " var nav_element = $('');\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
@@ -859,7 +855,7 @@
{
"data": {
"text/html": [
- "
"
+ "
"
],
"text/plain": [
""
@@ -870,20 +866,39 @@
}
],
"source": [
- "# Running t-SNE on the full MNIST in the default way\n",
+ "# Running t-SNE on the full MNIST in the default way (this uses perplexity=30)\n",
"\n",
- "Z = fast_tsne(X50, perplexity=50, seed=42)\n",
+ "%time Z = fast_tsne(X50, seed=42)\n",
"\n",
"plt.figure(figsize=(5,5))\n",
- "plt.scatter(Z[:,0], Z[:,1], c=col[y], s=1)\n",
+ "plt.scatter(Z[:,0], Z[:,1], c=col[y], s=.1)\n",
"plt.tight_layout()"
]
},
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "It is almost always a good idea to use PCA initialization\n",
+ "\n",
+ "And for large datasets it is always a good idea to increase the learning rate. Here we use 1000, but for even larger data sets n/12 is a good choice. \n",
+ "\n",
+ "See here for discussion: https://www.nature.com/articles/s41467-019-13056-x."
+ ]
+ },
{
"cell_type": "code",
- "execution_count": 4,
+ "execution_count": 16,
"metadata": {},
"outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "CPU times: user 719 ms, sys: 99.5 ms, total: 819 ms\n",
+ "Wall time: 1min 20s\n"
+ ]
+ },
{
"data": {
"application/javascript": [
@@ -897,7 +912,7 @@
" } else if (typeof(MozWebSocket) !== 'undefined') {\n",
" return MozWebSocket;\n",
" } else {\n",
- " alert('Your browser does not have WebSocket support.' +\n",
+ " alert('Your browser does not have WebSocket support. ' +\n",
" 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
" 'Firefox 4 and 5 are also supported but you ' +\n",
" 'have to enable WebSockets in about:config.');\n",
@@ -1114,7 +1129,7 @@
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
- " var nav_element = $('')\n",
+ " var nav_element = $('');\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
@@ -1171,7 +1186,7 @@
" var fmt = mpl.extensions[ind];\n",
" var option = $(\n",
" '', {selected: fmt === mpl.default_extension}).html(fmt);\n",
- " fmt_picker.append(option)\n",
+ " fmt_picker.append(option);\n",
" }\n",
"\n",
" // Add hover states to the ui-buttons\n",
@@ -1235,7 +1250,7 @@
" var height = Math.abs(y1 - y0);\n",
"\n",
" fig.rubberband_context.clearRect(\n",
- " 0, 0, fig.canvas.width, fig.canvas.height);\n",
+ " 0, 0, fig.canvas.width / mpl.ratio, fig.canvas.height / mpl.ratio);\n",
"\n",
" fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
"}\n",
@@ -1441,7 +1456,7 @@
"mpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) {\n",
" this.message.textContent = tooltip;\n",
"};\n",
- "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
+ "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
"\n",
"mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
"\n",
@@ -1540,7 +1555,7 @@
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
- " var nav_element = $('')\n",
+ " var nav_element = $('');\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
@@ -1667,7 +1682,7 @@
{
"data": {
"text/html": [
- "
"
+ "
"
],
"text/plain": [
""
@@ -1678,23 +1693,13 @@
}
],
"source": [
- "# Two classes are separated into two parts on the above figure. \n",
- "# This can be fixed if one uses stronger/longer early exaggeration (e.g. stop_early_exag_iter=500)\n",
- "# or higher learning rate (e.g. learning rate=1000)\n",
- "\n",
- "# PCA initialization is not important for MNIST, but can be handy for other datasets\n",
+ "# With PCA initialization and high learning rate\n",
"\n",
- "Z1 = fast_tsne(X50, perplexity=50, stop_early_exag_iter=500, initialization=PCAinit)\n",
- "\n",
- "Z2 = fast_tsne(X50, perplexity=50, learning_rate=1000, initialization=PCAinit)\n",
+ "PCAinit = X50[:,:2] / np.std(X50[:,0]) * 0.0001\n",
+ "%time Z = fast_tsne(X50, initialization=PCAinit, learning_rate=1000)\n",
"\n",
- "plt.figure(figsize=(8,4))\n",
- "plt.subplot(121)\n",
- "plt.scatter(Z1[:,0], Z1[:,1], c=col[y], s=1)\n",
- "plt.title('Early exaggeration for 500 iterations')\n",
- "plt.subplot(122)\n",
- "plt.scatter(Z2[:,0], Z2[:,1], c=col[y], s=1)\n",
- "plt.title('Learning rate 1000')\n",
+ "plt.figure(figsize=(5,5))\n",
+ "plt.scatter(Z[:,0], Z[:,1], c=col[y], s=.1)\n",
"plt.tight_layout()"
]
},
@@ -1702,34 +1707,24 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "## Showcase of various other options"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 5,
- "metadata": {},
- "outputs": [],
- "source": [
- "# Subsampling \n",
- "\n",
- "np.random.seed(42)\n",
- "ind1k = np.random.choice(X.shape[0], 1000, replace=False)\n",
- "ind5k = np.random.choice(X.shape[0], 5000, replace=False)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### 1-dimensional projection"
+ "## Changing the random seed used for initialization"
]
},
{
"cell_type": "code",
- "execution_count": 6,
+ "execution_count": 18,
"metadata": {},
"outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "CPU times: user 751 ms, sys: 104 ms, total: 854 ms\n",
+ "Wall time: 59.6 s\n",
+ "CPU times: user 711 ms, sys: 136 ms, total: 847 ms\n",
+ "Wall time: 1min 4s\n"
+ ]
+ },
{
"data": {
"application/javascript": [
@@ -1743,7 +1738,7 @@
" } else if (typeof(MozWebSocket) !== 'undefined') {\n",
" return MozWebSocket;\n",
" } else {\n",
- " alert('Your browser does not have WebSocket support.' +\n",
+ " alert('Your browser does not have WebSocket support. ' +\n",
" 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
" 'Firefox 4 and 5 are also supported but you ' +\n",
" 'have to enable WebSockets in about:config.');\n",
@@ -1960,7 +1955,7 @@
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
- " var nav_element = $('')\n",
+ " var nav_element = $('');\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
@@ -2017,7 +2012,7 @@
" var fmt = mpl.extensions[ind];\n",
" var option = $(\n",
" '', {selected: fmt === mpl.default_extension}).html(fmt);\n",
- " fmt_picker.append(option)\n",
+ " fmt_picker.append(option);\n",
" }\n",
"\n",
" // Add hover states to the ui-buttons\n",
@@ -2081,7 +2076,7 @@
" var height = Math.abs(y1 - y0);\n",
"\n",
" fig.rubberband_context.clearRect(\n",
- " 0, 0, fig.canvas.width, fig.canvas.height);\n",
+ " 0, 0, fig.canvas.width / mpl.ratio, fig.canvas.height / mpl.ratio);\n",
"\n",
" fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
"}\n",
@@ -2287,7 +2282,7 @@
"mpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) {\n",
" this.message.textContent = tooltip;\n",
"};\n",
- "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
+ "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
"\n",
"mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
"\n",
@@ -2386,7 +2381,7 @@
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
- " var nav_element = $('')\n",
+ " var nav_element = $('');\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
@@ -2513,7 +2508,7 @@
{
"data": {
"text/html": [
- "
"
+ "
"
],
"text/plain": [
""
@@ -2524,20 +2519,16 @@
}
],
"source": [
- "Z1 = fast_tsne(X50[ind5k,:], perplexity=50, max_iter=500, early_exag_coeff=4, \n",
- " stop_early_exag_iter=100, initialization=PCAinit[ind5k,:])\n",
- "\n",
- "Z2 = fast_tsne(X50[ind5k,:], perplexity=50, max_iter=500, early_exag_coeff=4, \n",
- " stop_early_exag_iter=100, initialization=PCAinit[ind5k,:], map_dims=1)\n",
+ "%time Z1 = fast_tsne(X50, seed=1)\n",
+ "%time Z2 = fast_tsne(X50, seed=2)\n",
"\n",
"plt.figure(figsize=(8,4))\n",
"plt.subplot(121)\n",
- "plt.scatter(Z1[:,0], Z1[:,1], c=col[y[ind5k]], s=1)\n",
- "plt.title('2D')\n",
+ "plt.scatter(Z1[:,0], Z1[:,1], c=col[y], s=.1)\n",
+ "plt.title('Random seed = 1')\n",
"plt.subplot(122)\n",
- "plt.scatter(Z2[:,0], np.random.uniform(size=Z2.shape[0]), c=col[y[ind5k]], s=1)\n",
- "plt.ylim([-2,3])\n",
- "plt.title('1D')\n",
+ "plt.scatter(Z2[:,0], Z2[:,1], c=col[y], s=.1)\n",
+ "plt.title('Random seed = 2')\n",
"plt.tight_layout()"
]
},
@@ -2545,14 +2536,24 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "### VP tree vs ANNOY"
+ "Note how clusters sometimes get split into multiple subclusters. This can be fixed by increasing the learning rate, as shown below for the same random seeds."
]
},
{
"cell_type": "code",
- "execution_count": 7,
+ "execution_count": 20,
"metadata": {},
"outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "CPU times: user 686 ms, sys: 90.3 ms, total: 776 ms\n",
+ "Wall time: 1min 18s\n",
+ "CPU times: user 682 ms, sys: 108 ms, total: 791 ms\n",
+ "Wall time: 1min 22s\n"
+ ]
+ },
{
"data": {
"application/javascript": [
@@ -2566,7 +2567,7 @@
" } else if (typeof(MozWebSocket) !== 'undefined') {\n",
" return MozWebSocket;\n",
" } else {\n",
- " alert('Your browser does not have WebSocket support.' +\n",
+ " alert('Your browser does not have WebSocket support. ' +\n",
" 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
" 'Firefox 4 and 5 are also supported but you ' +\n",
" 'have to enable WebSockets in about:config.');\n",
@@ -2783,7 +2784,7 @@
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
- " var nav_element = $('')\n",
+ " var nav_element = $('');\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
@@ -2840,7 +2841,7 @@
" var fmt = mpl.extensions[ind];\n",
" var option = $(\n",
" '', {selected: fmt === mpl.default_extension}).html(fmt);\n",
- " fmt_picker.append(option)\n",
+ " fmt_picker.append(option);\n",
" }\n",
"\n",
" // Add hover states to the ui-buttons\n",
@@ -2904,7 +2905,7 @@
" var height = Math.abs(y1 - y0);\n",
"\n",
" fig.rubberband_context.clearRect(\n",
- " 0, 0, fig.canvas.width, fig.canvas.height);\n",
+ " 0, 0, fig.canvas.width / mpl.ratio, fig.canvas.height / mpl.ratio);\n",
"\n",
" fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
"}\n",
@@ -3110,7 +3111,7 @@
"mpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) {\n",
" this.message.textContent = tooltip;\n",
"};\n",
- "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
+ "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
"\n",
"mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
"\n",
@@ -3209,7 +3210,7 @@
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
- " var nav_element = $('')\n",
+ " var nav_element = $('');\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
@@ -3336,7 +3337,7 @@
{
"data": {
"text/html": [
- "
"
+ "
"
],
"text/plain": [
""
@@ -3347,20 +3348,16 @@
}
],
"source": [
- "Z1 = fast_tsne(X50[ind5k,:], perplexity=50, max_iter=500, early_exag_coeff=4, \n",
- " stop_early_exag_iter=100, initialization=PCAinit[ind5k,:])\n",
- "\n",
- "Z2 = fast_tsne(X50[ind5k,:], perplexity=50, max_iter=500, early_exag_coeff=4, \n",
- " stop_early_exag_iter=100, initialization=PCAinit[ind5k,:], \n",
- " knn_algo='vp-tree')\n",
+ "%time Z1 = fast_tsne(X50, seed=1, learning_rate=1000)\n",
+ "%time Z2 = fast_tsne(X50, seed=2, learning_rate=1000)\n",
"\n",
"plt.figure(figsize=(8,4))\n",
"plt.subplot(121)\n",
- "plt.scatter(Z1[:,0], Z1[:,1], c=col[y[ind5k]], s=1)\n",
- "plt.title('Annoy')\n",
+ "plt.scatter(Z1[:,0], Z1[:,1], c=col[y], s=.1)\n",
+ "plt.title('Random seed = 1, learning rate = 1000')\n",
"plt.subplot(122)\n",
- "plt.scatter(Z2[:,0], Z2[:,1], c=col[y[ind5k]], s=1)\n",
- "plt.title('VP tree')\n",
+ "plt.scatter(Z2[:,0], Z2[:,1], c=col[y], s=.1)\n",
+ "plt.title('Random seed = 2, learning rate = 1000')\n",
"plt.tight_layout()"
]
},
@@ -3368,14 +3365,24 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "### Barnes-Hut vs FFT"
+ "## Changing perplexity"
]
},
{
"cell_type": "code",
- "execution_count": 8,
+ "execution_count": 52,
"metadata": {},
"outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "CPU times: user 683 ms, sys: 80.3 ms, total: 763 ms\n",
+ "Wall time: 1min 18s\n",
+ "CPU times: user 725 ms, sys: 52.9 ms, total: 777 ms\n",
+ "Wall time: 6min 46s\n"
+ ]
+ },
{
"data": {
"application/javascript": [
@@ -3389,7 +3396,7 @@
" } else if (typeof(MozWebSocket) !== 'undefined') {\n",
" return MozWebSocket;\n",
" } else {\n",
- " alert('Your browser does not have WebSocket support.' +\n",
+ " alert('Your browser does not have WebSocket support. ' +\n",
" 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
" 'Firefox 4 and 5 are also supported but you ' +\n",
" 'have to enable WebSockets in about:config.');\n",
@@ -3606,7 +3613,7 @@
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
- " var nav_element = $('')\n",
+ " var nav_element = $('');\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
@@ -3663,7 +3670,7 @@
" var fmt = mpl.extensions[ind];\n",
" var option = $(\n",
" '', {selected: fmt === mpl.default_extension}).html(fmt);\n",
- " fmt_picker.append(option)\n",
+ " fmt_picker.append(option);\n",
" }\n",
"\n",
" // Add hover states to the ui-buttons\n",
@@ -3727,7 +3734,7 @@
" var height = Math.abs(y1 - y0);\n",
"\n",
" fig.rubberband_context.clearRect(\n",
- " 0, 0, fig.canvas.width, fig.canvas.height);\n",
+ " 0, 0, fig.canvas.width / mpl.ratio, fig.canvas.height / mpl.ratio);\n",
"\n",
" fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
"}\n",
@@ -3933,7 +3940,7 @@
"mpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) {\n",
" this.message.textContent = tooltip;\n",
"};\n",
- "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
+ "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
"\n",
"mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
"\n",
@@ -4032,7 +4039,7 @@
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
- " var nav_element = $('')\n",
+ " var nav_element = $('');\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
@@ -4159,7 +4166,7 @@
{
"data": {
"text/html": [
- "
"
+ "
"
],
"text/plain": [
""
@@ -4170,20 +4177,16 @@
}
],
"source": [
- "Z1 = fast_tsne(X50[ind5k,:], perplexity=50, max_iter=500, early_exag_coeff=4, \n",
- " stop_early_exag_iter=100, initialization=PCAinit[ind5k,:])\n",
- "\n",
- "Z2 = fast_tsne(X50[ind5k,:], perplexity=50, max_iter=500, early_exag_coeff=4, \n",
- " stop_early_exag_iter=100, initialization=PCAinit[ind5k,:], \n",
- " nbody_algo='Barnes-Hut')\n",
+ "%time Z1 = fast_tsne(X50, perplexity=3, initialization=PCAinit, learning_rate=1000)\n",
+ "%time Z2 = fast_tsne(X50, perplexity=300, initialization=PCAinit, learning_rate=1000)\n",
"\n",
"plt.figure(figsize=(8,4))\n",
"plt.subplot(121)\n",
- "plt.scatter(Z1[:,0], Z1[:,1], c=col[y[ind5k]], s=1)\n",
- "plt.title('FFT')\n",
+ "plt.scatter(Z1[:,0], Z1[:,1], c=col[y], s=.1)\n",
+ "plt.title('Perplexity 3')\n",
"plt.subplot(122)\n",
- "plt.scatter(Z2[:,0], Z2[:,1], c=col[y[ind5k]], s=1)\n",
- "plt.title('Barnes-Hut')\n",
+ "plt.scatter(Z2[:,0], Z2[:,1], c=col[y], s=.1)\n",
+ "plt.title('Perplexity 300')\n",
"plt.tight_layout()"
]
},
@@ -4191,14 +4194,25 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "### Exact t-SNE"
+ "## Exaggeration\n",
+ "Exaggeration is applied after the early exaggeration phase is over. By default, early exaggeration lasts 250 iterations, with coefficient 12."
]
},
{
"cell_type": "code",
- "execution_count": 9,
+ "execution_count": 25,
"metadata": {},
"outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "CPU times: user 732 ms, sys: 68.6 ms, total: 801 ms\n",
+ "Wall time: 1min\n",
+ "CPU times: user 724 ms, sys: 72.4 ms, total: 796 ms\n",
+ "Wall time: 1min 7s\n"
+ ]
+ },
{
"data": {
"application/javascript": [
@@ -4212,7 +4226,7 @@
" } else if (typeof(MozWebSocket) !== 'undefined') {\n",
" return MozWebSocket;\n",
" } else {\n",
- " alert('Your browser does not have WebSocket support.' +\n",
+ " alert('Your browser does not have WebSocket support. ' +\n",
" 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
" 'Firefox 4 and 5 are also supported but you ' +\n",
" 'have to enable WebSockets in about:config.');\n",
@@ -4429,7 +4443,7 @@
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
- " var nav_element = $('')\n",
+ " var nav_element = $('');\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
@@ -4486,7 +4500,7 @@
" var fmt = mpl.extensions[ind];\n",
" var option = $(\n",
" '', {selected: fmt === mpl.default_extension}).html(fmt);\n",
- " fmt_picker.append(option)\n",
+ " fmt_picker.append(option);\n",
" }\n",
"\n",
" // Add hover states to the ui-buttons\n",
@@ -4550,7 +4564,7 @@
" var height = Math.abs(y1 - y0);\n",
"\n",
" fig.rubberband_context.clearRect(\n",
- " 0, 0, fig.canvas.width, fig.canvas.height);\n",
+ " 0, 0, fig.canvas.width / mpl.ratio, fig.canvas.height / mpl.ratio);\n",
"\n",
" fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
"}\n",
@@ -4756,7 +4770,7 @@
"mpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) {\n",
" this.message.textContent = tooltip;\n",
"};\n",
- "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
+ "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
"\n",
"mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
"\n",
@@ -4855,7 +4869,7 @@
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
- " var nav_element = $('')\n",
+ " var nav_element = $('');\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
@@ -4982,7 +4996,7 @@
{
"data": {
"text/html": [
- "
"
+ "
"
],
"text/plain": [
""
@@ -4993,19 +5007,16 @@
}
],
"source": [
- "Z1 = fast_tsne(X50[ind5k,:], perplexity=50, max_iter=500, early_exag_coeff=4, \n",
- " stop_early_exag_iter=100, initialization=PCAinit[ind5k,:])\n",
- "\n",
- "Z2 = fast_tsne(X50[ind5k,:], perplexity=50, max_iter=500, early_exag_coeff=4, \n",
- " stop_early_exag_iter=100, initialization=PCAinit[ind5k,:], theta=0)\n",
+ "%time Z1 = fast_tsne(X50, learning_rate=1000, initialization=PCAinit, late_exag_coeff=2, start_late_exag_iter=250)\n",
+ "%time Z2 = fast_tsne(X50, learning_rate=1000, initialization=PCAinit, late_exag_coeff=4, start_late_exag_iter=250)\n",
"\n",
"plt.figure(figsize=(8,4))\n",
"plt.subplot(121)\n",
- "plt.scatter(Z1[:,0], Z1[:,1], c=col[y[ind5k]], s=1)\n",
- "plt.title('Annoy and FFT')\n",
+ "plt.scatter(Z1[:,0], Z1[:,1], c=col[y], s=.1)\n",
+ "plt.title('Exaggeration 2')\n",
"plt.subplot(122)\n",
- "plt.scatter(Z2[:,0], Z2[:,1], c=col[y[ind5k]], s=1)\n",
- "plt.title('Exact t-SNE')\n",
+ "plt.scatter(Z2[:,0], Z2[:,1], c=col[y], s=.1)\n",
+ "plt.title('Exaggeration 4')\n",
"plt.tight_layout()"
]
},
@@ -5013,14 +5024,27 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "### Late exaggeration"
+ "## Making the kernel more/less heavy-tailed\n",
+ "The default Cauchy kernel corresponds to `df=1`. Large `df` corresponds to the Gaussian kernel.\n",
+ "\n",
+ "See https://ecmlpkdd2019.org/downloads/paper/327.pdf"
]
},
{
"cell_type": "code",
- "execution_count": 10,
+ "execution_count": 38,
"metadata": {},
"outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "CPU times: user 690 ms, sys: 80.9 ms, total: 770 ms\n",
+ "Wall time: 1min 8s\n",
+ "CPU times: user 691 ms, sys: 72.3 ms, total: 763 ms\n",
+ "Wall time: 1min 19s\n"
+ ]
+ },
{
"data": {
"application/javascript": [
@@ -5034,7 +5058,7 @@
" } else if (typeof(MozWebSocket) !== 'undefined') {\n",
" return MozWebSocket;\n",
" } else {\n",
- " alert('Your browser does not have WebSocket support.' +\n",
+ " alert('Your browser does not have WebSocket support. ' +\n",
" 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
" 'Firefox 4 and 5 are also supported but you ' +\n",
" 'have to enable WebSockets in about:config.');\n",
@@ -5251,7 +5275,7 @@
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
- " var nav_element = $('')\n",
+ " var nav_element = $('');\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
@@ -5308,7 +5332,7 @@
" var fmt = mpl.extensions[ind];\n",
" var option = $(\n",
" '', {selected: fmt === mpl.default_extension}).html(fmt);\n",
- " fmt_picker.append(option)\n",
+ " fmt_picker.append(option);\n",
" }\n",
"\n",
" // Add hover states to the ui-buttons\n",
@@ -5372,7 +5396,7 @@
" var height = Math.abs(y1 - y0);\n",
"\n",
" fig.rubberband_context.clearRect(\n",
- " 0, 0, fig.canvas.width, fig.canvas.height);\n",
+ " 0, 0, fig.canvas.width / mpl.ratio, fig.canvas.height / mpl.ratio);\n",
"\n",
" fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
"}\n",
@@ -5578,7 +5602,7 @@
"mpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) {\n",
" this.message.textContent = tooltip;\n",
"};\n",
- "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
+ "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
"\n",
"mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
"\n",
@@ -5677,7 +5701,7 @@
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
- " var nav_element = $('')\n",
+ " var nav_element = $('');\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
@@ -5804,7 +5828,7 @@
{
"data": {
"text/html": [
- "
"
+ "
"
],
"text/plain": [
""
@@ -5815,18 +5839,16 @@
}
],
"source": [
- "Z1 = fast_tsne(X50, perplexity=50, learning_rate=1000, initialization=PCAinit)\n",
- "\n",
- "Z2 = fast_tsne(X50, perplexity=50, learning_rate=1000, initialization=PCAinit, \n",
- " late_exag_coeff=4, start_late_exag_iter=250)\n",
+ "%time Z1 = fast_tsne(X50, learning_rate=1000, initialization=PCAinit, df=100)\n",
+ "%time Z2 = fast_tsne(X50, learning_rate=1000, initialization=PCAinit, df=.5)\n",
"\n",
"plt.figure(figsize=(8,4))\n",
"plt.subplot(121)\n",
- "plt.scatter(Z1[:,0], Z1[:,1], c=col[y], s=1)\n",
- "plt.title('Early exagg=12 until iter=250')\n",
+ "plt.scatter(Z1[:,0], Z1[:,1], c=col[y], s=.1)\n",
+ "plt.title('1/(1+x^2/100)^100 kernel')\n",
"plt.subplot(122)\n",
- "plt.scatter(Z2[:,0], Z2[:,1], c=col[y], s=1)\n",
- "plt.title('Early exagg=12 until iter=250\\nLate exagg=4 from iter=250')\n",
+ "plt.scatter(Z2[:,0], Z2[:,1], c=col[y], s=.1)\n",
+ "plt.title('1/(1+x^2/0.5)^0.5 kernel')\n",
"plt.tight_layout()"
]
},
@@ -5834,14 +5856,24 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "### Loading and saving input similarities"
+ "## 1-dimensional embedding"
]
},
{
"cell_type": "code",
- "execution_count": 11,
+ "execution_count": 26,
"metadata": {},
"outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "CPU times: user 682 ms, sys: 92 ms, total: 774 ms\n",
+ "Wall time: 1min 6s\n",
+ "CPU times: user 654 ms, sys: 72.6 ms, total: 727 ms\n",
+ "Wall time: 29.6 s\n"
+ ]
+ },
{
"data": {
"application/javascript": [
@@ -5855,7 +5887,7 @@
" } else if (typeof(MozWebSocket) !== 'undefined') {\n",
" return MozWebSocket;\n",
" } else {\n",
- " alert('Your browser does not have WebSocket support.' +\n",
+ " alert('Your browser does not have WebSocket support. ' +\n",
" 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
" 'Firefox 4 and 5 are also supported but you ' +\n",
" 'have to enable WebSockets in about:config.');\n",
@@ -6072,7 +6104,7 @@
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
- " var nav_element = $('')\n",
+ " var nav_element = $('');\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
@@ -6129,7 +6161,7 @@
" var fmt = mpl.extensions[ind];\n",
" var option = $(\n",
" '', {selected: fmt === mpl.default_extension}).html(fmt);\n",
- " fmt_picker.append(option)\n",
+ " fmt_picker.append(option);\n",
" }\n",
"\n",
" // Add hover states to the ui-buttons\n",
@@ -6193,7 +6225,7 @@
" var height = Math.abs(y1 - y0);\n",
"\n",
" fig.rubberband_context.clearRect(\n",
- " 0, 0, fig.canvas.width, fig.canvas.height);\n",
+ " 0, 0, fig.canvas.width / mpl.ratio, fig.canvas.height / mpl.ratio);\n",
"\n",
" fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
"}\n",
@@ -6399,7 +6431,7 @@
"mpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) {\n",
" this.message.textContent = tooltip;\n",
"};\n",
- "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
+ "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
"\n",
"mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
"\n",
@@ -6498,7 +6530,7 @@
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
- " var nav_element = $('')\n",
+ " var nav_element = $('');\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
@@ -6625,7 +6657,7 @@
{
"data": {
"text/html": [
- "
"
+ "
"
],
"text/plain": [
""
@@ -6633,6 +6665,44 @@
},
"metadata": {},
"output_type": "display_data"
+ }
+ ],
+ "source": [
+ "%time Z1 = fast_tsne(X50, initialization=PCAinit, learning_rate=1000)\n",
+ "%time Z2 = fast_tsne(X50, initialization=PCAinit, learning_rate=1000, map_dims=1)\n",
+ "\n",
+ "plt.figure(figsize=(8,4))\n",
+ "plt.subplot(121)\n",
+ "plt.scatter(Z1[:,0], Z1[:,1], c=col[y], s=.1)\n",
+ "plt.title('2D')\n",
+ "plt.subplot(122)\n",
+ "plt.scatter(Z2[:,0], np.random.uniform(size=Z2.shape[0]), c=col[y], s=.1)\n",
+ "plt.ylim([-2,3])\n",
+ "plt.title('1D')\n",
+ "plt.tight_layout()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Fixed sigma instead of perplexity"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 51,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "CPU times: user 701 ms, sys: 80.6 ms, total: 781 ms\n",
+ "Wall time: 1min 22s\n",
+ "CPU times: user 692 ms, sys: 79.1 ms, total: 771 ms\n",
+ "Wall time: 57.6 s\n"
+ ]
},
{
"data": {
@@ -6647,7 +6717,7 @@
" } else if (typeof(MozWebSocket) !== 'undefined') {\n",
" return MozWebSocket;\n",
" } else {\n",
- " alert('Your browser does not have WebSocket support.' +\n",
+ " alert('Your browser does not have WebSocket support. ' +\n",
" 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
" 'Firefox 4 and 5 are also supported but you ' +\n",
" 'have to enable WebSockets in about:config.');\n",
@@ -6864,7 +6934,7 @@
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
- " var nav_element = $('')\n",
+ " var nav_element = $('');\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
@@ -6921,7 +6991,7 @@
" var fmt = mpl.extensions[ind];\n",
" var option = $(\n",
" '', {selected: fmt === mpl.default_extension}).html(fmt);\n",
- " fmt_picker.append(option)\n",
+ " fmt_picker.append(option);\n",
" }\n",
"\n",
" // Add hover states to the ui-buttons\n",
@@ -6985,7 +7055,7 @@
" var height = Math.abs(y1 - y0);\n",
"\n",
" fig.rubberband_context.clearRect(\n",
- " 0, 0, fig.canvas.width, fig.canvas.height);\n",
+ " 0, 0, fig.canvas.width / mpl.ratio, fig.canvas.height / mpl.ratio);\n",
"\n",
" fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
"}\n",
@@ -7191,7 +7261,7 @@
"mpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) {\n",
" this.message.textContent = tooltip;\n",
"};\n",
- "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
+ "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
"\n",
"mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
"\n",
@@ -7290,7 +7360,7 @@
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
- " var nav_element = $('')\n",
+ " var nav_element = $('');\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
@@ -7417,7 +7487,7 @@
{
"data": {
"text/html": [
- "
"
+ "
"
],
"text/plain": [
""
@@ -7428,40 +7498,16 @@
}
],
"source": [
- "Z1 = fast_tsne(X50[ind5k,:], perplexity=50, max_iter=500, early_exag_coeff=4, \n",
- " stop_early_exag_iter=100, initialization=PCAinit[ind5k,:], \n",
- " load_affinities = 'save')\n",
- "\n",
- "Z2 = fast_tsne(X50[ind5k,:], perplexity=50, max_iter=500, early_exag_coeff=4, \n",
- " stop_early_exag_iter=100, initialization=PCAinit[ind5k,:], \n",
- " load_affinities = 'load')\n",
- "\n",
- "plt.figure(figsize=(8,4))\n",
- "plt.subplot(121)\n",
- "plt.scatter(Z1[:,0], Z1[:,1], c=col[y[ind5k]], s=1)\n",
- "plt.title('Similarities saved...')\n",
- "plt.subplot(122)\n",
- "plt.scatter(Z2[:,0], Z2[:,1], c=col[y[ind5k]], s=1)\n",
- "plt.title('... similarities loaded')\n",
- "plt.tight_layout()\n",
- "\n",
- "# And now for the exact t-SNE\n",
- "\n",
- "Z1 = fast_tsne(X50[ind1k,:], perplexity=50, max_iter=500, early_exag_coeff=4, \n",
- " stop_early_exag_iter=100, initialization=PCAinit[ind1k,:], theta=0, \n",
- " load_affinities = 'save')\n",
- "\n",
- "Z2 = fast_tsne(X50[ind1k,:], perplexity=50, max_iter=500, early_exag_coeff=4, \n",
- " stop_early_exag_iter=100, initialization=PCAinit[ind1k,:], theta=0, \n",
- " load_affinities = 'load')\n",
+ "%time Z1 = fast_tsne(X50, initialization=PCAinit, learning_rate=1000)\n",
+ "%time Z2 = fast_tsne(X50, initialization=PCAinit, learning_rate=1000, sigma=10, K=10)\n",
"\n",
"plt.figure(figsize=(8,4))\n",
"plt.subplot(121)\n",
- "plt.scatter(Z1[:,0], Z1[:,1], c=col[y[ind1k]], s=1)\n",
- "plt.title('Exact similarities saved...')\n",
+ "plt.scatter(Z1[:,0], Z1[:,1], c=col[y], s=.1)\n",
+ "plt.title('Perplexity=30 (3*30 neighbors used)')\n",
"plt.subplot(122)\n",
- "plt.scatter(Z2[:,0], Z2[:,1], c=col[y[ind1k]], s=1)\n",
- "plt.title('... exact similarities loaded')\n",
+ "plt.scatter(Z2[:,0], Z2[:,1], c=col[y], s=.1)\n",
+ "plt.title('Fixed sigma=10 with kNN=10')\n",
"plt.tight_layout()"
]
},
@@ -7469,14 +7515,25 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "### Fixed sigma instead of perplexity"
+ "## Perplexity combination\n",
+ "Is not useful for MNIST, but can be useful in other cases, see https://www.nature.com/articles/s41467-019-13056-x."
]
},
{
"cell_type": "code",
- "execution_count": 12,
+ "execution_count": 53,
"metadata": {},
"outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "CPU times: user 686 ms, sys: 96.5 ms, total: 782 ms\n",
+ "Wall time: 1min 14s\n",
+ "CPU times: user 716 ms, sys: 88.3 ms, total: 804 ms\n",
+ "Wall time: 7min 8s\n"
+ ]
+ },
{
"data": {
"application/javascript": [
@@ -7490,7 +7547,7 @@
" } else if (typeof(MozWebSocket) !== 'undefined') {\n",
" return MozWebSocket;\n",
" } else {\n",
- " alert('Your browser does not have WebSocket support.' +\n",
+ " alert('Your browser does not have WebSocket support. ' +\n",
" 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
" 'Firefox 4 and 5 are also supported but you ' +\n",
" 'have to enable WebSockets in about:config.');\n",
@@ -7707,7 +7764,7 @@
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
- " var nav_element = $('')\n",
+ " var nav_element = $('');\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
@@ -7764,7 +7821,7 @@
" var fmt = mpl.extensions[ind];\n",
" var option = $(\n",
" '', {selected: fmt === mpl.default_extension}).html(fmt);\n",
- " fmt_picker.append(option)\n",
+ " fmt_picker.append(option);\n",
" }\n",
"\n",
" // Add hover states to the ui-buttons\n",
@@ -7828,7 +7885,7 @@
" var height = Math.abs(y1 - y0);\n",
"\n",
" fig.rubberband_context.clearRect(\n",
- " 0, 0, fig.canvas.width, fig.canvas.height);\n",
+ " 0, 0, fig.canvas.width / mpl.ratio, fig.canvas.height / mpl.ratio);\n",
"\n",
" fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
"}\n",
@@ -8034,7 +8091,7 @@
"mpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) {\n",
" this.message.textContent = tooltip;\n",
"};\n",
- "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
+ "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
"\n",
"mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
"\n",
@@ -8133,7 +8190,7 @@
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
- " var nav_element = $('')\n",
+ " var nav_element = $('');\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
@@ -8260,7 +8317,7 @@
{
"data": {
"text/html": [
- "
"
+ "
"
],
"text/plain": [
""
@@ -8271,19 +8328,16 @@
}
],
"source": [
- "Z1 = fast_tsne(X50[ind5k,:], perplexity=50, max_iter=500, early_exag_coeff=4, \n",
- " stop_early_exag_iter=100, initialization=PCAinit[ind5k,:])\n",
- "\n",
- "Z2 = fast_tsne(X50[ind5k,:], sigma=10, K=10, max_iter=500, early_exag_coeff=4, \n",
- " stop_early_exag_iter=100, initialization=PCAinit[ind5k,:])\n",
+ "%time Z1 = fast_tsne(X50, initialization=PCAinit, learning_rate=1000)\n",
+ "%time Z2 = fast_tsne(X50, perplexity_list=[3,30,300], initialization=PCAinit, learning_rate=1000)\n",
"\n",
"plt.figure(figsize=(8,4))\n",
"plt.subplot(121)\n",
- "plt.scatter(Z1[:,0], Z1[:,1], c=col[y[ind5k]], s=1)\n",
- "plt.title('Perplexity=50 (3*50 neighbors used)')\n",
+ "plt.scatter(Z1[:,0], Z1[:,1], c=col[y], s=.1)\n",
+ "plt.title('Perplexity = 30')\n",
"plt.subplot(122)\n",
- "plt.scatter(Z2[:,0], Z2[:,1], c=col[y[ind5k]], s=1)\n",
- "plt.title('Fixed sigma=10 with knn=10')\n",
+ "plt.scatter(Z2[:,0], Z2[:,1], c=col[y], s=.1)\n",
+ "plt.title('Perplexity list = [3, 30, 300]')\n",
"plt.tight_layout()"
]
},
@@ -8291,14 +8345,24 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "### Perplexity combination"
+ "## VP tree vs ANNOY for kNN search"
]
},
{
"cell_type": "code",
- "execution_count": 13,
+ "execution_count": 37,
"metadata": {},
"outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "CPU times: user 701 ms, sys: 80.3 ms, total: 781 ms\n",
+ "Wall time: 1min 16s\n",
+ "CPU times: user 725 ms, sys: 52.9 ms, total: 778 ms\n",
+ "Wall time: 2min 29s\n"
+ ]
+ },
{
"data": {
"application/javascript": [
@@ -8312,7 +8376,7 @@
" } else if (typeof(MozWebSocket) !== 'undefined') {\n",
" return MozWebSocket;\n",
" } else {\n",
- " alert('Your browser does not have WebSocket support.' +\n",
+ " alert('Your browser does not have WebSocket support. ' +\n",
" 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
" 'Firefox 4 and 5 are also supported but you ' +\n",
" 'have to enable WebSockets in about:config.');\n",
@@ -8529,7 +8593,7 @@
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
- " var nav_element = $('')\n",
+ " var nav_element = $('');\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
@@ -8586,7 +8650,7 @@
" var fmt = mpl.extensions[ind];\n",
" var option = $(\n",
" '', {selected: fmt === mpl.default_extension}).html(fmt);\n",
- " fmt_picker.append(option)\n",
+ " fmt_picker.append(option);\n",
" }\n",
"\n",
" // Add hover states to the ui-buttons\n",
@@ -8650,7 +8714,7 @@
" var height = Math.abs(y1 - y0);\n",
"\n",
" fig.rubberband_context.clearRect(\n",
- " 0, 0, fig.canvas.width, fig.canvas.height);\n",
+ " 0, 0, fig.canvas.width / mpl.ratio, fig.canvas.height / mpl.ratio);\n",
"\n",
" fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
"}\n",
@@ -8856,7 +8920,7 @@
"mpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) {\n",
" this.message.textContent = tooltip;\n",
"};\n",
- "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
+ "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
"\n",
"mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
"\n",
@@ -8955,7 +9019,7 @@
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
- " var nav_element = $('')\n",
+ " var nav_element = $('');\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
@@ -9082,7 +9146,7 @@
{
"data": {
"text/html": [
- "
"
+ "
"
],
"text/plain": [
""
@@ -9093,19 +9157,16 @@
}
],
"source": [
- "Z1 = fast_tsne(X50[ind5k,:], perplexity=50, max_iter=500, early_exag_coeff=4, \n",
- " stop_early_exag_iter=100, initialization=PCAinit[ind5k,:])\n",
- "\n",
- "Z2 = fast_tsne(X50[ind5k,:], perplexity_list=[10,50,100], max_iter=500, early_exag_coeff=4, \n",
- " stop_early_exag_iter=100, initialization=PCAinit[ind5k,:])\n",
+ "%time Z1 = fast_tsne(X50, initialization=PCAinit, learning_rate=1000)\n",
+ "%time Z2 = fast_tsne(X50, initialization=PCAinit, learning_rate=1000, knn_algo='vp-tree')\n",
"\n",
"plt.figure(figsize=(8,4))\n",
"plt.subplot(121)\n",
- "plt.scatter(Z1[:,0], Z1[:,1], c=col[y[ind5k]], s=1)\n",
- "plt.title('Perplexity = 50')\n",
+ "plt.scatter(Z1[:,0], Z1[:,1], c=col[y], s=.1)\n",
+ "plt.title('Annoy (approximate NN)')\n",
"plt.subplot(122)\n",
- "plt.scatter(Z2[:,0], Z2[:,1], c=col[y[ind5k]], s=1)\n",
- "plt.title('Perplexity = [10, 50, 100]')\n",
+ "plt.scatter(Z2[:,0], Z2[:,1], c=col[y], s=.1)\n",
+ "plt.title('VP tree (exact NN)')\n",
"plt.tight_layout()"
]
},
@@ -9113,14 +9174,37 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "### Making the kernel more heavy-tailed"
+ "## Barnes-Hut vs FFT to approximate repulsive forces during gradient descent \n",
+ "Using a subsampled dataset here, to speed up Barnes-Hut."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 35,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Subsampling \n",
+ "\n",
+ "np.random.seed(42)\n",
+ "ind10k = np.random.choice(X.shape[0], 10000, replace=False)"
]
},
{
"cell_type": "code",
- "execution_count": 14,
+ "execution_count": 36,
"metadata": {},
"outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "CPU times: user 608 ms, sys: 52 ms, total: 660 ms\n",
+ "Wall time: 28.3 s\n",
+ "CPU times: user 607 ms, sys: 52.8 ms, total: 660 ms\n",
+ "Wall time: 2min 37s\n"
+ ]
+ },
{
"data": {
"application/javascript": [
@@ -9134,7 +9218,7 @@
" } else if (typeof(MozWebSocket) !== 'undefined') {\n",
" return MozWebSocket;\n",
" } else {\n",
- " alert('Your browser does not have WebSocket support.' +\n",
+ " alert('Your browser does not have WebSocket support. ' +\n",
" 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
" 'Firefox 4 and 5 are also supported but you ' +\n",
" 'have to enable WebSockets in about:config.');\n",
@@ -9351,7 +9435,7 @@
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
- " var nav_element = $('')\n",
+ " var nav_element = $('');\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
@@ -9408,7 +9492,7 @@
" var fmt = mpl.extensions[ind];\n",
" var option = $(\n",
" '', {selected: fmt === mpl.default_extension}).html(fmt);\n",
- " fmt_picker.append(option)\n",
+ " fmt_picker.append(option);\n",
" }\n",
"\n",
" // Add hover states to the ui-buttons\n",
@@ -9472,7 +9556,7 @@
" var height = Math.abs(y1 - y0);\n",
"\n",
" fig.rubberband_context.clearRect(\n",
- " 0, 0, fig.canvas.width, fig.canvas.height);\n",
+ " 0, 0, fig.canvas.width / mpl.ratio, fig.canvas.height / mpl.ratio);\n",
"\n",
" fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
"}\n",
@@ -9678,7 +9762,7 @@
"mpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) {\n",
" this.message.textContent = tooltip;\n",
"};\n",
- "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
+ "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
"\n",
"mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
"\n",
@@ -9777,7 +9861,7 @@
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
- " var nav_element = $('')\n",
+ " var nav_element = $('');\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
@@ -9904,7 +9988,7 @@
{
"data": {
"text/html": [
- "
"
+ "
"
],
"text/plain": [
""
@@ -9915,26 +9999,2512 @@
}
],
"source": [
- "Z1 = fast_tsne(X50, perplexity=50, learning_rate=1000, initialization=PCAinit)\n",
- "\n",
- "Z2 = fast_tsne(X50, perplexity=50, learning_rate=1000, initialization=PCAinit, df=.5)\n",
+ "%time Z1 = fast_tsne(X50[ind10k,:], initialization=PCAinit[ind10k,:])\n",
+ "%time Z2 = fast_tsne(X50[ind10k,:], initialization=PCAinit[ind10k,:], nbody_algo='Barnes-Hut')\n",
"\n",
"plt.figure(figsize=(8,4))\n",
"plt.subplot(121)\n",
- "plt.scatter(Z1[:,0], Z1[:,1], c=col[y], s=1)\n",
- "plt.title('1/(1+x^2) kernel')\n",
+ "plt.scatter(Z1[:,0], Z1[:,1], c=col[y[ind10k]], s=.1)\n",
+ "plt.title('FFT')\n",
"plt.subplot(122)\n",
- "plt.scatter(Z2[:,0], Z2[:,1], c=col[y], s=1)\n",
- "plt.title('1/(1+x^2/0.5)^0.5 kernel')\n",
+ "plt.scatter(Z2[:,0], Z2[:,1], c=col[y[ind10k]], s=.1)\n",
+ "plt.title('Barnes-Hut')\n",
"plt.tight_layout()"
]
},
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Exact t-SNE"
+ ]
+ },
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 44,
"metadata": {},
"outputs": [],
- "source": []
+ "source": [
+ "# Subsampling \n",
+ "\n",
+ "np.random.seed(42)\n",
+ "ind2k = np.random.choice(X.shape[0], 2000, replace=False)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 47,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "CPU times: user 6.21 ms, sys: 48.5 ms, total: 54.7 ms\n",
+ "Wall time: 23.4 s\n",
+ "CPU times: user 9.03 ms, sys: 24.2 ms, total: 33.3 ms\n",
+ "Wall time: 2min 30s\n"
+ ]
+ },
+ {
+ "data": {
+ "application/javascript": [
+ "/* Put everything inside the global mpl namespace */\n",
+ "window.mpl = {};\n",
+ "\n",
+ "\n",
+ "mpl.get_websocket_type = function() {\n",
+ " if (typeof(WebSocket) !== 'undefined') {\n",
+ " return WebSocket;\n",
+ " } else if (typeof(MozWebSocket) !== 'undefined') {\n",
+ " return MozWebSocket;\n",
+ " } else {\n",
+ " alert('Your browser does not have WebSocket support. ' +\n",
+ " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
+ " 'Firefox 4 and 5 are also supported but you ' +\n",
+ " 'have to enable WebSockets in about:config.');\n",
+ " };\n",
+ "}\n",
+ "\n",
+ "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n",
+ " this.id = figure_id;\n",
+ "\n",
+ " this.ws = websocket;\n",
+ "\n",
+ " this.supports_binary = (this.ws.binaryType != undefined);\n",
+ "\n",
+ " if (!this.supports_binary) {\n",
+ " var warnings = document.getElementById(\"mpl-warnings\");\n",
+ " if (warnings) {\n",
+ " warnings.style.display = 'block';\n",
+ " warnings.textContent = (\n",
+ " \"This browser does not support binary websocket messages. \" +\n",
+ " \"Performance may be slow.\");\n",
+ " }\n",
+ " }\n",
+ "\n",
+ " this.imageObj = new Image();\n",
+ "\n",
+ " this.context = undefined;\n",
+ " this.message = undefined;\n",
+ " this.canvas = undefined;\n",
+ " this.rubberband_canvas = undefined;\n",
+ " this.rubberband_context = undefined;\n",
+ " this.format_dropdown = undefined;\n",
+ "\n",
+ " this.image_mode = 'full';\n",
+ "\n",
+ " this.root = $('');\n",
+ " this._root_extra_style(this.root)\n",
+ " this.root.attr('style', 'display: inline-block');\n",
+ "\n",
+ " $(parent_element).append(this.root);\n",
+ "\n",
+ " this._init_header(this);\n",
+ " this._init_canvas(this);\n",
+ " this._init_toolbar(this);\n",
+ "\n",
+ " var fig = this;\n",
+ "\n",
+ " this.waiting = false;\n",
+ "\n",
+ " this.ws.onopen = function () {\n",
+ " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n",
+ " fig.send_message(\"send_image_mode\", {});\n",
+ " if (mpl.ratio != 1) {\n",
+ " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n",
+ " }\n",
+ " fig.send_message(\"refresh\", {});\n",
+ " }\n",
+ "\n",
+ " this.imageObj.onload = function() {\n",
+ " if (fig.image_mode == 'full') {\n",
+ " // Full images could contain transparency (where diff images\n",
+ " // almost always do), so we need to clear the canvas so that\n",
+ " // there is no ghosting.\n",
+ " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
+ " }\n",
+ " fig.context.drawImage(fig.imageObj, 0, 0);\n",
+ " };\n",
+ "\n",
+ " this.imageObj.onunload = function() {\n",
+ " fig.ws.close();\n",
+ " }\n",
+ "\n",
+ " this.ws.onmessage = this._make_on_message_function(this);\n",
+ "\n",
+ " this.ondownload = ondownload;\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype._init_header = function() {\n",
+ " var titlebar = $(\n",
+ " '');\n",
+ " var titletext = $(\n",
+ " '');\n",
+ " titlebar.append(titletext)\n",
+ " this.root.append(titlebar);\n",
+ " this.header = titletext[0];\n",
+ "}\n",
+ "\n",
+ "\n",
+ "\n",
+ "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n",
+ "\n",
+ "}\n",
+ "\n",
+ "\n",
+ "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n",
+ "\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype._init_canvas = function() {\n",
+ " var fig = this;\n",
+ "\n",
+ " var canvas_div = $('');\n",
+ "\n",
+ " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n",
+ "\n",
+ " function canvas_keyboard_event(event) {\n",
+ " return fig.key_event(event, event['data']);\n",
+ " }\n",
+ "\n",
+ " canvas_div.keydown('key_press', canvas_keyboard_event);\n",
+ " canvas_div.keyup('key_release', canvas_keyboard_event);\n",
+ " this.canvas_div = canvas_div\n",
+ " this._canvas_extra_style(canvas_div)\n",
+ " this.root.append(canvas_div);\n",
+ "\n",
+ " var canvas = $('');\n",
+ " canvas.addClass('mpl-canvas');\n",
+ " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n",
+ "\n",
+ " this.canvas = canvas[0];\n",
+ " this.context = canvas[0].getContext(\"2d\");\n",
+ "\n",
+ " var backingStore = this.context.backingStorePixelRatio ||\n",
+ "\tthis.context.webkitBackingStorePixelRatio ||\n",
+ "\tthis.context.mozBackingStorePixelRatio ||\n",
+ "\tthis.context.msBackingStorePixelRatio ||\n",
+ "\tthis.context.oBackingStorePixelRatio ||\n",
+ "\tthis.context.backingStorePixelRatio || 1;\n",
+ "\n",
+ " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
+ "\n",
+ " var rubberband = $('');\n",
+ " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n",
+ "\n",
+ " var pass_mouse_events = true;\n",
+ "\n",
+ " canvas_div.resizable({\n",
+ " start: function(event, ui) {\n",
+ " pass_mouse_events = false;\n",
+ " },\n",
+ " resize: function(event, ui) {\n",
+ " fig.request_resize(ui.size.width, ui.size.height);\n",
+ " },\n",
+ " stop: function(event, ui) {\n",
+ " pass_mouse_events = true;\n",
+ " fig.request_resize(ui.size.width, ui.size.height);\n",
+ " },\n",
+ " });\n",
+ "\n",
+ " function mouse_event_fn(event) {\n",
+ " if (pass_mouse_events)\n",
+ " return fig.mouse_event(event, event['data']);\n",
+ " }\n",
+ "\n",
+ " rubberband.mousedown('button_press', mouse_event_fn);\n",
+ " rubberband.mouseup('button_release', mouse_event_fn);\n",
+ " // Throttle sequential mouse events to 1 every 20ms.\n",
+ " rubberband.mousemove('motion_notify', mouse_event_fn);\n",
+ "\n",
+ " rubberband.mouseenter('figure_enter', mouse_event_fn);\n",
+ " rubberband.mouseleave('figure_leave', mouse_event_fn);\n",
+ "\n",
+ " canvas_div.on(\"wheel\", function (event) {\n",
+ " event = event.originalEvent;\n",
+ " event['data'] = 'scroll'\n",
+ " if (event.deltaY < 0) {\n",
+ " event.step = 1;\n",
+ " } else {\n",
+ " event.step = -1;\n",
+ " }\n",
+ " mouse_event_fn(event);\n",
+ " });\n",
+ "\n",
+ " canvas_div.append(canvas);\n",
+ " canvas_div.append(rubberband);\n",
+ "\n",
+ " this.rubberband = rubberband;\n",
+ " this.rubberband_canvas = rubberband[0];\n",
+ " this.rubberband_context = rubberband[0].getContext(\"2d\");\n",
+ " this.rubberband_context.strokeStyle = \"#000000\";\n",
+ "\n",
+ " this._resize_canvas = function(width, height) {\n",
+ " // Keep the size of the canvas, canvas container, and rubber band\n",
+ " // canvas in synch.\n",
+ " canvas_div.css('width', width)\n",
+ " canvas_div.css('height', height)\n",
+ "\n",
+ " canvas.attr('width', width * mpl.ratio);\n",
+ " canvas.attr('height', height * mpl.ratio);\n",
+ " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n",
+ "\n",
+ " rubberband.attr('width', width);\n",
+ " rubberband.attr('height', height);\n",
+ " }\n",
+ "\n",
+ " // Set the figure to an initial 600x600px, this will subsequently be updated\n",
+ " // upon first draw.\n",
+ " this._resize_canvas(600, 600);\n",
+ "\n",
+ " // Disable right mouse context menu.\n",
+ " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n",
+ " return false;\n",
+ " });\n",
+ "\n",
+ " function set_focus () {\n",
+ " canvas.focus();\n",
+ " canvas_div.focus();\n",
+ " }\n",
+ "\n",
+ " window.setTimeout(set_focus, 100);\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype._init_toolbar = function() {\n",
+ " var fig = this;\n",
+ "\n",
+ " var nav_element = $('');\n",
+ " nav_element.attr('style', 'width: 100%');\n",
+ " this.root.append(nav_element);\n",
+ "\n",
+ " // Define a callback function for later on.\n",
+ " function toolbar_event(event) {\n",
+ " return fig.toolbar_button_onclick(event['data']);\n",
+ " }\n",
+ " function toolbar_mouse_event(event) {\n",
+ " return fig.toolbar_button_onmouseover(event['data']);\n",
+ " }\n",
+ "\n",
+ " for(var toolbar_ind in mpl.toolbar_items) {\n",
+ " var name = mpl.toolbar_items[toolbar_ind][0];\n",
+ " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+ " var image = mpl.toolbar_items[toolbar_ind][2];\n",
+ " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+ "\n",
+ " if (!name) {\n",
+ " // put a spacer in here.\n",
+ " continue;\n",
+ " }\n",
+ " var button = $('');\n",
+ " button.addClass('ui-button ui-widget ui-state-default ui-corner-all ' +\n",
+ " 'ui-button-icon-only');\n",
+ " button.attr('role', 'button');\n",
+ " button.attr('aria-disabled', 'false');\n",
+ " button.click(method_name, toolbar_event);\n",
+ " button.mouseover(tooltip, toolbar_mouse_event);\n",
+ "\n",
+ " var icon_img = $('');\n",
+ " icon_img.addClass('ui-button-icon-primary ui-icon');\n",
+ " icon_img.addClass(image);\n",
+ " icon_img.addClass('ui-corner-all');\n",
+ "\n",
+ " var tooltip_span = $('');\n",
+ " tooltip_span.addClass('ui-button-text');\n",
+ " tooltip_span.html(tooltip);\n",
+ "\n",
+ " button.append(icon_img);\n",
+ " button.append(tooltip_span);\n",
+ "\n",
+ " nav_element.append(button);\n",
+ " }\n",
+ "\n",
+ " var fmt_picker_span = $('');\n",
+ "\n",
+ " var fmt_picker = $('');\n",
+ " fmt_picker.addClass('mpl-toolbar-option ui-widget ui-widget-content');\n",
+ " fmt_picker_span.append(fmt_picker);\n",
+ " nav_element.append(fmt_picker_span);\n",
+ " this.format_dropdown = fmt_picker[0];\n",
+ "\n",
+ " for (var ind in mpl.extensions) {\n",
+ " var fmt = mpl.extensions[ind];\n",
+ " var option = $(\n",
+ " '', {selected: fmt === mpl.default_extension}).html(fmt);\n",
+ " fmt_picker.append(option);\n",
+ " }\n",
+ "\n",
+ " // Add hover states to the ui-buttons\n",
+ " $( \".ui-button\" ).hover(\n",
+ " function() { $(this).addClass(\"ui-state-hover\");},\n",
+ " function() { $(this).removeClass(\"ui-state-hover\");}\n",
+ " );\n",
+ "\n",
+ " var status_bar = $('');\n",
+ " nav_element.append(status_bar);\n",
+ " this.message = status_bar[0];\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.request_resize = function(x_pixels, y_pixels) {\n",
+ " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
+ " // which will in turn request a refresh of the image.\n",
+ " this.send_message('resize', {'width': x_pixels, 'height': y_pixels});\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.send_message = function(type, properties) {\n",
+ " properties['type'] = type;\n",
+ " properties['figure_id'] = this.id;\n",
+ " this.ws.send(JSON.stringify(properties));\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.send_draw_message = function() {\n",
+ " if (!this.waiting) {\n",
+ " this.waiting = true;\n",
+ " this.ws.send(JSON.stringify({type: \"draw\", figure_id: this.id}));\n",
+ " }\n",
+ "}\n",
+ "\n",
+ "\n",
+ "mpl.figure.prototype.handle_save = function(fig, msg) {\n",
+ " var format_dropdown = fig.format_dropdown;\n",
+ " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
+ " fig.ondownload(fig, format);\n",
+ "}\n",
+ "\n",
+ "\n",
+ "mpl.figure.prototype.handle_resize = function(fig, msg) {\n",
+ " var size = msg['size'];\n",
+ " if (size[0] != fig.canvas.width || size[1] != fig.canvas.height) {\n",
+ " fig._resize_canvas(size[0], size[1]);\n",
+ " fig.send_message(\"refresh\", {});\n",
+ " };\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.handle_rubberband = function(fig, msg) {\n",
+ " var x0 = msg['x0'] / mpl.ratio;\n",
+ " var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n",
+ " var x1 = msg['x1'] / mpl.ratio;\n",
+ " var y1 = (fig.canvas.height - msg['y1']) / mpl.ratio;\n",
+ " x0 = Math.floor(x0) + 0.5;\n",
+ " y0 = Math.floor(y0) + 0.5;\n",
+ " x1 = Math.floor(x1) + 0.5;\n",
+ " y1 = Math.floor(y1) + 0.5;\n",
+ " var min_x = Math.min(x0, x1);\n",
+ " var min_y = Math.min(y0, y1);\n",
+ " var width = Math.abs(x1 - x0);\n",
+ " var height = Math.abs(y1 - y0);\n",
+ "\n",
+ " fig.rubberband_context.clearRect(\n",
+ " 0, 0, fig.canvas.width / mpl.ratio, fig.canvas.height / mpl.ratio);\n",
+ "\n",
+ " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.handle_figure_label = function(fig, msg) {\n",
+ " // Updates the figure title.\n",
+ " fig.header.textContent = msg['label'];\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.handle_cursor = function(fig, msg) {\n",
+ " var cursor = msg['cursor'];\n",
+ " switch(cursor)\n",
+ " {\n",
+ " case 0:\n",
+ " cursor = 'pointer';\n",
+ " break;\n",
+ " case 1:\n",
+ " cursor = 'default';\n",
+ " break;\n",
+ " case 2:\n",
+ " cursor = 'crosshair';\n",
+ " break;\n",
+ " case 3:\n",
+ " cursor = 'move';\n",
+ " break;\n",
+ " }\n",
+ " fig.rubberband_canvas.style.cursor = cursor;\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.handle_message = function(fig, msg) {\n",
+ " fig.message.textContent = msg['message'];\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.handle_draw = function(fig, msg) {\n",
+ " // Request the server to send over a new figure.\n",
+ " fig.send_draw_message();\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.handle_image_mode = function(fig, msg) {\n",
+ " fig.image_mode = msg['mode'];\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.updated_canvas_event = function() {\n",
+ " // Called whenever the canvas gets updated.\n",
+ " this.send_message(\"ack\", {});\n",
+ "}\n",
+ "\n",
+ "// A function to construct a web socket function for onmessage handling.\n",
+ "// Called in the figure constructor.\n",
+ "mpl.figure.prototype._make_on_message_function = function(fig) {\n",
+ " return function socket_on_message(evt) {\n",
+ " if (evt.data instanceof Blob) {\n",
+ " /* FIXME: We get \"Resource interpreted as Image but\n",
+ " * transferred with MIME type text/plain:\" errors on\n",
+ " * Chrome. But how to set the MIME type? It doesn't seem\n",
+ " * to be part of the websocket stream */\n",
+ " evt.data.type = \"image/png\";\n",
+ "\n",
+ " /* Free the memory for the previous frames */\n",
+ " if (fig.imageObj.src) {\n",
+ " (window.URL || window.webkitURL).revokeObjectURL(\n",
+ " fig.imageObj.src);\n",
+ " }\n",
+ "\n",
+ " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
+ " evt.data);\n",
+ " fig.updated_canvas_event();\n",
+ " fig.waiting = false;\n",
+ " return;\n",
+ " }\n",
+ " else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n",
+ " fig.imageObj.src = evt.data;\n",
+ " fig.updated_canvas_event();\n",
+ " fig.waiting = false;\n",
+ " return;\n",
+ " }\n",
+ "\n",
+ " var msg = JSON.parse(evt.data);\n",
+ " var msg_type = msg['type'];\n",
+ "\n",
+ " // Call the \"handle_{type}\" callback, which takes\n",
+ " // the figure and JSON message as its only arguments.\n",
+ " try {\n",
+ " var callback = fig[\"handle_\" + msg_type];\n",
+ " } catch (e) {\n",
+ " console.log(\"No handler for the '\" + msg_type + \"' message type: \", msg);\n",
+ " return;\n",
+ " }\n",
+ "\n",
+ " if (callback) {\n",
+ " try {\n",
+ " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
+ " callback(fig, msg);\n",
+ " } catch (e) {\n",
+ " console.log(\"Exception inside the 'handler_\" + msg_type + \"' callback:\", e, e.stack, msg);\n",
+ " }\n",
+ " }\n",
+ " };\n",
+ "}\n",
+ "\n",
+ "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
+ "mpl.findpos = function(e) {\n",
+ " //this section is from http://www.quirksmode.org/js/events_properties.html\n",
+ " var targ;\n",
+ " if (!e)\n",
+ " e = window.event;\n",
+ " if (e.target)\n",
+ " targ = e.target;\n",
+ " else if (e.srcElement)\n",
+ " targ = e.srcElement;\n",
+ " if (targ.nodeType == 3) // defeat Safari bug\n",
+ " targ = targ.parentNode;\n",
+ "\n",
+ " // jQuery normalizes the pageX and pageY\n",
+ " // pageX,Y are the mouse positions relative to the document\n",
+ " // offset() returns the position of the element relative to the document\n",
+ " var x = e.pageX - $(targ).offset().left;\n",
+ " var y = e.pageY - $(targ).offset().top;\n",
+ "\n",
+ " return {\"x\": x, \"y\": y};\n",
+ "};\n",
+ "\n",
+ "/*\n",
+ " * return a copy of an object with only non-object keys\n",
+ " * we need this to avoid circular references\n",
+ " * http://stackoverflow.com/a/24161582/3208463\n",
+ " */\n",
+ "function simpleKeys (original) {\n",
+ " return Object.keys(original).reduce(function (obj, key) {\n",
+ " if (typeof original[key] !== 'object')\n",
+ " obj[key] = original[key]\n",
+ " return obj;\n",
+ " }, {});\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.mouse_event = function(event, name) {\n",
+ " var canvas_pos = mpl.findpos(event)\n",
+ "\n",
+ " if (name === 'button_press')\n",
+ " {\n",
+ " this.canvas.focus();\n",
+ " this.canvas_div.focus();\n",
+ " }\n",
+ "\n",
+ " var x = canvas_pos.x * mpl.ratio;\n",
+ " var y = canvas_pos.y * mpl.ratio;\n",
+ "\n",
+ " this.send_message(name, {x: x, y: y, button: event.button,\n",
+ " step: event.step,\n",
+ " guiEvent: simpleKeys(event)});\n",
+ "\n",
+ " /* This prevents the web browser from automatically changing to\n",
+ " * the text insertion cursor when the button is pressed. We want\n",
+ " * to control all of the cursor setting manually through the\n",
+ " * 'cursor' event from matplotlib */\n",
+ " event.preventDefault();\n",
+ " return false;\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype._key_event_extra = function(event, name) {\n",
+ " // Handle any extra behaviour associated with a key event\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.key_event = function(event, name) {\n",
+ "\n",
+ " // Prevent repeat events\n",
+ " if (name == 'key_press')\n",
+ " {\n",
+ " if (event.which === this._key)\n",
+ " return;\n",
+ " else\n",
+ " this._key = event.which;\n",
+ " }\n",
+ " if (name == 'key_release')\n",
+ " this._key = null;\n",
+ "\n",
+ " var value = '';\n",
+ " if (event.ctrlKey && event.which != 17)\n",
+ " value += \"ctrl+\";\n",
+ " if (event.altKey && event.which != 18)\n",
+ " value += \"alt+\";\n",
+ " if (event.shiftKey && event.which != 16)\n",
+ " value += \"shift+\";\n",
+ "\n",
+ " value += 'k';\n",
+ " value += event.which.toString();\n",
+ "\n",
+ " this._key_event_extra(event, name);\n",
+ "\n",
+ " this.send_message(name, {key: value,\n",
+ " guiEvent: simpleKeys(event)});\n",
+ " return false;\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.toolbar_button_onclick = function(name) {\n",
+ " if (name == 'download') {\n",
+ " this.handle_save(this, null);\n",
+ " } else {\n",
+ " this.send_message(\"toolbar_button\", {name: name});\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) {\n",
+ " this.message.textContent = tooltip;\n",
+ "};\n",
+ "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
+ "\n",
+ "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
+ "\n",
+ "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n",
+ " // Create a \"websocket\"-like object which calls the given IPython comm\n",
+ " // object with the appropriate methods. Currently this is a non binary\n",
+ " // socket, so there is still some room for performance tuning.\n",
+ " var ws = {};\n",
+ "\n",
+ " ws.close = function() {\n",
+ " comm.close()\n",
+ " };\n",
+ " ws.send = function(m) {\n",
+ " //console.log('sending', m);\n",
+ " comm.send(m);\n",
+ " };\n",
+ " // Register the callback with on_msg.\n",
+ " comm.on_msg(function(msg) {\n",
+ " //console.log('receiving', msg['content']['data'], msg);\n",
+ " // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
+ " ws.onmessage(msg['content']['data'])\n",
+ " });\n",
+ " return ws;\n",
+ "}\n",
+ "\n",
+ "mpl.mpl_figure_comm = function(comm, msg) {\n",
+ " // This is the function which gets called when the mpl process\n",
+ " // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
+ "\n",
+ " var id = msg.content.data.id;\n",
+ " // Get hold of the div created by the display call when the Comm\n",
+ " // socket was opened in Python.\n",
+ " var element = $(\"#\" + id);\n",
+ " var ws_proxy = comm_websocket_adapter(comm)\n",
+ "\n",
+ " function ondownload(figure, format) {\n",
+ " window.open(figure.imageObj.src);\n",
+ " }\n",
+ "\n",
+ " var fig = new mpl.figure(id, ws_proxy,\n",
+ " ondownload,\n",
+ " element.get(0));\n",
+ "\n",
+ " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
+ " // web socket which is closed, not our websocket->open comm proxy.\n",
+ " ws_proxy.onopen();\n",
+ "\n",
+ " fig.parent_element = element.get(0);\n",
+ " fig.cell_info = mpl.find_output_cell(\"\");\n",
+ " if (!fig.cell_info) {\n",
+ " console.error(\"Failed to find cell for figure\", id, fig);\n",
+ " return;\n",
+ " }\n",
+ "\n",
+ " var output_index = fig.cell_info[2]\n",
+ " var cell = fig.cell_info[0];\n",
+ "\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_close = function(fig, msg) {\n",
+ " var width = fig.canvas.width/mpl.ratio\n",
+ " fig.root.unbind('remove')\n",
+ "\n",
+ " // Update the output cell to use the data from the current canvas.\n",
+ " fig.push_to_output();\n",
+ " var dataURL = fig.canvas.toDataURL();\n",
+ " // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
+ " // the notebook keyboard shortcuts fail.\n",
+ " IPython.keyboard_manager.enable()\n",
+ " $(fig.parent_element).html('
');\n",
+ " fig.close_ws(fig, msg);\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.close_ws = function(fig, msg){\n",
+ " fig.send_message('closing', msg);\n",
+ " // fig.ws.close()\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.push_to_output = function(remove_interactive) {\n",
+ " // Turn the data on the canvas into data in the output cell.\n",
+ " var width = this.canvas.width/mpl.ratio\n",
+ " var dataURL = this.canvas.toDataURL();\n",
+ " this.cell_info[1]['text/html'] = '
';\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.updated_canvas_event = function() {\n",
+ " // Tell IPython that the notebook contents must change.\n",
+ " IPython.notebook.set_dirty(true);\n",
+ " this.send_message(\"ack\", {});\n",
+ " var fig = this;\n",
+ " // Wait a second, then push the new image to the DOM so\n",
+ " // that it is saved nicely (might be nice to debounce this).\n",
+ " setTimeout(function () { fig.push_to_output() }, 1000);\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype._init_toolbar = function() {\n",
+ " var fig = this;\n",
+ "\n",
+ " var nav_element = $('');\n",
+ " nav_element.attr('style', 'width: 100%');\n",
+ " this.root.append(nav_element);\n",
+ "\n",
+ " // Define a callback function for later on.\n",
+ " function toolbar_event(event) {\n",
+ " return fig.toolbar_button_onclick(event['data']);\n",
+ " }\n",
+ " function toolbar_mouse_event(event) {\n",
+ " return fig.toolbar_button_onmouseover(event['data']);\n",
+ " }\n",
+ "\n",
+ " for(var toolbar_ind in mpl.toolbar_items){\n",
+ " var name = mpl.toolbar_items[toolbar_ind][0];\n",
+ " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+ " var image = mpl.toolbar_items[toolbar_ind][2];\n",
+ " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+ "\n",
+ " if (!name) { continue; };\n",
+ "\n",
+ " var button = $('');\n",
+ " button.click(method_name, toolbar_event);\n",
+ " button.mouseover(tooltip, toolbar_mouse_event);\n",
+ " nav_element.append(button);\n",
+ " }\n",
+ "\n",
+ " // Add the status bar.\n",
+ " var status_bar = $('');\n",
+ " nav_element.append(status_bar);\n",
+ " this.message = status_bar[0];\n",
+ "\n",
+ " // Add the close button to the window.\n",
+ " var buttongrp = $('');\n",
+ " var button = $('');\n",
+ " button.click(function (evt) { fig.handle_close(fig, {}); } );\n",
+ " button.mouseover('Stop Interaction', toolbar_mouse_event);\n",
+ " buttongrp.append(button);\n",
+ " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n",
+ " titlebar.prepend(buttongrp);\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype._root_extra_style = function(el){\n",
+ " var fig = this\n",
+ " el.on(\"remove\", function(){\n",
+ "\tfig.close_ws(fig, {});\n",
+ " });\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype._canvas_extra_style = function(el){\n",
+ " // this is important to make the div 'focusable\n",
+ " el.attr('tabindex', 0)\n",
+ " // reach out to IPython and tell the keyboard manager to turn it's self\n",
+ " // off when our div gets focus\n",
+ "\n",
+ " // location in version 3\n",
+ " if (IPython.notebook.keyboard_manager) {\n",
+ " IPython.notebook.keyboard_manager.register_events(el);\n",
+ " }\n",
+ " else {\n",
+ " // location in version 2\n",
+ " IPython.keyboard_manager.register_events(el);\n",
+ " }\n",
+ "\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype._key_event_extra = function(event, name) {\n",
+ " var manager = IPython.notebook.keyboard_manager;\n",
+ " if (!manager)\n",
+ " manager = IPython.keyboard_manager;\n",
+ "\n",
+ " // Check for shift+enter\n",
+ " if (event.shiftKey && event.which == 13) {\n",
+ " this.canvas_div.blur();\n",
+ " event.shiftKey = false;\n",
+ " // Send a \"J\" for go to next cell\n",
+ " event.which = 74;\n",
+ " event.keyCode = 74;\n",
+ " manager.command_mode();\n",
+ " manager.handle_keydown(event);\n",
+ " }\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.handle_save = function(fig, msg) {\n",
+ " fig.ondownload(fig, null);\n",
+ "}\n",
+ "\n",
+ "\n",
+ "mpl.find_output_cell = function(html_output) {\n",
+ " // Return the cell and output element which can be found *uniquely* in the notebook.\n",
+ " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
+ " // IPython event is triggered only after the cells have been serialised, which for\n",
+ " // our purposes (turning an active figure into a static one), is too late.\n",
+ " var cells = IPython.notebook.get_cells();\n",
+ " var ncells = cells.length;\n",
+ " for (var i=0; i= 3 moved mimebundle to data attribute of output\n",
+ " data = data.data;\n",
+ " }\n",
+ " if (data['text/html'] == html_output) {\n",
+ " return [cell, data, j];\n",
+ " }\n",
+ " }\n",
+ " }\n",
+ " }\n",
+ "}\n",
+ "\n",
+ "// Register the function which deals with the matplotlib target/channel.\n",
+ "// The kernel may be null if the page has been refreshed.\n",
+ "if (IPython.notebook.kernel != null) {\n",
+ " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n",
+ "}\n"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "
"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "%time Z1 = fast_tsne(X50[ind2k,:], initialization=PCAinit[ind2k,:])\n",
+ "%time Z2 = fast_tsne(X50[ind2k,:], initialization=PCAinit[ind2k,:], theta=0)\n",
+ "\n",
+ "plt.figure(figsize=(8,4))\n",
+ "plt.subplot(121)\n",
+ "plt.scatter(Z1[:,0], Z1[:,1], c=col[y[ind2k]], s=1)\n",
+ "plt.title('Annoy and FFT')\n",
+ "plt.subplot(122)\n",
+ "plt.scatter(Z2[:,0], Z2[:,1], c=col[y[ind2k]], s=1)\n",
+ "plt.title('Exact t-SNE')\n",
+ "plt.tight_layout()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Loading and saving input similarities"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 50,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "CPU times: user 675 ms, sys: 85 ms, total: 760 ms\n",
+ "Wall time: 1min 8s\n",
+ "CPU times: user 708 ms, sys: 85 ms, total: 793 ms\n",
+ "Wall time: 1min 6s\n"
+ ]
+ },
+ {
+ "data": {
+ "application/javascript": [
+ "/* Put everything inside the global mpl namespace */\n",
+ "window.mpl = {};\n",
+ "\n",
+ "\n",
+ "mpl.get_websocket_type = function() {\n",
+ " if (typeof(WebSocket) !== 'undefined') {\n",
+ " return WebSocket;\n",
+ " } else if (typeof(MozWebSocket) !== 'undefined') {\n",
+ " return MozWebSocket;\n",
+ " } else {\n",
+ " alert('Your browser does not have WebSocket support. ' +\n",
+ " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
+ " 'Firefox 4 and 5 are also supported but you ' +\n",
+ " 'have to enable WebSockets in about:config.');\n",
+ " };\n",
+ "}\n",
+ "\n",
+ "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n",
+ " this.id = figure_id;\n",
+ "\n",
+ " this.ws = websocket;\n",
+ "\n",
+ " this.supports_binary = (this.ws.binaryType != undefined);\n",
+ "\n",
+ " if (!this.supports_binary) {\n",
+ " var warnings = document.getElementById(\"mpl-warnings\");\n",
+ " if (warnings) {\n",
+ " warnings.style.display = 'block';\n",
+ " warnings.textContent = (\n",
+ " \"This browser does not support binary websocket messages. \" +\n",
+ " \"Performance may be slow.\");\n",
+ " }\n",
+ " }\n",
+ "\n",
+ " this.imageObj = new Image();\n",
+ "\n",
+ " this.context = undefined;\n",
+ " this.message = undefined;\n",
+ " this.canvas = undefined;\n",
+ " this.rubberband_canvas = undefined;\n",
+ " this.rubberband_context = undefined;\n",
+ " this.format_dropdown = undefined;\n",
+ "\n",
+ " this.image_mode = 'full';\n",
+ "\n",
+ " this.root = $('');\n",
+ " this._root_extra_style(this.root)\n",
+ " this.root.attr('style', 'display: inline-block');\n",
+ "\n",
+ " $(parent_element).append(this.root);\n",
+ "\n",
+ " this._init_header(this);\n",
+ " this._init_canvas(this);\n",
+ " this._init_toolbar(this);\n",
+ "\n",
+ " var fig = this;\n",
+ "\n",
+ " this.waiting = false;\n",
+ "\n",
+ " this.ws.onopen = function () {\n",
+ " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n",
+ " fig.send_message(\"send_image_mode\", {});\n",
+ " if (mpl.ratio != 1) {\n",
+ " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n",
+ " }\n",
+ " fig.send_message(\"refresh\", {});\n",
+ " }\n",
+ "\n",
+ " this.imageObj.onload = function() {\n",
+ " if (fig.image_mode == 'full') {\n",
+ " // Full images could contain transparency (where diff images\n",
+ " // almost always do), so we need to clear the canvas so that\n",
+ " // there is no ghosting.\n",
+ " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
+ " }\n",
+ " fig.context.drawImage(fig.imageObj, 0, 0);\n",
+ " };\n",
+ "\n",
+ " this.imageObj.onunload = function() {\n",
+ " fig.ws.close();\n",
+ " }\n",
+ "\n",
+ " this.ws.onmessage = this._make_on_message_function(this);\n",
+ "\n",
+ " this.ondownload = ondownload;\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype._init_header = function() {\n",
+ " var titlebar = $(\n",
+ " '');\n",
+ " var titletext = $(\n",
+ " '');\n",
+ " titlebar.append(titletext)\n",
+ " this.root.append(titlebar);\n",
+ " this.header = titletext[0];\n",
+ "}\n",
+ "\n",
+ "\n",
+ "\n",
+ "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n",
+ "\n",
+ "}\n",
+ "\n",
+ "\n",
+ "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n",
+ "\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype._init_canvas = function() {\n",
+ " var fig = this;\n",
+ "\n",
+ " var canvas_div = $('');\n",
+ "\n",
+ " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n",
+ "\n",
+ " function canvas_keyboard_event(event) {\n",
+ " return fig.key_event(event, event['data']);\n",
+ " }\n",
+ "\n",
+ " canvas_div.keydown('key_press', canvas_keyboard_event);\n",
+ " canvas_div.keyup('key_release', canvas_keyboard_event);\n",
+ " this.canvas_div = canvas_div\n",
+ " this._canvas_extra_style(canvas_div)\n",
+ " this.root.append(canvas_div);\n",
+ "\n",
+ " var canvas = $('');\n",
+ " canvas.addClass('mpl-canvas');\n",
+ " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n",
+ "\n",
+ " this.canvas = canvas[0];\n",
+ " this.context = canvas[0].getContext(\"2d\");\n",
+ "\n",
+ " var backingStore = this.context.backingStorePixelRatio ||\n",
+ "\tthis.context.webkitBackingStorePixelRatio ||\n",
+ "\tthis.context.mozBackingStorePixelRatio ||\n",
+ "\tthis.context.msBackingStorePixelRatio ||\n",
+ "\tthis.context.oBackingStorePixelRatio ||\n",
+ "\tthis.context.backingStorePixelRatio || 1;\n",
+ "\n",
+ " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
+ "\n",
+ " var rubberband = $('');\n",
+ " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n",
+ "\n",
+ " var pass_mouse_events = true;\n",
+ "\n",
+ " canvas_div.resizable({\n",
+ " start: function(event, ui) {\n",
+ " pass_mouse_events = false;\n",
+ " },\n",
+ " resize: function(event, ui) {\n",
+ " fig.request_resize(ui.size.width, ui.size.height);\n",
+ " },\n",
+ " stop: function(event, ui) {\n",
+ " pass_mouse_events = true;\n",
+ " fig.request_resize(ui.size.width, ui.size.height);\n",
+ " },\n",
+ " });\n",
+ "\n",
+ " function mouse_event_fn(event) {\n",
+ " if (pass_mouse_events)\n",
+ " return fig.mouse_event(event, event['data']);\n",
+ " }\n",
+ "\n",
+ " rubberband.mousedown('button_press', mouse_event_fn);\n",
+ " rubberband.mouseup('button_release', mouse_event_fn);\n",
+ " // Throttle sequential mouse events to 1 every 20ms.\n",
+ " rubberband.mousemove('motion_notify', mouse_event_fn);\n",
+ "\n",
+ " rubberband.mouseenter('figure_enter', mouse_event_fn);\n",
+ " rubberband.mouseleave('figure_leave', mouse_event_fn);\n",
+ "\n",
+ " canvas_div.on(\"wheel\", function (event) {\n",
+ " event = event.originalEvent;\n",
+ " event['data'] = 'scroll'\n",
+ " if (event.deltaY < 0) {\n",
+ " event.step = 1;\n",
+ " } else {\n",
+ " event.step = -1;\n",
+ " }\n",
+ " mouse_event_fn(event);\n",
+ " });\n",
+ "\n",
+ " canvas_div.append(canvas);\n",
+ " canvas_div.append(rubberband);\n",
+ "\n",
+ " this.rubberband = rubberband;\n",
+ " this.rubberband_canvas = rubberband[0];\n",
+ " this.rubberband_context = rubberband[0].getContext(\"2d\");\n",
+ " this.rubberband_context.strokeStyle = \"#000000\";\n",
+ "\n",
+ " this._resize_canvas = function(width, height) {\n",
+ " // Keep the size of the canvas, canvas container, and rubber band\n",
+ " // canvas in synch.\n",
+ " canvas_div.css('width', width)\n",
+ " canvas_div.css('height', height)\n",
+ "\n",
+ " canvas.attr('width', width * mpl.ratio);\n",
+ " canvas.attr('height', height * mpl.ratio);\n",
+ " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n",
+ "\n",
+ " rubberband.attr('width', width);\n",
+ " rubberband.attr('height', height);\n",
+ " }\n",
+ "\n",
+ " // Set the figure to an initial 600x600px, this will subsequently be updated\n",
+ " // upon first draw.\n",
+ " this._resize_canvas(600, 600);\n",
+ "\n",
+ " // Disable right mouse context menu.\n",
+ " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n",
+ " return false;\n",
+ " });\n",
+ "\n",
+ " function set_focus () {\n",
+ " canvas.focus();\n",
+ " canvas_div.focus();\n",
+ " }\n",
+ "\n",
+ " window.setTimeout(set_focus, 100);\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype._init_toolbar = function() {\n",
+ " var fig = this;\n",
+ "\n",
+ " var nav_element = $('');\n",
+ " nav_element.attr('style', 'width: 100%');\n",
+ " this.root.append(nav_element);\n",
+ "\n",
+ " // Define a callback function for later on.\n",
+ " function toolbar_event(event) {\n",
+ " return fig.toolbar_button_onclick(event['data']);\n",
+ " }\n",
+ " function toolbar_mouse_event(event) {\n",
+ " return fig.toolbar_button_onmouseover(event['data']);\n",
+ " }\n",
+ "\n",
+ " for(var toolbar_ind in mpl.toolbar_items) {\n",
+ " var name = mpl.toolbar_items[toolbar_ind][0];\n",
+ " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+ " var image = mpl.toolbar_items[toolbar_ind][2];\n",
+ " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+ "\n",
+ " if (!name) {\n",
+ " // put a spacer in here.\n",
+ " continue;\n",
+ " }\n",
+ " var button = $('');\n",
+ " button.addClass('ui-button ui-widget ui-state-default ui-corner-all ' +\n",
+ " 'ui-button-icon-only');\n",
+ " button.attr('role', 'button');\n",
+ " button.attr('aria-disabled', 'false');\n",
+ " button.click(method_name, toolbar_event);\n",
+ " button.mouseover(tooltip, toolbar_mouse_event);\n",
+ "\n",
+ " var icon_img = $('');\n",
+ " icon_img.addClass('ui-button-icon-primary ui-icon');\n",
+ " icon_img.addClass(image);\n",
+ " icon_img.addClass('ui-corner-all');\n",
+ "\n",
+ " var tooltip_span = $('');\n",
+ " tooltip_span.addClass('ui-button-text');\n",
+ " tooltip_span.html(tooltip);\n",
+ "\n",
+ " button.append(icon_img);\n",
+ " button.append(tooltip_span);\n",
+ "\n",
+ " nav_element.append(button);\n",
+ " }\n",
+ "\n",
+ " var fmt_picker_span = $('');\n",
+ "\n",
+ " var fmt_picker = $('');\n",
+ " fmt_picker.addClass('mpl-toolbar-option ui-widget ui-widget-content');\n",
+ " fmt_picker_span.append(fmt_picker);\n",
+ " nav_element.append(fmt_picker_span);\n",
+ " this.format_dropdown = fmt_picker[0];\n",
+ "\n",
+ " for (var ind in mpl.extensions) {\n",
+ " var fmt = mpl.extensions[ind];\n",
+ " var option = $(\n",
+ " '', {selected: fmt === mpl.default_extension}).html(fmt);\n",
+ " fmt_picker.append(option);\n",
+ " }\n",
+ "\n",
+ " // Add hover states to the ui-buttons\n",
+ " $( \".ui-button\" ).hover(\n",
+ " function() { $(this).addClass(\"ui-state-hover\");},\n",
+ " function() { $(this).removeClass(\"ui-state-hover\");}\n",
+ " );\n",
+ "\n",
+ " var status_bar = $('');\n",
+ " nav_element.append(status_bar);\n",
+ " this.message = status_bar[0];\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.request_resize = function(x_pixels, y_pixels) {\n",
+ " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
+ " // which will in turn request a refresh of the image.\n",
+ " this.send_message('resize', {'width': x_pixels, 'height': y_pixels});\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.send_message = function(type, properties) {\n",
+ " properties['type'] = type;\n",
+ " properties['figure_id'] = this.id;\n",
+ " this.ws.send(JSON.stringify(properties));\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.send_draw_message = function() {\n",
+ " if (!this.waiting) {\n",
+ " this.waiting = true;\n",
+ " this.ws.send(JSON.stringify({type: \"draw\", figure_id: this.id}));\n",
+ " }\n",
+ "}\n",
+ "\n",
+ "\n",
+ "mpl.figure.prototype.handle_save = function(fig, msg) {\n",
+ " var format_dropdown = fig.format_dropdown;\n",
+ " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
+ " fig.ondownload(fig, format);\n",
+ "}\n",
+ "\n",
+ "\n",
+ "mpl.figure.prototype.handle_resize = function(fig, msg) {\n",
+ " var size = msg['size'];\n",
+ " if (size[0] != fig.canvas.width || size[1] != fig.canvas.height) {\n",
+ " fig._resize_canvas(size[0], size[1]);\n",
+ " fig.send_message(\"refresh\", {});\n",
+ " };\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.handle_rubberband = function(fig, msg) {\n",
+ " var x0 = msg['x0'] / mpl.ratio;\n",
+ " var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n",
+ " var x1 = msg['x1'] / mpl.ratio;\n",
+ " var y1 = (fig.canvas.height - msg['y1']) / mpl.ratio;\n",
+ " x0 = Math.floor(x0) + 0.5;\n",
+ " y0 = Math.floor(y0) + 0.5;\n",
+ " x1 = Math.floor(x1) + 0.5;\n",
+ " y1 = Math.floor(y1) + 0.5;\n",
+ " var min_x = Math.min(x0, x1);\n",
+ " var min_y = Math.min(y0, y1);\n",
+ " var width = Math.abs(x1 - x0);\n",
+ " var height = Math.abs(y1 - y0);\n",
+ "\n",
+ " fig.rubberband_context.clearRect(\n",
+ " 0, 0, fig.canvas.width / mpl.ratio, fig.canvas.height / mpl.ratio);\n",
+ "\n",
+ " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.handle_figure_label = function(fig, msg) {\n",
+ " // Updates the figure title.\n",
+ " fig.header.textContent = msg['label'];\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.handle_cursor = function(fig, msg) {\n",
+ " var cursor = msg['cursor'];\n",
+ " switch(cursor)\n",
+ " {\n",
+ " case 0:\n",
+ " cursor = 'pointer';\n",
+ " break;\n",
+ " case 1:\n",
+ " cursor = 'default';\n",
+ " break;\n",
+ " case 2:\n",
+ " cursor = 'crosshair';\n",
+ " break;\n",
+ " case 3:\n",
+ " cursor = 'move';\n",
+ " break;\n",
+ " }\n",
+ " fig.rubberband_canvas.style.cursor = cursor;\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.handle_message = function(fig, msg) {\n",
+ " fig.message.textContent = msg['message'];\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.handle_draw = function(fig, msg) {\n",
+ " // Request the server to send over a new figure.\n",
+ " fig.send_draw_message();\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.handle_image_mode = function(fig, msg) {\n",
+ " fig.image_mode = msg['mode'];\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.updated_canvas_event = function() {\n",
+ " // Called whenever the canvas gets updated.\n",
+ " this.send_message(\"ack\", {});\n",
+ "}\n",
+ "\n",
+ "// A function to construct a web socket function for onmessage handling.\n",
+ "// Called in the figure constructor.\n",
+ "mpl.figure.prototype._make_on_message_function = function(fig) {\n",
+ " return function socket_on_message(evt) {\n",
+ " if (evt.data instanceof Blob) {\n",
+ " /* FIXME: We get \"Resource interpreted as Image but\n",
+ " * transferred with MIME type text/plain:\" errors on\n",
+ " * Chrome. But how to set the MIME type? It doesn't seem\n",
+ " * to be part of the websocket stream */\n",
+ " evt.data.type = \"image/png\";\n",
+ "\n",
+ " /* Free the memory for the previous frames */\n",
+ " if (fig.imageObj.src) {\n",
+ " (window.URL || window.webkitURL).revokeObjectURL(\n",
+ " fig.imageObj.src);\n",
+ " }\n",
+ "\n",
+ " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
+ " evt.data);\n",
+ " fig.updated_canvas_event();\n",
+ " fig.waiting = false;\n",
+ " return;\n",
+ " }\n",
+ " else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n",
+ " fig.imageObj.src = evt.data;\n",
+ " fig.updated_canvas_event();\n",
+ " fig.waiting = false;\n",
+ " return;\n",
+ " }\n",
+ "\n",
+ " var msg = JSON.parse(evt.data);\n",
+ " var msg_type = msg['type'];\n",
+ "\n",
+ " // Call the \"handle_{type}\" callback, which takes\n",
+ " // the figure and JSON message as its only arguments.\n",
+ " try {\n",
+ " var callback = fig[\"handle_\" + msg_type];\n",
+ " } catch (e) {\n",
+ " console.log(\"No handler for the '\" + msg_type + \"' message type: \", msg);\n",
+ " return;\n",
+ " }\n",
+ "\n",
+ " if (callback) {\n",
+ " try {\n",
+ " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
+ " callback(fig, msg);\n",
+ " } catch (e) {\n",
+ " console.log(\"Exception inside the 'handler_\" + msg_type + \"' callback:\", e, e.stack, msg);\n",
+ " }\n",
+ " }\n",
+ " };\n",
+ "}\n",
+ "\n",
+ "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
+ "mpl.findpos = function(e) {\n",
+ " //this section is from http://www.quirksmode.org/js/events_properties.html\n",
+ " var targ;\n",
+ " if (!e)\n",
+ " e = window.event;\n",
+ " if (e.target)\n",
+ " targ = e.target;\n",
+ " else if (e.srcElement)\n",
+ " targ = e.srcElement;\n",
+ " if (targ.nodeType == 3) // defeat Safari bug\n",
+ " targ = targ.parentNode;\n",
+ "\n",
+ " // jQuery normalizes the pageX and pageY\n",
+ " // pageX,Y are the mouse positions relative to the document\n",
+ " // offset() returns the position of the element relative to the document\n",
+ " var x = e.pageX - $(targ).offset().left;\n",
+ " var y = e.pageY - $(targ).offset().top;\n",
+ "\n",
+ " return {\"x\": x, \"y\": y};\n",
+ "};\n",
+ "\n",
+ "/*\n",
+ " * return a copy of an object with only non-object keys\n",
+ " * we need this to avoid circular references\n",
+ " * http://stackoverflow.com/a/24161582/3208463\n",
+ " */\n",
+ "function simpleKeys (original) {\n",
+ " return Object.keys(original).reduce(function (obj, key) {\n",
+ " if (typeof original[key] !== 'object')\n",
+ " obj[key] = original[key]\n",
+ " return obj;\n",
+ " }, {});\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.mouse_event = function(event, name) {\n",
+ " var canvas_pos = mpl.findpos(event)\n",
+ "\n",
+ " if (name === 'button_press')\n",
+ " {\n",
+ " this.canvas.focus();\n",
+ " this.canvas_div.focus();\n",
+ " }\n",
+ "\n",
+ " var x = canvas_pos.x * mpl.ratio;\n",
+ " var y = canvas_pos.y * mpl.ratio;\n",
+ "\n",
+ " this.send_message(name, {x: x, y: y, button: event.button,\n",
+ " step: event.step,\n",
+ " guiEvent: simpleKeys(event)});\n",
+ "\n",
+ " /* This prevents the web browser from automatically changing to\n",
+ " * the text insertion cursor when the button is pressed. We want\n",
+ " * to control all of the cursor setting manually through the\n",
+ " * 'cursor' event from matplotlib */\n",
+ " event.preventDefault();\n",
+ " return false;\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype._key_event_extra = function(event, name) {\n",
+ " // Handle any extra behaviour associated with a key event\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.key_event = function(event, name) {\n",
+ "\n",
+ " // Prevent repeat events\n",
+ " if (name == 'key_press')\n",
+ " {\n",
+ " if (event.which === this._key)\n",
+ " return;\n",
+ " else\n",
+ " this._key = event.which;\n",
+ " }\n",
+ " if (name == 'key_release')\n",
+ " this._key = null;\n",
+ "\n",
+ " var value = '';\n",
+ " if (event.ctrlKey && event.which != 17)\n",
+ " value += \"ctrl+\";\n",
+ " if (event.altKey && event.which != 18)\n",
+ " value += \"alt+\";\n",
+ " if (event.shiftKey && event.which != 16)\n",
+ " value += \"shift+\";\n",
+ "\n",
+ " value += 'k';\n",
+ " value += event.which.toString();\n",
+ "\n",
+ " this._key_event_extra(event, name);\n",
+ "\n",
+ " this.send_message(name, {key: value,\n",
+ " guiEvent: simpleKeys(event)});\n",
+ " return false;\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.toolbar_button_onclick = function(name) {\n",
+ " if (name == 'download') {\n",
+ " this.handle_save(this, null);\n",
+ " } else {\n",
+ " this.send_message(\"toolbar_button\", {name: name});\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) {\n",
+ " this.message.textContent = tooltip;\n",
+ "};\n",
+ "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
+ "\n",
+ "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
+ "\n",
+ "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n",
+ " // Create a \"websocket\"-like object which calls the given IPython comm\n",
+ " // object with the appropriate methods. Currently this is a non binary\n",
+ " // socket, so there is still some room for performance tuning.\n",
+ " var ws = {};\n",
+ "\n",
+ " ws.close = function() {\n",
+ " comm.close()\n",
+ " };\n",
+ " ws.send = function(m) {\n",
+ " //console.log('sending', m);\n",
+ " comm.send(m);\n",
+ " };\n",
+ " // Register the callback with on_msg.\n",
+ " comm.on_msg(function(msg) {\n",
+ " //console.log('receiving', msg['content']['data'], msg);\n",
+ " // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
+ " ws.onmessage(msg['content']['data'])\n",
+ " });\n",
+ " return ws;\n",
+ "}\n",
+ "\n",
+ "mpl.mpl_figure_comm = function(comm, msg) {\n",
+ " // This is the function which gets called when the mpl process\n",
+ " // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
+ "\n",
+ " var id = msg.content.data.id;\n",
+ " // Get hold of the div created by the display call when the Comm\n",
+ " // socket was opened in Python.\n",
+ " var element = $(\"#\" + id);\n",
+ " var ws_proxy = comm_websocket_adapter(comm)\n",
+ "\n",
+ " function ondownload(figure, format) {\n",
+ " window.open(figure.imageObj.src);\n",
+ " }\n",
+ "\n",
+ " var fig = new mpl.figure(id, ws_proxy,\n",
+ " ondownload,\n",
+ " element.get(0));\n",
+ "\n",
+ " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
+ " // web socket which is closed, not our websocket->open comm proxy.\n",
+ " ws_proxy.onopen();\n",
+ "\n",
+ " fig.parent_element = element.get(0);\n",
+ " fig.cell_info = mpl.find_output_cell(\"\");\n",
+ " if (!fig.cell_info) {\n",
+ " console.error(\"Failed to find cell for figure\", id, fig);\n",
+ " return;\n",
+ " }\n",
+ "\n",
+ " var output_index = fig.cell_info[2]\n",
+ " var cell = fig.cell_info[0];\n",
+ "\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_close = function(fig, msg) {\n",
+ " var width = fig.canvas.width/mpl.ratio\n",
+ " fig.root.unbind('remove')\n",
+ "\n",
+ " // Update the output cell to use the data from the current canvas.\n",
+ " fig.push_to_output();\n",
+ " var dataURL = fig.canvas.toDataURL();\n",
+ " // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
+ " // the notebook keyboard shortcuts fail.\n",
+ " IPython.keyboard_manager.enable()\n",
+ " $(fig.parent_element).html('
');\n",
+ " fig.close_ws(fig, msg);\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.close_ws = function(fig, msg){\n",
+ " fig.send_message('closing', msg);\n",
+ " // fig.ws.close()\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.push_to_output = function(remove_interactive) {\n",
+ " // Turn the data on the canvas into data in the output cell.\n",
+ " var width = this.canvas.width/mpl.ratio\n",
+ " var dataURL = this.canvas.toDataURL();\n",
+ " this.cell_info[1]['text/html'] = '
';\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.updated_canvas_event = function() {\n",
+ " // Tell IPython that the notebook contents must change.\n",
+ " IPython.notebook.set_dirty(true);\n",
+ " this.send_message(\"ack\", {});\n",
+ " var fig = this;\n",
+ " // Wait a second, then push the new image to the DOM so\n",
+ " // that it is saved nicely (might be nice to debounce this).\n",
+ " setTimeout(function () { fig.push_to_output() }, 1000);\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype._init_toolbar = function() {\n",
+ " var fig = this;\n",
+ "\n",
+ " var nav_element = $('');\n",
+ " nav_element.attr('style', 'width: 100%');\n",
+ " this.root.append(nav_element);\n",
+ "\n",
+ " // Define a callback function for later on.\n",
+ " function toolbar_event(event) {\n",
+ " return fig.toolbar_button_onclick(event['data']);\n",
+ " }\n",
+ " function toolbar_mouse_event(event) {\n",
+ " return fig.toolbar_button_onmouseover(event['data']);\n",
+ " }\n",
+ "\n",
+ " for(var toolbar_ind in mpl.toolbar_items){\n",
+ " var name = mpl.toolbar_items[toolbar_ind][0];\n",
+ " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+ " var image = mpl.toolbar_items[toolbar_ind][2];\n",
+ " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+ "\n",
+ " if (!name) { continue; };\n",
+ "\n",
+ " var button = $('');\n",
+ " button.click(method_name, toolbar_event);\n",
+ " button.mouseover(tooltip, toolbar_mouse_event);\n",
+ " nav_element.append(button);\n",
+ " }\n",
+ "\n",
+ " // Add the status bar.\n",
+ " var status_bar = $('');\n",
+ " nav_element.append(status_bar);\n",
+ " this.message = status_bar[0];\n",
+ "\n",
+ " // Add the close button to the window.\n",
+ " var buttongrp = $('');\n",
+ " var button = $('');\n",
+ " button.click(function (evt) { fig.handle_close(fig, {}); } );\n",
+ " button.mouseover('Stop Interaction', toolbar_mouse_event);\n",
+ " buttongrp.append(button);\n",
+ " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n",
+ " titlebar.prepend(buttongrp);\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype._root_extra_style = function(el){\n",
+ " var fig = this\n",
+ " el.on(\"remove\", function(){\n",
+ "\tfig.close_ws(fig, {});\n",
+ " });\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype._canvas_extra_style = function(el){\n",
+ " // this is important to make the div 'focusable\n",
+ " el.attr('tabindex', 0)\n",
+ " // reach out to IPython and tell the keyboard manager to turn it's self\n",
+ " // off when our div gets focus\n",
+ "\n",
+ " // location in version 3\n",
+ " if (IPython.notebook.keyboard_manager) {\n",
+ " IPython.notebook.keyboard_manager.register_events(el);\n",
+ " }\n",
+ " else {\n",
+ " // location in version 2\n",
+ " IPython.keyboard_manager.register_events(el);\n",
+ " }\n",
+ "\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype._key_event_extra = function(event, name) {\n",
+ " var manager = IPython.notebook.keyboard_manager;\n",
+ " if (!manager)\n",
+ " manager = IPython.keyboard_manager;\n",
+ "\n",
+ " // Check for shift+enter\n",
+ " if (event.shiftKey && event.which == 13) {\n",
+ " this.canvas_div.blur();\n",
+ " event.shiftKey = false;\n",
+ " // Send a \"J\" for go to next cell\n",
+ " event.which = 74;\n",
+ " event.keyCode = 74;\n",
+ " manager.command_mode();\n",
+ " manager.handle_keydown(event);\n",
+ " }\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.handle_save = function(fig, msg) {\n",
+ " fig.ondownload(fig, null);\n",
+ "}\n",
+ "\n",
+ "\n",
+ "mpl.find_output_cell = function(html_output) {\n",
+ " // Return the cell and output element which can be found *uniquely* in the notebook.\n",
+ " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
+ " // IPython event is triggered only after the cells have been serialised, which for\n",
+ " // our purposes (turning an active figure into a static one), is too late.\n",
+ " var cells = IPython.notebook.get_cells();\n",
+ " var ncells = cells.length;\n",
+ " for (var i=0; i= 3 moved mimebundle to data attribute of output\n",
+ " data = data.data;\n",
+ " }\n",
+ " if (data['text/html'] == html_output) {\n",
+ " return [cell, data, j];\n",
+ " }\n",
+ " }\n",
+ " }\n",
+ " }\n",
+ "}\n",
+ "\n",
+ "// Register the function which deals with the matplotlib target/channel.\n",
+ "// The kernel may be null if the page has been refreshed.\n",
+ "if (IPython.notebook.kernel != null) {\n",
+ " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n",
+ "}\n"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "
"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "%time Z1 = fast_tsne(X50, initialization=PCAinit, learning_rate=1000, load_affinities = 'save')\n",
+ "%time Z2 = fast_tsne(X50, initialization=PCAinit, learning_rate=1000, load_affinities = 'load')\n",
+ "\n",
+ "plt.figure(figsize=(8,4))\n",
+ "plt.subplot(121)\n",
+ "plt.scatter(Z1[:,0], Z1[:,1], c=col[y], s=.1)\n",
+ "plt.title('Similarities saved...')\n",
+ "plt.subplot(122)\n",
+ "plt.scatter(Z2[:,0], Z2[:,1], c=col[y], s=.1)\n",
+ "plt.title('... similarities loaded')\n",
+ "plt.tight_layout()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 49,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "CPU times: user 6.43 ms, sys: 36.4 ms, total: 42.8 ms\n",
+ "Wall time: 2min 30s\n",
+ "CPU times: user 5.82 ms, sys: 28.1 ms, total: 33.9 ms\n",
+ "Wall time: 2min 27s\n"
+ ]
+ },
+ {
+ "data": {
+ "application/javascript": [
+ "/* Put everything inside the global mpl namespace */\n",
+ "window.mpl = {};\n",
+ "\n",
+ "\n",
+ "mpl.get_websocket_type = function() {\n",
+ " if (typeof(WebSocket) !== 'undefined') {\n",
+ " return WebSocket;\n",
+ " } else if (typeof(MozWebSocket) !== 'undefined') {\n",
+ " return MozWebSocket;\n",
+ " } else {\n",
+ " alert('Your browser does not have WebSocket support. ' +\n",
+ " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
+ " 'Firefox 4 and 5 are also supported but you ' +\n",
+ " 'have to enable WebSockets in about:config.');\n",
+ " };\n",
+ "}\n",
+ "\n",
+ "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n",
+ " this.id = figure_id;\n",
+ "\n",
+ " this.ws = websocket;\n",
+ "\n",
+ " this.supports_binary = (this.ws.binaryType != undefined);\n",
+ "\n",
+ " if (!this.supports_binary) {\n",
+ " var warnings = document.getElementById(\"mpl-warnings\");\n",
+ " if (warnings) {\n",
+ " warnings.style.display = 'block';\n",
+ " warnings.textContent = (\n",
+ " \"This browser does not support binary websocket messages. \" +\n",
+ " \"Performance may be slow.\");\n",
+ " }\n",
+ " }\n",
+ "\n",
+ " this.imageObj = new Image();\n",
+ "\n",
+ " this.context = undefined;\n",
+ " this.message = undefined;\n",
+ " this.canvas = undefined;\n",
+ " this.rubberband_canvas = undefined;\n",
+ " this.rubberband_context = undefined;\n",
+ " this.format_dropdown = undefined;\n",
+ "\n",
+ " this.image_mode = 'full';\n",
+ "\n",
+ " this.root = $('');\n",
+ " this._root_extra_style(this.root)\n",
+ " this.root.attr('style', 'display: inline-block');\n",
+ "\n",
+ " $(parent_element).append(this.root);\n",
+ "\n",
+ " this._init_header(this);\n",
+ " this._init_canvas(this);\n",
+ " this._init_toolbar(this);\n",
+ "\n",
+ " var fig = this;\n",
+ "\n",
+ " this.waiting = false;\n",
+ "\n",
+ " this.ws.onopen = function () {\n",
+ " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n",
+ " fig.send_message(\"send_image_mode\", {});\n",
+ " if (mpl.ratio != 1) {\n",
+ " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n",
+ " }\n",
+ " fig.send_message(\"refresh\", {});\n",
+ " }\n",
+ "\n",
+ " this.imageObj.onload = function() {\n",
+ " if (fig.image_mode == 'full') {\n",
+ " // Full images could contain transparency (where diff images\n",
+ " // almost always do), so we need to clear the canvas so that\n",
+ " // there is no ghosting.\n",
+ " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
+ " }\n",
+ " fig.context.drawImage(fig.imageObj, 0, 0);\n",
+ " };\n",
+ "\n",
+ " this.imageObj.onunload = function() {\n",
+ " fig.ws.close();\n",
+ " }\n",
+ "\n",
+ " this.ws.onmessage = this._make_on_message_function(this);\n",
+ "\n",
+ " this.ondownload = ondownload;\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype._init_header = function() {\n",
+ " var titlebar = $(\n",
+ " '');\n",
+ " var titletext = $(\n",
+ " '');\n",
+ " titlebar.append(titletext)\n",
+ " this.root.append(titlebar);\n",
+ " this.header = titletext[0];\n",
+ "}\n",
+ "\n",
+ "\n",
+ "\n",
+ "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n",
+ "\n",
+ "}\n",
+ "\n",
+ "\n",
+ "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n",
+ "\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype._init_canvas = function() {\n",
+ " var fig = this;\n",
+ "\n",
+ " var canvas_div = $('');\n",
+ "\n",
+ " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n",
+ "\n",
+ " function canvas_keyboard_event(event) {\n",
+ " return fig.key_event(event, event['data']);\n",
+ " }\n",
+ "\n",
+ " canvas_div.keydown('key_press', canvas_keyboard_event);\n",
+ " canvas_div.keyup('key_release', canvas_keyboard_event);\n",
+ " this.canvas_div = canvas_div\n",
+ " this._canvas_extra_style(canvas_div)\n",
+ " this.root.append(canvas_div);\n",
+ "\n",
+ " var canvas = $('');\n",
+ " canvas.addClass('mpl-canvas');\n",
+ " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n",
+ "\n",
+ " this.canvas = canvas[0];\n",
+ " this.context = canvas[0].getContext(\"2d\");\n",
+ "\n",
+ " var backingStore = this.context.backingStorePixelRatio ||\n",
+ "\tthis.context.webkitBackingStorePixelRatio ||\n",
+ "\tthis.context.mozBackingStorePixelRatio ||\n",
+ "\tthis.context.msBackingStorePixelRatio ||\n",
+ "\tthis.context.oBackingStorePixelRatio ||\n",
+ "\tthis.context.backingStorePixelRatio || 1;\n",
+ "\n",
+ " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
+ "\n",
+ " var rubberband = $('');\n",
+ " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n",
+ "\n",
+ " var pass_mouse_events = true;\n",
+ "\n",
+ " canvas_div.resizable({\n",
+ " start: function(event, ui) {\n",
+ " pass_mouse_events = false;\n",
+ " },\n",
+ " resize: function(event, ui) {\n",
+ " fig.request_resize(ui.size.width, ui.size.height);\n",
+ " },\n",
+ " stop: function(event, ui) {\n",
+ " pass_mouse_events = true;\n",
+ " fig.request_resize(ui.size.width, ui.size.height);\n",
+ " },\n",
+ " });\n",
+ "\n",
+ " function mouse_event_fn(event) {\n",
+ " if (pass_mouse_events)\n",
+ " return fig.mouse_event(event, event['data']);\n",
+ " }\n",
+ "\n",
+ " rubberband.mousedown('button_press', mouse_event_fn);\n",
+ " rubberband.mouseup('button_release', mouse_event_fn);\n",
+ " // Throttle sequential mouse events to 1 every 20ms.\n",
+ " rubberband.mousemove('motion_notify', mouse_event_fn);\n",
+ "\n",
+ " rubberband.mouseenter('figure_enter', mouse_event_fn);\n",
+ " rubberband.mouseleave('figure_leave', mouse_event_fn);\n",
+ "\n",
+ " canvas_div.on(\"wheel\", function (event) {\n",
+ " event = event.originalEvent;\n",
+ " event['data'] = 'scroll'\n",
+ " if (event.deltaY < 0) {\n",
+ " event.step = 1;\n",
+ " } else {\n",
+ " event.step = -1;\n",
+ " }\n",
+ " mouse_event_fn(event);\n",
+ " });\n",
+ "\n",
+ " canvas_div.append(canvas);\n",
+ " canvas_div.append(rubberband);\n",
+ "\n",
+ " this.rubberband = rubberband;\n",
+ " this.rubberband_canvas = rubberband[0];\n",
+ " this.rubberband_context = rubberband[0].getContext(\"2d\");\n",
+ " this.rubberband_context.strokeStyle = \"#000000\";\n",
+ "\n",
+ " this._resize_canvas = function(width, height) {\n",
+ " // Keep the size of the canvas, canvas container, and rubber band\n",
+ " // canvas in synch.\n",
+ " canvas_div.css('width', width)\n",
+ " canvas_div.css('height', height)\n",
+ "\n",
+ " canvas.attr('width', width * mpl.ratio);\n",
+ " canvas.attr('height', height * mpl.ratio);\n",
+ " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n",
+ "\n",
+ " rubberband.attr('width', width);\n",
+ " rubberband.attr('height', height);\n",
+ " }\n",
+ "\n",
+ " // Set the figure to an initial 600x600px, this will subsequently be updated\n",
+ " // upon first draw.\n",
+ " this._resize_canvas(600, 600);\n",
+ "\n",
+ " // Disable right mouse context menu.\n",
+ " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n",
+ " return false;\n",
+ " });\n",
+ "\n",
+ " function set_focus () {\n",
+ " canvas.focus();\n",
+ " canvas_div.focus();\n",
+ " }\n",
+ "\n",
+ " window.setTimeout(set_focus, 100);\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype._init_toolbar = function() {\n",
+ " var fig = this;\n",
+ "\n",
+ " var nav_element = $('');\n",
+ " nav_element.attr('style', 'width: 100%');\n",
+ " this.root.append(nav_element);\n",
+ "\n",
+ " // Define a callback function for later on.\n",
+ " function toolbar_event(event) {\n",
+ " return fig.toolbar_button_onclick(event['data']);\n",
+ " }\n",
+ " function toolbar_mouse_event(event) {\n",
+ " return fig.toolbar_button_onmouseover(event['data']);\n",
+ " }\n",
+ "\n",
+ " for(var toolbar_ind in mpl.toolbar_items) {\n",
+ " var name = mpl.toolbar_items[toolbar_ind][0];\n",
+ " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+ " var image = mpl.toolbar_items[toolbar_ind][2];\n",
+ " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+ "\n",
+ " if (!name) {\n",
+ " // put a spacer in here.\n",
+ " continue;\n",
+ " }\n",
+ " var button = $('');\n",
+ " button.addClass('ui-button ui-widget ui-state-default ui-corner-all ' +\n",
+ " 'ui-button-icon-only');\n",
+ " button.attr('role', 'button');\n",
+ " button.attr('aria-disabled', 'false');\n",
+ " button.click(method_name, toolbar_event);\n",
+ " button.mouseover(tooltip, toolbar_mouse_event);\n",
+ "\n",
+ " var icon_img = $('');\n",
+ " icon_img.addClass('ui-button-icon-primary ui-icon');\n",
+ " icon_img.addClass(image);\n",
+ " icon_img.addClass('ui-corner-all');\n",
+ "\n",
+ " var tooltip_span = $('');\n",
+ " tooltip_span.addClass('ui-button-text');\n",
+ " tooltip_span.html(tooltip);\n",
+ "\n",
+ " button.append(icon_img);\n",
+ " button.append(tooltip_span);\n",
+ "\n",
+ " nav_element.append(button);\n",
+ " }\n",
+ "\n",
+ " var fmt_picker_span = $('');\n",
+ "\n",
+ " var fmt_picker = $('');\n",
+ " fmt_picker.addClass('mpl-toolbar-option ui-widget ui-widget-content');\n",
+ " fmt_picker_span.append(fmt_picker);\n",
+ " nav_element.append(fmt_picker_span);\n",
+ " this.format_dropdown = fmt_picker[0];\n",
+ "\n",
+ " for (var ind in mpl.extensions) {\n",
+ " var fmt = mpl.extensions[ind];\n",
+ " var option = $(\n",
+ " '', {selected: fmt === mpl.default_extension}).html(fmt);\n",
+ " fmt_picker.append(option);\n",
+ " }\n",
+ "\n",
+ " // Add hover states to the ui-buttons\n",
+ " $( \".ui-button\" ).hover(\n",
+ " function() { $(this).addClass(\"ui-state-hover\");},\n",
+ " function() { $(this).removeClass(\"ui-state-hover\");}\n",
+ " );\n",
+ "\n",
+ " var status_bar = $('');\n",
+ " nav_element.append(status_bar);\n",
+ " this.message = status_bar[0];\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.request_resize = function(x_pixels, y_pixels) {\n",
+ " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
+ " // which will in turn request a refresh of the image.\n",
+ " this.send_message('resize', {'width': x_pixels, 'height': y_pixels});\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.send_message = function(type, properties) {\n",
+ " properties['type'] = type;\n",
+ " properties['figure_id'] = this.id;\n",
+ " this.ws.send(JSON.stringify(properties));\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.send_draw_message = function() {\n",
+ " if (!this.waiting) {\n",
+ " this.waiting = true;\n",
+ " this.ws.send(JSON.stringify({type: \"draw\", figure_id: this.id}));\n",
+ " }\n",
+ "}\n",
+ "\n",
+ "\n",
+ "mpl.figure.prototype.handle_save = function(fig, msg) {\n",
+ " var format_dropdown = fig.format_dropdown;\n",
+ " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
+ " fig.ondownload(fig, format);\n",
+ "}\n",
+ "\n",
+ "\n",
+ "mpl.figure.prototype.handle_resize = function(fig, msg) {\n",
+ " var size = msg['size'];\n",
+ " if (size[0] != fig.canvas.width || size[1] != fig.canvas.height) {\n",
+ " fig._resize_canvas(size[0], size[1]);\n",
+ " fig.send_message(\"refresh\", {});\n",
+ " };\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.handle_rubberband = function(fig, msg) {\n",
+ " var x0 = msg['x0'] / mpl.ratio;\n",
+ " var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n",
+ " var x1 = msg['x1'] / mpl.ratio;\n",
+ " var y1 = (fig.canvas.height - msg['y1']) / mpl.ratio;\n",
+ " x0 = Math.floor(x0) + 0.5;\n",
+ " y0 = Math.floor(y0) + 0.5;\n",
+ " x1 = Math.floor(x1) + 0.5;\n",
+ " y1 = Math.floor(y1) + 0.5;\n",
+ " var min_x = Math.min(x0, x1);\n",
+ " var min_y = Math.min(y0, y1);\n",
+ " var width = Math.abs(x1 - x0);\n",
+ " var height = Math.abs(y1 - y0);\n",
+ "\n",
+ " fig.rubberband_context.clearRect(\n",
+ " 0, 0, fig.canvas.width / mpl.ratio, fig.canvas.height / mpl.ratio);\n",
+ "\n",
+ " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.handle_figure_label = function(fig, msg) {\n",
+ " // Updates the figure title.\n",
+ " fig.header.textContent = msg['label'];\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.handle_cursor = function(fig, msg) {\n",
+ " var cursor = msg['cursor'];\n",
+ " switch(cursor)\n",
+ " {\n",
+ " case 0:\n",
+ " cursor = 'pointer';\n",
+ " break;\n",
+ " case 1:\n",
+ " cursor = 'default';\n",
+ " break;\n",
+ " case 2:\n",
+ " cursor = 'crosshair';\n",
+ " break;\n",
+ " case 3:\n",
+ " cursor = 'move';\n",
+ " break;\n",
+ " }\n",
+ " fig.rubberband_canvas.style.cursor = cursor;\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.handle_message = function(fig, msg) {\n",
+ " fig.message.textContent = msg['message'];\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.handle_draw = function(fig, msg) {\n",
+ " // Request the server to send over a new figure.\n",
+ " fig.send_draw_message();\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.handle_image_mode = function(fig, msg) {\n",
+ " fig.image_mode = msg['mode'];\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.updated_canvas_event = function() {\n",
+ " // Called whenever the canvas gets updated.\n",
+ " this.send_message(\"ack\", {});\n",
+ "}\n",
+ "\n",
+ "// A function to construct a web socket function for onmessage handling.\n",
+ "// Called in the figure constructor.\n",
+ "mpl.figure.prototype._make_on_message_function = function(fig) {\n",
+ " return function socket_on_message(evt) {\n",
+ " if (evt.data instanceof Blob) {\n",
+ " /* FIXME: We get \"Resource interpreted as Image but\n",
+ " * transferred with MIME type text/plain:\" errors on\n",
+ " * Chrome. But how to set the MIME type? It doesn't seem\n",
+ " * to be part of the websocket stream */\n",
+ " evt.data.type = \"image/png\";\n",
+ "\n",
+ " /* Free the memory for the previous frames */\n",
+ " if (fig.imageObj.src) {\n",
+ " (window.URL || window.webkitURL).revokeObjectURL(\n",
+ " fig.imageObj.src);\n",
+ " }\n",
+ "\n",
+ " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
+ " evt.data);\n",
+ " fig.updated_canvas_event();\n",
+ " fig.waiting = false;\n",
+ " return;\n",
+ " }\n",
+ " else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n",
+ " fig.imageObj.src = evt.data;\n",
+ " fig.updated_canvas_event();\n",
+ " fig.waiting = false;\n",
+ " return;\n",
+ " }\n",
+ "\n",
+ " var msg = JSON.parse(evt.data);\n",
+ " var msg_type = msg['type'];\n",
+ "\n",
+ " // Call the \"handle_{type}\" callback, which takes\n",
+ " // the figure and JSON message as its only arguments.\n",
+ " try {\n",
+ " var callback = fig[\"handle_\" + msg_type];\n",
+ " } catch (e) {\n",
+ " console.log(\"No handler for the '\" + msg_type + \"' message type: \", msg);\n",
+ " return;\n",
+ " }\n",
+ "\n",
+ " if (callback) {\n",
+ " try {\n",
+ " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
+ " callback(fig, msg);\n",
+ " } catch (e) {\n",
+ " console.log(\"Exception inside the 'handler_\" + msg_type + \"' callback:\", e, e.stack, msg);\n",
+ " }\n",
+ " }\n",
+ " };\n",
+ "}\n",
+ "\n",
+ "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
+ "mpl.findpos = function(e) {\n",
+ " //this section is from http://www.quirksmode.org/js/events_properties.html\n",
+ " var targ;\n",
+ " if (!e)\n",
+ " e = window.event;\n",
+ " if (e.target)\n",
+ " targ = e.target;\n",
+ " else if (e.srcElement)\n",
+ " targ = e.srcElement;\n",
+ " if (targ.nodeType == 3) // defeat Safari bug\n",
+ " targ = targ.parentNode;\n",
+ "\n",
+ " // jQuery normalizes the pageX and pageY\n",
+ " // pageX,Y are the mouse positions relative to the document\n",
+ " // offset() returns the position of the element relative to the document\n",
+ " var x = e.pageX - $(targ).offset().left;\n",
+ " var y = e.pageY - $(targ).offset().top;\n",
+ "\n",
+ " return {\"x\": x, \"y\": y};\n",
+ "};\n",
+ "\n",
+ "/*\n",
+ " * return a copy of an object with only non-object keys\n",
+ " * we need this to avoid circular references\n",
+ " * http://stackoverflow.com/a/24161582/3208463\n",
+ " */\n",
+ "function simpleKeys (original) {\n",
+ " return Object.keys(original).reduce(function (obj, key) {\n",
+ " if (typeof original[key] !== 'object')\n",
+ " obj[key] = original[key]\n",
+ " return obj;\n",
+ " }, {});\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.mouse_event = function(event, name) {\n",
+ " var canvas_pos = mpl.findpos(event)\n",
+ "\n",
+ " if (name === 'button_press')\n",
+ " {\n",
+ " this.canvas.focus();\n",
+ " this.canvas_div.focus();\n",
+ " }\n",
+ "\n",
+ " var x = canvas_pos.x * mpl.ratio;\n",
+ " var y = canvas_pos.y * mpl.ratio;\n",
+ "\n",
+ " this.send_message(name, {x: x, y: y, button: event.button,\n",
+ " step: event.step,\n",
+ " guiEvent: simpleKeys(event)});\n",
+ "\n",
+ " /* This prevents the web browser from automatically changing to\n",
+ " * the text insertion cursor when the button is pressed. We want\n",
+ " * to control all of the cursor setting manually through the\n",
+ " * 'cursor' event from matplotlib */\n",
+ " event.preventDefault();\n",
+ " return false;\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype._key_event_extra = function(event, name) {\n",
+ " // Handle any extra behaviour associated with a key event\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.key_event = function(event, name) {\n",
+ "\n",
+ " // Prevent repeat events\n",
+ " if (name == 'key_press')\n",
+ " {\n",
+ " if (event.which === this._key)\n",
+ " return;\n",
+ " else\n",
+ " this._key = event.which;\n",
+ " }\n",
+ " if (name == 'key_release')\n",
+ " this._key = null;\n",
+ "\n",
+ " var value = '';\n",
+ " if (event.ctrlKey && event.which != 17)\n",
+ " value += \"ctrl+\";\n",
+ " if (event.altKey && event.which != 18)\n",
+ " value += \"alt+\";\n",
+ " if (event.shiftKey && event.which != 16)\n",
+ " value += \"shift+\";\n",
+ "\n",
+ " value += 'k';\n",
+ " value += event.which.toString();\n",
+ "\n",
+ " this._key_event_extra(event, name);\n",
+ "\n",
+ " this.send_message(name, {key: value,\n",
+ " guiEvent: simpleKeys(event)});\n",
+ " return false;\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.toolbar_button_onclick = function(name) {\n",
+ " if (name == 'download') {\n",
+ " this.handle_save(this, null);\n",
+ " } else {\n",
+ " this.send_message(\"toolbar_button\", {name: name});\n",
+ " }\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) {\n",
+ " this.message.textContent = tooltip;\n",
+ "};\n",
+ "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
+ "\n",
+ "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
+ "\n",
+ "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n",
+ " // Create a \"websocket\"-like object which calls the given IPython comm\n",
+ " // object with the appropriate methods. Currently this is a non binary\n",
+ " // socket, so there is still some room for performance tuning.\n",
+ " var ws = {};\n",
+ "\n",
+ " ws.close = function() {\n",
+ " comm.close()\n",
+ " };\n",
+ " ws.send = function(m) {\n",
+ " //console.log('sending', m);\n",
+ " comm.send(m);\n",
+ " };\n",
+ " // Register the callback with on_msg.\n",
+ " comm.on_msg(function(msg) {\n",
+ " //console.log('receiving', msg['content']['data'], msg);\n",
+ " // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
+ " ws.onmessage(msg['content']['data'])\n",
+ " });\n",
+ " return ws;\n",
+ "}\n",
+ "\n",
+ "mpl.mpl_figure_comm = function(comm, msg) {\n",
+ " // This is the function which gets called when the mpl process\n",
+ " // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
+ "\n",
+ " var id = msg.content.data.id;\n",
+ " // Get hold of the div created by the display call when the Comm\n",
+ " // socket was opened in Python.\n",
+ " var element = $(\"#\" + id);\n",
+ " var ws_proxy = comm_websocket_adapter(comm)\n",
+ "\n",
+ " function ondownload(figure, format) {\n",
+ " window.open(figure.imageObj.src);\n",
+ " }\n",
+ "\n",
+ " var fig = new mpl.figure(id, ws_proxy,\n",
+ " ondownload,\n",
+ " element.get(0));\n",
+ "\n",
+ " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
+ " // web socket which is closed, not our websocket->open comm proxy.\n",
+ " ws_proxy.onopen();\n",
+ "\n",
+ " fig.parent_element = element.get(0);\n",
+ " fig.cell_info = mpl.find_output_cell(\"\");\n",
+ " if (!fig.cell_info) {\n",
+ " console.error(\"Failed to find cell for figure\", id, fig);\n",
+ " return;\n",
+ " }\n",
+ "\n",
+ " var output_index = fig.cell_info[2]\n",
+ " var cell = fig.cell_info[0];\n",
+ "\n",
+ "};\n",
+ "\n",
+ "mpl.figure.prototype.handle_close = function(fig, msg) {\n",
+ " var width = fig.canvas.width/mpl.ratio\n",
+ " fig.root.unbind('remove')\n",
+ "\n",
+ " // Update the output cell to use the data from the current canvas.\n",
+ " fig.push_to_output();\n",
+ " var dataURL = fig.canvas.toDataURL();\n",
+ " // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
+ " // the notebook keyboard shortcuts fail.\n",
+ " IPython.keyboard_manager.enable()\n",
+ " $(fig.parent_element).html('
');\n",
+ " fig.close_ws(fig, msg);\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.close_ws = function(fig, msg){\n",
+ " fig.send_message('closing', msg);\n",
+ " // fig.ws.close()\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.push_to_output = function(remove_interactive) {\n",
+ " // Turn the data on the canvas into data in the output cell.\n",
+ " var width = this.canvas.width/mpl.ratio\n",
+ " var dataURL = this.canvas.toDataURL();\n",
+ " this.cell_info[1]['text/html'] = '
';\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.updated_canvas_event = function() {\n",
+ " // Tell IPython that the notebook contents must change.\n",
+ " IPython.notebook.set_dirty(true);\n",
+ " this.send_message(\"ack\", {});\n",
+ " var fig = this;\n",
+ " // Wait a second, then push the new image to the DOM so\n",
+ " // that it is saved nicely (might be nice to debounce this).\n",
+ " setTimeout(function () { fig.push_to_output() }, 1000);\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype._init_toolbar = function() {\n",
+ " var fig = this;\n",
+ "\n",
+ " var nav_element = $('');\n",
+ " nav_element.attr('style', 'width: 100%');\n",
+ " this.root.append(nav_element);\n",
+ "\n",
+ " // Define a callback function for later on.\n",
+ " function toolbar_event(event) {\n",
+ " return fig.toolbar_button_onclick(event['data']);\n",
+ " }\n",
+ " function toolbar_mouse_event(event) {\n",
+ " return fig.toolbar_button_onmouseover(event['data']);\n",
+ " }\n",
+ "\n",
+ " for(var toolbar_ind in mpl.toolbar_items){\n",
+ " var name = mpl.toolbar_items[toolbar_ind][0];\n",
+ " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+ " var image = mpl.toolbar_items[toolbar_ind][2];\n",
+ " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+ "\n",
+ " if (!name) { continue; };\n",
+ "\n",
+ " var button = $('');\n",
+ " button.click(method_name, toolbar_event);\n",
+ " button.mouseover(tooltip, toolbar_mouse_event);\n",
+ " nav_element.append(button);\n",
+ " }\n",
+ "\n",
+ " // Add the status bar.\n",
+ " var status_bar = $('');\n",
+ " nav_element.append(status_bar);\n",
+ " this.message = status_bar[0];\n",
+ "\n",
+ " // Add the close button to the window.\n",
+ " var buttongrp = $('');\n",
+ " var button = $('');\n",
+ " button.click(function (evt) { fig.handle_close(fig, {}); } );\n",
+ " button.mouseover('Stop Interaction', toolbar_mouse_event);\n",
+ " buttongrp.append(button);\n",
+ " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n",
+ " titlebar.prepend(buttongrp);\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype._root_extra_style = function(el){\n",
+ " var fig = this\n",
+ " el.on(\"remove\", function(){\n",
+ "\tfig.close_ws(fig, {});\n",
+ " });\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype._canvas_extra_style = function(el){\n",
+ " // this is important to make the div 'focusable\n",
+ " el.attr('tabindex', 0)\n",
+ " // reach out to IPython and tell the keyboard manager to turn it's self\n",
+ " // off when our div gets focus\n",
+ "\n",
+ " // location in version 3\n",
+ " if (IPython.notebook.keyboard_manager) {\n",
+ " IPython.notebook.keyboard_manager.register_events(el);\n",
+ " }\n",
+ " else {\n",
+ " // location in version 2\n",
+ " IPython.keyboard_manager.register_events(el);\n",
+ " }\n",
+ "\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype._key_event_extra = function(event, name) {\n",
+ " var manager = IPython.notebook.keyboard_manager;\n",
+ " if (!manager)\n",
+ " manager = IPython.keyboard_manager;\n",
+ "\n",
+ " // Check for shift+enter\n",
+ " if (event.shiftKey && event.which == 13) {\n",
+ " this.canvas_div.blur();\n",
+ " event.shiftKey = false;\n",
+ " // Send a \"J\" for go to next cell\n",
+ " event.which = 74;\n",
+ " event.keyCode = 74;\n",
+ " manager.command_mode();\n",
+ " manager.handle_keydown(event);\n",
+ " }\n",
+ "}\n",
+ "\n",
+ "mpl.figure.prototype.handle_save = function(fig, msg) {\n",
+ " fig.ondownload(fig, null);\n",
+ "}\n",
+ "\n",
+ "\n",
+ "mpl.find_output_cell = function(html_output) {\n",
+ " // Return the cell and output element which can be found *uniquely* in the notebook.\n",
+ " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
+ " // IPython event is triggered only after the cells have been serialised, which for\n",
+ " // our purposes (turning an active figure into a static one), is too late.\n",
+ " var cells = IPython.notebook.get_cells();\n",
+ " var ncells = cells.length;\n",
+ " for (var i=0; i= 3 moved mimebundle to data attribute of output\n",
+ " data = data.data;\n",
+ " }\n",
+ " if (data['text/html'] == html_output) {\n",
+ " return [cell, data, j];\n",
+ " }\n",
+ " }\n",
+ " }\n",
+ " }\n",
+ "}\n",
+ "\n",
+ "// Register the function which deals with the matplotlib target/channel.\n",
+ "// The kernel may be null if the page has been refreshed.\n",
+ "if (IPython.notebook.kernel != null) {\n",
+ " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n",
+ "}\n"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "
"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "# And now for the exact t-SNE\n",
+ "\n",
+ "%time Z1 = fast_tsne(X50[ind2k,:], initialization=PCAinit[ind2k,:], theta=0, load_affinities = 'save')\n",
+ "%time Z2 = fast_tsne(X50[ind2k,:], initialization=PCAinit[ind2k,:], theta=0, load_affinities = 'load')\n",
+ "\n",
+ "plt.figure(figsize=(8,4))\n",
+ "plt.subplot(121)\n",
+ "plt.scatter(Z1[:,0], Z1[:,1], c=col[y[ind2k]], s=1)\n",
+ "plt.title('Exact similarities saved...')\n",
+ "plt.subplot(122)\n",
+ "plt.scatter(Z2[:,0], Z2[:,1], c=col[y[ind2k]], s=1)\n",
+ "plt.title('... exact similarities loaded')\n",
+ "plt.tight_layout()"
+ ]
}
],
"metadata": {
@@ -9953,7 +12523,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.6.5"
+ "version": "3.7.4"
}
},
"nbformat": 4,
diff --git a/fast_tsne.R b/fast_tsne.R
index ff9c939..a19ef7f 100644
--- a/fast_tsne.R
+++ b/fast_tsne.R
@@ -1,8 +1,10 @@
# Note: this script should be sourced as: source('', chdir=T)
-FAST_TSNE_SCRIPT_DIR <<-getwd()
-
-cat(sprintf("FIt-SNE R wrapper loading.\nFIt-SNE root directory was set to %s\n", FAST_TSNE_SCRIPT_DIR))
+FAST_TSNE_SCRIPT_DIR <<- getwd()
+
+message("FIt-SNE R wrapper loading.")
+message("FIt-SNE root directory was set to ", FAST_TSNE_SCRIPT_DIR)
+
# Compute FIt-SNE of a dataset
# dims - dimensionality of the embedding. Default 2.
# perplexity - perplexity is used to determine the
@@ -68,40 +70,40 @@ cat(sprintf("FIt-SNE R wrapper loading.\nFIt-SNE root directory was set to %s\n"
# details. Default is 1.0
#
fftRtsne <- function(X,
- dims=2, perplexity=30, theta=0.5,
- check_duplicates=TRUE,
- max_iter=1000,
+ dims = 2, perplexity = 30, theta = 0.5,
+ max_iter = 1000,
fft_not_bh = TRUE,
ann_not_vptree = TRUE,
- stop_early_exag_iter=250,
- exaggeration_factor=12.0, no_momentum_during_exag=FALSE,
- start_late_exag_iter=-1.0,late_exag_coeff=1.0,
- mom_switch_iter=250, momentum=.5, final_momentum=.8, learning_rate=200,
- n_trees=50, search_k = -1,rand_seed=-1,
- nterms=3, intervals_per_integer=1, min_num_intervals=50,
- K=-1, sigma=-30, initialization=NULL,
- data_path=NULL, result_path=NULL,
- load_affinities=NULL,
- fast_tsne_path=NULL, nthreads=0, perplexity_list = NULL,
- get_costs = FALSE, df = 1.0,... ) {
- version_number = '1.1.0'
+ stop_early_exag_iter = 250,
+ exaggeration_factor = 12.0, no_momentum_during_exag = FALSE,
+ start_late_exag_iter = -1.0, late_exag_coeff = 1.0,
+ mom_switch_iter = 250, momentum = 0.5, final_momentum = 0.8, learning_rate = 200,
+ n_trees = 50, search_k = -1, rand_seed = -1,
+ nterms = 3, intervals_per_integer = 1, min_num_intervals = 50,
+ K = -1, sigma = -30, initialization = NULL,
+ data_path = NULL, result_path = NULL,
+ load_affinities = NULL,
+ fast_tsne_path = NULL, nthreads = 0, perplexity_list = NULL,
+ get_costs = FALSE, df = 1.0) {
+
+ version_number <- '1.1.0'
if (is.null(fast_tsne_path)) {
- if(.Platform$OS.type == "unix") {
- fast_tsne_path = sprintf('%s/bin/fast_tsne', FAST_TSNE_SCRIPT_DIR )
+ if (.Platform$OS.type == "unix") {
+ fast_tsne_path <- file.path(FAST_TSNE_SCRIPT_DIR, "bin", "fast_tsne")
} else {
- fast_tsne_path = sprintf('%s/bin/FItSNE.exe', FAST_TSNE_SCRIPT_DIR)
+ fast_tsne_path <- file.path(FAST_TSNE_SCRIPT_DIR, "bin", "FItSNE.exe")
}
}
if (is.null(data_path)) {
- data_path <- tempfile(pattern='fftRtsne_data_', fileext='.dat')
+ data_path <- tempfile(pattern = 'fftRtsne_data_', fileext = '.dat')
}
if (is.null(result_path)) {
- result_path <- tempfile(pattern='fftRtsne_result_', fileext='.dat')
+ result_path <- tempfile(pattern = 'fftRtsne_result_', fileext = '.dat')
}
if (is.null(fast_tsne_path)) {
- fast_tsne_path <- system2('which', 'fast_tsne', stdout=TRUE)
+ fast_tsne_path <- system2('which', 'fast_tsne', stdout = TRUE)
}
fast_tsne_path <- normalizePath(fast_tsne_path)
if (!file_test('-x', fast_tsne_path)) {
@@ -110,115 +112,109 @@ fftRtsne <- function(X,
is.wholenumber <- function(x, tol = .Machine$double.eps^0.5) abs(x - round(x)) < tol
- if (!is.numeric(theta) || (theta<0.0) || (theta>1.0) ) { stop("Incorrect theta.")}
+ if (!is.numeric(theta) || (theta < 0.0) || (theta > 1.0) ) { stop("Incorrect theta.")}
if (nrow(X) - 1 < 3 * perplexity) { stop("Perplexity is too large.")}
if (!is.matrix(X)) { stop("Input X is not a matrix")}
- if (!(max_iter>0)) { stop("Incorrect number of iterations.")}
- if (!is.wholenumber(stop_early_exag_iter) || stop_early_exag_iter<0) { stop("stop_early_exag_iter should be a positive integer")}
+ if (!(max_iter > 0)) { stop("Incorrect number of iterations.")}
+ if (!is.wholenumber(stop_early_exag_iter) || stop_early_exag_iter < 0) { stop("stop_early_exag_iter should be a positive integer")}
if (!is.numeric(exaggeration_factor)) { stop("exaggeration_factor should be numeric")}
if (!is.numeric(df)) { stop("df should be numeric")}
- if (!is.wholenumber(dims) || dims<=0) { stop("Incorrect dimensionality.")}
+ if (!is.wholenumber(dims) || dims <= 0) { stop("Incorrect dimensionality.")}
if (search_k == -1) {
- if (perplexity>0) {
- search_k = n_trees*perplexity*3
- } else if (perplexity==0) {
- search_k = n_trees*max(perplexity_list)*3
- } else {
- search_k = n_trees*K
- }
+ if (perplexity > 0) {
+ search_k <- n_trees * perplexity * 3
+ } else if (perplexity == 0) {
+ search_k <- n_trees * max(perplexity_list) * 3
+ } else {
+ search_k <- n_trees * K
}
+ }
- if (fft_not_bh){
- nbody_algo = 2;
- }else{
- nbody_algo = 1;
+ if (fft_not_bh) {
+ nbody_algo <- 2
+ } else {
+ nbody_algo <- 1
}
if (is.null(load_affinities)) {
- load_affinities = 0;
+ load_affinities <- 0
} else {
if (load_affinities == 'load') {
- load_affinities = 1;
+ load_affinities <- 1
} else if (load_affinities == 'save') {
- load_affinities = 2;
+ load_affinities <- 2
} else {
- load_affinities = 0;
+ load_affinities <- 0
}
}
- if (ann_not_vptree){
- knn_algo = 1;
- }else{
- knn_algo = 2;
+ if (ann_not_vptree) {
+ knn_algo <- 1
+ } else {
+ knn_algo <- 2
}
- tX = c(t(X))
+ tX <- as.numeric(t(X))
f <- file(data_path, "wb")
- n = nrow(X);
- D = ncol(X);
- writeBin(as.integer(n), f,size= 4)
- writeBin( as.integer(D),f,size= 4)
- writeBin( as.numeric(theta), f,size= 8) #theta
- writeBin( as.numeric(perplexity), f,size= 8) #theta
-
- if (perplexity == 0) {
- writeBin( as.integer(length(perplexity_list)), f, size=4)
- writeBin( perplexity_list, f)
- }
-
- writeBin( as.integer(dims), f,size=4) #theta
- writeBin( as.integer(max_iter),f,size=4)
- writeBin( as.integer(stop_early_exag_iter),f,size=4)
- writeBin( as.integer(mom_switch_iter),f,size=4)
- writeBin( as.numeric(momentum),f,size=8)
- writeBin( as.numeric(final_momentum),f,size=8)
- writeBin( as.numeric(learning_rate),f,size=8)
- writeBin( as.integer(K),f,size=4) #K
- writeBin( as.numeric(sigma), f,size=8) #sigma
- writeBin( as.integer(nbody_algo), f,size=4) #not barnes hut
- writeBin( as.integer(knn_algo), f,size=4)
- writeBin( as.numeric(exaggeration_factor), f,size=8) #compexag
- writeBin( as.integer(no_momentum_during_exag), f,size=4)
- writeBin( as.integer(n_trees), f,size=4)
- writeBin( as.integer(search_k), f,size=4)
- writeBin( as.integer(start_late_exag_iter), f,size=4)
- writeBin( as.numeric(late_exag_coeff), f,size=8)
+ n <- nrow(X)
+ D <- ncol(X)
+ writeBin(as.integer(n), f, size = 4)
+ writeBin(as.integer(D), f, size = 4)
+ writeBin(as.numeric(theta), f, size = 8) #theta
+ writeBin(as.numeric(perplexity), f, size = 8)
+
+ if (perplexity == 0) {
+ writeBin(as.integer(length(perplexity_list)), f, size = 4)
+ writeBin(perplexity_list, f)
+ }
+
+ writeBin(as.integer(dims), f, size = 4)
+ writeBin(as.integer(max_iter), f, size = 4)
+ writeBin(as.integer(stop_early_exag_iter), f, size = 4)
+ writeBin(as.integer(mom_switch_iter), f, size = 4)
+ writeBin(as.numeric(momentum), f, size = 8)
+ writeBin(as.numeric(final_momentum), f, size = 8)
+ writeBin(as.numeric(learning_rate), f, size = 8)
+ writeBin(as.integer(K), f, size = 4) #K
+ writeBin(as.numeric(sigma), f, size = 8) #sigma
+ writeBin(as.integer(nbody_algo), f, size = 4) #not barnes hut
+ writeBin(as.integer(knn_algo), f, size = 4)
+ writeBin(as.numeric(exaggeration_factor), f, size = 8) #compexag
+ writeBin(as.integer(no_momentum_during_exag), f, size = 4)
+ writeBin(as.integer(n_trees), f, size = 4)
+ writeBin(as.integer(search_k), f, size = 4)
+ writeBin(as.integer(start_late_exag_iter), f, size = 4)
+ writeBin(as.numeric(late_exag_coeff), f, size = 8)
- writeBin( as.integer(nterms), f,size=4)
- writeBin( as.numeric(intervals_per_integer), f,size=8)
- writeBin( as.integer(min_num_intervals), f,size=4)
- tX = c(t(X))
- writeBin( tX, f)
- writeBin( as.integer(rand_seed), f,size=4)
- writeBin(as.numeric(df), f, size=8)
- writeBin( as.integer(load_affinities), f,size=4)
- if (! is.null(initialization)){ writeBin( c(t(initialization)), f) }
- print(df)
+ writeBin(as.integer(nterms), f, size = 4)
+ writeBin(as.numeric(intervals_per_integer), f, size = 8)
+ writeBin(as.integer(min_num_intervals), f, size = 4)
+ writeBin(tX, f)
+ writeBin(as.integer(rand_seed), f, size = 4)
+ writeBin(as.numeric(df), f, size = 8)
+ writeBin(as.integer(load_affinities), f, size = 4)
+ if (!is.null(initialization)) { writeBin( c(t(initialization)), f) }
close(f)
- flag= system2(command=fast_tsne_path, args=c(version_number,data_path, result_path, nthreads));
+ flag <- system2(command = fast_tsne_path,
+ args = c(version_number, data_path, result_path, nthreads))
if (flag != 0) {
- stop('tsne call failed');
+ stop('tsne call failed')
}
f <- file(result_path, "rb")
- n <- readBin(f, integer(), n=1, size=4);
- d <- readBin(f, integer(), n=1, size=4);
- Y <- readBin(f, numeric(), n=n*d);
- Y <- t(matrix(Y, nrow=d));
- if (get_costs ) {
- tmp <- readBin(f, integer(), n=1, size=4);
- costs <- readBin(f, numeric(), n=max_iter,size=8);
- Yout <- list( Y=Y, costs=costs);
- }else {
- Yout <- Y;
- }
- close(f)
- file.remove(data_path)
- file.remove(result_path)
- return(Yout)
+ n <- readBin(f, integer(), n = 1, size = 4)
+ d <- readBin(f, integer(), n = 1, size = 4)
+ Y <- readBin(f, numeric(), n = n * d)
+ Y <- t(matrix(Y, nrow = d))
+ if (get_costs) {
+ readBin(f, integer(), n = 1, size = 4)
+ costs <- readBin(f, numeric(), n = max_iter, size = 8)
+ Yout <- list(Y = Y, costs = costs)
+ } else {
+ Yout <- Y
+ }
+ close(f)
+ file.remove(data_path)
+ file.remove(result_path)
+ Yout
}
-
-
-
-
-
diff --git a/src/annoylib.h b/src/annoylib.h
index 70af2e7..08440d5 100644
--- a/src/annoylib.h
+++ b/src/annoylib.h
@@ -17,15 +17,10 @@
#define ANNOYLIB_H
#include
-#include
#include
-
#ifndef _MSC_VER
-
#include
-
#endif
-
#include
#include
#include
@@ -35,22 +30,22 @@
#if defined(_MSC_VER) && _MSC_VER == 1500
typedef unsigned char uint8_t;
typedef signed __int32 int32_t;
+typedef unsigned __int64 uint64_t;
#else
-
#include
-
#endif
-#ifdef _MSC_VER
-#define NOMINMAX
-#include "winlibs/mman.h"
-#include
+#if defined(_MSC_VER) || defined(__MINGW32__)
+ #ifndef NOMINMAX
+ #define NOMINMAX
+ #endif
+ #include "mman.h"
+ #include
#else
-
-#include
-
+ #include
#endif
+#include
#include
#include
#include
@@ -58,785 +53,1251 @@ typedef signed __int32 int32_t;
#include
#include
+#ifdef _MSC_VER
// Needed for Visual Studio to disable runtime checks for mempcy
#pragma runtime_checks("s", off)
+#endif
// This allows others to supply their own logger / error printer without
// requiring Annoy to import their headers. See RcppAnnoy for a use case.
#ifndef __ERROR_PRINTER_OVERRIDE__
-#define showUpdate(...) { fprintf(stderr, __VA_ARGS__ ); }
+ #define showUpdate(...) { fprintf(stderr, __VA_ARGS__ ); }
#else
-#define showUpdate(...) { __ERROR_PRINTER_OVERRIDE__( __VA_ARGS__ ); }
+ #define showUpdate(...) { __ERROR_PRINTER_OVERRIDE__( __VA_ARGS__ ); }
#endif
#ifndef _MSC_VER
#define popcount __builtin_popcountll
+#else // See #293, #358
+#define isnan(x) _isnan(x)
+#define popcount cole_popcount
+#endif
+
+#if !defined(NO_MANUAL_VECTORIZATION) && defined(__GNUC__) && (__GNUC__ >6) && defined(__AVX512F__) // See #402
+#pragma message "Using 512-bit AVX instructions"
+#define USE_AVX512
+#elif !defined(NO_MANUAL_VECTORIZATION) && defined(__AVX__) && defined (__SSE__) && defined(__SSE2__) && defined(__SSE3__)
+#pragma message "Using 128-bit AVX instructions"
+#define USE_AVX
#else
-#define popcount __popcnt64
+#pragma message "Using no AVX instructions"
#endif
+#if defined(USE_AVX) || defined(USE_AVX512)
+#if defined(_MSC_VER)
+#include
+#elif defined(__GNUC__)
+#include
+#endif
+#endif
#ifndef ANNOY_NODE_ATTRIBUTE
-#ifndef _MSC_VER
-#define ANNOY_NODE_ATTRIBUTE __attribute__((__packed__))
-// TODO: this is turned on by default, but may not work for all architectures! Need to investigate.
-#else
-#define ANNOY_NODE_ATTRIBUTE
-#endif
+ #ifndef _MSC_VER
+ #define ANNOY_NODE_ATTRIBUTE __attribute__((__packed__))
+ // TODO: this is turned on by default, but may not work for all architectures! Need to investigate.
+ #else
+ #define ANNOY_NODE_ATTRIBUTE
+ #endif
#endif
using std::vector;
-using std::string;
using std::pair;
using std::numeric_limits;
using std::make_pair;
-template
-inline T get_norm(T *v, int f) {
- T sq_norm = 0;
- for (int z = 0; z < f; z++)
- sq_norm += v[z] * v[z];
- return sqrt(sq_norm);
+inline void* remap_memory(void* _ptr, int _fd, size_t old_size, size_t new_size) {
+#ifdef __linux__
+ _ptr = mremap(_ptr, old_size, new_size, MREMAP_MAYMOVE);
+#else
+ munmap(_ptr, old_size);
+#ifdef MAP_POPULATE
+ _ptr = mmap(_ptr, new_size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, _fd, 0);
+#else
+ _ptr = mmap(_ptr, new_size, PROT_READ | PROT_WRITE, MAP_SHARED, _fd, 0);
+#endif
+#endif
+ return _ptr;
}
-template
-inline void normalize(T *v, int f) {
- T norm = get_norm(v, f);
- for (int z = 0; z < f; z++)
- v[z] /= norm;
+namespace {
+
+template
+inline Node* get_node_ptr(const void* _nodes, const size_t _s, const S i) {
+ return (Node*)((uint8_t *)_nodes + (_s * i));
}
-template
-inline void two_means(const vector &nodes, int f, Random &random, bool cosine, T *iv, T *jv) {
- /*
- This algorithm is a huge heuristic. Empirically it works really well, but I
- can't motivate it well. The basic idea is to keep two centroids and assign
- points to either one of them. We weight each centroid by the number of points
- assigned to it, so to balance it.
- */
- static int iteration_steps = 200;
- size_t count = nodes.size();
-
- size_t i = random.index(count);
- size_t j = random.index(count - 1);
- j += (j >= i); // ensure that i != j
- memcpy(iv, nodes[i]->v, f * sizeof(T));
- memcpy(jv, nodes[j]->v, f * sizeof(T));
- if (cosine) {
- normalize(&iv[0], f);
- normalize(&jv[0], f);
- }
-
- int ic = 1, jc = 1;
- for (int l = 0; l < iteration_steps; l++) {
- size_t k = random.index(count);
- T di = ic * Distance::distance(&iv[0], nodes[k]->v, f),
- dj = jc * Distance::distance(&jv[0], nodes[k]->v, f);
- T norm = cosine ? get_norm(nodes[k]->v, f) : 1.0;
- if (di < dj) {
- for (int z = 0; z < f; z++)
- iv[z] = (iv[z] * ic + nodes[k]->v[z] / norm) / (ic + 1);
- ic++;
- } else if (dj < di) {
- for (int z = 0; z < f; z++)
- jv[z] = (jv[z] * jc + nodes[k]->v[z] / norm) / (jc + 1);
- jc++;
- }
- }
+template
+inline T dot(const T* x, const T* y, int f) {
+ T s = 0;
+ for (int z = 0; z < f; z++) {
+ s += (*x) * (*y);
+ x++;
+ y++;
+ }
+ return s;
}
+template
+inline T manhattan_distance(const T* x, const T* y, int f) {
+ T d = 0.0;
+ for (int i = 0; i < f; i++)
+ d += fabs(x[i] - y[i]);
+ return d;
+}
-struct Angular {
- template
- struct ANNOY_NODE_ATTRIBUTE Node {
- /*
- * We store a binary tree where each node has two things
- * - A vector associated with it
- * - Two children
- * All nodes occupy the same amount of memory
- * All nodes with n_descendants == 1 are leaf nodes.
- * A memory optimization is that for nodes with 2 <= n_descendants <= K,
- * we skip the vector. Instead we store a list of all descendants. K is
- * determined by the number of items that fits in the space of the vector.
- * For nodes with n_descendants == 1 the vector is a data point.
- * For nodes with n_descendants > K the vector is the normal of the split plane.
- * Note that we can't really do sizeof(node) because we cheat and allocate
- * more memory to be able to fit the vector outside
- */
- S n_descendants;
- S children[2]; // Will possibly store more than 2
- T v[1]; // We let this one overflow intentionally. Need to allocate at least 1 to make GCC happy
- };
+template
+inline T euclidean_distance(const T* x, const T* y, int f) {
+ // Don't use dot-product: avoid catastrophic cancellation in #314.
+ T d = 0.0;
+ for (int i = 0; i < f; ++i) {
+ const T tmp=*x - *y;
+ d += tmp * tmp;
+ ++x;
+ ++y;
+ }
+ return d;
+}
- template
- static inline T distance(const T *x, const T *y, int f) {
- // want to calculate (a/|a| - b/|b|)^2
- // = a^2 / a^2 + b^2 / b^2 - 2ab/|a||b|
- // = 2 - 2cos
- T pp = 0, qq = 0, pq = 0;
- for (int z = 0; z < f; z++, x++, y++) {
- pp += (*x) * (*x);
- qq += (*y) * (*y);
- pq += (*x) * (*y);
- }
- T ppqq = pp * qq;
- if (ppqq > 0) return 2.0 - 2.0 * pq / sqrt(ppqq);
- else return 2.0; // cos is 0
- }
+#ifdef USE_AVX
+// Horizontal single sum of 256bit vector.
+inline float hsum256_ps_avx(__m256 v) {
+ const __m128 x128 = _mm_add_ps(_mm256_extractf128_ps(v, 1), _mm256_castps256_ps128(v));
+ const __m128 x64 = _mm_add_ps(x128, _mm_movehl_ps(x128, x128));
+ const __m128 x32 = _mm_add_ss(x64, _mm_shuffle_ps(x64, x64, 0x55));
+ return _mm_cvtss_f32(x32);
+}
- template
- static inline T margin(const Node *n, const T *y, int f) {
- T dot = 0;
- for (int z = 0; z < f; z++)
- dot += n->v[z] * y[z];
- return dot;
+template<>
+inline float dot(const float* x, const float *y, int f) {
+ float result = 0;
+ if (f > 7) {
+ __m256 d = _mm256_setzero_ps();
+ for (; f > 7; f -= 8) {
+ d = _mm256_add_ps(d, _mm256_mul_ps(_mm256_loadu_ps(x), _mm256_loadu_ps(y)));
+ x += 8;
+ y += 8;
}
+ // Sum all floats in dot register.
+ result += hsum256_ps_avx(d);
+ }
+ // Don't forget the remaining values.
+ for (; f > 0; f--) {
+ result += *x * *y;
+ x++;
+ y++;
+ }
+ return result;
+}
- template
- static inline bool side(const Node *n, const T *y, int f, Random &random) {
- T dot = margin(n, y, f);
- if (dot != 0)
- return (dot > 0);
- else
- return random.flip();
+template<>
+inline float manhattan_distance(const float* x, const float* y, int f) {
+ float result = 0;
+ int i = f;
+ if (f > 7) {
+ __m256 manhattan = _mm256_setzero_ps();
+ __m256 minus_zero = _mm256_set1_ps(-0.0f);
+ for (; i > 7; i -= 8) {
+ const __m256 x_minus_y = _mm256_sub_ps(_mm256_loadu_ps(x), _mm256_loadu_ps(y));
+ const __m256 distance = _mm256_andnot_ps(minus_zero, x_minus_y); // Absolute value of x_minus_y (forces sign bit to zero)
+ manhattan = _mm256_add_ps(manhattan, distance);
+ x += 8;
+ y += 8;
}
+ // Sum all floats in manhattan register.
+ result = hsum256_ps_avx(manhattan);
+ }
+ // Don't forget the remaining values.
+ for (; i > 0; i--) {
+ result += fabsf(*x - *y);
+ x++;
+ y++;
+ }
+ return result;
+}
- template
- static inline void create_split(const vector *> &nodes, int f, Random &random, Node *n) {
- vector best_iv(f, 0), best_jv(f, 0); // TODO: avoid allocation
- two_means >(nodes, f, random, true, &best_iv[0], &best_jv[0]);
- for (int z = 0; z < f; z++)
- n->v[z] = best_iv[z] - best_jv[z];
- normalize(n->v, f);
+template<>
+inline float euclidean_distance(const float* x, const float* y, int f) {
+ float result=0;
+ if (f > 7) {
+ __m256 d = _mm256_setzero_ps();
+ for (; f > 7; f -= 8) {
+ const __m256 diff = _mm256_sub_ps(_mm256_loadu_ps(x), _mm256_loadu_ps(y));
+ d = _mm256_add_ps(d, _mm256_mul_ps(diff, diff)); // no support for fmadd in AVX...
+ x += 8;
+ y += 8;
}
+ // Sum all floats in dot register.
+ result = hsum256_ps_avx(d);
+ }
+ // Don't forget the remaining values.
+ for (; f > 0; f--) {
+ float tmp = *x - *y;
+ result += tmp * tmp;
+ x++;
+ y++;
+ }
+ return result;
+}
- template
- static inline T normalized_distance(T distance) {
- // Used when requesting distances from Python layer
- // Turns out sometimes the squared distance is -0.0
- // so we have to make sure it's a positive number.
- return sqrt(std::max(distance, T(0)));
- }
+#endif
- template
- static inline T pq_distance(T distance, T margin, int child_nr) {
- if (child_nr == 0)
- margin = -margin;
- return std::min(distance, margin);
+#ifdef USE_AVX512
+template<>
+inline float dot(const float* x, const float *y, int f) {
+ float result = 0;
+ if (f > 15) {
+ __m512 d = _mm512_setzero_ps();
+ for (; f > 15; f -= 16) {
+ //AVX512F includes FMA
+ d = _mm512_fmadd_ps(_mm512_loadu_ps(x), _mm512_loadu_ps(y), d);
+ x += 16;
+ y += 16;
}
+ // Sum all floats in dot register.
+ result += _mm512_reduce_add_ps(d);
+ }
+ // Don't forget the remaining values.
+ for (; f > 0; f--) {
+ result += *x * *y;
+ x++;
+ y++;
+ }
+ return result;
+}
- template
- static inline T pq_initial_value() {
- return numeric_limits::infinity();
+template<>
+inline float manhattan_distance(const float* x, const float* y, int f) {
+ float result = 0;
+ int i = f;
+ if (f > 15) {
+ __m512 manhattan = _mm512_setzero_ps();
+ for (; i > 15; i -= 16) {
+ const __m512 x_minus_y = _mm512_sub_ps(_mm512_loadu_ps(x), _mm512_loadu_ps(y));
+ manhattan = _mm512_add_ps(manhattan, _mm512_abs_ps(x_minus_y));
+ x += 16;
+ y += 16;
}
+ // Sum all floats in manhattan register.
+ result = _mm512_reduce_add_ps(manhattan);
+ }
+ // Don't forget the remaining values.
+ for (; i > 0; i--) {
+ result += fabsf(*x - *y);
+ x++;
+ y++;
+ }
+ return result;
+}
- static const char *name() {
- return "angular";
+template<>
+inline float euclidean_distance(const float* x, const float* y, int f) {
+ float result=0;
+ if (f > 15) {
+ __m512 d = _mm512_setzero_ps();
+ for (; f > 15; f -= 16) {
+ const __m512 diff = _mm512_sub_ps(_mm512_loadu_ps(x), _mm512_loadu_ps(y));
+ d = _mm512_fmadd_ps(diff, diff, d);
+ x += 16;
+ y += 16;
}
-};
-
-struct Hamming {
-
- template
- struct ANNOY_NODE_ATTRIBUTE Node {
- S n_descendants;
- S children[2];
- T v[1];
- };
-
- static const size_t max_iterations = 20;
+ // Sum all floats in dot register.
+ result = _mm512_reduce_add_ps(d);
+ }
+ // Don't forget the remaining values.
+ for (; f > 0; f--) {
+ float tmp = *x - *y;
+ result += tmp * tmp;
+ x++;
+ y++;
+ }
+ return result;
+}
- template
- static inline T pq_distance(T distance, T margin, int child_nr) {
- return distance - (margin != child_nr);
- }
+#endif
- template
- static inline T pq_initial_value() {
- return 0;
- }
+
+template
+inline T get_norm(T* v, int f) {
+ return sqrt(dot(v, v, f));
+}
- template
- static inline T distance(const T *x, const T *y, int f) {
- size_t dist = 0;
- for (size_t i = 0; i < f; i++) {
- dist += popcount(x[i] ^ y[i]);
- }
- return dist;
- }
-
- template
- static inline bool margin(const Node *n, const T *y, int f) {
- static const size_t n_bits = sizeof(T) * 8;
- T chunk = n->v[0] / n_bits;
- return (y[chunk] & (static_cast(1) << (n_bits - 1 - (n->v[0] % n_bits)))) != 0;
- }
-
- template
- static inline bool side(const Node *n, const T *y, int f, Random &random) {
- return margin(n, y, f);
- }
-
- template
- static inline void create_split(const vector *> &nodes, int f, Random &random, Node *n) {
- size_t cur_size = 0;
- int i = 0;
- for (; i < max_iterations; i++) {
- // choose random position to split at
- n->v[0] = random.index(f);
- cur_size = 0;
- for (typename vector *>::const_iterator it = nodes.begin(); it != nodes.end(); ++it) {
- if (margin(n, (*it)->v, f)) {
- cur_size++;
- }
- }
- if (cur_size > 0 && cur_size < nodes.size()) {
- break;
- }
- }
- // brute-force search for splitting coordinate
- if (i == max_iterations) {
- int j = 0;
- for (; j < f; j++) {
- n->v[0] = j;
- cur_size = 0;
- for (typename vector *>::const_iterator it = nodes.begin(); it != nodes.end(); ++it) {
- if (margin(n, (*it)->v, f)) {
- cur_size++;
- }
- }
- if (cur_size > 0 && cur_size < nodes.size()) {
- break;
- }
- }
- }
+template
+inline void two_means(const vector& nodes, int f, Random& random, bool cosine, Node* p, Node* q) {
+ /*
+ This algorithm is a huge heuristic. Empirically it works really well, but I
+ can't motivate it well. The basic idea is to keep two centroids and assign
+ points to either one of them. We weight each centroid by the number of points
+ assigned to it, so to balance it.
+ */
+ static int iteration_steps = 200;
+ size_t count = nodes.size();
+
+ size_t i = random.index(count);
+ size_t j = random.index(count-1);
+ j += (j >= i); // ensure that i != j
+
+ Distance::template copy_node(p, nodes[i], f);
+ Distance::template copy_node(q, nodes[j], f);
+
+ if (cosine) { Distance::template normalize(p, f); Distance::template normalize(q, f); }
+ Distance::init_node(p, f);
+ Distance::init_node(q, f);
+
+ int ic = 1, jc = 1;
+ for (int l = 0; l < iteration_steps; l++) {
+ size_t k = random.index(count);
+ T di = ic * Distance::distance(p, nodes[k], f),
+ dj = jc * Distance::distance(q, nodes[k], f);
+ T norm = cosine ? get_norm(nodes[k]->v, f) : 1.0;
+ if (!(norm > T(0))) {
+ continue;
}
-
- template
- static inline T normalized_distance(T distance) {
- return distance;
+ if (di < dj) {
+ for (int z = 0; z < f; z++)
+ p->v[z] = (p->v[z] * ic + nodes[k]->v[z] / norm) / (ic + 1);
+ Distance::init_node(p, f);
+ ic++;
+ } else if (dj < di) {
+ for (int z = 0; z < f; z++)
+ q->v[z] = (q->v[z] * jc + nodes[k]->v[z] / norm) / (jc + 1);
+ Distance::init_node(q, f);
+ jc++;
}
-
- static const char *name() {
- return "hamming";
+ }
+}
+} // namespace
+
+struct Base {
+ template
+ static inline void preprocess(void* nodes, size_t _s, const S node_count, const int f) {
+ // Override this in specific metric structs below if you need to do any pre-processing
+ // on the entire set of nodes passed into this index.
+ }
+
+ template
+ static inline void zero_value(Node* dest) {
+ // Initialize any fields that require sane defaults within this node.
+ }
+
+ template
+ static inline void copy_node(Node* dest, const Node* source, const int f) {
+ memcpy(dest->v, source->v, f * sizeof(T));
+ }
+
+ template
+ static inline void normalize(Node* node, int f) {
+ T norm = get_norm(node->v, f);
+ if (norm > 0) {
+ for (int z = 0; z < f; z++)
+ node->v[z] /= norm;
}
+ }
};
-struct Minkowski {
- template
- struct ANNOY_NODE_ATTRIBUTE Node {
- S n_descendants;
- T a; // need an extra constant term to determine the offset of the plane
- S children[2];
- T v[1];
+struct Angular : Base {
+ template
+ struct ANNOY_NODE_ATTRIBUTE Node {
+ /*
+ * We store a binary tree where each node has two things
+ * - A vector associated with it
+ * - Two children
+ * All nodes occupy the same amount of memory
+ * All nodes with n_descendants == 1 are leaf nodes.
+ * A memory optimization is that for nodes with 2 <= n_descendants <= K,
+ * we skip the vector. Instead we store a list of all descendants. K is
+ * determined by the number of items that fits in the space of the vector.
+ * For nodes with n_descendants == 1 the vector is a data point.
+ * For nodes with n_descendants > K the vector is the normal of the split plane.
+ * Note that we can't really do sizeof(node) because we cheat and allocate
+ * more memory to be able to fit the vector outside
+ */
+ S n_descendants;
+ union {
+ S children[2]; // Will possibly store more than 2
+ T norm;
};
+ T v[1]; // We let this one overflow intentionally. Need to allocate at least 1 to make GCC happy
+ };
+ template
+ static inline T distance(const Node* x, const Node* y, int f) {
+ // want to calculate (a/|a| - b/|b|)^2
+ // = a^2 / a^2 + b^2 / b^2 - 2ab/|a||b|
+ // = 2 - 2cos
+ T pp = x->norm ? x->norm : dot(x->v, x->v, f); // For backwards compatibility reasons, we need to fall back and compute the norm here
+ T qq = y->norm ? y->norm : dot(y->v, y->v, f);
+ T pq = dot(x->v, y->v, f);
+ T ppqq = pp * qq;
+ if (ppqq > 0) return 2.0 - 2.0 * pq / sqrt(ppqq);
+ else return 2.0; // cos is 0
+ }
+ template
+ static inline T margin(const Node* n, const T* y, int f) {
+ return dot(n->v, y, f);
+ }
+ template
+ static inline bool side(const Node* n, const T* y, int f, Random& random) {
+ T dot = margin(n, y, f);
+ if (dot != 0)
+ return (dot > 0);
+ else
+ return random.flip();
+ }
+ template
+ static inline void create_split(const vector*>& nodes, int f, size_t s, Random& random, Node* n) {
+ Node* p = (Node*)alloca(s);
+ Node* q = (Node*)alloca(s);
+ two_means >(nodes, f, random, true, p, q);
+ for (int z = 0; z < f; z++)
+ n->v[z] = p->v[z] - q->v[z];
+ Base::normalize >(n, f);
+ }
+ template
+ static inline T normalized_distance(T distance) {
+ // Used when requesting distances from Python layer
+ // Turns out sometimes the squared distance is -0.0
+ // so we have to make sure it's a positive number.
+ return sqrt(std::max(distance, T(0)));
+ }
+ template
+ static inline T pq_distance(T distance, T margin, int child_nr) {
+ if (child_nr == 0)
+ margin = -margin;
+ return std::min(distance, margin);
+ }
+ template
+ static inline T pq_initial_value() {
+ return numeric_limits::infinity();
+ }
+ template
+ static inline void init_node(Node* n, int f) {
+ n->norm = dot(n->v, n->v, f);
+ }
+ static const char* name() {
+ return "angular";
+ }
+};
- template
- static inline T margin(const Node *n, const T *y, int f) {
- T dot = n->a;
- for (int z = 0; z < f; z++)
- dot += n->v[z] * y[z];
- return dot;
- }
-
- template
- static inline bool side(const Node *n, const T *y, int f, Random &random) {
- T dot = margin(n, y, f);
- if (dot != 0)
- return (dot > 0);
- else
- return random.flip();
- }
- template
- static inline T pq_distance(T distance, T margin, int child_nr) {
- if (child_nr == 0)
- margin = -margin;
- return std::min(distance, margin);
+struct DotProduct : Angular {
+ template
+ struct ANNOY_NODE_ATTRIBUTE Node {
+ /*
+ * This is an extension of the Angular node with an extra attribute for the scaled norm.
+ */
+ S n_descendants;
+ S children[2]; // Will possibly store more than 2
+ T dot_factor;
+ T v[1]; // We let this one overflow intentionally. Need to allocate at least 1 to make GCC happy
+ };
+
+ static const char* name() {
+ return "dot";
+ }
+ template
+ static inline T distance(const Node* x, const Node* y, int f) {
+ return -dot(x->v, y->v, f);
+ }
+
+ template
+ static inline void zero_value(Node* dest) {
+ dest->dot_factor = 0;
+ }
+
+ template
+ static inline void init_node(Node* n, int f) {
+ }
+
+ template
+ static inline void copy_node(Node* dest, const Node* source, const int f) {
+ memcpy(dest->v, source->v, f * sizeof(T));
+ dest->dot_factor = source->dot_factor;
+ }
+
+ template
+ static inline void create_split(const vector*>& nodes, int f, size_t s, Random& random, Node* n) {
+ Node* p = (Node*)alloca(s);
+ Node* q = (Node*)alloca(s);
+ DotProduct::zero_value(p);
+ DotProduct::zero_value(q);
+ two_means >(nodes, f, random, true, p, q);
+ for (int z = 0; z < f; z++)
+ n->v[z] = p->v[z] - q->v[z];
+ n->dot_factor = p->dot_factor - q->dot_factor;
+ DotProduct::normalize >(n, f);
+ }
+
+ template
+ static inline void normalize(Node* node, int f) {
+ T norm = sqrt(dot(node->v, node->v, f) + pow(node->dot_factor, 2));
+ if (norm > 0) {
+ for (int z = 0; z < f; z++)
+ node->v[z] /= norm;
+ node->dot_factor /= norm;
}
-
- template
- static inline T pq_initial_value() {
- return numeric_limits::infinity();
+ }
+
+ template
+ static inline T margin(const Node* n, const T* y, int f) {
+ return dot(n->v, y, f) + (n->dot_factor * n->dot_factor);
+ }
+
+ template
+ static inline bool side(const Node* n, const T* y, int f, Random& random) {
+ T dot = margin(n, y, f);
+ if (dot != 0)
+ return (dot > 0);
+ else
+ return random.flip();
+ }
+
+ template
+ static inline T normalized_distance(T distance) {
+ return -distance;
+ }
+
+ template
+ static inline void preprocess(void* nodes, size_t _s, const S node_count, const int f) {
+ // This uses a method from Microsoft Research for transforming inner product spaces to cosine/angular-compatible spaces.
+ // (Bachrach et al., 2014, see https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/XboxInnerProduct.pdf)
+
+ // Step one: compute the norm of each vector and store that in its extra dimension (f-1)
+ for (S i = 0; i < node_count; i++) {
+ Node* node = get_node_ptr(nodes, _s, i);
+ T norm = sqrt(dot(node->v, node->v, f));
+ if (isnan(norm)) norm = 0;
+ node->dot_factor = norm;
}
-};
-
-struct Euclidean : Minkowski {
- template
- static inline T distance(const T *x, const T *y, int f) {
- T d = 0.0;
- for (int i = 0; i < f; i++, x++, y++)
- d += ((*x) - (*y)) * ((*x) - (*y));
- return d;
+ // Step two: find the maximum norm
+ T max_norm = 0;
+ for (S i = 0; i < node_count; i++) {
+ Node* node = get_node_ptr(nodes, _s, i);
+ if (node->dot_factor > max_norm) {
+ max_norm = node->dot_factor;
+ }
}
- template
- static inline void create_split(const vector *
+ // Step three: set each vector's extra dimension to sqrt(max_norm^2 - norm^2)
+ for (S i = 0; i < node_count; i++) {
+ Node* node = get_node_ptr(nodes, _s, i);
+ T node_norm = node->dot_factor;
- >& nodes,
- int f, Random
- & random,
- Node *n
- ) {
- vector best_iv(f, 0), best_jv(f, 0);
- two_means > (nodes, f, random, false, &best_iv[0], &best_jv[0]);
+ T dot_factor = sqrt(pow(max_norm, static_cast(2.0)) - pow(node_norm, static_cast(2.0)));
+ if (isnan(dot_factor)) dot_factor = 0;
- for (int z = 0; z < f; z++)
- n->v[z] = best_iv[z] - best_jv[z];
- normalize(n->v, f);
- n->a = 0.0;
- for (int z = 0; z < f; z++)
- n->a += -n->v[z] * (best_iv[z] + best_jv[z]) / 2;
- }
-
- template
- static inline T normalized_distance(T distance) {
- return sqrt(std::max(distance, T(0)));
- }
-
- static const char *name() {
- return "euclidean";
+ node->dot_factor = dot_factor;
}
+ }
};
-struct Manhattan : Minkowski {
- template
- static inline T distance(const T *x, const T *y, int f) {
- T d = 0.0;
- for (int i = 0; i < f; i++, x++, y++)
- d += fabs((*x) - (*y));
- return d;
- }
-
- template
- static inline void create_split(const vector *
-
- >& nodes,
- int f, Random
- & random,
- Node *n
- ) {
- vector best_iv(f, 0), best_jv(f, 0);
- two_means > (nodes, f, random, false, &best_iv[0], &best_jv[0]);
-
- for (int z = 0; z < f; z++)
- n->v[z] = best_iv[z] - best_jv[z];
- normalize(n->v, f);
- n->a = 0.0;
- for (int z = 0; z < f; z++)
- n->a += -n->v[z] * (best_iv[z] + best_jv[z]) / 2;
+struct Hamming : Base {
+ template
+ struct ANNOY_NODE_ATTRIBUTE Node {
+ S n_descendants;
+ S children[2];
+ T v[1];
+ };
+
+ static const size_t max_iterations = 20;
+
+ template
+ static inline T pq_distance(T distance, T margin, int child_nr) {
+ return distance - (margin != (unsigned int) child_nr);
+ }
+
+ template
+ static inline T pq_initial_value() {
+ return numeric_limits::max();
+ }
+ template
+ static inline int cole_popcount(T v) {
+ // Note: Only used with MSVC 9, which lacks intrinsics and fails to
+ // calculate std::bitset::count for v > 32bit. Uses the generalized
+ // approach by Eric Cole.
+ // See https://graphics.stanford.edu/~seander/bithacks.html#CountBitsSet64
+ v = v - ((v >> 1) & (T)~(T)0/3);
+ v = (v & (T)~(T)0/15*3) + ((v >> 2) & (T)~(T)0/15*3);
+ v = (v + (v >> 4)) & (T)~(T)0/255*15;
+ return (T)(v * ((T)~(T)0/255)) >> (sizeof(T) - 1) * 8;
+ }
+ template
+ static inline T distance(const Node* x, const Node* y, int f) {
+ size_t dist = 0;
+ for (int i = 0; i < f; i++) {
+ dist += popcount(x->v[i] ^ y->v[i]);
}
-
- template
- static inline T normalized_distance(T distance) {
- return std::max(distance, T(0));
+ return dist;
+ }
+ template
+ static inline bool margin(const Node* n, const T* y, int f) {
+ static const size_t n_bits = sizeof(T) * 8;
+ T chunk = n->v[0] / n_bits;
+ return (y[chunk] & (static_cast(1) << (n_bits - 1 - (n->v[0] % n_bits)))) != 0;
+ }
+ template
+ static inline bool side(const Node* n, const T* y, int f, Random& random) {
+ return margin(n, y, f);
+ }
+ template
+ static inline void create_split(const vector*>& nodes, int f, size_t s, Random& random, Node* n) {
+ size_t cur_size = 0;
+ size_t i = 0;
+ int dim = f * 8 * sizeof(T);
+ for (; i < max_iterations; i++) {
+ // choose random position to split at
+ n->v[0] = random.index(dim);
+ cur_size = 0;
+ for (typename vector*>::const_iterator it = nodes.begin(); it != nodes.end(); ++it) {
+ if (margin(n, (*it)->v, f)) {
+ cur_size++;
+ }
+ }
+ if (cur_size > 0 && cur_size < nodes.size()) {
+ break;
+ }
}
-
- static const char *name() {
- return "manhattan";
+ // brute-force search for splitting coordinate
+ if (i == max_iterations) {
+ int j = 0;
+ for (; j < dim; j++) {
+ n->v[0] = j;
+ cur_size = 0;
+ for (typename vector*>::const_iterator it = nodes.begin(); it != nodes.end(); ++it) {
+ if (margin(n, (*it)->v, f)) {
+ cur_size++;
+ }
+ }
+ if (cur_size > 0 && cur_size < nodes.size()) {
+ break;
+ }
+ }
}
+ }
+ template
+ static inline T normalized_distance(T distance) {
+ return distance;
+ }
+ template
+ static inline void init_node(Node* n, int f) {
+ }
+ static const char* name() {
+ return "hamming";
+ }
};
-template
-class AnnoyIndexInterface {
-public:
- virtual ~AnnoyIndexInterface() {};
-
- virtual void add_item(S item, const T *w) = 0;
-
- virtual void build(int q) = 0;
-
- virtual void unbuild() = 0;
-
- virtual bool save(const char *filename) = 0;
-
- virtual void unload() = 0;
- virtual bool load(const char *filename) = 0;
+struct Minkowski : Base {
+ template
+ struct ANNOY_NODE_ATTRIBUTE Node {
+ S n_descendants;
+ T a; // need an extra constant term to determine the offset of the plane
+ S children[2];
+ T v[1];
+ };
+ template
+ static inline T margin(const Node* n, const T* y, int f) {
+ return n->a + dot(n->v, y, f);
+ }
+ template
+ static inline bool side(const Node* n, const T* y, int f, Random& random) {
+ T dot = margin(n, y, f);
+ if (dot != 0)
+ return (dot > 0);
+ else
+ return random.flip();
+ }
+ template
+ static inline T pq_distance(T distance, T margin, int child_nr) {
+ if (child_nr == 0)
+ margin = -margin;
+ return std::min(distance, margin);
+ }
+ template
+ static inline T pq_initial_value() {
+ return numeric_limits::infinity();
+ }
+};
- virtual T get_distance(S i, S j) = 0;
- virtual void get_nns_by_item(S item, size_t n, size_t search_k, vector *result, vector *distances) = 0;
+struct Euclidean : Minkowski {
+ template
+ static inline T distance(const Node* x, const Node* y, int f) {
+ return euclidean_distance(x->v, y->v, f);
+ }
+ template
+ static inline void create_split(const vector*>& nodes, int f, size_t s, Random& random, Node* n) {
+ Node* p = (Node*)alloca(s);
+ Node* q = (Node*)alloca(s);
+ two_means >(nodes, f, random, false, p, q);
- virtual void get_nns_by_vector(const T *w, size_t n, size_t search_k, vector *result,
- vector *distances) = 0;
+ for (int z = 0; z < f; z++)
+ n->v[z] = p->v[z] - q->v[z];
+ Base::normalize >(n, f);
+ n->a = 0.0;
+ for (int z = 0; z < f; z++)
+ n->a += -n->v[z] * (p->v[z] + q->v[z]) / 2;
+ }
+ template
+ static inline T normalized_distance(T distance) {
+ return sqrt(std::max(distance, T(0)));
+ }
+ template
+ static inline void init_node(Node* n, int f) {
+ }
+ static const char* name() {
+ return "euclidean";
+ }
- virtual S get_n_items() = 0;
+};
- virtual void verbose(bool v) = 0;
+struct Manhattan : Minkowski {
+ template
+ static inline T distance(const Node* x, const Node* y, int f) {
+ return manhattan_distance(x->v, y->v, f);
+ }
+ template
+ static inline void create_split(const vector*>& nodes, int f, size_t s, Random& random, Node* n) {
+ Node* p = (Node*)alloca(s);
+ Node* q = (Node*)alloca(s);
+ two_means >(nodes, f, random, false, p, q);
- virtual void get_item(S item, T *v) = 0;
+ for (int z = 0; z < f; z++)
+ n->v[z] = p->v[z] - q->v[z];
+ Base::normalize >(n, f);
+ n->a = 0.0;
+ for (int z = 0; z < f; z++)
+ n->a += -n->v[z] * (p->v[z] + q->v[z]) / 2;
+ }
+ template
+ static inline T normalized_distance(T distance) {
+ return std::max(distance, T(0));
+ }
+ template
+ static inline void init_node(Node* n, int f) {
+ }
+ static const char* name() {
+ return "manhattan";
+ }
+};
- virtual void set_seed(int q) = 0;
+template
+class AnnoyIndexInterface {
+ public:
+ virtual ~AnnoyIndexInterface() {};
+ virtual bool add_item(S item, const T* w, char** error=NULL) = 0;
+ virtual bool build(int q, char** error=NULL) = 0;
+ virtual bool unbuild(char** error=NULL) = 0;
+ virtual bool save(const char* filename, bool prefault=false, char** error=NULL) = 0;
+ virtual void unload() = 0;
+ virtual bool load(const char* filename, bool prefault=false, char** error=NULL) = 0;
+ virtual T get_distance(S i, S j) const = 0;
+ virtual void get_nns_by_item(S item, size_t n, size_t search_k, vector* result, vector* distances) const = 0;
+ virtual void get_nns_by_vector(const T* w, size_t n, size_t search_k, vector* result, vector* distances) const = 0;
+ virtual S get_n_items() const = 0;
+ virtual S get_n_trees() const = 0;
+ virtual void verbose(bool v) = 0;
+ virtual void get_item(S item, T* v) const = 0;
+ virtual void set_seed(int q) = 0;
+ virtual bool on_disk_build(const char* filename, char** error=NULL) = 0;
};
template
-class AnnoyIndex : public AnnoyIndexInterface {
- /*
- * We use random projection to build a forest of binary trees of all items.
- * Basically just split the hyperspace into two sides by a hyperplane,
- * then recursively split each of those subtrees etc.
- * We create a tree like this q times. The default q is determined automatically
- * in such a way that we at most use 2x as much memory as the vectors take.
- */
+ class AnnoyIndex : public AnnoyIndexInterface {
+ /*
+ * We use random projection to build a forest of binary trees of all items.
+ * Basically just split the hyperspace into two sides by a hyperplane,
+ * then recursively split each of those subtrees etc.
+ * We create a tree like this q times. The default q is determined automatically
+ * in such a way that we at most use 2x as much memory as the vectors take.
+ */
public:
- typedef Distance D;
- typedef typename D::template Node Node;
+ typedef Distance D;
+ typedef typename D::template Node Node;
protected:
- const int _f;
- size_t _s;
- S _n_items;
- Random _random;
- void *_nodes; // Could either be mmapped, or point to a memory buffer that we reallocate
- S _n_nodes;
- S _nodes_size;
- vector _roots;
- S _K;
- bool _loaded;
- bool _verbose;
- int _fd;
+ const int _f;
+ size_t _s;
+ S _n_items;
+ Random _random;
+ void* _nodes; // Could either be mmapped, or point to a memory buffer that we reallocate
+ S _n_nodes;
+ S _nodes_size;
+ vector _roots;
+ S _K;
+ bool _loaded;
+ bool _verbose;
+ int _fd;
+ bool _on_disk;
+ bool _built;
public:
- AnnoyIndex(int f) : _f(f), _random() {
- _s = offsetof(Node, v) + f * sizeof(T); // Size of each node
- _verbose = false;
- _K = (_s - offsetof(Node, children)) / sizeof(S); // Max number of descendants to fit into node
- reinitialize(); // Reset everything
+ AnnoyIndex(int f) : _f(f), _random() {
+ _s = offsetof(Node, v) + _f * sizeof(T); // Size of each node
+ _verbose = false;
+ _built = false;
+ _K = (S) (((size_t) (_s - offsetof(Node, children))) / sizeof(S)); // Max number of descendants to fit into node
+ reinitialize(); // Reset everything
+ }
+ ~AnnoyIndex() {
+ unload();
+ }
+
+ int get_f() const {
+ return _f;
+ }
+
+ bool add_item(S item, const T* w, char** error=NULL) {
+ return add_item_impl(item, w, error);
+ }
+
+ template
+ bool add_item_impl(S item, const W& w, char** error=NULL) {
+ if (_loaded) {
+ showUpdate("You can't add an item to a loaded index\n");
+ if (error) *error = (char *)"You can't add an item to a loaded index";
+ return false;
}
-
- ~AnnoyIndex() {
- unload();
+ _allocate_size(item + 1);
+ Node* n = _get(item);
+
+ D::zero_value(n);
+
+ n->children[0] = 0;
+ n->children[1] = 0;
+ n->n_descendants = 1;
+
+ for (int z = 0; z < _f; z++)
+ n->v[z] = w[z];
+
+ D::init_node(n, _f);
+
+ if (item >= _n_items)
+ _n_items = item + 1;
+
+ return true;
+ }
+
+ bool on_disk_build(const char* file, char** error=NULL) {
+ _on_disk = true;
+ _fd = open(file, O_RDWR | O_CREAT | O_TRUNC, (int) 0600);
+ if (_fd == -1) {
+ showUpdate("Error: file descriptor is -1\n");
+ if (error) *error = strerror(errno);
+ _fd = 0;
+ return false;
}
-
- int get_f() const {
- return _f;
+ _nodes_size = 1;
+ if (ftruncate(_fd, _s * _nodes_size) == -1) {
+ showUpdate("Error truncating file: %s\n", strerror(errno));
+ if (error) *error = strerror(errno);
+ return false;
+ }
+#ifdef MAP_POPULATE
+ _nodes = (Node*) mmap(0, _s * _nodes_size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, _fd, 0);
+#else
+ _nodes = (Node*) mmap(0, _s * _nodes_size, PROT_READ | PROT_WRITE, MAP_SHARED, _fd, 0);
+#endif
+ return true;
+ }
+
+ bool build(int q, char** error=NULL) {
+ if (_loaded) {
+ showUpdate("You can't build a loaded index\n");
+ if (error) *error = (char *)"You can't build a loaded index";
+ return false;
}
- void add_item(S item, const T *w) {
- add_item_impl(item, w);
+ if (_built) {
+ showUpdate("You can't build a built index\n");
+ if (error) *error = (char *)"You can't build a built index";
+ return false;
}
- template