各类降维方法总结

在现实生活中很多机器学习问题有上千维,甚至上万维特征,这不仅影响了训练速度,通常还很难找到比较好的解。这样的问题成为维数灾难(curse of dimensionality)

幸运的是,理论上降低维度是可行的。比如MNIST数据集大部分的像素总是白的,因此可以去掉这些特征;相邻的像素之间是高度相关的,如果变为一个像素,相差也并不大。

需要注意:降低维度肯定会损失一些信息,这可能会让表现稍微变差。因此应该先在原维度训练一次,如果训练速度太慢再选择降维。虽然有时候降为能去除噪声和一些不必要的细节,但通常不会,主要是能加快训练速度。

降维除了能提高训练速度以外,还能用于数据可视化。把高维数据降到2维或3维,然后就能把特征在2维空间(3维空间)表示出来,能直观地发现一些规则。

1. 降维的主要方法

降维的方法主要为两种:projection 和 Manifold Learning。

1.1 投影(Projection)

在大多数的真实问题,训练样例都不是均匀分散在所有的维度,许多特征都是固定的,同时还有一些特征是强相关的。因此所有的训练样例实际上可以投影在高维空间中的低维子空间中,下面看一个例子。


可以看到3维空间中的训练样例其实都分布在同一个2维平面,因此我们能够将所有样例都投影在2维平面。对于更高维的空间可能能投影到低维的子空间中。

然而投影(projection)不总是降维最好的方法在,比如许多情况下,空间可以扭转,如著名的瑞士卷(Swiss roll)数据。



如果简单的使用投影(project)降维(例如通过压平第3维),那么会变成如下左图的样子,不同类别的样例都混在了一起,而我们的预想是变成右下图的形式。


1.2 流形学习(Manifold Learning)

瑞士卷(Swiss roll)是二维流形的例子。它可以在高维空间中弯曲。更一般地,一个d维流形在n维空间弯曲(其中d<n)。在瑞士卷的情况下,D=2和n=3。

基于流行数据进行建模的降维算法称为流形学习(Manifold Learning)。它假设大多数现实世界的高维数据集接近于一个低维流形。

流行假设通常隐含着另一个假设:通过流形在低维空间中表达,任务(例如分类或回归)应该变得简单。如下图第一行,Swiss roll分为两类,在3D的空间看起来很复杂,但通过流行假设到2D就能变得简单。

但是这个假设并不总是能成立,比如下图第二行,决策线为x=5,2D的的决策线明显比3D的要复杂。因此在训练模型之前先降维能够加快训练速度,但是效果可能会又增有减,这取决于数据的形式。


下面介绍几种降维方法

2.PCA 主成分分析:

原理:

PCA是一个线性方法,由于PCA只是简单对输入数据进行变换,所以它既可以用在分类问题,也可以用在回归问题。非线性的情况可以使用核方法kernelized PCA,但是由于PCA有良好的数学性质、发现转换后特征空间的速度、以及再原始和变换后特征间相互转换的能力,在降维或者说特征抽取时,它已经可以满足大部分情况。
给定原始空间,PCA会找到一个到更低维度空间的线性映射。因为需要使所有样本的投影尽可能分开,则需要最大化投影点的方差。
它具有如下性质:
1.保留方差是最大的
2.最终的重构误差(从变换后回到原始情况)是最小的

PCA

图片来自:http://markus.com/deep-learning-101/

保留最大方差:
  首先需要选择一个好的超平面。先看下图的例子,需要将2D降为1D,选择不同的平面得到右图不一样的结果,第1个投影以后方差最大,第3个方差最小,选择最大方差的一个感觉上应该是比较合理的,因为这样能保留更多的信息。



另外一种判断的方式是:通过最小化原数据和投影后的数据之间的均方误差。

2.1 流程:

1.去除平均值(每个元素减去自己特征的平均值)
2.计算协方差矩阵


协方差.png

协方差介绍(摘自百度百科):
协方差表示的是两个变量的总体的误差,这与只表示一个变量误差的方差不同。 如果两个变量的变化趋势一致,也就是说如果其中一个大于自身的期望值,另外一个也大于自身的期望值,那么两个变量之间的协方差就是正值。 如果两个变量的变化趋势相反,即其中一个大于自身的期望值,另外一个却小于自身的期望值,那么两个变量之间的协方差就是负值。

协方差可以理解判断成两条线的趋势是否一致,线a线b,线a变大时线b也变大,线a变小是线b也变小,协方差则为正(可以理解成正相关)。协方差的值大小代表相关性的强弱。线a和自己的协方差即自己本身的方差。

两个特征的协方差计算例子:
Xi 1.1 1.9 3
Yi 5.0 10.4 14.6
E(X) = (1.1+1.9+3)/3=2
E(Y) = (5.0+10.4+14.6)/3=10
E(XY)=(1.1×5.0+1.9×10.4+3×14.6)/3=23.02
Cov(X,Y)=E(XY)-E(X)E(Y)=23.02-2×10=3.02
3.计算协方差矩阵的特征值和特征向量
一个向量v是方阵A的特征向量,将一定可以表示成下面的形式:


特征分解.png

其中A为方阵,V是特征向量,lambda是特征值。
λ为特征向量 v 对应的特征值。特征值分解是将一个矩阵分解为如下形式:


特征分解

Q是这个矩阵A的特征向量组成的矩阵,Σ是一个对角矩阵,每一个对角线元素就是一个特征值,里面的特征值是由大到小排列。
特征值表示的是这个特征到底有多重要,而特征向量表示这个特征是什么

4.将特征值从大到小排列
5.保留最上面的N个特征向量
6.将数据转换到上述N个特征向量构建的新的空间中

2.2 numpy中实现PCA(【机器学习实战】):

import numpy as np
#读取文件
def loadDataSet(fileName, delim='\t'):
    fr = open(fileName)
    stringArr = [line.strip().split(delim) for line in fr.readlines()]
    datArr = [map(float,line) for line in stringArr]
    return mat(datArr)

def pca(dataMat, topNfeat=n):
    meanVals = np.mean(dataMat, axis=0) 
#(axis=0)按列求均值
    meanRemoved = dataMat - meanVals 
#减去均值,
#即:每个元素减去自己特征的平均值
    covMat = np.cov(meanRemoved, rowvar=0)  
#计算协方差矩阵,rowvar为0,
#一行为一个样本,不为0一列为一个样本
    eigVals,eigVects = np.linalg.eig(mat(covMat))
#求特征值和特征向量,
#特征向量是按列放的,即一列代表一个特征向量 。
#eigVals以行向量形式存放特征值。
#eigVects存放特征向量,每一列代表一个特征向量。
    eigValInd = np.argsort(eigVals)            
#对特征值从小到大排序 ,函数argsort()返回从小到大排序的index
    eigValInd = eigValInd[:-(topNfeat+1):-1]  
#列表逆序以后,从头到位取前topNfeat个特征值index,
#即最大的n个特征值的index (python里面,list[a:b:c]代表从下标a开始到b,
#步长为c。list[::-1]可以看作是列表逆序)
    redEigVects = eigVects[:,eigValInd]       
#最大的n个特征值对应的特征向量  
    lowDDataMat = meanRemoved * redEigVects
#低维特征空间的数据 
    reconMat = (lowDDataMat * redEigVects.T) + meanVals
#把数据转换到新空间
    return lowDDataMat, reconMat
#返回结果

2.3 使用sklearn中的PCA:

2.3.1.参数说明:

n_components:
意义:PCA算法中所要保留的主成分个数n,也即保留下来的特征个数n
类型:int 或者 string,缺省时默认为None,所有成分被保留。

copy:
类型:bool,True或者False,默认为True。意义:表示是否在运行算法时,将原始训练数据复制一份。若为True,则运行PCA算法后,原始训练数据的值不会有任何改变,因为是在原始数据的副本上进行运算;若为False,则运行PCA算法后,原始训练数据的值会改,因为是在原始数据上进行降维计算。

whiten:
类型:bool,默认为False
意义:白化,使得每个特征具有相同的方差。就是对降维后的数据的每个特征进行归一化,一般不需要白化。

2.3.2.PCA对象的属性

components_ :返回具有最大方差的成分。
explained_variance_:所保留的n个成分各自的方差
explained_variance_ratio_:返回 所保留的n个成分各自的方差百分比。
n_components_:返回所保留的成分个数n。
noise variance:噪声方差大小
mean_:特征均值

2.3.3.PCA对象的方法

fit(X,y=None)
fit()是scikit-learn中通用的方法。因为PCA是无监督学习算法,此处y等于None。

fit(X),表示用数据X来训练PCA模型。

fit_transform(X)
用X来训练PCA模型,同时返回降维后的数据。

inverse_transform()
将降维后的数据转换成原始数据,X=pca.inverse_transform(newX)

transform(X)
将数据X转换成降维后的数据。

2.3.4.使用

通常来说使用PCA降维以后需要保留95%以上的方差,因此sklearn中的PCA有三种种使用方式:
第一种:手动设置维度int类型,即降维后的维度,查看保留的方差百分比(explained_variance_ratio_)来调整合适的n_components,此时n_components大于1。
第二种:手动设置保留的方差百分比int类型,系统会自动选择维度,此时n_components范围在(0,1],例如0.95。
第三种:还可以将参数设置为string类型"mle", 此时PCA类会用MLE最大似然算法根据特征的方差分布情况自己去选择一定数量的主成分特征来降维。

通常使用pandas读取数据的时候得到的dataframe可能需要处理一下,变成array格式。

import numpy as np
import pandas as pd

df =pd.read_excel('data')
#这三种方法都可以
matrix1 = df.values
matrix2 = df.as_matrix()
matrix3 = np.array(df)


from sklearn.decomposition import PCA
pca1 = PCA(n_components=30)
pca2 = PCA(n_components=0.3)
pca3= PCA(n_components="mle")
matrix_pca = pca.fit_transform(matrix)
print (explained_variance_ratio_) #查看保留的维度各自的方差比例
variance_pca = pca.explained_variance_ratio_.sum()
#查看保留的总方差
print (variance_pca)

2.4 PCA的一些变种以及KPCA(Kernel PCA)的小例子

2.4.1 增量PCA(IPCA)

当数据量较大时,使用SVD分解会耗费很大的内存以及运算速度较慢。幸运的是,可以使用IPCA算法来解决。先将训练样本分为mini-batches,每次给IPCA算法一个mini-batch,这样就能处理大量的数据,也能实现在线学习(当有新的数据加入时)。

下面是使用Numpy的array_split()方法将MNIST数据集分为100份,再分别喂给IPCA,将数据降到154维。需要注意,这里对于mini-batch使用partial_fit()方法,而不是对于整个数据集的fit()方法。

#加载数据
from sklearn.datasets import fetch_mldata
mnist = fetch_mldata('MNIST original')
X = mnist["data"]
#使用np.array_split()方法的IPCA
from sklearn.decomposition import IncrementalPCA
n_batches = 100
inc_pca = IncrementalPCA(n_components=154)
for X_batch in np.array_split(X, n_batches):
    inc_pca.partial_fit(X_batch)
X_mnist_reduced = inc_pca.transform(X)

还可以使用Numpy 的memmap类来操纵储存在硬盘上的二进制编码的大型数据,只有当数据被用到的时候才会将数据放入内存。由于IPCA每次只需将一部分数据,因此能通过memmap来控制内存。由于使用的是输入的是整个数据集,因此使用的是fit()方法。

X_mm = np.memmap(filename, dtype="float32", mode="readonly", shape=(m, n))
batch_size = m // n_batches
inc_pca = IncrementalPCA(n_components=154, batch_size=batch_size)
inc_pca.fit(X_mm)
2.4.2 随机PCA

随机PCA是个随机算法,能快速找到接近前d个主成分,它的计算复杂度与d相关而不与n相关。

rnd_pca = PCA(n_components=154, svd_solver="randomized")
X_reduced = rnd_pca.fit_transform(X_mnist)
2.4.3 核PCA(Kernel PCA)

了解SVM都应该知道核技巧,即通过数学方法达到增加特征类似的功能来实现非线性分类。类似的技巧还能用在PCA上,使得可以实现复杂的非线性投影降维,称为KPCA。该算法善于保持聚类后的集群(clusters)后投影,有时展开数据接近于扭曲的流形。下面是使用RBF核的例子。

#生成Swiss roll数据
from sklearn.datasets import make_swiss_roll
data=make_swiss_roll(n_samples=1000, noise=0.0, random_state=None)
X=data[0]
y=data[1]
#画3维图
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
ax = plt.subplot(111, projection='3d')
ax.scatter(X[:,0], X[:,1], X[:,2],c=y)
plt.show()
#kPCA
from sklearn.decomposition import KernelPCA
rbf_pca = KernelPCA(n_components = 2, kernel="rbf", gamma=0.04)
X_reduced = rbf_pca.fit_transform(X)

需要注意,此方法要使用大量内存,可能会使内存溢出。

选择合适的核与参数

由于kPCA是非监督算法,因此无法判断性能的好坏,因此需要结合分类或回归问题来判断。通过GridSearch来选择合适的核与参数,下面是一个例子:

from sklearn.datasets import fetch_mldata
mnist = fetch_mldata('MNIST original')
X,y = mnist["data"],mnist["target"]

from sklearn.decomposition import KernelPCA
from sklearn.model_selection import GridSearchCV
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
clf = Pipeline([
("kpca", KernelPCA(n_components=2)),
("log_reg", LogisticRegression())
])
param_grid = [{
"kpca__gamma": np.linspace(0.03, 0.05, 10),
"kpca__kernel": ["rbf", "sigmoid"]
}]
grid_search = GridSearchCV(clf, param_grid, cv=3)
grid_search.fit(X, y)
print(grid_search.best_params_)

3. T-SNE,t-分布邻域嵌入算法:

3.1 基本原理(【t-SNE完整笔记】)

SNE是通过仿射(affinitie)变换将数据点映射到概率分布上,主要包括两个步骤:
SNE构建一个高维对象之间的概率分布,使得相似的对象有更高的概率被选择,而不相似的对象有较低的概率被选择。
SNE在低维空间里在构建这些点的概率分布,使得这两个概率分布之间尽可能的相似。
我们看到t-SNE模型是非监督的降维,他跟kmeans等不同,他不能通过训练得到一些东西之后再用于其它数据(比如kmeans可以通过训练得到k个点,再用于其它数据集,而t-SNE只能单独的对数据做操作,也就是说他只有fit_transform,而没有fit操作)

3.2 T-SNE算法流程:

有空再补

3.3 PCA和T-SNE:

PCA和T-SNE同为降维工具,主要区别在于:
机制和原理不同,所以不在同一个包内

from sklearn.decomposition import PCA
from sklearn.manifold import TSNE

因为原理不同,导致,tsne 保留下的属性信息,更具代表性,也即最能体现样本间的差异;
T-SNE 运行极慢,PCA 则相对较快;
因此通常来说,T-SNE只能用于展示(可视化)高维数据,由于速度慢常常先用 PCA 进行降维,再使用 tsne:

from sklearn.manifold import TSNE
from sklearn.decomposition import PCA
data_pca = PCA(n_components=50).fit_transform(data)
data_pca_tsne = TSNE(n_components=2).fit_transform(data_pca)

4. MDS:多维标度法(【机器学习系统设计】)

也称作多维缩放,类似T-SNE,经常用于数据可视化,维度大多是时候是2-3,他在降维的同时尽可能保留样本间的相对距离。
MDS算法通过距离函数d0对所有N个k维数据计算距离矩阵,它衡量的是原始特征空间中的距离(大多数时候是欧式距离)

from sklearn.manifold import MDS
mds = manifold.MDS(n_components=3)
Xtrans = mds.fit_transform(X)

多维标度法解决的问题是:当n个对象(object)中各对对象之间的相似性(或距离)给定时,确定这些对象在低维空间中的表示,并使其尽可能与原先的相似性(或距离)“大体匹配”,使得由降维所引起的任何变形达到最小。多维空间中排列的每一个点代表一个对象,因此点间的距离与对象间的相似性高度相关。也就是说,两个相似的对象由多维空间中两个距离相近的点表示,而两个不相似的对象则由多维空间两个距离较远的点表示。多维空间通常为二维或三维的欧氏空间,但也可以是非欧氏三维以上空间。

5. SVD:SVD矩阵分解/奇异值分解

5.1. 原理:

scipy和numpy中都有奇异值分解
R=UΣV^T
R为m * n的矩阵
U为m * m的矩阵
V为n * n的矩阵
Σ(sigma)为m*n的矩阵,除了对角元素不为0,其他元素都为0,并且对角元素是从大到小排列的,前面的元素比较大,后面的很多元素接近0。这些对角元素就是奇异值。

5.2. 使用:

numpy中调用方式和求特征值特征向量类似(实际上特征分解是一种特殊的奇异值分解,

特征分解只能分解方阵,奇异值分解可以分解任意矩阵,pca中的特征分解通常会使用svd)

import numpy as np
U,Sigma,VT = linalg.svd(matrix)

5.3. 降维:

5.3.1TruncatedSVD(截断SVD):

sklearn中代替PCA来给稀疏矩阵降维,稀疏矩阵具体查看库scipy,原理和sklearn中的PCA一样是舍去了较小的奇异值,我试验了几次发现相同参数下稠密矩阵非稀疏矩阵降维的结果和PCA完全一样。
也就是说sklearn中的pca就是使用svd分解再选取三个在矩阵中间的对角矩阵中最大的一部分值,再还原这个矩阵。

5.3.2 SVD的应用:

利用SVD降维实际上是用来简化数据,使用了奇异值分解以后仅需保留着三个比较小的矩阵,就能表示原矩阵,不仅节省存储量,在计算的时候更是减少了计算量。SVD在信息检索(隐性语义索引)、图像压缩、推荐系统等等领域中都有应用。

6. LDA:线性判别分析(【机器学习系统设计】&【机器学习—周志华】)

经典的线性学习方法,也被称为“Fisher”判别分析。
LDA试图让不同类别样本之间的距离最大,同时让相同类别样本之间的距离最小。简单来说LDA是为了使降维后的数据点尽可能的可分。

6.1. 原理和例子:

有空再补

6.2. LDA和PCA

当类别特别多的时候,每个类中的样本就越少,此时更加适合使用PCA而不是LDA。PCA不像LDA那样敏感,应该首先考虑pca,再根据具体情况来分析。
另外:PCA是无监督的特征抽取方法,LDA是一个有监督的方法。

7. isomap和之前提到的一些降维技术比较:

1、Multidimensional Scaling (MDS)降维的同时保留样本之间的距离,如下图。
2、Isomap通过连接每个样本和它的最近邻居来创建一个图,然后降低维的同时尝试保留样本间的测地距离(两个样本之间最少经过几个点)。
3、t-Distributed Stochastic Neighbor Embedding (t-SNE),减少维度的同时试图保持相似的样本靠近和不同的样本分离。它主要用于可视化,特别是可视化高维空间中的聚类。
4、Linear Discriminant Analysis (LDA),是一种分类算法,但是在训练定义了一个超平面来投影数据。投影使得同一类的样本靠近,不同一类的样本分开,所以在运行另一分类算法(如SVM分类器)之前,LDA是一种很好的减少维数的技术。


image.png

References:

【机器学习系统设计】
【机器学习—周志华】
【机器学习实战】
http://blog.csdn.net/baimafujinji/article/details/79407478

推荐阅读更多精彩内容