zoukankan      html  css  js  c++  java
  • ML-降维:PCA、SVD、LDA、MDS、LLE、LE算法总结

    1.PCA主成分分析

    PCA是不考虑样本类别输出的无监督降维技术,实现的是高维数据映射到低维的降维。

    PCA原理这个介绍的不错:https://www.cnblogs.com/pinard/p/6239403.html

    线性代数矩阵性质背景:特征值表示的是矩阵在特征值对应的特征向量方向上的伸缩大小;线性代数的本质这个课有不错介绍:https://www.bilibili.com/video/av6731067

    步骤:

    1)组成数据矩阵

    def get_date():
        m_vec = np.array([0, 0, 0])
        cov_vec = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]])
        # 20个3维,且每个维度以m_vec为均值,以cov_vec为方差的多元正态分布随机数
        c1 = np.random.multivariate_normal(m_vec, cov_vec, 20).T
    
        m_vec2 = np.array([1, 1, 1])
        cov_vec2 = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]])
        # 20个3维,且每个维度以m_vec2为均值,以cov_vec2为方差的多元正态分布随机数
        c2 = np.random.multivariate_normal(m_vec2, cov_vec2, 20).T
        # 合并构造一个3 x 40 的数据集
        data = np.concatenate((c1, c2), axis=1)
        return data
    
    
    X = get_date()

    2) 计算特征均值

    mean_X = np.mean(X, axis=1).reshape(X.shape[0], 1)

    3) 去中心化的新数据矩阵

    dmean_X = X - mean_X

    4)计算协方差矩阵

    m = dmean_X.shape[1]
    C = np.dot(dmean_X, dmean_X.T) / m

     5)C的分解

    计算C的特征值,特征向量,(大到小排列,特征值-向量一一对应)

    eig_val_sc, eig_vec_sc = np.linalg.eig(C)

     6)取得投影矩阵

    选择取C分解的前k(k<n)个特征向量,按行排列组成基向量:

    k=2
    # 从大到小特征值的k个索引
    argsort_eigv = np.argsort(eig_val_sc)[-k:][::-1]
    # 从大到小特征值的k个索引对应的特征向量,构造新矩阵
    W = eig_vec_sc[argsort_eigv]

     7)实现数据降维,n维到k维

    # 构造的新矩阵对原数据X降维
    new_X = np.dot(W, X)

    然后用Yk.m替代Xn.m,作为降维数据集使用

    import numpy as np
    import pandas as pd
    import matplotlib.pyplot as plt
    
    
    def get_date():
        m_vec = np.array([0, 0, 0])
        cov_vec = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]])
        # 20个3维,且每个维度以m_vec为均值,以cov_vec为方差的多元正态分布随机数
        c1 = np.random.multivariate_normal(m_vec, cov_vec, 20).T
    
        m_vec2 = np.array([1, 1, 1])
        cov_vec2 = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]])
        # 20个3维,且每个维度以m_vec2为均值,以cov_vec2为方差的多元正态分布随机数
        c2 = np.random.multivariate_normal(m_vec2, cov_vec2, 20).T
        # 合并构造一个3 x 40 的数据集
        data = np.concatenate((c1, c2), axis=1)
        return data
    
    
    X = get_date()
    
    k = 2
    
    # ====基于线性代数实现PCA降维======
    mean_X = np.mean(X, axis=1).reshape(X.shape[0], 1)
    dmean_X = X - mean_X
    m = dmean_X.shape[1]
    C = np.dot(dmean_X, dmean_X.T) / m
    
    # 矩阵分解 奇异值,特征向量
    eig_val_sc, eig_vec_sc = np.linalg.eig(C)
    
    # 从大到小特征值的k个索引
    argsort_eigv = np.argsort(eig_val_sc)[-k:][::-1]
    # 从大到小特征值的k个索引对应的特征向量,构造新矩阵
    W = eig_vec_sc[argsort_eigv]
    # 构造的新矩阵对原数据X降维
    new_X = np.dot(W, X)
    # 显示降维后的数据
    plt.scatter(new_X.T[:, 0], new_X.T[:, 1], c='y')
    
    plt.show()
    

     可以看到降维到2的数据,还是有明显的区分的。

    sklearn-PCA

    decomposition.PCA(n_components=None, copy=True, whiten=False)

    from sklearn.decomposition import PCA
    pca = PCA(n_components=3)
    pca.fit(X)
    print pca.explained_variance_ratio_
    print pca.explained_variance_
    
    X_new = pca.transform(X)#实现对X的降维

    n_components:  PCA算法中所要保留的主成分个数n,也即保留下来的特征个数n
    copy:表示是否在运行算法时,将原始训练数据复制一份。
    若为True,则运行PCA算法后,原始训练数据的值不会有任何改变,因为是在原始数据的副本上进行运算;若为False,则运行PCA算法后,原始训练数据的值会改,因为是在原始数据上进行降维计算。
    whiten: bool,缺省时默认为False,True使得每个特征具有相同的方差。
    explained_variance_ratio_:计算了每个特征方差贡献率,所有总和为1
    explained_variance_:方差值 

    可以看到,sklearn的效果和前面编程实现的一样。 


    梯度上升PCA

    前面是基于矩阵分解,这个是用梯度下降:

    https://www.jianshu.com/p/1e9cab07d54d


    2.SVD

    原理参考:https://www.cnblogs.com/pinard/p/6251584.html

    这里不加证明的写出如下总结:

    对于任意矩阵Amxn,其奇异值large A^TA_{n	imes n}特征值的平方根;

    large A^TA_{n	imes n}特征向量是:v1,v2,v3,则A的特征向量是:

    若特征值<0,m<n时直接不取,m>n时需要根据已求出的特征向量计算正交,得出多个正交向量要正交化。 

    2.1SVD降维:

    通常,按大小排列的奇异值,减少特别的快,在很多情况下,前10%甚至1%的奇异值的和就占了全部的奇异值之和的99%以上的比例。也就是说,我们也可以用最大的k个的奇异值和对应的左右奇异向量来近似描述矩阵。也就是说:A_{m 	imes n} = U_{m 	imes m}Sigma_{m 	imes n} V^T_{n 	imes n} approx U_{m 	imes k}Sigma_{k 	imes k} V^T_{k 	imes n},可以实现降维。(线性代数中,特征向量意味着方向,对应的特征值意味着矩阵在特征向量上的拉伸程度,取大的k个特征值对应的特征向量,可以近似的描述这个拉伸过程,极大程度上保存原有数据形状)

    sklearn实现:

    import numpy as np
    import pandas as pd
    import matplotlib.pyplot as plt
    from mpl_toolkits.mplot3d import Axes3D
    from sklearn.datasets import load_iris
    
    # ======创建数据集========
    iris = load_iris()
    df = pd.DataFrame(iris.data, columns=iris.feature_names)
    df['label'] = iris.target
    df.columns = ['sepal length', 'sepal width', 'petal length', 'petal width', 'label']
    labels = df.iloc[:, -1].values
    data = df.iloc[:, :-1].values
    samples, features = df.shape
    print(samples, features)
    
    # =======这里SVD========
    U, s, V = np.linalg.svd(data)
    
    # =====取前3维数据显示===========
    newdata = U[:, :3]
    fig = plt.figure()
    ax = Axes3D(fig)
    
    # =======每个类型的形状和颜色=====
    marks = ['o', '*', '+']
    colors = ['r', 'b', 'g']
    for i in range(samples):
        ax.scatter(newdata[i, 0], newdata[i, 1], newdata[i, 2], c=colors[int(labels[i])], marker=marks[int(labels[i])])
    
    plt.show()
    


    3 LDA

    LDA是一种监督学习的降维技术,也就是说它的数据集的每个样本是有类别输出的。这点和PCA不同。核心思想是投影后类内方差最小,类间方差最大,如下右图(2维到1维),显然比左图更符合这个思想,LDA就是希望降维后的数据,能最大化的满足这个。

    原理介绍https://www.cnblogs.com/pinard/p/6244265.html

    考虑二类别情况:

    定义均值向量:

    large mu_j = frac{1}{N_j}sumlimits_{x in X_j}x;;(j=0,1)

    协方差:

    large Sigma_j = sumlimits_{x in X_j}(x-mu_j)(x-mu_j)^T;;(j=0,1)

    希望同类更近,异类更远,意味着希望如下关系尽可能大:

    large underbrace{arg;max}_w;;J(w) = frac{||w^Tmu_0-w^Tmu_1||_2^2}{w^TSigma_0w+w^TSigma_1w} = frac{w^T(mu_0-mu_1)(mu_0-mu_1)^Tw}{w^T(Sigma_0+Sigma_1)w}

    定义类内散度矩阵:

    large S_w = Sigma_0 + Sigma_1 = sumlimits_{x in X_0}(x-mu_0)(x-mu_0)^T + sumlimits_{x in X_1}(x-mu_1)(x-mu_1)^T

    类间散度矩阵:

    large S_b = (mu_0-mu_1)(mu_0-mu_1)^T

    目标变成了:

    large underbrace{arg;max}_w;;J(w) = frac{w^TS_bw}{w^TS_ww}

    对上目标进行求解,其实是转为对某矩阵的分解,我的另一个文章的最后一张图有介绍:https://blog.csdn.net/jiang425776024/article/details/87607301 ,最后会得出结论,问题的求解等价于关系large (S_w^{-1}S_b)w'=lambda w',只要对其矩阵分解得到large w'=S_w^{-1}(mu_0-mu_1),就找到了w的解(最大维度<2)。

    多分类的情况也是一样的,最后的关键也是对large (S_w^{-1}S_b)w'=lambda w'矩阵分解求w。

    3.1LDA算法流程

    有数据集:

    large D={(x_1,y_1), (x_2,y_2), ...,((x_m,y_m))}large y_i in {C_1,C_2,...,C_k}

    希望降维到d<k;

    1) 计算类内散度矩阵Sw::

    large S_w = sumlimits_{j=1}^{k}S_{wj} = sumlimits_{j=1}^{k}sumlimits_{x in X_j}(x-mu_j)(x-mu_j)^T

    2)计算类间散度矩阵Sb,u为所有样本均值向量:

    large S_b = sumlimits_{j=1}^{k}N_j(mu_j-mu)(mu_j-mu)^T

       3) 计算矩阵large S_w^{-1}S_b

    4)矩阵分解large S_w^{-1}S_b,得到最大的d个特征值及对应的d个特征向量large (w_1,w_2,...w_d),得到投影矩阵W

    5) 对样本集中的每一个样本特征xi转化为新的样本large zi=W^Txi

    6) 得到输出新样本集:large D'={(z_1,y_1), (z_2,y_2), ...,((z_m,y_m))}


    numpy实现,加强对原理的理解: 

    import numpy as np
    import pandas as pd
    import matplotlib.pyplot as plt
    from sklearn.datasets import load_iris
    
    iris = load_iris()
    df = pd.DataFrame(iris.data, columns=iris.feature_names)
    df['label'] = iris.target
    df.columns = ['sepal length', 'sepal width', 'petal length', 'petal width', 'label']
    data = np.array(df.iloc[:100, [0, 1, 2, -1]])
    X = data[:, :-1]
    Y = data[:, -1]
    
    # 类别数
    class_labels = np.unique(Y)
    n_class = class_labels.shape[0]
    
    # 类均值向量
    mean_vec = []
    for c in range(n_class):
        mean_vec.append(np.mean(X[Y == class_labels[c]], axis=0))
    
    # 类内散度矩阵Sw
    SW = np.zeros((3, 3))
    for cl, mv in zip(class_labels, mean_vec):
        sc_matrix_class = np.zeros((3, 3))
        for row in X[Y == cl]:  # 所有X中类别=cl的样本
            row, mv = row.reshape(3, 1), mv.reshape(3, 1)
            sc_matrix_class += (row - mv).dot((row - mv).T)
        SW += sc_matrix_class
    
    # Sb
    all_mean_vec = np.mean(X, axis=0)
    n_features = X.shape[1]
    SB = np.zeros((n_features, n_features))
    for i, mea_v in enumerate(mean_vec):
        n = X[Y == class_labels[i]].shape[0]
        mea_v = mea_v.reshape(n_features, 1)
        all_mean_vec = all_mean_vec.reshape(n_features, 1)
        SB += n * (mea_v - all_mean_vec).dot((mea_v - all_mean_vec).T)
    
    # Sw^-1 Sb矩阵分解
    e_val, e_vec = np.linalg.eig(np.linalg.inv(SW).dot(SB))
    
    dindex = np.argsort(e_val)[-2:][::-1]
    W = e_vec[dindex]
    new_X = np.dot(W, X.T)
    plt.scatter(new_X.T[:, 0], new_X.T[:, 1])
    plt.show()
    

     

     

    sklearn 实现3维到2维:

    import numpy as np
    import matplotlib.pyplot as plt
    from mpl_toolkits.mplot3d import Axes3D
    from sklearn.datasets.samples_generator import make_classification
    
    X, y = make_classification(n_samples=1000, n_features=3, n_redundant=0, n_classes=3, n_informative=2,
                               n_clusters_per_class=1, class_sep=0.5, random_state=10)
    # fig = plt.figure()
    # ax = Axes3D(fig, rect=[0, 0, 1, 1], elev=30, azim=20)
    # ax.scatter(X[:, 0], X[:, 1], X[:, 2], marker='o', c=y)
    
    from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
    
    lda = LinearDiscriminantAnalysis(n_components=2)
    lda.fit(X, y)
    X_new = lda.transform(X)
    plt.scatter(X_new[:, 0], X_new[:, 1], marker='o', c=y)
    plt.show()
    


    4 MDS 流形学习(Manifold Learning)

    流形学习(Manifold Learning)的一类,多维缩放MDS要求原始空间中的样本之间的距离在低维空间中保持,思想是构造距离矩阵,然后矩阵分解,和前面的降维流程基本上是一致的,只是矩阵分解时的构造不一样,没有特别之处。

    具体细节参考《机器学习》周志华 P228。或者博客:https://blog.csdn.net/AI_BigData_wh/article/details/78242052

    MDS流程:

    sklearn API:https://scikit-learn.org/stable/modules/generated/sklearn.manifold.MDS.html

    from sklearn.datasets import load_digits
    from sklearn.manifold import MDS
    X, _ = load_digits(return_X_y=True)
    X.shape
    
    embedding = MDS(n_components=2)
    X_transformed = embedding.fit_transform(X[:100])
    X_transformed.shape
    

    4.1 Isomap 等度量映射

      Isomap:通过“改造一种原本适用于欧氏空间的算法”,达到了“将流形映射到一个欧氏空间”的目的。

    流形Manifold:“嵌入在高维空间中的低维流形”,最直观的例子通常都会是嵌入在三维空间中的二维或者一维流形。比如说一块布,可以把它看成一个二维平面,这是一个二维的欧氏空间,现在我们(在三维)中把它扭一扭,它就变成了一个流形(当然,不扭的时候,它也是一个流形,欧氏空间是流形的一种特殊情况)。

    MDS 是针对欧氏空间设计的,对于距离的计算也是使用欧氏距离来完成的。如果数据分布在流形上欧氏距离不适用

    Isomap通过把数据点连接起来构成一个邻接 Graph 来离散地近似原来的流形,而测地距离也相应地通过 Graph 上的最短路径来近似,通过这样的距离计算,把 MDS 中原始空间中距离的计算从欧氏距离换为了流形上的测地距离

    如下图:两点间的距离不再是欧氏距离!!

    算法流程: 核心就是把MDS的距离计算改为了最短路径(Dijikstra、Floyd)等算法的计算。

    sklearn API:https://scikit-learn.org/stable/modules/generated/sklearn.manifold.Isomap.html#sklearn.manifold.Isomap

    from sklearn.datasets import load_digits
    from sklearn.manifold import Isomap
    X, _ = load_digits(return_X_y=True)
    X.shape
    
    embedding = Isomap(n_components=2)
    X_transformed = embedding.fit_transform(X[:100])
    X_transformed.shape

    Isomap等距映射算法有一个问题就是他要找所有样本全局的最优解,当数据量很大,样本维度很高时,计算非常的耗时,鉴于这个问题,出现了只关注局部的LLE

     


    5 LLE 局部线性嵌入

    局部线性嵌入(Locally Linear Embedding,以下简称LLE),也是流形学习算法,LLE关注于降维时保持样本局部的线性关系,由于LLE在降维时保持了样本的局部关系,它广泛的用于图像图像识别,高维数据可视化等领域。

    参考:https://www.cnblogs.com/pinard/p/6266408.html

    LLE首先假设数据在较小的局部是线性的,也就是说,某一个数据可以由它邻域中的几个样本来线性表示,如原来的线性关系:

    large x_1 = w_{12}x_2 + w_{13}x_3 +w_{14}x_4

    降维后的线性关系:

    large x_1' approx w_{12}x_2' + w_{13}x_3' +w_{14}x_4'

    也就是说降维前后(局部)线性权重不变。


    sklearn API:https://scikit-learn.org/stable/modules/generated/sklearn.manifold.locally_linear_embedding.html#sklearn.manifold.locally_linear_embedding

    官方例子:https://scikit-learn.org/stable/auto_examples/manifold/plot_swissroll.html#sphx-glr-auto-examples-manifold-plot-swissroll-py


    6 LE 拉普拉斯特征映射

    其思路和LLE很相似,也是基于图的降维算法,希望相互关联的点降维后的空间尽可能靠近,通过构建邻接矩阵,最后推导,矩阵分解等步骤,实现降维。

    sklearn API:https://scikit-learn.org/stable/modules/generated/sklearn.manifold.SpectralEmbedding.html#sklearn.manifold.SpectralEmbedding

    from sklearn import manifold, datasets
    import matplotlib.pyplot as plt
    from mpl_toolkits.mplot3d import Axes3D
    
    fig = plt.figure()
    ax = Axes3D(fig)
    
    X, color = datasets.samples_generator.make_swiss_roll(n_samples=1500)
    
    se = manifold.SpectralEmbedding(n_components=2, n_neighbors=10)
    Y = se.fit_transform(X)
    # 原3维
    # ax.scatter(X[:, 0], X[:, 1], X[:, 2], c=color)
    # 降维 2d
    plt.scatter(Y[:, 0], Y[:, 1], c=color)
    plt.show()
    

     


  • 相关阅读:
    《孙子兵法》【行军第九】
    《孙子兵法》【虚实第六】
    《孙子兵法》【地形第十】
    企业无线局域网的搭建
    企业无线局域网的搭建
    UDDI
    (转载)Linux:Ldd命令介绍及使用方法
    (转载)传递给const引用形参的实参要求
    (转载)千万不要把bool设计成函数参数
    (转载)Linux下如何修改终端提示符?
  • 原文地址:https://www.cnblogs.com/onenoteone/p/12441714.html
Copyright © 2011-2022 走看看