LLE原理及推导过程

1.概述

      所谓LLE(局部线性嵌入)即”Locally Linear Embedding”的降维算法,在处理所谓流形降维的时候,效果比PCA要好很多。
      首先,所谓流形,我们脑海里最直观的印象就是Swiss roll,在吃它的时候喜欢把它整个摊开成一张饼再吃,其实这个过程就实现了对瑞士卷的降维操作,即从三维降到了两维。降维前,我们看到相邻的卷层之间看着距离很近,但其实摊开成饼状后才发现其实距离很远,所以如果不进行降维操作,而是直接根据近邻原则去判断相似性其实是不准确的。

     LLE的降维实现过程,直观的可视化效果如下图1所示:

                                                LLE原理及推导过程_第1张图片

LLE原理及推导过程_第2张图片

2.LLE的原理

所谓局部线性,即认为在整个数据集的某个小范围内,数据是线性的,就比如虽然地球是圆的,但我们还是可以认为我们的篮球场是个平面;而这个“小范围”,最直接的办法就是k-近邻原则。这个“近邻”的判断也可以是不同的依据:比如欧氏距离,测地距离等。

既然是线性,则对每个数据点 x_i (D维数据,即D\times 1  的列向量),可以用其\large k近邻数据点的线性组合来表示(见上图2),即:

                                                           \large x_i = \sum_{j=1}^{k}w_{ji}x_{ji}

上式中,w_i 是 k\times1  的列向量,w_{ji}  是 w_{i}  的第 j  行, x_{ji}  是 x_{i}  的第  j  个近邻点\small (1\leq j\leq k),即

                                                LLE原理及推导过程_第3张图片,     LLE原理及推导过程_第4张图片   

特别注意:此处\large x_i中的维度为D,切不可把\large x_{ji}视为\large x_i中的第j个元素,只是这里的表示刚好凑巧相同。

通过使下述Loss-function最小化: 

                                                        \large \arg\min \limits_{w}\sum_{i=1}^{N}(\|x_i-\sum_{j=1}^{k}w_{ji}x_{ji}\|_2^2)

求解上式可得到权重系数

                                                            \large w = [w_1, w_2, ..., w_N] 

其中 w (维度为kXN)对应于 N  个数据点,即 N  列 w_i(i=1,2,...N)  。

接下来是LLE中又一个假设:即认为将原始数据从D维降到d维后,x_i(D\times 1) \rightarrow y_i(d\times 1) ,其依旧可以表示为其k近邻的线性组合,且组合系数不变,即 仍然是w_i  ,再一次通过最小化loss function: 

                                               \large \arg\min \limits_{Y}\sum_{i=1}^{N}(\|y_i-\sum_{j=1}^{k}w_{ji}y_{ji}\|_2^2)                                                       

 最终得到降维后位于低维空间的的数据:

                                ​​​​​​​        ​​​​​​​        ​​​​​​​    \large Y=[y_1,y_2,...,y_N]

3.算法推导过程

step 1:确定近邻点

运用k近邻算法得到每个数据 x_i 的 k 近邻点:

                                              \large N_i = knn(x_i,k), N_i = [x_{1i}, ..., x_{ki}]

step 2: 求解权重系数矩阵

即求解如下有约束优化问题:

                                       \arg\min \limits_{w}\sum_{i=1}^{N} \|x_i - \sum_{j=1}^{k}w_{ji}x_{ji}\|^2, s.t. \sum_{j=1}^k w_{ji}=1

在推导之前,我们首先统一下数据的矩阵表达形式

  输入:                                 \large X = [x_1, x_2, ..., x_N], D\times N

  权重:                                 \large w = [w_1, w_2, ..., w_N], k\times N 

则可逐步推导出权重系数矩阵的表达式:

                                                 \large \Phi(w)=\sum_{i=1}^{N} \|x_i - \sum_{j=1}^{k}w_{ji}x_{ji}\|^2\\ =\sum_{i=1}^{N} \|\sum_{j=1}^k (x_i-x_{ji})w_{ji}\|^2\\ =\sum_{i=1}^N \|(X_i-N_i)w_i\|^2, X_i =\underbrace {[x_i, ..., x_i]}_k, N_i = [x_{1i}, ..., x_{ki}]\\ = \sum_{i=1}^N w_i^T(X_i-N_i)^T(X_i-N_i)w_i
将 \ S_i 看做局部协方差矩阵:

                                                        \large \ S_i = (X_i-N_i)^T(X_i-N_i)

                                                        \large \Phi(w)=\sum_{i=1}^N w_i^T S_i w_i
 运用拉格朗日乘子法

                                                  \large \large L(w_i) = \sum_{i=1}^N w_i^T S_i w_i+ \lambda(w_i^T1_k-1)
 
求导可得:

                                                   \large \frac{\partial L(w_i)}{\partial w_i} = 2S_iw_i+\lambda 1_k = 0

                                                             \large \\ w_i = \frac{S_i^{-1}1_k}{1_k^T S_i^{-1}1_k}

其中 1_k 为 k\times 1的元素全1的列向量,就上述表达式而言,局部协方差矩阵S_i 是个k\times k  的矩阵,其分母实质是矩阵S_i  逆矩阵的所有元素之和,而其分子是S_i  的逆矩阵对行求和后得到的列向量。

step 3: 映射到低维空间

即求解下述有约束优化问题:

                                             \large \arg \min \limits_{Y} \Psi(Y) = \sum_{i=1}^N\|y_i - \sum_{j=1}^k w_{ji}y_{ji}\|^2,

                                               \large \\ s.t. \sum_{i=1}^N y_i = 0, \sum_{i=1}^N y_i y_i^T = NI_{d\times d}

  输出结果即低维空间向量组成的矩阵Y

                                                    \large \large Y = [y_1, y_2, ..., y_N], d\times N 

首先,用一个 N\times N  的稀疏矩阵(sparse matrix) W  来表示w  :

     对 i  的近邻点 j : \large W_{ji} = w_{ji} 

     否则 :                      \large W_{ji} = 0 

因此:

                                                \dpi{100} \large \sum_{j=1}^N w_{ji}y_{ji} =\sum_{j=1}^k w_{ji}y_{ji} =YW_i

其中,

                                            \large w_{k+1,i}=w_{k+2,i}=...=w_{N,i}=0

其中,W_i是方阵 W(N\times{N})的第i列 , I_i是单位矩阵 I(N\times{N})的第i列,y_i 对应矩阵Y第 i 列,所以 :

                                                      \dpi{100} \large \Psi(Y) = \sum_{i=1}^{N}||Y(I_i-W_i)\|^2

由矩阵相关知识可知:对矩阵 A = [a_1, a_2, ..., a_N]  ,有 

                                                 \large \sum_i (a_i)^2 = \sum_i a_i^T a_i = tr(AA^T)
由于Y(I_i-W_i)对应的是矩阵的一列,好比上述中\large a_i,所以对于整个矩阵而言Y(I-W)类比上述矩阵A,所以: 

                                            \large \Psi(Y) = tr(Y(I-W)(I-W)^TY^T)= tr(YMY^T)

                                                               \large \\\ M = (I-W)(I-W)^T
再一次使用拉格朗日乘子法: 

                                                           \large L(Y) = tr(YMY^T)+\lambda tr(YY^T-NI)
求导: 

                                                                \large \frac{\partial L}{\partial Y} = 2YM+2\lambda Y = 0   

进一步得到                                         \large M^TY^T=\lambda{}' Y^T  ,再由\large M^T=M,注意此时 

                        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        \large \large \lambda{}' =- \lambda

    即:                                                                       \large MY^T = \lambda'Y^T   

  可见Y^T其实是M的特征向量构成的矩阵,为了将数据降到 d  维,我们只需取M的最小的 d 个非零特征值对应的特征向量,而一般第一个最小的特征值接近0,我们将其舍弃,最终按从小到大取前 [2, d+1]  个特征值对应的特征向量。

  对swiss roll数据集进行了LLE降维。原始的数据如下:

LLE原理及推导过程_第5张图片

Fig.1. Swill roll数据集。

通过LLE降维后的结果如下:

LLE原理及推导过程_第6张图片

Fig. 2. LLE降维在不同k值的二维分布结果。k代表在k近邻算法时选取的最近邻个数。

  可以看出,当k取值较小(k=4)时,算法不能将数据很好地映射到低维空间,因为当近邻个数太少时,不能很好地反映数据的拓扑结构。当k值取值适合,这里选取的是k=15,可见不同颜色的数据能很好地被分开并保持适合的相对距离。但若k取值太大,如k=50,不同颜色的数据开始相互重叠,说明选取的近邻个数太多则不能反映数据的流形信息。

4.其它说明

1. 为什么上述推导最后取最小特征值而不是最大特征值:

        ​​​​​​​                                        \large min\left [tr(YMY^T) \right ]

由于最终待优化项是上述表达式,而基于拉格朗日乘子求偏微分得到:

                                                \large MY^T = \lambda'Y^T

两者结合,再有约束条件   \large YY^T=NI, 则:

               \large tr(YMY^T) =tr(Y \lambda'Y^T)=tr( \lambda'YY^T)=tr( \lambda'NI)=N\sum \lambda'

 显而易见, 此时要想使得优化表达式取最小值,则对应的只能是取最小的特征值。

2. LLE与其它流形学习算法比如LE、t-SNE的差异在哪里:

 关于不同流形学习算法的差异及比较可参阅 本专栏内以下博客 Laplacian Eigenmaps原理及推导过程 、t-SNE原理与推导 、 机器学习中的流形学习算法                      ​​​​​​​        ​​​​​​​                

5.代码实现

LLE原理及推导过程_第7张图片

LLE原理及推导过程_第8张图片

LLE原理及推导过程_第9张图片

 LLE原理及推导过程_第10张图片

# lle.py
"""
    LLE ALGORITHM (using k nearest neighbors)
    X: data as D x N matrix (D = dimensionality, N = #points)
    k: number of neighbors
    dmax = max embedding dimensionality
    Y: lle(X,k,dmax) -> embedding as dmax x N matrix

"""

import numpy as np
from scipy.sparse import csr_matrix,lil_matrix
from scipy.sparse.linalg import  eigs


def lle(X, k, d, sparse = True):

    D, N = X.shape
    print('LLE running on {} points in {} dimensions\n'.format(N,D))

    # Step1: compute pairwise distances & find neighbors
    print('-->Finding {} nearest neighbours.\n'.format(k))

    X2 = np.sum(X**2,axis = 0).reshape(1,-1) # 1xN
    distance = np.tile(X2,(N,1)) + np.tile(X2.T, (1, N)) - 2 * np.dot(X.T,X) # NxN

    index = np.argsort(distance,axis=0)
    neighborhood = index[1:1+k,:] # kxN filter itself

    # Step2: solve for reconstruction weights
    print('-->Solving for reconstruction weights.\n')

    if (k>D):
        print(' [note: k>D; regularization will be used]\n')
        tol = 1e-3 # regularlizer in case constrained fits are ill conditioned
    else:
        tol = 0

    w = np.zeros((k,N))
    for ii in range(N):
        xn = X[:,neighborhood[:,ii]] - np.tile(X[:,ii].reshape(-1,1),(1,k)) # shift ith pt to origin
        S = np.dot(xn.T, xn)                                 # local covariance,xn = Xi-Ni
        S = S + np.eye(k,k) * tol * np.trace(S)              # regularlization (k>D)
        Sinv = np.linalg.inv(S)                              # inv(S)
        w[:,ii] = np.dot(Sinv,np.ones((k,1))).reshape(-1,)   # solve Cw=1
        w[:,ii] = w[:,ii]/sum(w[:,ii])                       # enforce sum(wi)=1

    # Step 3: compute embedding from eigenvectors of cost matrix M = (I-W)'(I-W)
    print('-->Computing embedding to get eigenvectors .\n')
    
    if sparse: #  parse solution   
        M = lil_matrix(np.eye(N, N)) # use a sparse matrix lil_matrix((N,N))       
        for ii in range(N):
            wi = w[:,ii].reshape(-1,1) # kx1, i point neighborhood (wji)
            jj = neighborhood[:,ii].tolist() # k,
            M[ii,jj] = M[ii,jj] - wi.T
            M[jj,ii] = M[jj,ii] - wi
            M_temp = M[jj,:][:,jj].toarray() + np.dot(wi,wi.T)
            for ir, row in enumerate(jj): ### TO DO
                for ic, col in enumerate(jj):
                    M[row,col] = M_temp[ir,ic]
    else:  # dense solution           
        M = np.eye(N, N) # use a dense eye matrix         
        for ii in range(N):
            wi = w[:,ii].reshape(-1,1) # kx1
            jj = neighborhood[:,ii].tolist() # k,
            M[ii,jj] = M[ii,jj] - wi.reshape(-1,)
            M[jj,ii] = M[jj,ii] - wi.reshape(-1,)
            M_temp = M[jj,:][:,jj] + np.dot(wi,wi.T) # kxk
            for ir, row in enumerate(jj): ### TO DO
                for ic, col in enumerate(jj):
                    M[row,col] = M_temp[ir,ic]      
        M = lil_matrix(M)                
    # Calculation of embedding
    # note: eigenvalue(M) >=0
    eigenvals,Y = eigs(M, k = d+1, sigma = 0 ) # Y-> Nx(d+1)
    Y = np.real(Y)[:,:d+1]  # get d+1 eigenvalue -> eigenvectors
    Y = Y[:,1:d+1].T * np.sqrt(N) # bottom evect is [1,1,1,1...] with eval 0
    print('Done.\n')
    
    # other possible regularizers for k>D
    #   S = S + tol*np.diag(np.diag(S))           # regularlization
    #   S = S + np.eye(k,k)*tol*np.trace(S)*k     # regularlization
    return Y
# test_lle.py

import numpy as np
import math
#import matplotlib
#matplotlib.use('Agg')
import matplotlib.pyplot as plt
from matplotlib import cm
from mpl_toolkits.mplot3d import Axes3D
from lle import lle


# Swill Roll 
def plot_swill_roll(N =2000,k=12,d=2,sparse =True):
    
  # Plot true manifold 
  tt0 = (3*math.pi/2)*(1+2*np.linspace(0,1,51)).reshape(1,-1)
  hh = 30 * np.linspace(0,1,9).reshape(1,-1)
  xx = np.dot((tt0*np.cos(tt0)).T, np.ones(np.shape(hh)))
  yy = np.dot(np.ones(np.shape(tt0)).T,hh)
  zz = np.dot((tt0*np.sin(tt0)).T, np.ones(np.shape(hh)))
  cc = np.dot(tt0.T, np.ones(np.shape(hh)))
  
  fig = plt.figure(figsize=(18,12))
  ax = fig.add_subplot(131, projection='3d')
  #ax = Axes3D(fig) # change to 3D figure
  cc = cc / cc.max()  # normalize 0 to 1
  #ax.plot_surface(xx, yy, zz, rstride = 1, cstride = 1, cmap = cm.coolwarm,facecolors=cm.rainbow(cc))
  ax.plot_surface(xx, yy, zz, rstride = 1, cstride = 1, facecolors=cm.rainbow(cc))
  ax.grid(True)
  
  lnx = -5*np.array([[3,3,3],[3,-4,3]]).T
  lny = np.array([[0,0,0],[32,0,0]]).T
  lnz = -5*np.array([[3,3,3],[3,3,-3]]).T
  ax.plot3D(lnx[0], lny[0], lnz[0],color='red', linewidth = '2', linestyle='-')
  ax.plot3D(lnx[1], lny[1], lnz[1],color='red', linewidth = '2', linestyle='-')
  ax.plot3D(lnx[2], lny[2], lnz[2],color=[1,0,0], linewidth = '2', linestyle='-')
  #plt.axis([-15,20,0,32,-15,15])
  ax.set_xlim(-15, 20)
  ax.set_ylim(0, 32)
  ax.set_zlim(-15, 15)

  # Generate sampled data
  tt = (3*math.pi/2)*(1+2*np.random.rand(1,N))
  tts = tt.reshape(-1,)
  height = 21 * np.random.rand(1,N)
  X = np.concatenate([tt*np.cos(tt), height, tt*np.sin(tt)],axis=0)

  # Scatterplot of sampled data
  ax = fig.add_subplot(132, projection='3d')
  ax.scatter(X[0,:],X[1,:],X[2,:], marker = '+', s=12, c=tts )
  ax.plot3D(lnx[0], lny[0], lnz[0],color='red', linewidth = '2', linestyle='-')
  ax.plot3D(lnx[1], lny[1], lnz[1],color='red', linewidth = '2', linestyle='-')
  ax.plot3D(lnx[2], lny[2], lnz[2],color=[1,0,0], linewidth = '2', linestyle='-')
  #plt.axis([-15,20,0,32,-15,15])
  ax.set_xlim(-15, 20)
  ax.set_ylim(0, 32)
  ax.set_zlim(-15, 15)
  
  # Run LLE  algorithm
  Y = lle(X,k,d, sparse = sparse)
  # Scatterplot of embedding
  ax = fig.add_subplot(133)
  ax.scatter(Y[0,:],Y[1,:], marker = '+', s=12, c=tts )
  ax.grid(True)

#S-Curve  
def plot_s_curve(N =2000,k=12,d=2,sparse = False):

  # Plot true manifold 
  tt = math.pi * np.linspace(-1,0.5,16)
  uu = tt[::-1].reshape(1,-1)
  tt = tt.reshape(1,-1)
  hh = 5 * np.linspace(0,1,11).reshape(1,-1)
  xx = np.dot(np.concatenate([np.cos(tt), -np.cos(uu)],axis=1).T, np.ones(np.shape(hh)))
  yy = np.dot(np.ones(np.shape(np.concatenate([tt, uu],axis=1))).T, hh)
  zz = np.dot(np.concatenate([np.sin(tt), 2 - np.sin(uu)],axis=1).T, np.ones(np.shape(hh)))
  cc = np.dot(np.concatenate([tt, uu],axis=1).T , np.ones(np.shape(hh)))

  fig = plt.figure(figsize=(18,12))
  ax = fig.add_subplot(131, projection='3d')
  #ax = Axes3D(fig) # change to 3D figure
  #cc = cc / cc.max()  # normalize 0 to 1
  #ax.plot_surface(xx, yy, zz, rstride = 1, cstride = 1, cmap = cm.coolwarm)
  ax.plot_surface(xx, yy, zz, rstride = 1, cstride = 1, facecolors=cm.jet(cc))
  ax.grid(True)
  
  lnx = -1*np.array([[1,1,1],[1,-1,1]]).T
  lny = np.array([[0,0,0],[5,0,0]]).T
  lnz = -1*np.array([[1,1,1],[1,1,-3]]).T
  ax.plot3D(lnx[0], lny[0], lnz[0],color='red', linewidth = '2', linestyle='-')
  ax.plot3D(lnx[1], lny[1], lnz[1],color='red', linewidth = '2', linestyle='-')
  ax.plot3D(lnx[2], lny[2], lnz[2],color=[1,0,0], linewidth = '2', linestyle='-')
  #plt.axis([-1,1,0,5,-1,3])
  ax.set_xlim(-1, 1)
  ax.set_ylim(0, 5)
  ax.set_zlim(-1, 3)

  # Generate sampled data
  angle = math.pi*(1.5*np.random.rand(1,int(N/2))-1)
  angle2 = np.concatenate([angle,angle],axis=1).reshape(-1,)
  angle2 = angle2/angle2.max()
  height = 5 * np.random.rand(1,N)
  X = np.concatenate([np.concatenate([np.cos(angle), -np.cos(angle)],axis=1),
                      height, np.concatenate([np.sin(angle), 2-np.sin(angle)],axis=1)])

  # Scatterplot of sampled data
  ax = fig.add_subplot(132,projection='3d')
  ax.scatter(X[0,:],X[1,:],X[2,:], marker='+', s=12, c = angle2  ) #
  ax.plot3D(lnx[0], lny[0], lnz[0],color='red', linewidth = '2', linestyle='-')
  ax.plot3D(lnx[1], lny[1], lnz[1],color='red', linewidth = '2', linestyle='-')
  ax.plot3D(lnx[2], lny[2], lnz[2],color=[1,0,0], linewidth = '2', linestyle='-')
  #plt.axis([-1,1,0,5,-1,3])
  ax.set_xlim(-1, 1)
  ax.set_ylim(0, 5)
  ax.set_zlim(-1, 3)

  # Run LLE  algorithm
  Y = lle(X,k,d, sparse = sparse)
  # Scatterplot of embedding
  ax = fig.add_subplot(133)
  ax.scatter(Y[0,:],Y[1,:], marker='+',s=12, c=angle2 )
  ax.grid(True)

if __name__ == '__main__':
    plot_swill_roll(N =2000,k=12,d=2)
    plot_s_curve(N =2000,k=12,d=2)

你可能感兴趣的:(#,机器学习,流形学习,LLE,python)