{ "cells": [ { "cell_type": "markdown", "metadata": { "toc": "true" }, "source": [ "

Table of Contents

\n", "
" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from jupyterthemes import get_themes\n", "from jupyterthemes.stylefx import set_nb_theme\n", "themes = get_themes()\n", "set_nb_theme(themes[1])" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Ethen 2017-11-18 21:13:40 \n", "\n", "CPython 3.5.2\n", "IPython 6.2.1\n", "\n", "numpy 1.13.3\n", "scipy 0.19.1\n", "pandas 0.20.3\n", "sklearn 0.19.1\n", "matplotlib 2.1.0\n" ] } ], "source": [ "# 1. magic for inline plot\n", "# 2. magic to print version\n", "# 3. magic so that the notebook will reload external python modules\n", "# 4. magic to enable retina (high resolution) plots\n", "# https://gist.github.com/minrk/3301035\n", "%matplotlib inline\n", "%load_ext watermark\n", "%load_ext autoreload\n", "%autoreload 2\n", "%config InlineBackend.figure_format = 'retina'\n", "\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from scipy.linalg import svd as scipy_svd\n", "from sklearn.pipeline import Pipeline\n", "from sklearn.datasets import load_iris\n", "from sklearn.decomposition import PCA, TruncatedSVD\n", "from sklearn.preprocessing import normalize, StandardScaler\n", "from sklearn.feature_extraction.text import TfidfVectorizer\n", "\n", "%watermark -a 'Ethen' -d -t -v -p numpy,scipy,pandas,sklearn,matplotlib" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Singular Value Decomposition (SVD)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "When conducting data analysis project, it's very common to encounter dataset that contains some useful information to the task at hand, but also contains low quality information that do not contribute too much to the end goal. When facing this issue, there are numerous ways to isolating the signal from the noise. e.g. We can employ regularization methods such as [Lasso regression](http://nbviewer.jupyter.org/github/ethen8181/machine-learning/blob/master/regularization/regularization.ipynb) to perform constrained optimization, automatically dropping uninformative features from the model or use [tree-based methods](https://github.com/ethen8181/machine-learning#trees--20161210) to identify the features that were most often used for constructing the tree.\n", "\n", "Or use a variance maximization method such as [Principal Component Analysis (PCA)](http://nbviewer.jupyter.org/github/ethen8181/machine-learning/blob/master/dim_reduct/PCA.ipynb) that aims to transform the data into a new set of orthogonal components, ensuring that the first component aligns to the maximum variance in the dataset, and the subsequent component aligns with the next maximum component and so on. In other words, it makes a dataset more compact while preserving information. The convention method for calculating PCA requires us to compute the full covariance matrix, making it suffer from extensive use of memory and can be numerically unstable. It turns out, SVD is a method that can be used to compute PCA and obtain the principal component to transform our raw dataset.\n", "\n", "**Singular Value Decomposition (SVD)** is a particular decomposition method that decomposes an arbitrary matrix $A$ with $m$ rows and $n$ columns (assuming this matrix also has a rank of $r$, i.e. $r$ columns of the matrix $A$ are linear independent) into a set of related matrices:\n", "\n", "$$\n", "\\begin{align}\n", "A = U \\Sigma V^{T}\n", "\\end{align}\n", "$$\n", "\n", "where:\n", "\n", "- $\\Sigma$ (Sigma) is a $r * r$ non-negative, decreasing order diagonal matrix. All elements not on the main diagonal are\n", "0 and the elements of $\\Sigma$ are called the singular values. Another common notation that is used for this matrix is $S$. Thus in the following post, we'll use these two symbols interchangeably.\n", "\n", "- $U$ is a $m * r$ orthonormal matrix and $V$ is a $n * r$ orthonormal matrix.\n", " - Orthogonal matrix refers to a square matrix where the columns are 90 degrees between each other and its inner dot product is zero, i.e. Given a orthogonal matrix $Q$, $Q^T Q = Q Q^T = I$ and $Q^T = Q^{-1}$.\n", " - Orthonormal matrix: orthogonal matrix where columns are unit vectors.\n", "\n", "A classic pictorial representation of SVD.\n", "\n", "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Interpretation of SVD" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Geometric Interpretation" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We'll use a 2 dimensional dataset for the geometric interpretation for ease of visualization. Transformation of a matrix by $U \\Sigma V^T$ can be visualized as a rotation and reflection, scaling, rotation and reflection. We'll see this as a step-by-step visualization.\n", "\n", "Given a matrix $x = \\begin{bmatrix} -10 & -10 & 20 & 20\\\\ -10 & 20 & 20 & -10 \\end{bmatrix}$ and a transformation matrix $A = \\begin{bmatrix} 1 & 0.3 \\\\ 0.45 & 1.2 \\end{bmatrix}$.\n", "\n", "- $V^T x$ We can see that multiplying by $V^T$ rotates and reflects the input matrix $x$. Notice the swap of colors red-blue and green-yellow indicating a reflection along the x-axis.\n", "\n", "\n", "\n", "- $S V^T x$ Since $S$ only contains values on the diagonal, it scales the matrix. $V$ rotates the matrix to a position where the singular values now represent the scaling factor along the V-basis. In the picture below $V^Tx$ is dashed and $SV^Tx$ is solid.\n", "\n", "\n", "\n", "- $U S V^T$ Finally, $U$ rotates and reflects the matrix back to the standard basis.\n", "\n", "\n", "\n", "Putting all three steps into one picture below, the dashed square shows $x$ as the corners and the transformed matrix $Ax$ as the solid shape.\n", "\n", "\n", "\n", "The most useful property of the SVD is that the axes in the new space, which represent new latent attributes, are orthogonal. Hence original attributes are expressed in terms of new attributes that are independent of each other." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "singular values: [ 1.49065822 0.71444949]\n" ] }, { "data": { "text/plain": [ "array([[-10, -10],\n", " [-10, 20],\n", " [ 20, 20],\n", " [ 20, -10]])" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# we can confirm this with code\n", "A = np.array([[1, 0.3], [0.45, 1.2]])\n", "U, S, V = scipy_svd(A)\n", "print('singular values:', S)\n", "\n", "# the toy 2d matrix\n", "x = np.array([[-10, -10, 20, 20], [-10, 20, 20, -10]]).T\n", "x" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "scrolled": true }, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "" ] }, "metadata": { "image/png": { "height": 271, "width": 1427 } }, "output_type": "display_data" } ], "source": [ "# change default font size\n", "plt.rcParams['font.size'] = 12 \n", "\n", "# the plot is not as pretty as the diagram above,\n", "# but hopefully it gets the point across\n", "fig, ax = plt.subplots(1, 4, figsize = (20, 4))\n", "\n", "ax[0].scatter(x[:, 0], x[:, 1])\n", "ax[0].set_title('Original matrix')\n", "\n", "temp = x @ V.T\n", "ax[1].scatter(temp[:, 0], temp[:, 1])\n", "ax[1].set_title('$V^Tx$')\n", "\n", "temp = temp @ np.diag(S)\n", "ax[2].scatter(temp[:, 0], temp[:, 1])\n", "ax[2].set_title('$SV^Tx$')\n", "\n", "temp = temp @ U\n", "ax[3].scatter(temp[:, 0], temp[:, 1])\n", "ax[3].set_title('$USV^Tx$')\n", "\n", "plt.tight_layout()\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Factor Interpretation" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here we are given a rank 3 matrix $A$, representing ratings of movies by users.\n", "\n", "| Name | Matrix | Alien | Star Wars | Casablanca | Titanic |\n", "| ----- | ------ | ----- | --------- | ---------- | ------- |\n", "| Joe | 1 | 1 | 1 | 0 | 0 |\n", "| Jim | 3 | 3 | 3 | 0 | 0 |\n", "| John | 4 | 4 | 4 | 0 | 0 |\n", "| Jack | 5 | 5 | 5 | 0 | 0 |\n", "| Jill | 0 | 2 | 0 | 4 | 4 |\n", "| Jenny | 0 | 0 | 0 | 5 | 5 |\n", "| Jane | 0 | 1 | 0 | 2 | 2 |\n", "\n", "Applying SVD to this matrix will give us the following decomposition:\n", "\n", "\n", "\n", "The key to understanding what SVD offers is viewing the $r$ columns of $U$, $\\Sigma$, and $V$ as representing concepts that are hidden in the original matrix. In our contrived example, we can imagine there are two concepts underlying the movies, scientific fiction and romance.\n", "\n", "To be explicit:\n", "\n", "- The matrix $U$ connects people to concept. For example, looking at Joe (the first row in the original matrix). The value 0.14 in the first row and first column of $U$ is smaller than some of the other entries in that column. The rationale for this is because while Joe watches only science fiction, he doesn’t rate those movies highly.\n", "- The matrix $V$ relates movies to concept. The approximately 0.58 in each of the first three columns of the first row of $V^T$ indicates that the first three movies – The Matrix, Alien and Star Wars – each are of science-fiction genre.\n", "- Matrix $\\Sigma$ gives the strength of each of the concepts. In our example, the strength of the science-fiction concept is 12.4, while the strength of the romance concept is 9.5. Intuitively, the science-fiction concept is stronger because the data provides more information about the movies of that genre and the people who like them.\n", "- The third concept is a bit harder to interpret, but it doesn't matter that much, because its weight, given by the third nonzero diagonal entry in $\\Sigma$ is relatively low compared to the first two concepts.\n", "- Note that the matrix decomposition doesn't know the meaning of any column in the dataset, it discovers the underlying concept and it is up to us to interpret these latent factors." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Worked Example Full SVD" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's step through a worked example of \"Full\" SVD. In practice the full version is computationally expensive, since we must calculate the full matrices $U_{mr}$, $S_{rr}$, and $V_{nr}^{T}$. The \"truncated\" versions of SVD are usually preferred, where we can preselect the top $k < r$ dimensions of interest and calculate $U_{mk}$, $S_{kk}$ and $V_{nk}^{T}$. But the truncated version is a topic for another day." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[ 1.24810147e+01 9.50861406e+00 1.34555971e+00 3.04642685e-16\n", " 0.00000000e+00]\n" ] } ], "source": [ "# matrix form of the table above\n", "rank = 3\n", "A = np.array([\n", " [1, 1, 1, 0, 0],\n", " [3, 3, 3, 0, 0],\n", " [4, 4, 4, 0, 0],\n", " [5, 5, 5, 0, 0],\n", " [0, 2, 0, 4, 4],\n", " [0, 0, 0, 5, 5],\n", " [0, 1, 0, 2, 2]])\n", "\n", "# we'll use a library to perform the svd\n", "# so we can confirm our result with it\n", "U, S, V = scipy_svd(A, full_matrices = False)\n", "\n", "# we'll just print out S, a.k.a Sigma to show the numbers\n", "# are identical to the results shown earlier\n", "print(S)" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "True\n", "True\n", "True\n" ] } ], "source": [ "# the following cell verifies some properties of SVD\n", "\n", "# Verify calculation of A=USV^T\n", "print(np.allclose(A, U @ np.diag(S) @ V))\n", "\n", "# orthonormal, columns are unit vectors (length = 1)\n", "print(np.allclose(np.round(np.sum(U * U, axis = 0)), np.ones(S.size)))\n", "\n", "# orthogonal, dot product of itself is equivalent to\n", "# the identity matrix U^T U = I\n", "print(np.allclose(U.T @ U, np.eye(S.size)))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The SVD of a matrix $A$ is strongly connected to the eigenvalues of the symmetric matrices $A^{T}A$ and $AA^{T}$. We'll start with the expression for SVD: $A = U \\Sigma V^T$.\n", "\n", "$$\n", "\\begin{align}\n", "A^T\n", "&= (U \\Sigma V^T)^T \\\\\n", "&= (V^T)^T \\Sigma^T U^T \\\\\n", "&= V \\Sigma^T U^T \\\\\n", "&= V \\Sigma U^T\n", "\\end{align}\n", "$$\n", "\n", "In the second step we use the matrix property that $(BA)^T = A^T B^T$ and in the final step $\\Sigma$ is a diagonal matrix, thus $\\Sigma^T = \\Sigma$. Next:\n", "\n", "$$\n", "\\begin{align}\n", "A^T A\n", "&= (V \\Sigma U^T)(U \\Sigma V^T) \\\\\n", "&= V \\Sigma I \\Sigma V^T \\\\\n", "&= V \\Sigma \\Sigma V^T\n", "\\end{align}\n", "$$\n", "\n", "In the second step, we use the fact that $U$ is a orthonormal matrix, so $U^T U$ is an identity matrix of the appropriate size.\n", "\n", "We now multiply both sides of this equation by $V$ to get:\n", "\n", "$$\n", "\\begin{align}\n", "A^T A V\n", "&= V \\Sigma^2 V^T V \\\\\n", "&= V \\Sigma^2 I \\\\\n", "&= V \\Sigma^2\n", "\\end{align}\n", "$$\n", "\n", "Here we use the fact that $V$ is also a orthonormal matrix, so $V^T V$ is an identity matrix of the appropriate size. Looking at the equation $A^T A V = V \\Sigma^2$, we now see that $V$ is the eigenvector of the matrix $A^T A$ and $\\Sigma^2$ is the diagonal matrix whose entries are the corresponding eigenvalues. i.e. $V = eig(A^T A)$" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "True\n" ] } ], "source": [ "AtA = A.T @ A\n", "_, V1 = np.linalg.eig(AtA)\n", "\n", "# Note that the possible non-uniqueness of the decomposition means\n", "# that an axis can be flipped without changing anything fundamental,\n", "# thus we compare whether the absolute values are relatively close\n", "# instead of the raw value\n", "print(np.allclose(np.abs(V1[:, :rank]), np.abs(V1[:, :rank])))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Only $U$ remains to be computed, but it can be found in the same way we found $V$. Instead this time, we'll be starting with $A A^T$\n", "\n", "$$\n", "\\begin{align}\n", "A A^T U\n", "&= (U \\Sigma V^T)(V \\Sigma U^T) U \\\\\n", "&= V \\Sigma I \\Sigma V^T U \\\\\n", "&= U \\Sigma \\Sigma U^T U \\\\\n", "&= U \\Sigma \\Sigma I \\\\\n", "&= U \\Sigma^2\n", "\\end{align}\n", "$$\n", "\n", "In other words: $U = eig(A A^T)$" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "AAt = A @ A.T\n", "_, U1 = np.linalg.eig(AAt)\n", "np.allclose(np.abs(U1[:, :rank]), np.abs(U[:, :rank]))" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([ 12., 10., 1., 0., 0.])" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# notice that since this is a rank 3 matrix\n", "# only the first 3 values of 3 contains non-zero values\n", "np.round(S, 0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To sum this section up:\n", "\n", "- $U$ is a $m * r$ orthonormal matrix of \"left-singular\" (eigen)vectors of $AA^{T}$.\n", "- $V$ is a $n * r$ orthonormal matrix of 'right-singular' (eigen)vectors of $A^{T}A$. \n", "- $\\Sigma$ is a $r * r$ non-negative, decreasing order diagonal matrix. All elements not on the main diagonal are\n", "0 and the elements of $\\Sigma$ are called the singular values, which is the square root of nonzero eigenvalues.\n", "\n", "For those interested, the following link contains a detail walkthrough of the computation by hand. [Notes: Singular Value Decomposition Tutorial](https://datajobs.com/data-science-repo/SVD-Tutorial-[Kirk-Baker].pdf)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Relationships with PCA" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This usage of SVD is very similar to Principal Components Analysis (PCA) and in fact several numerical software libraries actually use SVD under the hood for their PCA routines, for example `sklearn.decomposition.PCA` within scikit-learn. This is due to the fact that it is more numerically stable and it's also possible to perform a truncated SVD, which only needs us to calculate $U \\Sigma V^T$ for the first $k" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Applications" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This is by no means an exhaustive list of SVD's application." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Dimensionality Reduction" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Due to its relationships with PCA, we can imagine a very frequent use of SVD is feature reduction. By only selecting the top $k$ singular values, we have in effect compressed the original information and represented it using fewer features. Note that because SVD is also a numerical algorithm, it is important to standardize the features to ensure the magnitude of the entries are of similar range." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Information Retrieval" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "SVD has also been used extensively in information retrieval, in this particular application, it is also known as Latent Semantic Analysis (LSA) or Latent Semantic Indexing (LSI). As we'll soon see, the idea is very similar to [topic modeling](http://nbviewer.jupyter.org/github/ethen8181/machine-learning/blob/master/clustering/topic_model/LDA.ipynb). The fundamental problem in information retrieval is: given some search terms, retrieve all of the documents that contain those search terms or, perhaps more usefully, return documents whose content is semantically related to the search terms. For example, if one of the search terms was \"automobile\" it might be appropriate to return also documents that contain the search term \"car\".\n", "\n", "One approach to this problem is: Given an information repository, we might convert a raw text to document-term matrix with one row per document and one column per word. Then convert the search term as a vector in the same space, and retrieving document vectors that are close to the search vector. There are several problems with vector-based retrieval.\n", "\n", "- First, the space is very high dimensional. For example, a typical collection of documents can easily mention more than 100,000 words even if stemming is used (i.e., \"skip\", \"skipping\", \"skipped\" are all treated as the same word). This creates problems for distance measurement due to the curse of dimensionality.\n", "- Second, it treats each word as independent, whereas in languages like English, the same word can mean two different things (\"bear\" a burden versus \"bear\" in the woods), and two different words can mean the same thing (\"car\" and \"automobile\").\n", "\n", "By applying SVD, we can reduce the dimension to speed up the search, words with similar meanings will get mapped to a similar truncated space. We'll take at this application in the following quick example:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[ 0.82714832, -0.20216821],\n", " [ 0.64317518, -0.27989764],\n", " [ 0.19952711, -0.19724375],\n", " [ 0.24907097, -0.13828783],\n", " [ 0.7392593 , 0.14892526],\n", " [ 0.1162772 , 0.73645697],\n", " [ 0.28427388, 0.79260792]])" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "example = [\n", " 'Machine learning is super fun',\n", " 'Python is super, super cool',\n", " 'Statistics is cool, too',\n", " 'Data science is fun',\n", " 'Python is great for machine learning',\n", " 'I like football',\n", " 'Football is great to watch']\n", "\n", "# a two-staged model pipeline,\n", "# first convert raw words to a tfidf document-term matrix\n", "# and apply svd decomposition after that\n", "tfidf = TfidfVectorizer(stop_words = 'english')\n", "svd = TruncatedSVD(n_components = 2)\n", "pipeline = Pipeline([\n", " ('tfidf', tfidf),\n", " ('svd', svd)\n", "])\n", "X_lsa = pipeline.fit_transform(example)\n", "X_lsa" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
cooldatafootballfungreatlearninglikemachinepythonsciencestatisticssuperwatch
concept10.2119030.0825240.1234900.2932060.2839660.4255310.0486110.4255310.3434900.0825240.0834140.5100290.100157
concept2-0.175362-0.0615540.654756-0.1248780.365768-0.0194310.413619-0.019431-0.029054-0.061554-0.110779-0.2405950.375162
\n", "
" ], "text/plain": [ " cool data football fun great learning \\\n", "concept1 0.211903 0.082524 0.123490 0.293206 0.283966 0.425531 \n", "concept2 -0.175362 -0.061554 0.654756 -0.124878 0.365768 -0.019431 \n", "\n", " like machine python science statistics super \\\n", "concept1 0.048611 0.425531 0.343490 0.082524 0.083414 0.510029 \n", "concept2 0.413619 -0.019431 -0.029054 -0.061554 -0.110779 -0.240595 \n", "\n", " watch \n", "concept1 0.100157 \n", "concept2 0.375162 " ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# mapping of words to latent factors/concepts,\n", "# i.e. each concept is a linear combination of words\n", "tfidf = pipeline.named_steps['tfidf']\n", "vocab = tfidf.get_feature_names()\n", "pd.DataFrame(svd.components_, index = ['concept1', 'concept2'], columns = vocab)" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "total variance explained: 0.252606886963\n" ] }, { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
concept1concept2
Machine learning is super fun0.827148-0.202168
Python is super, super cool0.643175-0.279898
Statistics is cool, too0.199527-0.197244
Data science is fun0.249071-0.138288
Python is great for machine learning0.7392590.148925
I like football0.1162770.736457
Football is great to watch0.2842740.792608
\n", "
" ], "text/plain": [ " concept1 concept2\n", "Machine learning is super fun 0.827148 -0.202168\n", "Python is super, super cool 0.643175 -0.279898\n", "Statistics is cool, too 0.199527 -0.197244\n", "Data science is fun 0.249071 -0.138288\n", "Python is great for machine learning 0.739259 0.148925\n", "I like football 0.116277 0.736457\n", "Football is great to watch 0.284274 0.792608" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "svd = pipeline.named_steps['svd']\n", "print('total variance explained:', np.sum(svd.explained_variance_))\n", "\n", "# mapping of document to latent factors/concepts,\n", "# i.e. Eech document is a linear combination of the concepts\n", "pd.DataFrame(X_lsa, index = example, columns = ['concept1', 'concept2'])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "After applying LSA, we can use the compressed features to see which documents are more similar to a particular document. The following code chunk shows the pairwise cosine similarity of all the documents." ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
Machine learning is super funPython is super, super coolStatistics is cool, tooData science is funPython is great for machine learningI like footballFootball is great to watch
Machine learning is super fun1.0000000.9854580.8577460.9645350.905386-0.0830260.104459
Python is super, super cool0.9854581.0000000.9326230.9953590.820075-0.251150-0.066049
Statistics is cool, too0.8577460.9326231.0000000.9630190.558322-0.583514-0.421662
Data science is fun0.9645350.9953590.9630191.0000000.761204-0.343126-0.161758
Python is great for machine learning0.9053860.8200750.5583220.7612041.0000000.3479520.516841
I like football-0.083026-0.251150-0.583514-0.3431260.3479521.0000000.982423
Football is great to watch0.104459-0.066049-0.421662-0.1617580.5168410.9824231.000000
\n", "
" ], "text/plain": [ " Machine learning is super fun \\\n", "Machine learning is super fun 1.000000 \n", "Python is super, super cool 0.985458 \n", "Statistics is cool, too 0.857746 \n", "Data science is fun 0.964535 \n", "Python is great for machine learning 0.905386 \n", "I like football -0.083026 \n", "Football is great to watch 0.104459 \n", "\n", " Python is super, super cool \\\n", "Machine learning is super fun 0.985458 \n", "Python is super, super cool 1.000000 \n", "Statistics is cool, too 0.932623 \n", "Data science is fun 0.995359 \n", "Python is great for machine learning 0.820075 \n", "I like football -0.251150 \n", "Football is great to watch -0.066049 \n", "\n", " Statistics is cool, too \\\n", "Machine learning is super fun 0.857746 \n", "Python is super, super cool 0.932623 \n", "Statistics is cool, too 1.000000 \n", "Data science is fun 0.963019 \n", "Python is great for machine learning 0.558322 \n", "I like football -0.583514 \n", "Football is great to watch -0.421662 \n", "\n", " Data science is fun \\\n", "Machine learning is super fun 0.964535 \n", "Python is super, super cool 0.995359 \n", "Statistics is cool, too 0.963019 \n", "Data science is fun 1.000000 \n", "Python is great for machine learning 0.761204 \n", "I like football -0.343126 \n", "Football is great to watch -0.161758 \n", "\n", " Python is great for machine learning \\\n", "Machine learning is super fun 0.905386 \n", "Python is super, super cool 0.820075 \n", "Statistics is cool, too 0.558322 \n", "Data science is fun 0.761204 \n", "Python is great for machine learning 1.000000 \n", "I like football 0.347952 \n", "Football is great to watch 0.516841 \n", "\n", " I like football \\\n", "Machine learning is super fun -0.083026 \n", "Python is super, super cool -0.251150 \n", "Statistics is cool, too -0.583514 \n", "Data science is fun -0.343126 \n", "Python is great for machine learning 0.347952 \n", "I like football 1.000000 \n", "Football is great to watch 0.982423 \n", "\n", " Football is great to watch \n", "Machine learning is super fun 0.104459 \n", "Python is super, super cool -0.066049 \n", "Statistics is cool, too -0.421662 \n", "Data science is fun -0.161758 \n", "Python is great for machine learning 0.516841 \n", "I like football 0.982423 \n", "Football is great to watch 1.000000 " ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "X_normed = normalize(X_lsa, axis = 1)\n", "similarity = X_normed @ X_normed.T\n", "pd.DataFrame(similarity, index = example, columns = example)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Collaborative Filtering" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This post is getting quite long, thus we'll just list it here that similar to the movie rating matrix in a couple of sections back, SVD can be applied to implementing recommendation system, namely collaborative filtering. I personally haven't checked it yet, but the following post seems to contain a walkthrough of SVD applied to collaborative filtering for people who are interested in diving deeper. [Blog: Matrix Factorization for Movie Recommendations in Python](https://beckernick.github.io/matrix-factorization-recommender/)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Reference" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- [Notes: Latent Semantic Analysis](http://www.datascienceassn.org/sites/default/files/users/user1/lsa_presentation_final.pdf)\n", "- [Notes: Singular Value Decomposition Tutorial](https://datajobs.com/data-science-repo/SVD-Tutorial-[Kirk-Baker].pdf)\n", "- [Blog: Feature Reduction using SVD](http://blog.applied.ai/feature-reduction-using-svd/)\n", "- [Blog: Singular Value Decomposition Demystified](http://makeyourowntextminingtoolkit.blogspot.co.uk/2017/02/singular-value-decomposition-demystified.html)\n", "- [Blog: Singular Value Decomposition (SVD) Visualisation](https://alyssaq.github.io/2015/singular-value-decomposition-visualisation/)\n", "- [Blog: Reducing Dimensionality from Dimensionality Reduction Techniques](https://towardsdatascience.com/reducing-dimensionality-from-dimensionality-reduction-techniques-f658aec24dfe)\n", "- [Online book: Mining Massive Dataset: Chapter 11 Dimensionality Reduction](http://infolab.stanford.edu/~ullman/mmds/ch11.pdf)\n", "- [Online book: Understanding Complex Datasets - Data Mining with Matrix Decomposition Chapter 3: Singular Value Decomposition (SVD)](http://lnfm1.sai.msu.ru/~rastor/Books/Skillicorn-Understanding_complex_datasets_data_mining_with_matrix_decompositions.pdf)\n", "- [StackExchange: Relationship between SVD and PCA. How to use SVD to perform PCA?](https://stats.stackexchange.com/questions/134282/relationship-between-svd-and-pca-how-to-use-svd-to-perform-pca)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.4" }, "toc": { "nav_menu": { "height": "210px", "width": "252px" }, "number_sections": true, "sideBar": true, "skip_h1_title": false, "title_cell": "Table of Contents", "title_sidebar": "Contents", "toc_cell": true, "toc_position": {}, "toc_section_display": "block", "toc_window_display": true }, "varInspector": { "cols": { "lenName": 16, "lenType": 16, "lenVar": 40 }, "kernels_config": { "python": { "delete_cmd_postfix": "", "delete_cmd_prefix": "del ", "library": "var_list.py", "varRefreshCmd": "print(var_dic_list())" }, "r": { "delete_cmd_postfix": ") ", "delete_cmd_prefix": "rm(", "library": "var_list.r", "varRefreshCmd": "cat(var_dic_list()) " } }, "types_to_exclude": [ "module", "function", "builtin_function_or_method", "instance", "_Feature" ], "window_display": false } }, "nbformat": 4, "nbformat_minor": 2 }