445 lines
18 KiB
Plaintext
445 lines
18 KiB
Plaintext
{
|
||
"cells": [
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"<!-- dom:TITLE: Data Analysis and Machine Learning: Preprocessing and Dimensionality Reduction -->\n",
|
||
"# Data Analysis and Machine Learning: Preprocessing and Dimensionality Reduction\n",
|
||
"<!-- dom:AUTHOR: Morten Hjorth-Jensen at Department of Physics, University of Oslo & Department of Physics and Astronomy and National Superconducting Cyclotron Laboratory, Michigan State University -->\n",
|
||
"<!-- Author: --> \n",
|
||
"**Morten Hjorth-Jensen**, Department of Physics, University of Oslo and Department of Physics and Astronomy and National Superconducting Cyclotron Laboratory, Michigan State University\n",
|
||
"\n",
|
||
"Date: **Oct 14, 2019**\n",
|
||
"\n",
|
||
"Copyright 1999-2019, Morten Hjorth-Jensen. Released under CC Attribution-NonCommercial 4.0 license\n",
|
||
"\n",
|
||
"\n",
|
||
"\n",
|
||
"\n",
|
||
"## Reducing the number of degrees of freedom, overarching view\n",
|
||
"\n",
|
||
"Many Machine Learning problems involve thousands or even millions of features for each training\n",
|
||
"instance. Not only does this make training extremely slow, it can also make it much harder to find a good\n",
|
||
"solution, as we will see. This problem is often referred to as the curse of dimensionality.\n",
|
||
"Fortunately, in real-world problems, it is often possible to reduce the number of features considerably,\n",
|
||
"turning an intractable problem into a tractable one.\n",
|
||
"\n",
|
||
"Here we will discuss some of the most popular dimensionality\n",
|
||
"reduction techniques: the principal component analysis PCA, Kernel PCA, and Locally Linear Embedding (LLE).\n",
|
||
"\n",
|
||
"\n",
|
||
"\n",
|
||
"\n",
|
||
"## Preprocessing our data\n",
|
||
"\n",
|
||
"Before we proceed however, we will discuss how to preprocess our\n",
|
||
"data. Till now and in connection with our previous examples we have not met so many cases\n",
|
||
"where we are too sensitive to the scaling of our data. Normally the\n",
|
||
"data may need a rescaling and/or may be sensitive to extreme\n",
|
||
"values. Scaling the data renders our inputs much more suitable for the\n",
|
||
"algorithms we want to employ.\n",
|
||
"\n",
|
||
"**Scikit-Learn** has several functions which allow us to rescale the data, normally resulting in much better results in terms of various accuracy scores. The **StandardScaler** function in **Scikit-Learn** ensures that for each feature/predictor we study the mean value is zero and the variance is zero (every column in the design/feature matrix).\n",
|
||
"This scaling has the drawback that it does not ensure that we have a particular maximum or minumum in our data set. Another function included in **Scikit-Learn** is the **MinMaxScaler** which ensures that all features are exactly between $0$ and $1$. The **Normalizer** function scales each column of the design matrix by its Euclidean norm.\n",
|
||
"\n",
|
||
"\n",
|
||
"\n",
|
||
"## Simple preprocessing examples, Franke function and regression"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 1,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [],
|
||
"source": [
|
||
"%matplotlib inline\n",
|
||
"\n",
|
||
"# Common imports\n",
|
||
"import os\n",
|
||
"import numpy as np\n",
|
||
"import pandas as pd\n",
|
||
"import matplotlib.pyplot as plt\n",
|
||
"import sklearn.linear_model as skl\n",
|
||
"from sklearn.metrics import mean_squared_error\n",
|
||
"from sklearn.model_selection import train_test_split\n",
|
||
"from sklearn.preprocessing import MinMaxScaler, StandardScaler, Normalizer\n",
|
||
"from sklearn.svm import SVR\n",
|
||
"\n",
|
||
"# Where to save the figures and data files\n",
|
||
"PROJECT_ROOT_DIR = \"Results\"\n",
|
||
"FIGURE_ID = \"Results/FigureFiles\"\n",
|
||
"DATA_ID = \"DataFiles/\"\n",
|
||
"\n",
|
||
"if not os.path.exists(PROJECT_ROOT_DIR):\n",
|
||
" os.mkdir(PROJECT_ROOT_DIR)\n",
|
||
"\n",
|
||
"if not os.path.exists(FIGURE_ID):\n",
|
||
" os.makedirs(FIGURE_ID)\n",
|
||
"\n",
|
||
"if not os.path.exists(DATA_ID):\n",
|
||
" os.makedirs(DATA_ID)\n",
|
||
"\n",
|
||
"def image_path(fig_id):\n",
|
||
" return os.path.join(FIGURE_ID, fig_id)\n",
|
||
"\n",
|
||
"def data_path(dat_id):\n",
|
||
" return os.path.join(DATA_ID, dat_id)\n",
|
||
"\n",
|
||
"def save_fig(fig_id):\n",
|
||
" plt.savefig(image_path(fig_id) + \".png\", format='png')\n",
|
||
"\n",
|
||
"\n",
|
||
"def FrankeFunction(x,y):\n",
|
||
"\tterm1 = 0.75*np.exp(-(0.25*(9*x-2)**2) - 0.25*((9*y-2)**2))\n",
|
||
"\tterm2 = 0.75*np.exp(-((9*x+1)**2)/49.0 - 0.1*(9*y+1))\n",
|
||
"\tterm3 = 0.5*np.exp(-(9*x-7)**2/4.0 - 0.25*((9*y-3)**2))\n",
|
||
"\tterm4 = -0.2*np.exp(-(9*x-4)**2 - (9*y-7)**2)\n",
|
||
"\treturn term1 + term2 + term3 + term4\n",
|
||
"\n",
|
||
"\n",
|
||
"def create_X(x, y, n ):\n",
|
||
"\tif len(x.shape) > 1:\n",
|
||
"\t\tx = np.ravel(x)\n",
|
||
"\t\ty = np.ravel(y)\n",
|
||
"\n",
|
||
"\tN = len(x)\n",
|
||
"\tl = int((n+1)*(n+2)/2)\t\t# Number of elements in beta\n",
|
||
"\tX = np.ones((N,l))\n",
|
||
"\n",
|
||
"\tfor i in range(1,n+1):\n",
|
||
"\t\tq = int((i)*(i+1)/2)\n",
|
||
"\t\tfor k in range(i+1):\n",
|
||
"\t\t\tX[:,q+k] = (x**(i-k))*(y**k)\n",
|
||
"\n",
|
||
"\treturn X\n",
|
||
"\n",
|
||
"\n",
|
||
"# Making meshgrid of datapoints and compute Franke's function\n",
|
||
"n = 5\n",
|
||
"N = 1000\n",
|
||
"x = np.sort(np.random.uniform(0, 1, N))\n",
|
||
"y = np.sort(np.random.uniform(0, 1, N))\n",
|
||
"z = FrankeFunction(x, y)\n",
|
||
"X = create_X(x, y, n=n) \n",
|
||
"# split in training and test data\n",
|
||
"X_train, X_test, y_train, y_test = train_test_split(X,z,test_size=0.2)\n",
|
||
"\n",
|
||
"\n",
|
||
"svm = SVR(gamma='auto',C=10.0)\n",
|
||
"svm.fit(X_train, y_train)\n",
|
||
"\n",
|
||
"# The mean squared error and R2 score\n",
|
||
"print(\"MSE before scaling: {:.2f}\".format(mean_squared_error(svm.predict(X_test), y_test)))\n",
|
||
"print(\"R2 score before scaling {:.2f}\".format(svm.score(X_test,y_test)))\n",
|
||
"\n",
|
||
"scaler = StandardScaler()\n",
|
||
"scaler.fit(X_train)\n",
|
||
"X_train_scaled = scaler.transform(X_train)\n",
|
||
"X_test_scaled = scaler.transform(X_test)\n",
|
||
"\n",
|
||
"print(\"Feature min values before scaling:\\n {}\".format(X_train.min(axis=0)))\n",
|
||
"print(\"Feature max values before scaling:\\n {}\".format(X_train.max(axis=0)))\n",
|
||
"\n",
|
||
"print(\"Feature min values after scaling:\\n {}\".format(X_train_scaled.min(axis=0)))\n",
|
||
"print(\"Feature max values after scaling:\\n {}\".format(X_train_scaled.max(axis=0)))\n",
|
||
"\n",
|
||
"svm = SVR(gamma='auto',C=10.0)\n",
|
||
"svm.fit(X_train_scaled, y_train)\n",
|
||
"\n",
|
||
"print(\"MSE after scaling: {:.2f}\".format(mean_squared_error(svm.predict(X_test_scaled), y_test)))\n",
|
||
"print(\"R2 score for scaled data: {:.2f}\".format(svm.score(X_test_scaled,y_test)))"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Simple preprocessing examples, breast cancer data and classification\n",
|
||
"\n",
|
||
"We show here how we can use a simple regression case on the breast cancer data using support vector machine as algorithm for classification"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 2,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [],
|
||
"source": [
|
||
"import matplotlib.pyplot as plt\n",
|
||
"import numpy as np\n",
|
||
"from sklearn.model_selection import train_test_split \n",
|
||
"from sklearn.datasets import load_breast_cancer\n",
|
||
"from sklearn.svm import SVC\n",
|
||
"cancer = load_breast_cancer()\n",
|
||
"\n",
|
||
"X_train, X_test, y_train, y_test = train_test_split(cancer.data,cancer.target,random_state=0)\n",
|
||
"print(X_train.shape)\n",
|
||
"print(X_test.shape)\n",
|
||
"\n",
|
||
"svm = SVC(C=100)\n",
|
||
"svm.fit(X_train, y_train)\n",
|
||
"print(\"Test set accuracy: {:.2f}\".format(svm.score(X_test,y_test)))\n",
|
||
"\n",
|
||
"from sklearn.preprocessing import MinMaxScaler, StandardScaler\n",
|
||
"\n",
|
||
"scaler = MinMaxScaler()\n",
|
||
"scaler.fit(X_train)\n",
|
||
"X_train_scaled = scaler.transform(X_train)\n",
|
||
"X_test_scaled = scaler.transform(X_test)\n",
|
||
"\n",
|
||
"print(\"Feature min values before scaling:\\n {}\".format(X_train.min(axis=0)))\n",
|
||
"print(\"Feature max values before scaling:\\n {}\".format(X_train.max(axis=0)))\n",
|
||
"\n",
|
||
"print(\"Feature min values before scaling:\\n {}\".format(X_train_scaled.min(axis=0)))\n",
|
||
"print(\"Feature max values before scaling:\\n {}\".format(X_train_scaled.max(axis=0)))\n",
|
||
"\n",
|
||
"\n",
|
||
"svm.fit(X_train_scaled, y_train)\n",
|
||
"print(\"Test set accuracy scaled data: {:.2f}\".format(svm.score(X_test_scaled,y_test)))\n",
|
||
"\n",
|
||
"scaler = StandardScaler()\n",
|
||
"scaler.fit(X_train)\n",
|
||
"X_train_scaled = scaler.transform(X_train)\n",
|
||
"X_test_scaled = scaler.transform(X_test)\n",
|
||
"\n",
|
||
"svm.fit(X_train_scaled, y_train)\n",
|
||
"print(\"Test set accuracy scaled data: {:.2f}\".format(svm.score(X_test_scaled,y_test)))"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Principal Component Analysis\n",
|
||
"Principal Component Analysis (PCA) is by far the most popular dimensionality reduction algorithm.\n",
|
||
"First it identifies the hyperplane that lies closest to the data, and then it projects the data onto it.\n",
|
||
"\n",
|
||
"The following Python code uses NumPy’s **svd()** function to obtain all the principal components of the\n",
|
||
"training set, then extracts the first two principal components"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 3,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [],
|
||
"source": [
|
||
"X_centered = X - X.mean(axis=0)\n",
|
||
"U, s, V = np.linalg.svd(X_centered)\n",
|
||
"c1 = V.T[:, 0]\n",
|
||
"c2 = V.T[:, 1]"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"PCA assumes that the dataset is centered around the origin. Scikit-Learn’s PCA classes take care of centering\n",
|
||
"the data for you. However, if you implement PCA yourself (as in the preceding example), or if you use other libraries, don’t\n",
|
||
"forget to center the data first.\n",
|
||
"\n",
|
||
"Once you have identified all the principal components, you can reduce the dimensionality of the dataset\n",
|
||
"down to $d$ dimensions by projecting it onto the hyperplane defined by the first $d$ principal components.\n",
|
||
"Selecting this hyperplane ensures that the projection will preserve as much variance as possible."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 4,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [],
|
||
"source": [
|
||
"W2 = V.T[:, :2]\n",
|
||
"X2D = X_centered.dot(W2)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"<!-- !split -->\n",
|
||
"## PCA and scikit-learn\n",
|
||
"\n",
|
||
"Scikit-Learn’s PCA class implements PCA using SVD decomposition just like we did before. The\n",
|
||
"following code applies PCA to reduce the dimensionality of the dataset down to two dimensions (note\n",
|
||
"that it automatically takes care of centering the data):"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 5,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [],
|
||
"source": [
|
||
"from sklearn.decomposition import PCA\n",
|
||
"pca = PCA(n_components = 2)\n",
|
||
"X2D = pca.fit_transform(X)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"After fitting the PCA transformer to the dataset, you can access the principal components using the\n",
|
||
"components variable (note that it contains the PCs as horizontal vectors, so, for example, the first\n",
|
||
"principal component is equal to"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 6,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [],
|
||
"source": [
|
||
"pca.components_.T[:, 0])."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Another very useful piece of information is the explained variance ratio of each principal component,\n",
|
||
"available via the $explained\\_variance\\_ratio$ variable. It indicates the proportion of the dataset’s\n",
|
||
"variance that lies along the axis of each principal component. \n",
|
||
"More material to come here.\n",
|
||
"\n",
|
||
"## More on the PCA\n",
|
||
"Instead of arbitrarily choosing the number of dimensions to reduce down to, it is generally preferable to\n",
|
||
"choose the number of dimensions that add up to a sufficiently large portion of the variance (e.g., 95%).\n",
|
||
"Unless, of course, you are reducing dimensionality for data visualization — in that case you will\n",
|
||
"generally want to reduce the dimensionality down to 2 or 3.\n",
|
||
"The following code computes PCA without reducing dimensionality, then computes the minimum number\n",
|
||
"of dimensions required to preserve 95% of the training set’s variance:"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 7,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [],
|
||
"source": [
|
||
"pca = PCA()\n",
|
||
"pca.fit(X)\n",
|
||
"cumsum = np.cumsum(pca.explained_variance_ratio_)\n",
|
||
"d = np.argmax(cumsum >= 0.95) + 1"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"You could then set $n\\_components=d$ and run PCA again. However, there is a much better option: instead\n",
|
||
"of specifying the number of principal components you want to preserve, you can set $n\\_components$ to be\n",
|
||
"a float between 0.0 and 1.0, indicating the ratio of variance you wish to preserve:"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 8,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [],
|
||
"source": [
|
||
"pca = PCA(n_components=0.95)\n",
|
||
"X_reduced = pca.fit_transform(X)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Incremental PCA\n",
|
||
"One problem with the preceding implementation of PCA is that it requires the whole training set to fit in\n",
|
||
"memory in order for the SVD algorithm to run. Fortunately, Incremental PCA (IPCA) algorithms have\n",
|
||
"been developed: you can split the training set into mini-batches and feed an IPCA algorithm one minibatch\n",
|
||
"at a time. This is useful for large training sets, and also to apply PCA online (i.e., on the fly, as new\n",
|
||
"instances arrive).\n",
|
||
"\n",
|
||
"## Randomized PCA\n",
|
||
"\n",
|
||
"Scikit-Learn offers yet another option to perform PCA, called Randomized PCA. This is a stochastic\n",
|
||
"algorithm that quickly finds an approximation of the first d principal components. Its computational\n",
|
||
"complexity is $O(m \\times d^2)+O(d^3)$, instead of $O(m \\times n^2) + O(n^3)$, so it is dramatically faster than the\n",
|
||
"previous algorithms when $d$ is much smaller than $n$.\n",
|
||
"\n",
|
||
"\n",
|
||
"\n",
|
||
"\n",
|
||
"## Kernel PCA\n",
|
||
"\n",
|
||
"The kernel trick is a mathematical technique that implicitly maps instances into a\n",
|
||
"very high-dimensional space (called the feature space), enabling nonlinear classification and regression\n",
|
||
"with Support Vector Machines. Recall that a linear decision boundary in the high-dimensional feature\n",
|
||
"space corresponds to a complex nonlinear decision boundary in the original space.\n",
|
||
"It turns out that the same trick can be applied to PCA, making it possible to perform complex nonlinear\n",
|
||
"projections for dimensionality reduction. This is called Kernel PCA (kPCA). It is often good at\n",
|
||
"preserving clusters of instances after projection, or sometimes even unrolling datasets that lie close to a\n",
|
||
"twisted manifold.\n",
|
||
"For example, the following code uses Scikit-Learn’s KernelPCA class to perform kPCA with an"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 9,
|
||
"metadata": {
|
||
"collapsed": false
|
||
},
|
||
"outputs": [],
|
||
"source": [
|
||
"from sklearn.decomposition import KernelPCA\n",
|
||
"rbf_pca = KernelPCA(n_components = 2, kernel=\"rbf\", gamma=0.04)\n",
|
||
"X_reduced = rbf_pca.fit_transform(X)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## LLE\n",
|
||
"\n",
|
||
"Locally Linear Embedding (LLE) is another very powerful nonlinear dimensionality reduction\n",
|
||
"(NLDR) technique. It is a Manifold Learning technique that does not rely on projections like the previous\n",
|
||
"algorithms. In a nutshell, LLE works by first measuring how each training instance linearly relates to its\n",
|
||
"closest neighbors (c.n.), and then looking for a low-dimensional representation of the training set where\n",
|
||
"these local relationships are best preserved (more details shortly). \n",
|
||
"\n",
|
||
"\n",
|
||
"\n",
|
||
"## Other techniques\n",
|
||
"\n",
|
||
"\n",
|
||
"There are many other dimensionality reduction techniques, several of which are available in Scikit-Learn.\n",
|
||
"\n",
|
||
"Here are some of the most popular:\n",
|
||
"* **Multidimensional Scaling (MDS)** reduces dimensionality while trying to preserve the distances between the instances.\n",
|
||
"\n",
|
||
"* **Isomap** creates a graph by connecting each instance to its nearest neighbors, then reduces dimensionality while trying to preserve the geodesic distances between the instances.\n",
|
||
"\n",
|
||
"* **t-Distributed Stochastic Neighbor Embedding** (t-SNE) reduces dimensionality while trying to keep similar instances close and dissimilar instances apart. It is mostly used for visualization, in particular to visualize clusters of instances in high-dimensional space (e.g., to visualize the MNIST images in 2D).\n",
|
||
"\n",
|
||
"* Linear Discriminant Analysis (LDA) is actually a classification algorithm, but during training it learns the most discriminative axes between the classes, and these axes can then be used to define a hyperplane onto which to project the data. The benefit is that the projection will keep classes as far apart as possible, so LDA is a good technique to reduce dimensionality before running another classification algorithm such as a Support Vector Machine (SVM) classifier discussed in the SVM lectures."
|
||
]
|
||
}
|
||
],
|
||
"metadata": {},
|
||
"nbformat": 4,
|
||
"nbformat_minor": 2
|
||
}
|