第十章 降维与度量学习

章节目录

  • k近邻学习
  • 低纬嵌入
  • 主成分分析
  • 核化线性降维
  • 流形学习
  • 度量学习

知识点

1.懒惰学习(lazy learning):此类学习技术在训练阶段仅仅是把样本保存起来,训练时间开销为零,待收到测试样本后再进行处理。

2.急切学习(eager learning):与懒惰学习相反,此类学习技术在训练阶段就对样本进行学习处理。

3.维度灾难(curse of dimensionality):在高维情形下出现的样本稀疏、距离计算困难等问题,是所有机器学习方法共同面临的严重障碍,被称为“维度灾难”。

4.矩阵的迹(trace):在线性代数中,一个n×n矩阵A的主对角线(从左上方至右下方的对角线)上各个元素的总和被称为矩阵A的迹(或迹数),一般记作tr(A)。

5.矩阵特征分解(Eigenvalue decomposition):将矩阵分解为由其特征值和特征向量表示的矩阵之积的方法。需要注意只有对可对角化矩阵才可以施以特征分解。简单理解就是将矩阵变换为几个相互垂直的向量的和,比如在二维空间中,任意一个向量可以被表示为x轴和y轴的组合。

(一)k近邻学习

看过三体的小伙伴们应该对“降维打击”这个词不陌生吧,在某些科幻小说中,生活在更高维度的外星人可以通过降低自己的维度来打击低纬度的生命体,用这种方式的打击由于带有部分高纬度的特性,所以低纬度的生物往往很难抵抗基于同样的理解,也就是说在我们机器学习领域中,我们是否也可以通过降低维度来获得某种优势呢?

  • 优点有哪些
    首先,近邻分类器不但简单,而且泛化错误率不超过贝叶斯最优分类器的错误率的两倍。这是它的优点。
    所谓k近邻(k-Nearest,简称kNN)学习,是一种常用的监督学习方法,其工作机制非常简单:给定测试样本,基于某种距离度量找出训练集中与其最靠近的k个训练样本。然后基于这k个“邻居”的信息进行预测。通常,在分类任务中可使用“投票法”,即选择这k个样本中出现最多的类别标记作为预测结果;在回归任务中可使用“平均法”,即将这k个样本的实际值输出标记的平均值作为预测结果。还可基于距离远近的加权平均或加权投票,距离越近的样本权重越大。
  • 独特的地方是
    与前面章节介绍的方法相比呢,k近邻学习有一个明显的不同之处,它似乎没有显示的训练过程。事实上,它是“懒惰学习”(lazy learning)的著名代表,此类学习技术在训练阶段仅仅是将样本存储起来,训练开销为零,待收到测试样本后再进行处理;相应的,那些在训练阶段就对样本进行学习处理的方法,称为“急切学习”(eager learning)。

k近邻分类器示意图如下

image.png

总结来说, k 近邻学习是简单常用的分类算法,在样本分布充足时,其最差误差不超过贝叶斯最优分类器的两倍。实际情况下由于属性维度过大,会导致“维数灾难”,这是所有机器学习中的共同障碍。

**KNN算法的一般流程 **

step.1---初始化距离为最大值
step.2---计算未知样本和每个训练样本的距离dist
step.3---得到目前K个最临近样本中的最大距离maxdist
step.4---如果dist小于maxdist,则将该训练样本作为K-最近邻样本
step.5---重复步骤2、3、4,直到未知样本和所有训练样本的距离都算完
step.6---统计K-最近邻样本中每个类标号出现的次数
step.7---选择出现频率最大的类标号作为未知样本的类标号

距离公式

在KNN中,通过计算对象间距离来作为各个对象之间的非相似性指标,避免了对象之间的匹配问题,在这里距离一般使用欧氏距离或曼哈顿距离:

image

k-近邻算法的实现

def classify0(intX, dataSet, labels, k):
    # intX是测试的用例,dataset训练集,labels是训练集对应的标签,k是用于选择最近邻的数目
    dataSetSize = dataSet.shape[0]
    # 用欧式距离公式进行距离计算
    diffMat = tile(intX, (dataSetSize,1)) - dataSet   # numpy.tile进行数组的重复生成
    sqdiffMat = diffMat**2
    sqDistances = sqdiffMat.sum(axis=1)
    distances = sqDistances**0.5
    sortedDistIndicies = distances.argsort()  # 返回的是数组值从小到大的索引值
    classCount = {}
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]]
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
    sortedClassCount = sorted(classCount.items(), key=op.itemgetter(1), reverse=True)
    # python3中函数为:items(),python2中函数为:iteritems()
    return sortedClassCount[0][0]

例如在手写识别系统中:

def handwrittingClassTest():
    hwLabels = []
    trainingFileList = listdir("D:\\Learn and work\\Code\\Books\\ML_code\Ch02\digits\\trainingDigits")
    m = len(trainingFileList)  # 获取目录内容,给出长度
    trainingMat = zeros((m, 1024))
    for i in range(m):
        # 从文件名中解析分类数字
        fileNameStr = trainingFileList[i]
        fileStr = fileNameStr.split('.')[0]
        classNumStr = int(fileStr.split('_')[0])
        hwLabels.append(classNumStr)
        trainingMat[i, :] = img2vector("D:\\Learn and work\\Code\\Books\\ML_code\Ch02\digits\\trainingDigits\\%s"
                                       % fileNameStr)
    testFileList = listdir("D:\\Learn and work\\Code\\Books\\ML_code\Ch02\digits\\testDigits")
    errorCount = 0.0
    mTest = len(testFileList)
    for i in range(mTest):
        fileNameStr = testFileList[i]
        fileStr = fileNameStr.split('.')[0]
        classNumStr = int(fileStr.split('_')[0])
        vectorUnderTest = img2vector("D:\\Learn and work\\Code\\Books\\ML_code\Ch02\digits\\testDigits\\%s"
                                     % fileNameStr)
        classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3)
        print("the classifier came back with: %d, the real answer is: %d" % (classifierResult, classNumStr))
        if(classifierResult != classNumStr):
            errorCount += 1.0
    print("\nthe total number of errors is: %d" % errorCount)
    print("\nthe total error rate is: %f" % (errorCount / float(mTest)))

执行该函数,得到结果:

the classifier came back with: 9, the real answer is: 9
the classifier came back with: 9, the real answer is: 9
the classifier came back with: 9, the real answer is: 9
the classifier came back with: 9, the real answer is: 9
the classifier came back with: 9, the real answer is: 9
the classifier came back with: 9, the real answer is: 9
the classifier came back with: 9, the real answer is: 9
the classifier came back with: 9, the real answer is: 9
the classifier came back with: 9, the real answer is: 9
the classifier came back with: 9, the real answer is: 9
the classifier came back with: 9, the real answer is: 9
the classifier came back with: 9, the real answer is: 9

the total number of errors is: 10
the total error rate is: 0.010571

参考资料:
https://blog.csdn.net/sunlianglong/article/details/80412258
https://www.cnblogs.com/zimuzimu/p/7256884.html

(二)低维嵌入

  • 低维嵌入主要解决的问题是上面k邻近学习方法可操作性性弱的问题。

第一节的假设前提,是任意测试样本x附近任意小的δ距离范围内总能找到一个训练样本,也就是说训练样本的采样密度足够大,称为“密采样”(dense sample)。但是,这个假设在现实任务中通常很难满足。事实上许多学习方法都涉及距离计算,而高维空间会给距离计算带来很大麻烦,例如当维数很高时,甚至连计算内积都不再容易。也就是说在高维情形下存在的数据样本稀疏、计算距离困难等问题,这些问题被称为“维数灾难”(curse of dimensionality)。

缓解维数灾难的一个重要途径是降维(dimension reduction),亦称“维数简约”,即通过某种数学变换将原始高纬度属性空间转变为一个低维“子空间”(subspace),在这个子空间中样本密度大幅提高,计算距离也变得更为容易。
为什么能进行降维?这是因为在很多时候,人们人们观测或收集到的数据样本虽是高维的,但与学习任务密切相关的也许仅是某个低维分布,即高维空间的一个底维“嵌入”(embedding)。

如下图所示,讲一个三维问题垂直投影,变成一个二维问题。这种方法叫做“多维缩放”(Multiple Dimensional Scaling,简称MDS)这样一种经典的降维方法。

image.png

这样做的好处就是当维数降低到二维或三维,则可通过可视化技术转化成可被感知的数据,来直观的判断降维效果。

MDS算法的具体流程是:

1.计算出距离矩阵dij;

2.算出低维空间映射后的 的内积矩阵B ;

3.对内积矩阵 B 进行特征分解得到一组特征值和特征向量,取最大的 d'个特征值构成的对角矩阵 A 和对应的特征向量矩阵 V;

4.通过对角矩阵 A 和 V,我们可以得到原始样本在低维 d' 空间上的映射。
这里第五章的图可以解释:


image.png

总结来说,缓解维数灾难的有效途径是降维,即将高维样本映射到低维空间中,这样不仅属性维度降低减少了计算开销,还增大了样本密度。降维过程中必定会对原始数据的信息有所丢失,所以根据不同的降维目标我们可以得到不同的降维方法。

(三)主成分分析

也是常见的降维方法的一种。简称PCA。
主成分分析仅需保留与样本的均值向量,也就是可通过简单的向量减法和矩阵向量乘法将样本投影到低维空间中。显然降维的结果就是对应于最小的d-d'个特征值的特征向量被舍弃了,这是降维导致的结果,存在这一定的偏差。但舍弃这样的信息往往是必要的:

  • 但是这种舍弃也有必要性
    第一,舍弃这部分信息之后能使样本的采样密度增大,这正是降维的重要动机;第二,当数据受到噪声影响时,最小的特征值所对应的特征向量往往与噪声有关,将它们舍弃能在一定程度上起到去噪的效果。

PCA有很多的应用场景,首先,压缩是一个明显的用途—用yi来代替xi,如果我们用高维数据集缩减为k=2或者3维,我们就可以画出yi从而使数据可视化,打个比方,如果我们将数据集缩减到二维,然后我们就可以把数据画到坐标平面里(图中一个点代表一类车型),然后观察哪些车型是类似的,哪一类车型可以被聚类。其他的标准用途包括在运行监督学习算法之前对数据集进行预处理使维度减少,除了可以减少计算开销之外,减少数据的维度同样可以减少考虑假设分类的复杂程度同时避免过度拟合。

下面提供一个python的pca实现,为了直观的描述pca算法,使用一个1000*2的矩阵作为测试数据集,降到1维,并画出图像来进行观察。

from numpy import *

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=9999999):
    meanVals=mean(dataMat,axis=0)
    meanRemoved=dataMat-meanVals
    covMat=cov(meanRemoved,rowvar=0)
    eigVals,eigVets=linalg.eig(mat(covMat))
    eigValInd=argsort(eigVals)
    eigValInd=eigValInd[:-(topNfeat+1):-1]
    redEigVects=eigVets[:,eigValInd]
    print meanRemoved
    print redEigVects
    lowDDatMat=meanRemoved*redEigVects
    reconMat=(lowDDatMat*redEigVects.T)+meanVals
    return lowDDatMat,reconMat
dataMat=loadDataSet('testSet.txt')
lowDMat,reconMat=pca(dataMat,1)

def plotPCA(dataMat,reconMat):
    import matplotlib
    import matplotlib.pyplot as plt
    datArr=array(dataMat)
    reconArr=array(reconMat)
    n1=shape(datArr)[0]
    n2=shape(reconArr)[0]
    xcord1=[];ycord1=[]
    xcord2=[];ycord2=[]
    for i in range(n1):
        xcord1.append(datArr[i,0]);ycord1.append(datArr[i,1])
    for i in range(n2):
        xcord2.append(reconArr[i,0]);ycord2.append(reconArr[i,1])
    fig=plt.figure()
    ax=fig.add_subplot(111)
    ax.scatter(xcord1,ycord1,s=90,c='red',marker='^')
    ax.scatter(xcord2,ycord2,s=50,c='yellow',marker='o')
    plt.title('PCA')
    plt.show()
plotPCA(dataMat,reconMat)

最后的降维结果如图所示

image.png

(四)核化线性降维

非线性降维的一种常用方法,是基于核技巧对线性降维方法进行”核化“(kernelized)。
线性降维方法假设从高维空间到低维空间的函数映射是线性的,然而,在不少实际任务中,可能需要非线性映射才能找到恰当的低维嵌入。

image.png

总结来说,和线性降维方法目标是要保证降维到的超平面能更好的表示原始数据不同的是,核线性降维方法目标是通过核函数和核方法来避免采样空间投影到高维空间再降维之后的低维结构丢失。

(五)流形学习

”流形“是在局部与欧氏空间同胚的空间,也就是说它在局部具有欧氏空间的性质,还能用欧氏距离来进行距离计算。这是一类借鉴了拓扑流形概念的降维方法。这给降维方法带来了很大的启发。如果低维流形嵌入到高维空间,那么数据样本在高维空间的分布虽然看上去很复杂,在局部上却仍具有欧氏空间的性质,所以可以容易的在局部建立降维映射关系。随后再设法将局部映射关系推广到全局。当维数被降至二维或三维时,能对数据进行可视化展示。

总结来说,等度量映射的目标是让样本在“流形”上的距离在降维之后仍能保持;局部线性嵌入的目标是让样本由其邻域向量重构关系在降维后仍能保持。

下面是LLE(局部线性嵌入算法)的python代码实现:
sklearn的manifold提供了LLE方法(LocallyLinearEmbedding)

主要参数:

def __init__(self, n_neighbors=5, n_components=2, reg=1E-3,
                 eigen_solver='auto', tol=1E-6, max_iter=100,
                 method='standard', hessian_tol=1E-4, modified_tol=1E-12,
                 neighbors_algorithm='auto', random_state=None, n_jobs=1):
        self.n_neighbors = n_neighbors
        self.n_components = n_components
        self.reg = reg
        self.eigen_solver = eigen_solver
        self.tol = tol
        self.max_iter = max_iter
        self.method = method
        self.hessian_tol = hessian_tol
        self.modified_tol = modified_tol
        self.random_state = random_state
        self.neighbors_algorithm = neighbors_algorithm
        self.n_jobs = n_jobs

n_neighbors:即我们搜索样本的近邻的个数,最重要的就是这个,默认是5。 一般来说,如果算法运行时间可以接受,我们可以尽量选择一个比较大一些的n_neighbors。
n_components:即我们降维到的维数。如果我们降维的目的是可视化,则一般可以选择2-5维。
reg :正则化系数,在n_neighbors大于n_components时,即近邻数大于降维的维数时,由于我们的样本权重矩阵不是满秩的,LLE通过正则化来解决这个问题。默认是0.001。
eigen_solver:特征分解的方法。有‘arpack’和‘dense’两者算法选择。
method: 即LLE的具体算法。LocallyLinearEmbedding支持4种LLE算法,分别是'standard'对应我们标准的LLE算法,'hessian'对应原理篇讲到的HLLE算法,'modified'对应原理篇讲到的MLLE算法,‘ltsa’对应原理篇讲到的LTSA算法。默认是'standard'。
neighbors_algorithm:这个是k近邻的搜索方法,和KNN算法的使用的搜索方法一样。算法一共有三种,第一种是蛮力实现,第二种是KD树实现,第三种是球树实现。对于这个参数,一共有4种可选输入,‘brute’对应第一种蛮力实现,‘kd_tree’对应第二种KD树实现,‘ball_tree’对应第三种的球树实现, ‘auto’则会在上面三种算法中做权衡,选择一个拟合最好的最优算法。

实例代码:瑞士卷

import numpy as np
import operator
import matplotlib.pyplot as plt
from sklearn import datasets,decomposition,manifold
from itertools import cycle
from mpl_toolkits.mplot3d import Axes3D
def load_data():
    swiss_roll =datasets.make_swiss_roll(n_samples=1000)
    return swiss_roll[0],np.floor(swiss_roll[1])
 
def LLE_components(*data):
    X,Y=data
    for n in [3,2,1]:
        lle=manifold.LocallyLinearEmbedding(n_components=n)
        lle.fit(X)
        print("n = %d 重建误差:"%n,lle.reconstruction_error_)
 
def LLE_neighbors(*data):
    X,Y=data
    Neighbors=[1,2,3,4,5,15,30,100,Y.size-1]
 
    fig=plt.figure("LLE",figsize=(9, 9))
  
    for i,k in enumerate(Neighbors):
        lle=manifold.LocallyLinearEmbedding(n_components=2,n_neighbors=k,eigen_solver='dense')
        X_r=lle.fit_transform(X)
        ax=fig.add_subplot(3,3,i+1)
        ax.scatter(X_r[:,0],X_r[:,1],marker='o',c=Y,alpha=0.5) 
        ax.set_title("k = %d"%k)   
        plt.xticks(fontsize=10, color="darkorange")  
        plt.yticks(fontsize=10, color="darkorange") 
    plt.suptitle("LLE")
    plt.show()
 
X,Y=load_data()
fig = plt.figure('data')
ax = Axes3D(fig)
ax.scatter(X[:, 0], X[:, 1], X[:, 2],marker='o',c=Y)
LLE_components(X,Y)
LLE_neighbors(X,Y)

运行结果:

image.png

LLE算法计算复杂度相对较小,实现也算比较容易。且可以学习任意维的局部线性的低维流形。但是对数据的流形分布特征有严格的要求。比如不能是闭合流形,不能是稀疏的数据集,不能是分布不均匀的数据集等等。通过上述代码也能看出,算法对最近邻样本数的选择非常敏感,不同的最近邻数对最后的降维结果有很大影响。

参考资料:https://blog.csdn.net/sm9sun/article/details/78815332

(六)度量学习

在机器学习中,如果能直接尝试”学习“出一个合适的距离度量,就是度量学习(metric learning)的基本动机。对高维数据进行降维的主要目的是希望找到一个合适的低维空间,在此空间中进行学习能比原始空间性能更好。事实上,每个空间对应了在样本属性上定义的一个距离度量,而寻找合适的空间,实质上就是在寻找一个合适的距离度量。
首先要学习出距离度量必须先定义一个合适的距离度量形式。对两个样本xi与xj,它们之间的平方欧式距离为:

image.png

若各个属性重要程度不一样即都有一个权重,则得到加权的平方欧式距离:
image.png

此时各个属性之间都是相互独立无关的,但现实中往往会存在属性之间有关联的情形,例如:身高和体重,一般人越高,体重也会重一些,他们之间存在较大的相关性。这样计算距离就不能分属性单独计算,于是就引入经典的马氏距离(Mahalanobis distance):
image.png

标准的马氏距离中M是协方差矩阵的逆,马氏距离是一种考虑属性之间相关性尺度无关(即无须去量纲)的距离度量。
image.png

矩阵M也称为度量矩阵”,为保证距离度量的非负性与对称性,M必须为(半)正定对称矩阵,这样就为度量学习定义好了距离度量的形式,换句话说:度量学习便是对度量矩阵进行学习

总结来说,就是度量学习绕过降维的过程,将学习目标转化为对距离度量计算的权重矩阵的学习。
参考资料:https://blog.csdn.net/u011826404/article/details/72123031

你可能感兴趣的:(第十章 降维与度量学习)