<?xml version="1.0" encoding="utf-8"?> 
<rss version="2.0">
 <channel>
  <title>SHRIPHANI PALAKODETY: Posts tagged 'dimension-reduction'</title>
  <description>SHRIPHANI PALAKODETY: Posts tagged 'dimension-reduction'</description>
  <link>http://blog.shriphani.com/tags/dimension-reduction.html</link>
  <lastBuildDate>Tue, 07 Apr 2015 02:54:43 UT</lastBuildDate>
  <pubDate>Tue, 07 Apr 2015 02:54:43 UT</pubDate>
  <ttl>1800</ttl>
  <item>
   <title>The Smallest Eigenvalues of a Graph Laplacian</title>
   <link>http://blog.shriphani.com/2015/04/06/the-smallest-eigenvalues-of-a-graph-laplacian/?utm_source=dimension-reduction&amp;utm_medium=RSS</link>
   <guid>urn:http-blog-shriphani-com:-2015-04-06-the-smallest-eigenvalues-of-a-graph-laplacian</guid>
   <pubDate>Tue, 07 Apr 2015 02:54:43 UT</pubDate>
   <description>&lt;html&gt;
&lt;p&gt;Given a graph $ G = (V, E) $, its adjacency matrix $ A $ contains an entry at $ A_{ij} $ if vertices $ i $ and $ j $ have an edge between them. The degree matrix $ D $ contains the degree of each vertex along its diagonal.&lt;/p&gt;

&lt;p&gt;The graph laplacian of $ G $ is given by $ D - A $.&lt;/p&gt;

&lt;p&gt;Several popular techniques leverage the information contained in this matrix. This blog post focuses on the two smallest eigenvalues.&lt;/p&gt;

&lt;p&gt;First, we look at the eigenvalue 0 and its eigenvectors. A very elegant result about its multiplicity forms the foundation of spectral clustering.&lt;/p&gt;

&lt;p&gt;Then we look at the second smallest eigenvalue and the corresponding eigenvector. A slightly more involved result (YMMV) allows us to partition the graph in question. A recent publication by John Urschel (who apparently moonlights as a sportsperson) focused on this quantity.&lt;/p&gt;

&lt;p&gt;The insights provided here sacrifice some rigor for the sake of brevity. I find such descriptions help me study without getting bogged down too much with details. A bibliography provided at the end contains links to actual proofs.&lt;/p&gt;
&lt;!-- more--&gt;

&lt;h1 id="eigenvalue-0"&gt;Eigenvalue 0&lt;/h1&gt;

&lt;h2 id="the-insight"&gt;The Insight&lt;/h2&gt;

&lt;p&gt;The zero-th eigenvalue tells us whether the graph is connected or not.&lt;/p&gt;

&lt;p&gt;In particular, if a graph has $ k $ connected components, then eigenvalue 0 has multiplicity $ k $ (i.e. $ k $ distinct non-trivial eigenvectors).&lt;/p&gt;

&lt;p&gt;A blueprint for the proof looks like this (detailed proof provided later):&lt;/p&gt;

&lt;ul&gt;
 &lt;li&gt;The eigenvector corresponding to eigenvalue 0 (known hereafter as $ \lambda_0  $) must contain some non-zero entries - (this is established by showing that $  L $ is positive semi-definite (psd)).&lt;/li&gt;
 &lt;li&gt;In fact, if vertices $ i $ and $ j $ are connected, then components $ i $ and  $ j $ in $ \lambda_0 $ must be equal.&lt;/li&gt;
 &lt;li&gt;If the graph is connected, apply transitive property and you get an  eigenvector where all the components are equal (or all components set to 1).&lt;/li&gt;&lt;/ul&gt;

&lt;p&gt; This looks like so:  &lt;img src="/img/ev_all_ones.svg" /&gt;&lt;/p&gt;

&lt;ul&gt;
 &lt;li&gt;If the graph is not connected, then consider each connected component  separately and run this procedure on it again. For $ k $ connected portions of  the graph, we should have $ k $ distinct eigenvectors, each of which contains  a distinct, disjoint set of components set to 1.&lt;/li&gt;&lt;/ul&gt;

&lt;p&gt; So, if the graph has 2 connected components, then the eigenvalue 0 has 2  non-trivial eigenvectors:
 &lt;br /&gt;  &lt;img src="/img/several_evs.svg" /&gt;&lt;/p&gt;

&lt;p&gt; In the diagram above, the vertices 1, 2, and 3 form one connected component  and vertices 0, 4, and 5 form the other component.&lt;/p&gt;

&lt;p&gt; A toy example illustrates this nicely.&lt;/p&gt;

&lt;p&gt; In this example, we have a graph with 6 vertices. Say the graph is connected.&lt;/p&gt;

&lt;p&gt; Let us see what the value of the desired eigenvector is $ &amp;lt; 1, 1, 1, 1, 1, 1 &amp;gt; $.&lt;/p&gt;

&lt;p&gt; For 2 connected components, this value possibly is $ &amp;lt; 1, 1, 1, 0, 0, 0 &amp;gt; and &amp;lt; 0, 0, 0, 1, 1, 1 &amp;gt; $&lt;/p&gt;

&lt;p&gt; And so on.&lt;/p&gt;

&lt;h2 id="from-eigenvectors-to-clustering"&gt;From Eigenvectors to Clustering&lt;/h2&gt;

&lt;p&gt;The gist of the previous section is:&lt;/p&gt;

&lt;p&gt;(i) The multiplicity of eigenvalue 0 equals the number of connected components. (ii) If vertices $ i $ and $ j $ are connected, then in a certain eigenvector corresponding to the eigenvalue zero, the $ i^{th} $ and $ j^{th} $ components are set to 1.&lt;/p&gt;

&lt;p&gt;The next task is to use these insights for clustering a set of points.&lt;/p&gt;

&lt;p&gt;A simple strategy seems to be (i) constructing a graph from this point set, (ii) ensuring that different clusters become different connected components in this graph, and (iii) look at the eigenvectors of the first eigenvalue for some assistance in discovering these clusters.&lt;/p&gt;

&lt;p&gt;The first two steps are trivial. We can build a $ k $-NN graph - a graph where each point in the dataset is a vertex and an edge exists between this point and the $ k $ points closest to it.&lt;/p&gt;

&lt;p&gt;So this diagram illustrates this setting. Let us say we have 6 points in the dataset and 2 clusters.&lt;/p&gt;

&lt;p&gt;Let us construct a $ k $-NN graph - I&amp;rsquo;m going to set $ k \leftarrow 2 $ (this is for convenience). The $ k $-NN graph looks like:&lt;/p&gt;

&lt;p&gt;&lt;img src="/img/toy_knn.svg" /&gt;&lt;/p&gt;

&lt;p&gt;Thus there are 2 connected components.&lt;/p&gt;

&lt;p&gt;The insight from the previous section tells us that the eigenvalue 0 has multiplicity 2 and the 2 distinct eigenvectors look like:&lt;/p&gt;

&lt;p&gt;&lt;img src="/img/toy_evs.svg" /&gt;&lt;/p&gt;

&lt;p&gt;Consider a matrix with these eigenvectors as its columns:&lt;/p&gt;

&lt;div&gt;$$ \begin{bmatrix} 1 &amp;amp; 0\\ 1 &amp;amp; 0\\ 1 &amp;amp; 0\\ 0 &amp;amp; 1\\ 0 &amp;amp; 1\\ 0 &amp;amp; 1\\ \end{bmatrix} $$&lt;/div&gt;

&lt;p&gt;This matrix has as many rows as the original dataset.&lt;/p&gt;

&lt;p&gt;It also has the effect of causing the clusters in the graph to pop out.&lt;/p&gt;

&lt;p&gt;In this example, I can tell that the first three points belong in the same cluster. The next three points form the second cluster.&lt;/p&gt;

&lt;p&gt;If you supply this matrix to any classic clustering algorithm (say $ k $-means), it should have no issues clustering this and assigning points to the correct clusters.&lt;/p&gt;

&lt;p&gt;This is exactly what spectral clustering does.&lt;/p&gt;

&lt;p&gt;Thus the steps involved are:&lt;/p&gt;

&lt;ul&gt;
 &lt;li&gt;Construct a $ k $-NN graph (or indeed any other graph - say one that uses a  threshold test on euclidean distances).&lt;/li&gt;
 &lt;li&gt;Obtain the laplacian of this graph.&lt;/li&gt;
 &lt;li&gt;Obtain the eigendecomposition of the laplacian, retain the first $ k $  columns of the eigenvector matrix.&lt;/li&gt;
 &lt;li&gt;Supply this matrix to $ k $-means (or your favorite clustering algorithm).&lt;/li&gt;&lt;/ul&gt;

&lt;p&gt;Spectral clustering deals well with non-convex cluster shapes because of the underlying graph constructed. The manifold considered as a result captures the shape of the clusters reasonably well - something we cannot accomplish if only euclidean distances are used:&lt;/p&gt;

&lt;p&gt;&lt;img src="/img/non_convex.svg" /&gt;&lt;/p&gt;

&lt;p&gt;This is a neat trick exploited by the &lt;a href="http://blog.shriphani.com/2014/11/12/the-isomap-algorithm/"&gt;isomap algorithm&lt;/a&gt; which was covered in a previous post.&lt;/p&gt;

&lt;p&gt;Constructing the graph tends to be a bit involved - often there isn&amp;rsquo;t a clear way to build one. The performance of spectral clustering depends on how the connected components in the graph reflect clusters in the dataset.&lt;/p&gt;

&lt;p&gt;A connected graph (which you can produce quite easily by picking a large $ k $) will yield poor results.&lt;/p&gt;

&lt;p&gt;Despite these issues, spectral clustering is a very powerful and well-studied technique and belongs in any practitioner&amp;rsquo;s toolbox (IMO).&lt;/p&gt;

&lt;h1 id="second-smallest-eigenvalue-of-the-laplacian"&gt;Second Smallest Eigenvalue of the Laplacian&lt;/h1&gt;

&lt;p&gt;For the sake of brevity, I will call this quantity $ \lambda_1 $. I will call the associated eigenvector $ v_1 $.&lt;/p&gt;

&lt;p&gt;M. Fiedler in his landmark monograph called this quantity the algebraic connectivity of a graph. $ \lambda_1 $ and its eigenvectors provide amazing insights.&lt;/p&gt;

&lt;p&gt;One of the insights is:&lt;/p&gt;

&lt;p&gt;If $ \lambda_1 = 0 $ clearly eigenvalue 0 has multiplicity greater than 1. Thus the graph is not connected.&lt;/p&gt;

&lt;p&gt;This is fairly trivial to establish - the insight from the previous section covers it.&lt;/p&gt;

&lt;p&gt;The next insight, my favorite, involves partitioning a graph.&lt;/p&gt;

&lt;p&gt;When we partition a graph (into say 2 partitions), we desire 2 reasonably large groups of vertices with very few edges between them.&lt;/p&gt;

&lt;p&gt;Observe that this exercise is a waste of time if the graph isn&amp;rsquo;t connected (there are already two distinct components with no edges between them). Thus it makes sense to only consider connected graphs.&lt;/p&gt;

&lt;p&gt;Let us try to give a formal shape to the partitioning problem.&lt;/p&gt;

&lt;p&gt;Partitioning can be defined as assigning a value of $ +1 $ or $ &amp;ndash;1 $ to each vertex. Vertices with different assignments are in different partitions.&lt;/p&gt;

&lt;p&gt;Say vertex $ v_i $ gets assigned value $ x_i $.&lt;/p&gt;

&lt;p&gt;Let us assume a perfect paritioning. Exactly $ |V|/2 $ points are assigned and $ x_i = +1 $ and the other half assigned $ x_i = &amp;ndash;1 $.&lt;/p&gt;

&lt;p&gt;Now, a pair of vertices $ v_i $ and $ v_j $ that belong to &lt;em&gt;different partitions&lt;/em&gt; are assigned values $ x_i $ and $ x_j $ where $ x_i \neq x_j $. Thus, the only possible value for $ (x_i - x_j)^{2} $ is $ 4 $.&lt;/p&gt;

&lt;p&gt;For each edge from one partition to the other, we have a value of 4. Thus, the number of edges from one partition to the other is given by:&lt;/p&gt;

&lt;div&gt; $$ \sum_{i=1, j=i}^{i=n, j=n} \frac{(x_i - x_j)^2}{4} $$&lt;/div&gt;

&lt;p&gt;Also, assuming a perfect paritioning, an equal number of vertices are assigned values $ +1 $ and $ &amp;ndash;1 $. Thus we have:&lt;/p&gt;

&lt;div&gt; $$ \sum_{i} x_i = 0 $$&lt;/div&gt;

&lt;p&gt;Our objective is to minimize the number of edges from one partition to the other while achieving a reasonable size for each partition.&lt;/p&gt;

&lt;p&gt;This can be expressed as the following optimization function:&lt;/p&gt;

&lt;p&gt;Minimize&lt;/p&gt;

&lt;div&gt;$$ \sum_{i=1, j=i}^{i=n, j=n} \frac{(x_i - x_j)^2}{4} $$&lt;/div&gt;

&lt;p&gt;with the constraint&lt;/p&gt;

&lt;div&gt;$$ \sum_{i} x_i = 0 $$&lt;/div&gt;

&lt;p&gt;.&lt;/p&gt;

&lt;p&gt;This unfortunately has a trivial solution. Set all $ x_i $ to 0.&lt;/p&gt;

&lt;p&gt;An additional constraint eliminates this problem. The new constraint is:&lt;/p&gt;

&lt;div&gt; $$ \sum_{i} {x_i}^{2} = |V| $$&lt;/div&gt;

&lt;p&gt;We work with matrix variants of these equations. Clearly, the component responsible for the number of edges looks like $ \frac{x^T \mathcal{L} x}{4} $.&lt;/p&gt;

&lt;p&gt;The constraint that enforces reasonable partition sizes is $ x^T\mathcal{1} = 0$. Here $ \mathcal{1} $ is a vector of all ones.&lt;/p&gt;

&lt;p&gt;Finally, the term responsible for ensuring a non-trivial solution is $ x^Tx = |V| $.&lt;/p&gt;

&lt;p&gt;And the lagrangian looks like:&lt;/p&gt;

&lt;div&gt; $$ \nabla \frac{x^T \mathcal{L} x}{4} - \nabla \eta_1 (x^Tx - |V|) - \nabla \eta_2 (x^Te) = 0 $$&lt;/div&gt;

&lt;p&gt;which becomes:&lt;/p&gt;

&lt;div&gt; $$ \mathcal{L} x - \eta_1 x - \eta_2 \mathcal{1} = 0 $$&lt;/div&gt;

&lt;p&gt;Multiply by $ \mathcal{1}^T $ on both sides:&lt;/p&gt;

&lt;div&gt; $$ \mathcal{1}^T \mathcal{L} x - \eta_1 \mathcal{1}^T x - \eta_2 \mathcal{1}^T \mathcal{1} = 0 $$&lt;/div&gt;

&lt;p&gt;This essentially becomes:&lt;/p&gt;

&lt;div&gt; $$ \mathcal{L}x - \eta_1x = 0 $$&lt;/div&gt;

&lt;p&gt;Thus $ x $ is clearly an eigenvector of the graph laplacian and $ \eta_1 $ is an eigenvalue.&lt;/p&gt;

&lt;p&gt;Obviously, the eigenvector of the eigenvalue 0 doesn&amp;rsquo;t work (it assigns the value 1 to all points).&lt;/p&gt;

&lt;p&gt;Clearly $ v_1 $ (the eigenvector of the second smallest eigenvalue) is a solution (the intuition is that the smaller the eigenvalue, the fewer the edges between the two partitions).&lt;/p&gt;

&lt;p&gt;Thus, the eigenvector $ v_1 $ (a.k.a the Fiedler vector) provides an assignment to each vertex in the graph. This assignment can be used to partition the graph.&lt;/p&gt;

&lt;p&gt;There is just one issue here. The eigenvector contains real values, not necessarily $ 1 $ and $ &amp;ndash;1 $.&lt;/p&gt;

&lt;p&gt;A whole host of tricks can be applied to convert the entries in the eigenvector to $ +1 $ and $ &amp;ndash;1 $:&lt;/p&gt;

&lt;div&gt;
 &lt;ul&gt;
  &lt;li&gt;$ sgn(v_{1_{i}}) $ i.e. vertex $ i $ gets assigned a value $ x_i = $ the sign of the $ i^{th} $ component of the eigenvector $ v_1 $.&lt;/li&gt;
  &lt;li&gt;$ x_i = +1 $ if $ v_{1_i} &amp;gt; m $, $ -1 $ otherwise. Here $ m $ is the median of all the eigenvalue components (or the mean or zero or whatever).&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;

&lt;p&gt;And this is how the Fiedler vector helps with graph partitioning.&lt;/p&gt;

&lt;p&gt;The bibliography attached to this post contains some amazing literature that I had a lot of fun reading. I have mirrored these documents in github and provided the github link.&lt;/p&gt;

&lt;h1 id="bibliography"&gt;Bibliography&lt;/h1&gt;

&lt;ol&gt;
 &lt;li&gt;&lt;a href="/docs/Luxburg07_tutorial.pdf"&gt;A Tutorial on Spectral Clustering&lt;/a&gt; - Ulrike von Luxburg - A self-contained and elaborate tutorial on spectral clustering.&lt;/li&gt;
 &lt;li&gt;&lt;a href="/docs/Algebraic_Connectivity.pdf"&gt;Algebraic Connectivity of Graphs&lt;/a&gt; - Miroslav Fiedler - A landmark paper on the properties of the second smallest eigenvalue and its associated eigenvector&lt;/li&gt;
 &lt;li&gt;&lt;a href="/docs/Pothen89Partition.pdf"&gt;Partitioning Sparse Matrices with Eigenvectors of Graphs&lt;/a&gt; - Alex Pothen, Horst Simon, Kang-Pu Paul Liu - An algorithm to leverage the  Fiedler vector for graph Partitioning&lt;/li&gt;&lt;/ol&gt;&lt;/html&gt;</description></item>
  <item>
   <title>Dimension Analysis: A Recap</title>
   <link>http://blog.shriphani.com/2015/01/21/dimension-analysis-a-recap/?utm_source=dimension-reduction&amp;utm_medium=RSS</link>
   <guid>urn:http-blog-shriphani-com:-2015-01-21-dimension-analysis-a-recap</guid>
   <pubDate>Thu, 22 Jan 2015 03:31:29 UT</pubDate>
   <description>&lt;html&gt;
&lt;p&gt;In the past few blog posts, I covered some details of popular dimension-reduction techniques and showed some common themes. In this post, I will collect all these materials and tie them together.&lt;/p&gt;
&lt;!-- more--&gt;

&lt;h3 id="dimension"&gt;Dimension?&lt;/h3&gt;

&lt;p&gt;The best definition I&amp;rsquo;ve seen for the topic comes from Benoit Mandelbrot&amp;rsquo;s work on &lt;a href="http://en.wikipedia.org/wiki/Fractal_Dimension"&gt;fractal geometry&lt;/a&gt;. The fractal dimension is associated with the ability of a pattern to fill space. Here&amp;rsquo;s a good example to illustrate what we mean.&lt;/p&gt;

&lt;p&gt;Consider a curve viewed at three different scales (image stolen from Chris Burges&amp;rsquo;s document on dimension reduction):&lt;/p&gt;

&lt;p&gt;&lt;img src="/img/rectangle_example.png" /&gt;&lt;/p&gt;

&lt;p&gt;Now, at a microscopic level, we begin observing. How do we observe? Assume that a there&amp;rsquo;s a sphere around the observer. Now, let this sphere expand a bit. At the microscopic level, your sphere encounters more of the curve&amp;rsquo;s material in 2 dimensions. This is illustrated in the rightmost figure.&lt;/p&gt;

&lt;p&gt;Now, at a slightly different scale, when our sphere expands, we observe more material along just 1 dimension. This is illustrated in the middle figure.&lt;/p&gt;

&lt;p&gt;On a scale like the one in the leftmost pic, we encounter no material at all. This is akin to a zero-dimension figure (a point).&lt;/p&gt;

&lt;p&gt;An intuitive explanation of why scale matters is provided in this Wikipedia example. Using a ruler of different lengths, we obtain different measures for the coastline of Great Britain. At various levels of scale, we acquire various measures of the coastline - using a ruler that is as long as the diameter of the earth, the coastline of britain is a negligible fraction of our instrument.&lt;/p&gt;

&lt;p&gt;&lt;img src="http://upload.wikimedia.org/wikipedia/commons/7/78/Britain-fractal-coastline-200km.png" /&gt; &lt;img src="http://upload.wikimedia.org/wikipedia/commons/c/c8/Britain-fractal-coastline-100km.png" /&gt; &lt;img src="http://upload.wikimedia.org/wikipedia/commons/f/f9/Britain-fractal-coastline-50km.png" /&gt;&lt;/p&gt;

&lt;p&gt;There&amp;rsquo;s a neat formula that can be used to estimate the fractal dimension of a dataset:&lt;/p&gt;

&lt;ul&gt;
 &lt;li&gt;$ n $: The number of pairs of points in our data.&lt;/li&gt;
 &lt;li&gt;$ r $: The radius of a sphere centered around the observer.&lt;/li&gt;
 &lt;li&gt;$ p $: The number of pairs of points in a sphere of radius $ r $.&lt;/li&gt;&lt;/ul&gt;

&lt;p&gt;The estimate of the fractal dimension is given by the slope of $ \log(p) $ vs $ \log(r) $.&lt;/p&gt;

&lt;p&gt;For the curve in the example above, this value is some real number between 1 and 2 (so the points on the curve have more freedom than those on a line but less freedom than those on a 2D-plane).&lt;/p&gt;

&lt;h3 id="a-first-stab-at-dimension-reduction"&gt;A First Stab at Dimension Reduction&lt;/h3&gt;

&lt;p&gt;Working with intuitions we developed in the first section, we can develop a greedy algorithm:&lt;/p&gt;

&lt;ul&gt;
 &lt;li&gt;Estimate the fractal-dimension of the dataset.&lt;/li&gt;
 &lt;li&gt;Choose a dimension (column) to drop, drop it and recompute the  fractal-dimension. If the dimension doesn&amp;rsquo;t change too much (stays  within a certain tolerance), consider this dimension dropped.&lt;/li&gt;
 &lt;li&gt;Repeat until no more dimensions can be dropped without significantly  altering the fractal-dimension.&lt;/li&gt;&lt;/ul&gt;

&lt;p&gt;This is the Grassberger-Procaccia algorithm.&lt;/p&gt;

&lt;p&gt;It is intuitive to grasp.&lt;/p&gt;

&lt;p&gt;However, &lt;strong&gt;in a high-dimension setting, our technique for estimating fractal dimensions falls apart&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In a high-dimensional setting, pairwise distances between the points in a dataset are tightly clustered about a mean. Essentially, the points seem to be equidistant from each other. A Hoeffding bound is provided in &lt;a href="/2013/11/29/a-comment-on-dimension-estimation/"&gt;this blog post&lt;/a&gt; that illustrates this point.&lt;/p&gt;

&lt;h3 id="the-pca"&gt;The PCA&lt;/h3&gt;

&lt;p&gt;One common attempt at reducing dimensions is capturing directions of maximum variance. The PCA projects points in the dataset along the eigenvectors of the covariance matrix. Since this technique is well-known, I&amp;rsquo;ll just point to this &lt;a href="http://en.wikipedia.org/wiki/Principal_component_analysis"&gt;Wikipedia article&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id="from-proximities-to-datasets"&gt;From Proximities to Datasets&lt;/h3&gt;

&lt;p&gt;A family of techniques I like a lot operate on proximity matrices. A proximity matrix is a symmetric matrix containing similarity scores between the points in a dataset (thus this matrix contains $ n $ rows and $ n $ columns where $ n $ is the number of points in the dataset).&lt;/p&gt;

&lt;p&gt;A simple argument demonstrates that proximity matrices are gram matrices (a gram matrix is a close cousin of the covariance matrix). One can retrieve a collection of points for a given gram matrix - see &lt;a href="/2015/01/15/multidimensional-scaling-and-pca-are-the-same-thing/"&gt;this blog post&lt;/a&gt; for a proof.&lt;/p&gt;

&lt;p&gt;This family of techniques formulates the dimension-reduction problem as such: &amp;ldquo;Find a configuration of points in a lower-dimensional place that preserves the proximities in the proximity matrix&amp;rdquo;.&lt;/p&gt;

&lt;p&gt;The standard MDS algorithm uses euclidean distances between points to populate the proximity matrix. &lt;a href="/2014/10/29/low-dimension-embeddings-for-visualization/"&gt;This blog post&lt;/a&gt; contains more info about this algorithm.&lt;/p&gt;

&lt;p&gt;A variant of this algorithm uses path-weights in a $k$-NN graph. This is the Isomap algorithm - covered &lt;a href="/2014/11/12/the-isomap-algorithm/"&gt;in this post&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id="the-kernel-trick"&gt;The Kernel Trick&lt;/h3&gt;

&lt;p&gt;The Kernel trick is leveraged in settings where we transform our points to a higher-dimensional space to make the desired insight pop out. This desired insight is a hyperplane to separate two different classes when working with a classifier. In Kernel PCA, the desired insight is capturing variances so you can run a PCA on the newer dataset in a higher-dimension.&lt;/p&gt;

&lt;p&gt;Interestingly, MDS and Isomap are all variants of the Kernel PCA - a topic explored in &lt;a href="/2015/01/20/the-kernel-pca/"&gt;this blog post&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id="up-next"&gt;Up Next&lt;/h3&gt;

&lt;p&gt;In future blog posts, I will discuss scaling issues with spectral algorithms, insights that can be transferred to other domains and so on.&lt;/p&gt;&lt;/html&gt;</description></item>
  <item>
   <title>Multidimensional Scaling and PCA are the Same Thing</title>
   <link>http://blog.shriphani.com/2015/01/15/multidimensional-scaling-and-pca-are-the-same-thing/?utm_source=dimension-reduction&amp;utm_medium=RSS</link>
   <guid>urn:http-blog-shriphani-com:-2015-01-15-multidimensional-scaling-and-pca-are-the-same-thing</guid>
   <pubDate>Fri, 16 Jan 2015 02:13:37 UT</pubDate>
   <description>&lt;html&gt;
&lt;p&gt;There is a very simple argument that shows that MDS and PCA achieve the same results.&lt;/p&gt;

&lt;p&gt;This argument has 2 important components. The first of these shows that an eigendecomposition of a gram matrix can be used for dimension-reduction.&lt;/p&gt;

&lt;p&gt;PCA leverages the singular value decomposition (SVD). Given a matrix $ X $, the SVD is $ X = USV^{T} $.&lt;/p&gt;

&lt;p&gt;We drop columns from X by using $ US_{t} $ where we drop some rows and columns from S.&lt;/p&gt;

&lt;p&gt;This is also conviently obtained using an eigendecomposition of the covariance matrix.&lt;/p&gt;

&lt;p&gt;Working with the gram matrix, we have $ XX^{T} $ and when expressed in terms of $ U $, $ S $ and $ V $, we have $ XX^{T} $ = $ (USV^{T})(VSU^{T}) $.&lt;/p&gt;

&lt;p&gt;Simple algebra tells us that this is equal to $ US^{2}U^{T} $. The &lt;a href="http://inst.eecs.berkeley.edu/~ee127a/book/login/l_sym_sed.html"&gt;spectral theorem&lt;/a&gt; tells us that this the eigendecomposition of the gram matrix will return this decomposition. $ U $ and $ S $ can be retrieved and a dataset with fewer dimensions can be obtained.&lt;/p&gt;

&lt;p&gt;The second part of the argument involves proving that a matrix of distances is indeed a gram matrix. This argument was discussed in a &lt;a href="http://blog.shriphani.com/2014/11/02/powerful-ideas-in-manifold-learning/"&gt;previous post&lt;/a&gt;.&lt;/p&gt;&lt;/html&gt;</description></item>
  <item>
   <title>Implementing Truncated Matrix Decompositions for Core.Matrix</title>
   <link>http://blog.shriphani.com/2014/11/28/implementing-truncated-matrix-decompositions-for-core-matrix/?utm_source=dimension-reduction&amp;utm_medium=RSS</link>
   <guid>urn:http-blog-shriphani-com:-2014-11-28-implementing-truncated-matrix-decompositions-for-core-matrix</guid>
   <pubDate>Fri, 28 Nov 2014 11:16:15 UT</pubDate>
   <description>&lt;html&gt;
&lt;p&gt;Eigendecompositions and Singular Value Decompositions appear in a variety of settings in machine learning and data mining. The eigendecomposition looks like so:&lt;/p&gt;

&lt;div&gt;$$ \mathbf{A}=\mathbf{Q}\mathbf{\Lambda}\mathbf{Q}^{-1} $$&lt;/div&gt;

&lt;p&gt;$ \mathbf{Q} $ contains the eigenvectors of $ \mathbf{A} $ and $ \mathbf{\Lambda} $ is a diagonal matrix containing the eigenvalues.&lt;/p&gt;

&lt;p&gt;The singular value decomposition looks like:&lt;/p&gt;

&lt;div&gt;$$ \mathbf{A} = \mathbf{U} \boldsymbol{\Sigma} \mathbf{V}^* $$&lt;/div&gt;

&lt;p&gt;$ \mathbf{U} $ contains the eigenvectors of the covariance matrix $ \mathbf{A}\mathbf{A^T} $. $ \mathbf{V} $ contains the eigenvectors of the gram matrix $ \mathbf{A^T}\mathbf{A} $.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;truncated&lt;/strong&gt; variants of these decompositions allow us to compute only a few eigenvalues(vectors) or singular values (vectors).&lt;/p&gt;

&lt;p&gt;This is important since (i) a lot of times, the smaller eigenvalues are discarded, and (ii) you don&amp;rsquo;t want to compute the entire decomposition and retain only a few of the rows and columns of the computed matrices each time.&lt;/p&gt;

&lt;p&gt;For core.matrix, I implemented these truncated decompositions in &lt;a href="http://github.com/shriphani/kublai"&gt;Kublai&lt;/a&gt;. Details below.&lt;/p&gt;
&lt;!-- more--&gt;

&lt;p&gt;For large matrices , on consumer grade hardware (like your laptop), it is near impossible to compute a full decomposition. If your algorithm performs a decomposition per iteration, then things get worse.&lt;/p&gt;

&lt;p&gt;The excellent &lt;a href="http://www.caam.rice.edu/software/ARPACK/"&gt;ARPACK library&lt;/a&gt; implements an efficient truncated SVD that is leveraged by several popular numerical libraries like the popular Python library &lt;a href="http://scikit-learn.org/stable/"&gt;scikit learn&lt;/a&gt; and &lt;a href="https://spark.apache.org/"&gt;Apache Spark&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ARPACK&amp;rsquo;s eigendecomposition is not tied to any particular matrix  library.&lt;/strong&gt; You only need to supply a routine that multiplies a vector  with the input matrix (which ARPACK uses for its  &lt;a href="http://en.wikipedia.org/wiki/Power_iteration"&gt;power-iteration&lt;/a&gt;  step).&lt;/p&gt;

&lt;p&gt;Clojure&amp;rsquo;s budding matrix library &lt;code&gt;core.matrix&lt;/code&gt; implements both the eigen and singular value decompositions but doesn&amp;rsquo;t contain a truncated version.&lt;/p&gt;

&lt;p&gt;In this post, I will describe how I used the ARPACK library to implement these truncated decompositions for &lt;code&gt;core.matrix&lt;/code&gt;. The resulting implementation is independent of the particular implementation of &lt;code&gt;core.matrix&lt;/code&gt; being used.&lt;/p&gt;

&lt;p&gt;While the details of the post are tied to &lt;code&gt;core.matrix&lt;/code&gt;, the lessons can be transferred to other numerical libraries in other languages.&lt;/p&gt;

&lt;p&gt;The excellent &lt;a href="https://github.com/fommil/netlib-java"&gt;netlib library&lt;/a&gt; provides a clean java interface to the ARPACK library (which is implemented in FORTRAN).&lt;/p&gt;

&lt;p&gt;I will assume that you have ARPACK installed on your machine. If so, netlib will be able to invoke ARPACK.&lt;/p&gt;

&lt;h3 id="truncated-eigendecomposition"&gt;Truncated Eigendecomposition:&lt;/h3&gt;

&lt;p&gt;There are a symmetric eigendecomposition that I have currently implemented (and a non-symmetric version is on the way).&lt;/p&gt;

&lt;p&gt;Invoking the truncated eigendecompositon routine is trival:&lt;/p&gt;

&lt;ul&gt;
 &lt;li&gt;
  &lt;p&gt;Call the  &lt;code&gt;&lt;a href="http://www.caam.rice.edu/software/ARPACK/UG/node136.html"&gt;DSAUPD&lt;/a&gt;&lt;/code&gt;  routine (for symmetric matrices. Use  &lt;a href="http://www.caam.rice.edu/software/ARPACK/UG/node137.html"&gt;DNEUPD&lt;/a&gt;  for the opposite)  till the &lt;code&gt;IDO&lt;/code&gt; flag is set to 99.&lt;/p&gt;&lt;/li&gt;
 &lt;li&gt;
  &lt;p&gt;Once this is done, the eigen vectors and values can be retrieved by  a call to  &lt;code&gt;&lt;a href="http://www.caam.rice.edu/software/ARPACK/UG/node40.html"&gt;DSEUPD&lt;/a&gt;&lt;/code&gt;.  The eigenpairs are returned in ascending order of eigenvalues.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;

&lt;p&gt;And that&amp;rsquo;s it. There are a few flags that tell you if there are fatal errors which you need to check that the docs for &lt;code&gt;DSAUPD&lt;/code&gt; and &lt;code&gt;DSEUPD&lt;/code&gt; contain.&lt;/p&gt;

&lt;h3 id="truncated-singular-value-decomposition"&gt;Truncated Singular Value Decomposition:&lt;/h3&gt;

&lt;p&gt;The truncated SVD can just invoke the eigendecomposition on the gram and covariance matrices. No ARPACK calls are needed here.&lt;/p&gt;

&lt;p&gt;The implementation for both the decompositions is available in this &lt;a href="https://github.com/shriphani/kublai"&gt;github repository&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id="usage"&gt;Usage&lt;/h3&gt;

&lt;p&gt;This module can be used in the following fashion:&lt;/p&gt;

&lt;p&gt;For computing symmetric eigendecompositions:&lt;/p&gt;

&lt;div class="brush: clojure"&gt;
 &lt;table class="sourcetable"&gt;
  &lt;tbody&gt;
   &lt;tr&gt;
    &lt;td class="linenos"&gt;
     &lt;div class="linenodiv"&gt;
      &lt;pre&gt; 1
 2
 3
 4
 5
 6
 7
 8
 9
10&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;
    &lt;td class="code"&gt;
     &lt;div class="source"&gt;
      &lt;pre&gt;&lt;span class="nv"&gt;user&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def &lt;/span&gt;&lt;span class="nv"&gt;M&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;matrix&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                   &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                   &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                   &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;]]))&lt;/span&gt;
&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="ss"&gt;&amp;#39;user/M&lt;/span&gt;
&lt;span class="nv"&gt;user&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt; &lt;span class="ss"&gt;&amp;#39;kublai.core&lt;/span&gt; &lt;span class="ss"&gt;:reload-all&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;nil&lt;/span&gt;
&lt;span class="nv"&gt;user&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;eigs&lt;/span&gt; &lt;span class="nv"&gt;M&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="ss"&gt;:symmetric&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;;; compute 2 eigenvectors for this matrix&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:Q&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="mf"&gt;-0.22593827269074584&lt;/span&gt; &lt;span class="mf"&gt;-0.4432218615090191&lt;/span&gt; &lt;span class="mf"&gt;-0.5727878807113498&lt;/span&gt; &lt;span class="mf"&gt;-0.6514754961809404&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0.7253136654558885&lt;/span&gt; &lt;span class="mf"&gt;0.3184697313242928&lt;/span&gt; &lt;span class="mf"&gt;0.1424607347013554&lt;/span&gt; &lt;span class="mf"&gt;-0.5934661371986827&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;, &lt;span class="ss"&gt;:A&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="mf"&gt;24.06253512439672&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt; &lt;span class="mf"&gt;-0.8054849155764637&lt;/span&gt;&lt;span class="p"&gt;]]}&lt;/span&gt;
&lt;span class="nv"&gt;user&amp;gt;&lt;/span&gt; 
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;

&lt;p&gt;For computing a truncated SVD:&lt;/p&gt;

&lt;div class="brush: clojure"&gt;
 &lt;table class="sourcetable"&gt;
  &lt;tbody&gt;
   &lt;tr&gt;
    &lt;td class="linenos"&gt;
     &lt;div class="linenodiv"&gt;
      &lt;pre&gt; 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;
    &lt;td class="code"&gt;
     &lt;div class="source"&gt;
      &lt;pre&gt;&lt;span class="nv"&gt;user&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def &lt;/span&gt;&lt;span class="nv"&gt;M&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;matrix&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                   &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                   &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                   &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;]]))&lt;/span&gt;
&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="ss"&gt;&amp;#39;user/M&lt;/span&gt;
&lt;span class="nv"&gt;user&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;svd&lt;/span&gt; &lt;span class="nv"&gt;M&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:U&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="mf"&gt;-0.13472212372225584&lt;/span&gt; &lt;span class="mf"&gt;0.8257420598345273&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;-0.3407576960799602&lt;/span&gt; &lt;span class="mf"&gt;0.4288172018031381&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;-0.5467932684376645&lt;/span&gt; &lt;span class="mf"&gt;0.03189234377176592&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;-0.7528288407953688&lt;/span&gt; &lt;span class="mf"&gt;-0.365032514259624&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;, &lt;span class="ss"&gt;:V*&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="mf"&gt;0.4284123959267892&lt;/span&gt; &lt;span class="mf"&gt;0.4743725155726848&lt;/span&gt; &lt;span class="mf"&gt;0.5203326352185806&lt;/span&gt; &lt;span class="mf"&gt;0.5662927548644766&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0.7186534763126667&lt;/span&gt; &lt;span class="mf"&gt;0.27380780936493887&lt;/span&gt; &lt;span class="mf"&gt;-0.17103785758268963&lt;/span&gt; &lt;span class="mf"&gt;-0.6158835245304229&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;, &lt;span class="ss"&gt;:S&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="mf"&gt;38.62265683187287&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt; &lt;span class="mf"&gt;2.0713230668787377&lt;/span&gt;&lt;span class="p"&gt;]]}&lt;/span&gt;
&lt;span class="nv"&gt;user&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;clojure.pprint/pprint&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;svd&lt;/span&gt; &lt;span class="nv"&gt;M&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:U&lt;/span&gt;
 &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="mf"&gt;-0.13472212372225592&lt;/span&gt; &lt;span class="mf"&gt;-0.825742059834525&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;-0.34075769607996026&lt;/span&gt; &lt;span class="mf"&gt;-0.42881720180314464&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;-0.5467932684376648&lt;/span&gt; &lt;span class="mf"&gt;-0.03189234377175876&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;-0.7528288407953688&lt;/span&gt; &lt;span class="mf"&gt;0.3650325142596215&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;,
 &lt;span class="ss"&gt;:V*&lt;/span&gt;
 &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="mf"&gt;-0.4284123959267895&lt;/span&gt;
   &lt;span class="mf"&gt;-0.4743725155726852&lt;/span&gt;
   &lt;span class="mf"&gt;-0.5203326352185804&lt;/span&gt;
   &lt;span class="mf"&gt;-0.5662927548644764&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;-0.7186534763126535&lt;/span&gt;
   &lt;span class="mf"&gt;-0.27380780936497917&lt;/span&gt;
   &lt;span class="mf"&gt;0.1710378575827312&lt;/span&gt;
   &lt;span class="mf"&gt;0.615883524530409&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;,
 &lt;span class="ss"&gt;:S&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="mf"&gt;38.62265683187287&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt; &lt;span class="mf"&gt;2.0713230668787403&lt;/span&gt;&lt;span class="p"&gt;]]}&lt;/span&gt;
&lt;span class="nv"&gt;nil&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;

&lt;h3 id="efficiency"&gt;Efficiency&lt;/h3&gt;

&lt;p&gt;In this example I shall demonstrate how valuable a truncated SVD is. Say we have a very large matrix and we only need 10 singular values/vectors. We will be using the &lt;code&gt;&lt;a href="https://github.com/mikera/vectorz-clj/"&gt;vectorz-clj&lt;/a&gt;&lt;/code&gt; implementation of &lt;code&gt;core.matrix&lt;/code&gt;.&lt;/p&gt;

&lt;div class="brush: clojure"&gt;
 &lt;table class="sourcetable"&gt;
  &lt;tbody&gt;
   &lt;tr&gt;
    &lt;td class="linenos"&gt;
     &lt;div class="linenodiv"&gt;
      &lt;pre&gt;1
2
3
4
5
6&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;
    &lt;td class="code"&gt;
     &lt;div class="source"&gt;
      &lt;pre&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;M1&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;reshape&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;matrix&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;range &lt;/span&gt;&lt;span class="mi"&gt;500000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="nv"&gt;M2&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;reshape&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;matrix&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;range &lt;/span&gt;&lt;span class="mi"&gt;5000000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="nv"&gt;M3&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;reshape&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;matrix&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;range &lt;/span&gt;&lt;span class="mi"&gt;5000000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;])]&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;time &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;kublai/svd&lt;/span&gt; &lt;span class="nv"&gt;M1&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;time &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;kublai/svd&lt;/span&gt; &lt;span class="nv"&gt;M2&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;time &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;kublai/svd&lt;/span&gt; &lt;span class="nv"&gt;M3&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;

&lt;p&gt;Here, &lt;code&gt;M1&lt;/code&gt; is a 10000 x 50 matrix, &lt;code&gt;M2&lt;/code&gt; is a 10000 x 500 matrix, and &lt;code&gt;M3&lt;/code&gt; is a 1000 x 5000 matrix.&lt;/p&gt;

&lt;p&gt;And the results are:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;"Elapsed time: 17372.943 msecs"
"Elapsed time: 78085.404 msecs"
"Elapsed time: 41511.266 msecs"&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now say we had to run a full decomposition on these matrices (using the standard stuff that ships with &lt;code&gt;core.matrix&lt;/code&gt;), the results look like:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;"Elapsed time: 19617.777 msecs"
"Elapsed time: 157861.467 msecs"
"Elapsed time: 97627.002 msecs"&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;For the larger matrices this is nearly twice as long as the truncated versions.&lt;/p&gt;

&lt;p&gt;(Experiments on a Macbook Air with 8 GB of memory and a 1.8 Ghz i5).&lt;/p&gt;

&lt;h3 id="links"&gt;Links:&lt;/h3&gt;

&lt;ul&gt;
 &lt;li&gt;&lt;a href="http://github.com/shriphani/kublai"&gt;Kublai&lt;/a&gt; - The decompositions  themselves&lt;/li&gt;
 &lt;li&gt;&lt;a href="http://github.com/shriphani/kublai-timing"&gt;Kublai Timing Tests&lt;/a&gt; -  Tests to time these decompositions&lt;/li&gt;&lt;/ul&gt;&lt;/html&gt;</description></item>
  <item>
   <title>The Isomap Algorithm</title>
   <link>http://blog.shriphani.com/2014/11/12/the-isomap-algorithm/?utm_source=dimension-reduction&amp;utm_medium=RSS</link>
   <guid>urn:http-blog-shriphani-com:-2014-11-12-the-isomap-algorithm</guid>
   <pubDate>Wed, 12 Nov 2014 21:24:54 UT</pubDate>
   <description>&lt;html&gt;
&lt;p&gt;&lt;em&gt;This is part of a series on a family of dimension-reduction algorithms called non-linear dimension reduction. The goal here is to reduce the dimensions of a dataset (i.e. discard some columns in your data)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In previous posts, I discussed the &lt;a href="http://blog.shriphani.com/2014/10/29/low-dimension-embeddings-for-visualization/"&gt;MDS algorithm&lt;/a&gt; and presented &lt;a href="http://blog.shriphani.com/2014/11/02/powerful-ideas-in-manifold-learning/"&gt;some key ideas&lt;/a&gt;. In this post, I will describe how those ideas are leveraged in the Isomap algorithm. A clojure implementation based on core.matrix is also included.&lt;/p&gt;
&lt;!-- more--&gt;

&lt;h3 id="intuition"&gt;Intuition&lt;/h3&gt;

&lt;p&gt;Isomap uses the same core ideas as the MDS algorithm:&lt;/p&gt;

&lt;ul&gt;
 &lt;li&gt;
  &lt;p&gt;Obtain a matrix of proximities (distances between points in a  dataset).&lt;/p&gt;&lt;/li&gt;
 &lt;li&gt;
  &lt;p&gt;This distance matrix is a matrix of inner products.&lt;/p&gt;&lt;/li&gt;
 &lt;li&gt;
  &lt;p&gt;An eigendecomposition of this matrix gives us the lower dimension  embedding.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;

&lt;p&gt;Isomap differs from MDS in one vital way - the construction of the distance matrix. In MDS, the distance between two points is just the euclidean distance.&lt;/p&gt;

&lt;p&gt;In Isomap, the distances between points are the weight of the shortest path in a point-graph.&lt;/p&gt;

&lt;p&gt;The point graph is constructed by placing an edge between two points if the euclidean distance between them falls under a certain threshold or between a point and its top $ k $ neighbors.&lt;/p&gt;

&lt;p&gt;This distance matrix captures the underlying manifold more accurately than one constructed using euclidean distances. The following toy example demonstrates this:&lt;/p&gt;

&lt;p&gt;&lt;img src="/img/isomap_data.png" /&gt;&lt;/p&gt;

&lt;p&gt;The data shown here looks like a swirl that starts at point 1 and ends at point 9. We would like to recover this phenomenon in our lower-dimension embedding.&lt;/p&gt;

&lt;p&gt;The first step is to build a distance matrix. Say we use euclidean distances between two points as the corresponding entry in the distance matrix.&lt;/p&gt;

&lt;p&gt;In this figure, it is clear that &lt;code&gt;euclidean_distance(1, 6) = euclidean_distance(1, 8)&lt;/code&gt; and &lt;code&gt;euclidean_distance(1, 5) = euclidean_distance(1, 9)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Clearly the distances computed here miss the &amp;ldquo;swirl&amp;rdquo; in the data entirely. Working with the point graph mentioned above helps us get around this problem.&lt;/p&gt;

&lt;p&gt;Let us build a point graph by adding an edge between a node and its nearest neighbor (so $ 1-NN $). The weight on the edge is the euclidean distance between the nodes. The distance between two points is the weight of the shortest path between these points. The point graph is shown below:&lt;/p&gt;

&lt;p&gt;&lt;img src="/img/isomap_point_graph.png" /&gt;&lt;/p&gt;

&lt;p&gt;Observe that when we use this newer distance metric, &lt;code&gt;distance(1, 6)&lt;/code&gt; is indeed less than  distance(1, 8) and  distance(1, 5) is indeed less than &lt;code&gt;distance(1, 9)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This distance function is clearly doing a better job of capturing the &amp;ldquo;swirl&amp;rdquo; in the data.&lt;/p&gt;

&lt;p&gt;The Isomap algorithm uses a distance matrix constructed like this in place of one constructed with euclidean distances. This distance matrix is then plugged into the MDS framework and an eigendecomposition is run on the double-centered matrix.&lt;/p&gt;

&lt;h3 id="implementation"&gt;Implementation&lt;/h3&gt;

&lt;p&gt;Let us do a clojure implementation.&lt;/p&gt;

&lt;p&gt;We have a point-set as a &lt;code&gt;core.matrix&lt;/code&gt; matrix. First, we compute the point-graph. I am going to place edges between a point and its 3 nearest neighbors (so $ 3-NN $). This routines expects a map of the type {point-index point-vector, &amp;hellip;}&lt;/p&gt;

&lt;div class="brush: clojure"&gt;
 &lt;table class="sourcetable"&gt;
  &lt;tbody&gt;
   &lt;tr&gt;
    &lt;td class="linenos"&gt;
     &lt;div class="linenodiv"&gt;
      &lt;pre&gt; 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;
    &lt;td class="code"&gt;
     &lt;div class="source"&gt;
      &lt;pre&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;defn &lt;/span&gt;&lt;span class="nv"&gt;build-point-graph&lt;/span&gt;
  &lt;span class="s"&gt;"A point graph is a k-NN graph. Edges between&lt;/span&gt;
&lt;span class="s"&gt;   a point and its 3 nearest neigbors"&lt;/span&gt;
  &lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nv"&gt;indexed-points&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
     &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;build-point-graph&lt;/span&gt; &lt;span class="nv"&gt;indexed-points&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

  &lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nv"&gt;indexed-points&lt;/span&gt; &lt;span class="nv"&gt;num-neighbors&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
     &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;acc&lt;/span&gt; &lt;span class="nv"&gt;pt&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;other-points&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;
                            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                              &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;not= &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;first &lt;/span&gt;&lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                                    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;first &lt;/span&gt;&lt;span class="nv"&gt;pt&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
                            &lt;span class="nv"&gt;indexed-points&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
          &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;merge&lt;/span&gt;
           &lt;span class="nv"&gt;acc&lt;/span&gt;
           &lt;span class="p"&gt;{(&lt;/span&gt;&lt;span class="nb"&gt;first &lt;/span&gt;&lt;span class="nv"&gt;pt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;
                        &lt;span class="nv"&gt;first&lt;/span&gt;
                        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;take &lt;/span&gt;&lt;span class="nv"&gt;num-neighbors&lt;/span&gt;
                              &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sort-by&lt;/span&gt;
                               &lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;distance&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;first &lt;/span&gt;&lt;span class="nv"&gt;pt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                                          &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;first &lt;/span&gt;&lt;span class="nv"&gt;%&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                               &lt;span class="nv"&gt;other-points&lt;/span&gt;&lt;span class="p"&gt;)))})))&lt;/span&gt;
      &lt;span class="p"&gt;{}&lt;/span&gt;
      &lt;span class="nv"&gt;indexed-points&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;

&lt;p&gt;Then a simple Floyd Warshall algorithm implementation that computes the weights on the shortest paths. It takes the graph built in the previous step and the original indexed points and builds the graph.&lt;/p&gt;

&lt;div class="brush: clojure"&gt;
 &lt;table class="sourcetable"&gt;
  &lt;tbody&gt;
   &lt;tr&gt;
    &lt;td class="linenos"&gt;
     &lt;div class="linenodiv"&gt;
      &lt;pre&gt; 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;
    &lt;td class="code"&gt;
     &lt;div class="source"&gt;
      &lt;pre&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;defn &lt;/span&gt;&lt;span class="nv"&gt;floyd-warshall-distance&lt;/span&gt;
  &lt;span class="s"&gt;"Expected graph representation:&lt;/span&gt;
&lt;span class="s"&gt;    {V -&amp;gt; neighboring-points}"&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;a-graph&lt;/span&gt; &lt;span class="nv"&gt;indexed-points&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;indexed-points-dict&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;into &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="nv"&gt;indexed-points&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nv"&gt;edges&lt;/span&gt;    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;
                  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;acc&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;x&lt;/span&gt; &lt;span class="nv"&gt;neighbors&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
                    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;concat &lt;/span&gt;&lt;span class="nv"&gt;acc&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;map &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;n&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;x&lt;/span&gt; &lt;span class="nv"&gt;n&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
                                     &lt;span class="nv"&gt;neighbors&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
                  &lt;span class="p"&gt;[]&lt;/span&gt;
                  &lt;span class="nv"&gt;a-graph&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="nv"&gt;inf-matrix&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;+ &lt;/span&gt;&lt;span class="nv"&gt;Double/POSITIVE_INFINITY&lt;/span&gt;
                      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;zero-matrix&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;count &lt;/span&gt;&lt;span class="nv"&gt;indexed-points&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                                   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;count &lt;/span&gt;&lt;span class="nv"&gt;indexed-points&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;

        &lt;span class="nv"&gt;zero-diag&lt;/span&gt;  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;
                    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;acc&lt;/span&gt; &lt;span class="nv"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;mset&lt;/span&gt; &lt;span class="nv"&gt;acc&lt;/span&gt; &lt;span class="nv"&gt;i&lt;/span&gt; &lt;span class="nv"&gt;i&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                    &lt;span class="nv"&gt;inf-matrix&lt;/span&gt;
                    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;-&amp;gt; &lt;/span&gt;&lt;span class="nv"&gt;indexed-points&lt;/span&gt; &lt;span class="nb"&gt;count &lt;/span&gt;&lt;span class="nv"&gt;range&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

        &lt;span class="nv"&gt;weights-init&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;
                      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;acc&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;x&lt;/span&gt; &lt;span class="nv"&gt;y&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
                        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;mset&lt;/span&gt; &lt;span class="nv"&gt;acc&lt;/span&gt;
                              &lt;span class="nv"&gt;x&lt;/span&gt;
                              &lt;span class="nv"&gt;y&lt;/span&gt;
                              &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;distance&lt;/span&gt;
                               &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;indexed-points-dict&lt;/span&gt; &lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                               &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;indexed-points-dict&lt;/span&gt; &lt;span class="nv"&gt;y&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;
                      &lt;span class="nv"&gt;zero-diag&lt;/span&gt;
                      &lt;span class="nv"&gt;edges&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;

    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;
     &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;old-distances&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;k&lt;/span&gt; &lt;span class="nv"&gt;i&lt;/span&gt; &lt;span class="nv"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
       &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;&amp;lt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;+ &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;mget&lt;/span&gt; &lt;span class="nv"&gt;old-distances&lt;/span&gt; &lt;span class="nv"&gt;i&lt;/span&gt; &lt;span class="nv"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                 &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;mget&lt;/span&gt; &lt;span class="nv"&gt;old-distances&lt;/span&gt; &lt;span class="nv"&gt;k&lt;/span&gt; &lt;span class="nv"&gt;j&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
              &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;mget&lt;/span&gt; &lt;span class="nv"&gt;old-distances&lt;/span&gt; &lt;span class="nv"&gt;i&lt;/span&gt; &lt;span class="nv"&gt;j&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
         &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;mset&lt;/span&gt; &lt;span class="nv"&gt;old-distances&lt;/span&gt;
               &lt;span class="nv"&gt;i&lt;/span&gt;
               &lt;span class="nv"&gt;j&lt;/span&gt;
               &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;+ &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;mget&lt;/span&gt; &lt;span class="nv"&gt;old-distances&lt;/span&gt; &lt;span class="nv"&gt;i&lt;/span&gt; &lt;span class="nv"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;mget&lt;/span&gt; &lt;span class="nv"&gt;old-distances&lt;/span&gt; &lt;span class="nv"&gt;k&lt;/span&gt; &lt;span class="nv"&gt;j&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
         &lt;span class="nv"&gt;old-distances&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
     &lt;span class="nv"&gt;weights-init&lt;/span&gt;
     &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;for &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;k&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;-&amp;gt; &lt;/span&gt;&lt;span class="nv"&gt;indexed-points&lt;/span&gt; &lt;span class="nb"&gt;count &lt;/span&gt;&lt;span class="nv"&gt;range&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
           &lt;span class="nv"&gt;i&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;-&amp;gt; &lt;/span&gt;&lt;span class="nv"&gt;indexed-points&lt;/span&gt; &lt;span class="nb"&gt;count &lt;/span&gt;&lt;span class="nv"&gt;range&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
           &lt;span class="nv"&gt;j&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;-&amp;gt; &lt;/span&gt;&lt;span class="nv"&gt;indexed-points&lt;/span&gt; &lt;span class="nb"&gt;count &lt;/span&gt;&lt;span class="nv"&gt;range&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
       &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;k&lt;/span&gt; &lt;span class="nv"&gt;i&lt;/span&gt; &lt;span class="nv"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]))))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;

&lt;p&gt;Once we have a distance matrix, we can simply feed it to MDS:&lt;/p&gt;

&lt;div class="brush: clojure"&gt;
 &lt;table class="sourcetable"&gt;
  &lt;tbody&gt;
   &lt;tr&gt;
    &lt;td class="linenos"&gt;
     &lt;div class="linenodiv"&gt;
      &lt;pre&gt;1
2
3
4
5
6
7&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;
    &lt;td class="code"&gt;
     &lt;div class="source"&gt;
      &lt;pre&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;defn &lt;/span&gt;&lt;span class="nv"&gt;isomap&lt;/span&gt;
  &lt;span class="s"&gt;"Takes indexed-points and the target dimension"&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;points&lt;/span&gt; &lt;span class="nv"&gt;n&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;indexed-points&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;map-indexed&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;i&lt;/span&gt; &lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;i&lt;/span&gt; &lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="nv"&gt;points&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nv"&gt;graph&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;build-point-graph&lt;/span&gt; &lt;span class="nv"&gt;indexed-points&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nv"&gt;distances&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;floyd-warshall-distance&lt;/span&gt; &lt;span class="nv"&gt;graph&lt;/span&gt; &lt;span class="nv"&gt;indexed-points&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;mds/distances-&amp;gt;points&lt;/span&gt; &lt;span class="nv"&gt;distances&lt;/span&gt; &lt;span class="nv"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;

&lt;p&gt;And that&amp;rsquo;s it!&lt;/p&gt;

&lt;h3 id="examples"&gt;Examples&lt;/h3&gt;

&lt;p&gt;I will use word-vectors from word2vec for these 10 words: &lt;code&gt;river
lake
city
town
actor
doctor
dog
cat
animal
home&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The word vectors for these words are available in &lt;a href="/img/foo.csv"&gt;foo.csv&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let us reduce these to two dimensions. We get:&lt;/p&gt;

&lt;h4 id="isomap-embeddings"&gt;ISOMAP Embeddings&lt;/h4&gt;

&lt;iframe src="https://docs.google.com/spreadsheets/d/1zb8WGpVov_aYSsWREG9gsjYKv1KB-kPQUNfRHmW9LLY/pubchart?oid=1644191968&amp;amp;amp;format=interactive" style="width:100%;height:50vh;"&gt;&lt;/iframe&gt;

&lt;p&gt;The embeddings produced by the MDS algorithm are:&lt;/p&gt;

&lt;h4 id="mds-embeddings"&gt;MDS Embeddings&lt;/h4&gt;

&lt;iframe src="https://docs.google.com/spreadsheets/d/1yDeqAtGh_7kHvmsdLGsCzFMwFno1iJ9fn7zVqkToVoI/pubchart?oid=762847171&amp;amp;amp;format=interactive" style="width:100%;height:50vh;"&gt;&lt;/iframe&gt;

&lt;p&gt;Compared to the plot produced by MDS, we have more separation between terms - for instance &lt;code&gt;cat&lt;/code&gt; and &lt;code&gt;dog&lt;/code&gt; are place close by but they don&amp;rsquo;t overlap (unlike the MDS plot). This is a qualitative analysis, it is pretty hard to gauge which embedding is better.&lt;/p&gt;

&lt;h3 id="full-source"&gt;Full Source&lt;/h3&gt;

&lt;p&gt;See this repo: &lt;a href="https://github.com/shriphani/clojure-manifold"&gt;https://github.com/shriphani/clojure-manifold&lt;/a&gt;&lt;/p&gt;&lt;/html&gt;</description></item>
  <item>
   <title>Powerful Ideas in Manifold Learning</title>
   <link>http://blog.shriphani.com/2014/11/02/powerful-ideas-in-manifold-learning/?utm_source=dimension-reduction&amp;utm_medium=RSS</link>
   <guid>urn:http-blog-shriphani-com:-2014-11-02-powerful-ideas-in-manifold-learning</guid>
   <pubDate>Sun, 02 Nov 2014 10:10:47 UT</pubDate>
   <description>&lt;html&gt;
&lt;p&gt;In a &lt;a href="/2014/10/29/low-dimension-embeddings-for-visualization/"&gt;previous post&lt;/a&gt;, I described the MDS (multidimensional scaling) algorithm. This algorithm operates on a proximity matrix which is a matrix of distances between the points in a dataset. From this matrix, a configuration of points is retrieved in a lower dimension.&lt;/p&gt;

&lt;p&gt;The MDS strategy is:&lt;/p&gt;

&lt;ul&gt;
 &lt;li&gt;We have a matrix $ D $ for distances between points in the data.  This matrix is &lt;em&gt;symmetric&lt;/em&gt;.&lt;/li&gt;
 &lt;li&gt;We express distances as dot-products (using a proof from Schonberg).  This means that $ D $ is expressed as $ X^T X $. (Observe that $  X^T X $ is a matrix of dot-products).&lt;/li&gt;
 &lt;li&gt;Once we have $ X^T X $, dimension reduction is trivial. Running an  eigendecomposition on this matrix will produced centered  coordinates. The low-dimension embedding is recovered by discarding  eigenvalues (eigenvectors).&lt;/li&gt;&lt;/ul&gt;

&lt;p&gt;Thus, assuming that we work with &lt;em&gt;euclidean distances&lt;/em&gt; between points, we retrieve an embedding that PCA itself would produce. Thus, &lt;strong&gt;MDS with euclidean distances is identical to PCA&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Then what exactly is the value of running MDS on a dataset?&lt;/p&gt;

&lt;p&gt;First, the PCA is not the most powerful approach. For certain datasets, euclidean distances do not capture the shape of the underlying manifold. Running the steps of the MDS on a different distance matrix (at least one that doesn&amp;rsquo;t contain euclidean distances) can lead to better results - a technique that the &lt;a href="http://en.wikipedia.org/wiki/Isomap"&gt;Isomap algorithm&lt;/a&gt; exploits.&lt;/p&gt;

&lt;p&gt;Second, the PCA requires a vector-representation for points. In several situations, the objects in the dataset are not points in a metric space (like strings). We can retrieve distances between objects (say edit-distance for strings) and then obtain a vector-representation for the objects using MDS.&lt;/p&gt;

&lt;p&gt;In the next blog post, I will describe and implement the Isomap algorithm that leverages the ideas in the MDS strategy. Isomap constructs a distance matrix that attempts to do a better job at recovering the underlying manifold.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PROOFS:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
 &lt;li&gt;&lt;em&gt;Distances are dot-products:&lt;/em&gt;  &lt;a href="http://www.math.pku.edu.cn/teachers/yaoy/Fall2011/lecture11.pdf"&gt;These notes from Peking U&lt;/a&gt;  are easy to follow. I have mirrored them &lt;a href="/img/mds_proof.pdf"&gt;here&lt;/a&gt; in case that link 404s.&lt;/li&gt;&lt;/ul&gt;&lt;/html&gt;</description></item>
  <item>
   <title>A Comment on Dimension-Estimation</title>
   <link>http://blog.shriphani.com/2013/11/29/a-comment-on-dimension-estimation/?utm_source=dimension-reduction&amp;utm_medium=RSS</link>
   <guid>urn:http-blog-shriphani-com:-2013-11-29-a-comment-on-dimension-estimation</guid>
   <pubDate>Sat, 30 Nov 2013 04:49:33 UT</pubDate>
   <description>&lt;html&gt;
&lt;p&gt;I saw this neat comment in a paper I was recently reading. If you have all &lt;code&gt;i.i.d&lt;/code&gt; features and you want to estimate its dimension using Grassberger-Procaccia (which estimates dimension using a distance-based metric) or want to classify using a k-NN classifier, it is bad if the data points are mostly pairwise equidistant (for instance, a correlation integral plot will look like a step function and thus will be useless; a k-NN classifier will break because the test point ends up equidistant from all the existing points).&lt;/p&gt;

&lt;p&gt;There is a trivial argument using the Hoeffding bound in Chris Burges&amp;rsquo; &lt;a href="http://research.microsoft.com/en-us/um/people/cburges/tech_reports/msr-tr-2009-2013.pdf"&gt;paper&lt;/a&gt; that suggests that if the features are all &lt;code&gt;i.i.d&lt;/code&gt;, a majority of pairwise distances will end up clustered tightly around a mean which means that k-NN or Grassberger-Procaccia won&amp;rsquo;t work well. I am going to repeat this argument here so I can remember it for later:&lt;/p&gt;

&lt;p&gt;Our vectors are of dimension $ d $ and the components are $ \pm1 $. Assuming all the components are $ iid $, the Hoeffding bound gives us:&lt;/p&gt;

&lt;p&gt;$$ P(||| x_{1} - x_{2} ||^{2} &amp;ndash; 2d| &amp;gt; d\epsilon) = P(| x_{1} \cdot x_{2} | &amp;gt; d\epsilon/2) \le 2exp(-\frac{d\epsilon^2}{8})$$&lt;/p&gt;

&lt;p&gt;and this shows us that most pairwise distances will end up clustered very tightly around a mean and this means that a majority of pairs of points in the dataset will end up equidistant and thus a $ k-NN $ classifier will fail.&lt;/p&gt;

&lt;p&gt;This also means that the correlation integral is a good way to determine if a k-NN classifier will work well. If the plot resembles a spike, the distance function needs to change.&lt;/p&gt;

&lt;p&gt;The correlation-integral is an immensely powerful tool and &lt;a href="https://github.com/shriphani/clj-dimension/blob/master/src/clj_dimension/estimation/correlation_integral.clj"&gt;here&amp;rsquo;s&lt;/a&gt; an implementation&lt;/p&gt;&lt;/html&gt;</description></item></channel></rss>