Locally Linear Embedding源码剖析

版权声明:龚丁禧版权所有,转载必须注明出处,否则追究法律责任!合作请添加微信wangqingzhuofeng。个人主页http://gongdingxi.cn https://blog.csdn.net/star_gdx/article/details/15812577

LLE是一个很经典的降维方法,关于它的理论我就不赘述了。最近我在学习LLE的时候,查看了很多LLE相关的论文,最初偷懒想在CNKI上找两篇中文文献作为入门,发现那些论文都是纯扯蛋,竟然连原文的意思都没有搞懂就敢发论文,对于这些混吃等S的教授,只能竖着中指说无数遍“SHIT“才能泄我心头之恨啊。

先推荐一篇LLE原论文作者对LLE的详细阐述的文章,http://www.cs.nyu.edu/~roweis/lle/publications.html, 在这个网址可以找到“Nonlinear dimensionality reduction by locally linear embedding”这篇论文,还可以看到一篇对LLE做二次阐述的长文“An Introduction to Locally Linear Embedding.”,认真地看完这两篇论文,并试着推导它的每一个公式,你一定会受益匪浅。

很多人可能在看了论文后依然心存各种疑惑,毕竟论文很多地方都是略写了,这时候看看Matlab源码可能会对你带来很大帮助。这篇文章就是为了引导大家更好地理解LLE的算法,对源码做了一些分析,有些地方我理解的不一定正确,欢迎大家拍砖。

% LLE ALGORITHM (using K nearest neighbors)
%
% [Y] = lle(X,K,dmax)
%
% X = data as D x N matrix (D = dimensionality, N = #points)
% K = number of neighbors
% dmax = max embedding dimensionality
% Y = embedding as dmax x N matrix

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

先对算法的输入和输出做简单介绍,输入D*N的一个矩阵,D是每个样本的维数,N代表有N个样本。注意一下数据是怎么放的,列是样本,行是每一维度的数据。假如你有1000幅关于人脸的大小为128*128的灰度图像,那么D=128*128=16384,N=1000。每一列是一个16384的向量,代表一张人脸图像,每一行代表不同人脸中某个位置的像素。dmax是你希望降维之后获得的维数,K是你采用K近邻算法设置的K。输出Y是dmax*N的矩阵,如果你把人脸图像降成2维,最后就是2*N的矩阵,如果你的人脸是由向左转或向右转的数据组成,最后在左边的可能就是往左转,在右边就是往右转。

function [Y] = lle(X,K,d)

[D,N] = size(X);
fprintf(1,'LLE running on %d points in %d dimensions\n',N,D);

X是D*N的样本矩阵,把维数D放在D,样本数N放在N。接下来就是构建K近邻图。

% STEP1: COMPUTE PAIRWISE DISTANCES & FIND NEIGHBORS 
fprintf(1,'-->Finding %d nearest neighbours.\n',K);

X2 = sum(X.^2,1);
distance = repmat(X2,N,1)+repmat(X2',1,N)-2*X'*X;

[sorted,index] = sort(distance);
neighborhood = index(2:(1+K),:);%取出每个点的K个邻居(即距离最小的前K个邻居),因为第一个是自身到自身的距离0,所以从第二个开始。获取一个K*N的矩阵

Matlab代码都是非常精简的,当然,这也跟使用他的人有很大关系,上面这么几行代码就完成了距离矩阵构建、距离排序、构建邻接图与索引这么多事,Amazing!

在Matlab中X.^2是对X矩阵中每个元素做平方运算,对于Matlab不熟悉的童鞋可以自己尝试以下代码:

获得的结果就是:

sum(X.^2,1)是将X各元素平方后按列求和,摆成一个1*N的行向量X2。将各个列向量组成的样本看成一个整体,实际上就是:

接下来我们看下一行:

distance = repmat(X2,N,1)+repmat(X2',1,N)-2*X'*X;

首先说明一下repmat(matrix,m,n)函数,它是将matrix矩阵填充到一个m*n的矩阵,这个矩阵的每个元素就是matrix。所以repmat(X2,N,1)是摆成一个N*1的矩阵,每个元素是X2,
最后就是如下矩阵:

repmat(X2',1,N)得到的矩阵如下:

X‘*X得到的矩阵是:


我们知道两个向量的距离公式为:

所以distance就在一行代码上完成了距离矩阵的计算。继续来看这行代码:

[sorted,index] = sort(distance);

matlab的sort函数默认是对列排序,即对每一列的元素从小到大排序,可以看下面的例子:

sorted就是按列排序后的矩阵,index对应排序之前的列索引。

neighborhood = index(2:(1+K),:);

因为index保存是相应的索引,所以对于第i列来说(实际上就是考察第i个样本),前面的K+1个数就是到样本i距离最小的K+1个样本,这里包括了自身,因为自己到自己的距离为0,所以肯定会出现在前面K个,这就是取K+1个的原因,从第2到K+1取出最近K邻居放在neighborhood。neighborhood就是截取了index矩阵的第2行起到第K+1行,形成一个K*N的矩阵。

第二步开始,求权值矩阵W。

% STEP2: SOLVE FOR RECONSTRUCTION WEIGHTS
fprintf(1,'-->Solving for reconstruction weights.\n');

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

W = zeros(K,N);
for ii=1:N
   z = X(:,neighborhood(:,ii))-repmat(X(:,ii),1,K); % shift ith pt to origin
   C = z'*z;                                        % local covariance
   C = C + eye(K,K)*tol*trace(C);           % regularlization (K>D)
   W(:,ii) = C\ones(K,1);                           % solve Cw=1
   W(:,ii) = W(:,ii)/sum(W(:,ii));                  % enforce sum(w)=1
end;

因为K>D的时候,C是奇异矩阵,不能求逆,所以对C进行了调整,但这种调整不能过大,以免获得的结果偏离真值过远。这里采取的方法是加上这么一个矩阵:

这是K*K的矩阵,对角线的元素值非常小,也就是保证在求解W过程中对原来的W影响非常小。

我们先回到论文,看看怎么求得权值矩阵W。在“An Introduction to locally linear embedding”这篇论文中 是这样表述的:

上式是目标函数,在下述限制条件下求得最小值:

其中C是由下式获得的:

上面的式子都是显而易见的,接下来的问题是如何通过上述条件怎么求得W。这里要用到拉格朗日乘子法,关于什么是拉格朗日乘子法请自行查阅相关资料。总之,我们会得到一个如下的函数,称为朗格朗日函数:

对这个函数求极值获得的W就是使epsilon获得最小值的W。对各变量分别求偏导,得到如下方程组:


将上述方程组写成矩阵的形式,得到下式:


这里求得的C矩阵和论文的C有点出入,作者的C矩阵对角线元素是没有2倍因子的,因为作者并没有写出推导过程,直说用拉格朗日乘子法就可以得到答案,也没说这个是近似解还是精确解,这是我很不解的地方。如果在求K近邻的时候,把自己算上的话,C矩阵的对角线元素是为0的,那样的话我这个C对角元素的两倍因子也是可以去掉的。可以进一步推出:

这就求出了跟第ii个样本相关的W(ii),这是一个1*K的向量。现在回过头来看看Matlab源码。

W = zeros(K,N);

这是获得一个所有元素为0的K*N矩阵W。

for ii=1:N
   z = X(:,neighborhood(:,ii))-repmat(X(:,ii),1,K); % shift ith pt to origin
   C = z'*z;                                        % local covariance
   C = C + eye(K,K)*tol*trace(C);                   % regularlization (K>D)
   W(:,ii) = C\ones(K,1);                           % solve Cw=1
   W(:,ii) = W(:,ii)/sum(W(:,ii));                  % enforce sum(w)=1
end;

上面的Z是这样获得的,先取出X的所有邻接节点,即K个邻居,然后每个邻居减去X(ii),求解过程如下:

C=z'*z是什么也就很清楚了。得到的C矩阵的每一项就是论文所述的C矩阵:

求出C矩阵后,还进行了调整,这是为了避免C是奇异矩阵的情况。


C = C + eye(K,K)*tol*trace(C);                   % regularlization (K>D)

这一行代码就是调整,已在前面说过。

W(:,ii) = C\ones(K,1);

上面这一行就是一个所有元素为1的K*1的行向量乘以C的逆。


W(:,ii) = W(:,ii)/sum(W(:,ii));

上面这一行就是使W(ii)各元素相加的和为1。

经过上述过程,第二步求解W权值矩阵就算完结了。现在看第三步,怎么求解Y。

% STEP 3: COMPUTE EMBEDDING FROM EIGENVECTS OF COST MATRIX M=(I-W)'(I-W)
fprintf(1,'-->Computing embedding.\n');

% M=eye(N,N); % use a sparse matrix with storage for 4KN nonzero elements
M = sparse(1:N,1:N,ones(1,N),N,N,4*K*N); 
for ii=1:N
   w = W(:,ii);
   jj = neighborhood(:,ii);
   M(ii,jj) = M(ii,jj) - w';
   M(jj,ii) = M(jj,ii) - w;
   M(jj,jj) = M(jj,jj) + w*w';
end;

M是一个N*N的稀疏单位矩阵矩阵,即对角线元素为1,其余元素均为0。它的构建方式不同,存储方式不同,但作用还是跟普通矩阵一样,不必深究稀疏矩阵是什么,当普通矩阵用就是了。

w = W(:,ii);

这是取出W(ii),没什么好解释道的。

jj = neighborhood(:,ii);

取出ii的所有邻居放在jj这个向量里,它当然是K*1的向量。

根据论文的推导,M这个矩阵元素如下:

因为M已经是一个单位矩阵,所有前面的delta就可以去掉了。反过来思考,假设我们现在取出了W(ii),然后去修改跟W(ii)相关的M元素也是等价的。可以由一下推导过程给出:


   M(ii,jj) = M(ii,jj) - w';
   M(jj,ii) = M(jj,ii) - w;
   M(jj,jj) = M(jj,jj) + w*w';

其实这几行代码实现的功能就是上述过程。M本来是N*N的矩阵,但是在处理的时候,只用到了跟第ii个节点相邻的K个分量。获得了M,再对M求特征值、特征向量就比较简单了。

% CALCULATION OF EMBEDDING
options.disp = 0; options.isreal = 1; options.issym = 1; 
[Y,eigenvals] = eigs(M,d+1,0,options);
Y = Y(:,2:d+1)'*sqrt(N); % bottom evect is [1,1,1,1...] with eval 0

Matlab的eigs是求特征值与特征向量的函数,详细资料请自行查阅相关资料。

最后取出对应Y的最小的d个特征值的特征向量即为Y。M是对称矩阵,并且行列式为0,所以它一定有特征值0,但是有特征值0就一定有特征向量[1 1 ...1]吗?这一直搞不明白。

猜你喜欢

转载自blog.csdn.net/star_gdx/article/details/15812577
今日推荐