☰
拉普拉斯特征映射(LE)流形学习降维:MATLAB实现与参数调优指南
2026/10/7 11:00:55 网站建设 项目流程

1. Laplacian Eigenmap的核心思想与算法流程

1.1 为什么需要流形学习与拉普拉斯特征映射

做数据可视化或者特征降维时,很多人第一反应就是PCA。但PCA是线性方法,它假设高维数据分布在某个线性子空间附近。真实场景里这个假设往往不成立,比如一堆照片、一段生理信号、一组基因表达数据,它们的本质自由度可能只有几个,但被高维观测空间包裹成了弯曲的流形。这时候线性方法很容易把原本连续的结构压成一团,丢失关键信息。Laplacian Eigenmap(简称LE)就是为这类非线性降维场景设计的,它通过局部邻域结构来恢复全局的低维坐标,本质上是在做“保局部”的嵌入。

LE的来源可以追溯到谱图理论。把每个样本看作图上的一个节点,如果两个样本在原始高维空间中离得近,就在它们之间连一条边,边的权重反映相似程度。然后构造图的拉普拉斯矩阵,求解一个广义特征值问题,取最小的几个非零特征值对应的特征向量作为低维嵌入。整个过程听起来不复杂,但数学上很漂亮,它让低维空间中的距离尽量保留“近邻”关系,同时把远距离样本拉开。这个算法对图像分割、聚类、可视化、特征提取都有用,尤其是数据本身存在流形结构时,效果比PCA、MDS这种传统方法好不少。

适合阅读这篇文章的读者,应该是已经有MATLAB基础、想动手实现谱方法降维的人。不需要很强的流形学习背景,但最好知道什么是特征值、特征向量,以及最基础的矩阵操作。我会把每一步的原理和代码都展开讲,包括参数怎么选、坑在哪里,尽量让你看完后能直接在自己的数据上跑起来。

1.2 算法步骤总览与数学表述

LE算法总共四步,我先把框架列出来,后面再逐步拆解:

  1. 构建近邻图:对每个样本,找到它的k个最近邻居,或者用epsilon半径内的邻居,形成无向图。
  2. 计算边的权重:常用热核权重(Heat Kernel),也就是 w_{ij} = exp(-||x_i - x_j||^2 / t),也可以用更简单的0-1权重,连接则为1,不连接为0。
  3. 构造拉普拉斯矩阵:定义度矩阵D和邻接矩阵W,拉普拉斯矩阵L = D - W。根据后续求解的不同也可以使用归一化拉普拉斯。
  4. 求解广义特征值问题:L y = λ D y,取最小的d个非零特征值对应的特征向量(跳过第一个零特征值),组成d维嵌入。

为什么要求广义特征值而不是普通特征值?因为引入D后,目标函数会变成“邻近点被映射后距离的加权平方和”,权重视为近邻图的度。这样度大的样本对误差的惩罚更重,可以避免把高密度区域的点强行挤在一起。

数学上,LE的优化目标可以写成:

min ∑_{i,j} ||y_i - y_j||^2 W_{ij}

加上约束 y^T D y = I,避免退化解。通过拉格朗日乘子法可以化成广义特征值问题。矩阵L是半正定的,最小特征值是0,对应一个常数向量。我们要的是第二小到第d+1小的特征值对应的特征向量,这些特征向量才携带了有效的流形展开信息。

理解了这套数学逻辑,去读MATLAB代码会轻松很多。很多博客直接丢代码,但不说L、D怎么来的,导致改参数时一头雾水。下面我把MATLAB实现的关键环节拆开讲。

2. MATLAB实现的关键环节

2.1 环境准备与数据预处理

在MATLAB里实现LE,不需要任何额外的工具箱,只用基础函数和Statistics Toolbox里的knnsearch(如果要在大数据集上用KD树找近邻)。数据输入是一个n×m矩阵X,n是样本数,m是原始特征维度。

第一步最好对数据做标准化或归一化,尤其是各特征量纲差异大的时候。比如有的特征数值在0~1,有的在几万,直接算欧氏距离会让量纲大的特征主导邻居关系。我一般先做zscore标准化,如果数据本身是灰度图像这种天然接近的值域,可以只做平移缩放。

第二步是计算距离矩阵。如果样本数不大(比如几千以内),可以直接用pdist2(X, X)计算成对欧氏距离,然后排序。样本数过万时,全矩阵会占用大量内存,建议改用knnsearch配合KD树,只保留近邻信息。这里有个容易忽视的点:pdist2默认返回稠密距离矩阵,n=10000时就有1e8个元素,约800MB,直接内存爆炸。所以大数据集一定要走knnsearch。

预处理完,才算进入LE的真正算法。

2.2 构图方式与权重计算的MATLAB代码

构图是LE算法的第一步,也是最影响结果的一步。常见的近邻图有三种:

  • k近邻图:每个点固定连接最近的k个点。优点是局部邻域大小稳定,不会出现某个点邻居数悬殊;缺点是k选太小会导致图不连通,选太大又会模糊流形边界。
  • epsilon邻域图:距离小于epsilon才连接。优点是对尺度敏感,可以保留空间密度信息;缺点是epsilon的参数难以设定,而且不同区域的密度差异会让图变得很“散”。
  • 全连接图:所有点之间都连边,边权用热核计算。此时算法退化成类似核PCA,但计算量大。一般只有在样本数很小的情况下才用。

在MATLAB里,我习惯先找到近邻索引,再填充稀疏邻接矩阵W。假设idx是n×k的矩阵,第i行存的是第i个样本的k个最近邻居的序号(注意第一列是自己)。代码如下:

n = size(X, 1); k = 10; % 近邻数 idx = knnsearch(X, X, 'K', k+1); % k+1 是因为包含自身 idx = idx(:, 2:end); % 去掉自身 % 构建对称邻接矩阵,避免有向边 W = sparse(n, n); for i = 1:n W(i, idx(i,:)) = 1; W(idx(i,:), i) = 1; end % 去掉自环 W = W - diag(diag(W));

上面的W用的是0-1权重。如果想用热核权重,需要在确定邻居后计算距离,再代入exp(-d.^2 / t)。要注意,热核中的距离是原始空间距离,不是图上距离。t的选择后面专门讲。一个完整的构图函数可以写成:

function W = constructGraph(X, k, t, weightType) idx = knnsearch(X, X, 'K', k+1); idx = idx(:, 2:end); [n, ~] = size(X); D2 = zeros(n, k); for i = 1:n D2(i,:) = sum((X(i,:) - X(idx(i,:),:)).^2, 2); end I = repmat((1:n)', 1, k); J = idx; if strcmp(weightType, 'heat') V = exp(-D2 / t); else V = ones(n, k); end W = sparse(I(:), J(:), V(:), n, n); W = max(W, W'); % 对称化 end

这里对称化用了max(W, W'),效果是只要任意方向有边就保留,权重取较大值。这只是一种方案,也有用(W + W')/2平均的。实际测试中,如果构图时两个方向距离完全相同,max不会引入额外误差,而且能保证无向图。

2.3 拉普拉斯矩阵的两种构造方式

拉普拉斯矩阵不是唯一写法,但MATLAB实现时需要注意矩阵符号和求解方式。

最常用的是组合拉普拉斯(Combinatorial Laplacian):

D = diag(sum(W, 2)); L = D - W;

度矩阵D是对角阵,每个对角元素是第i个样本的加权度。注意如果某个样本没有任何邻居(孤立点),D(i,i)=0,这会导致后面特征值求解出现多个零特征值,说明图不连通,需要处理。

另一种是归一化拉普拉斯:

D_inv_sqrt = diag(1 ./ sqrt(sum(W, 2) + eps)); L_sym = D_inv_sqrt * (D - W) * D_inv_sqrt;

归一化拉普拉斯的好处是特征值范围在[0,2],对图的规模不敏感,数值稳定性更好。但对应的广义特征问题会变化,有的论文直接用对称归一化矩阵的普通特征分解,得到特征向量后再除以sqrt(D)来恢复嵌入。这个细节很多人搞混。

如果使用组合拉普拉斯,通常求解广义特征值问题L*Y = D*Y*Lambda。MATLAB里可以写成:

[Y, Lambda] = eigs(L, D, d+1, 'smallestabs');

这里eigs返回最小的d+1个特征值,但要注意,如果矩阵很大,eigs需要提供合适的初始向量,否则可能不收敛。另一个办法是直接用eig,对小数据集(比如几千样本)足够快,而且不会出现迭代收敛问题:

[Y, Lambda] = eig(full(L), full(D));

但eig不支持稀疏矩阵直接求解广义问题,所以需要full转换,内存占用会变大。这是个取舍。

2.4 特征值排序与特征向量归一化

eig返回的特征值不保证有序。我们要取最小的d个非零特征值对应的特征向量。常见坑:用eigs(..., 'smallestabs')时,MATLAB默认返回按特征值绝对值排序,而不是代数最小。如果矩阵有负特征值(理论上LE的L是半正定,不会负,但数值误差可能导致很小的负特征值),排序就会出错。所以我建议拿到特征值后手动排序:

[Lambda_sorted, order] = sort(diag(Lambda)); Y_sorted = Y(:, order); % 跳过第一个特征值(接近0的常数特征向量) Y_embed = Y_sorted(:, 2:d+1);

这里跳过的第一个特征值理论上对应0特征值,特征向量是全1向量,没有信息。如果前几个特征值都接近0,说明图不连通,需要增大k或者检查数据是否有离群点。

特征向量的尺度问题也很关键。LE的坐标不是唯一的,任何非零缩放都是合法的,但为了可视化和后续分析,一般要对特征向量做归一化。常见做法是:

Y_embed = bsxfun(@rdivide, Y_embed, sqrt(sum(Y_embed.^2, 2) + eps));

把每个样本映射后的向量长度归一化到单位长度。注意这是在样本方向归一化,不是特征方向。做完之后,低维坐标就稳定了,多次运行不会因为矩阵求解器的微小数值差异产生明显变动。

3. 完整MATLAB代码实现与仿真实验

3.1 完整函数代码(可直接复制使用)

我写了一个可直接调用的MATLAB函数,里面整合了构图、拉普拉斯、特征求解和输出。函数签名如下:

function [Y, eigenvalues] = laplacian_eigenmap(X, k, t, d) % LAPLACIAN_EIGENMAP 拉普拉斯特征映射降维 % 输入: % X - n×m 数据矩阵,n个样本,m维原始特征 % k - 近邻个数,默认10 % t - 热核宽度,默认1 % d - 目标降维维度,默认2 % 输出: % Y - n×d 低维嵌入坐标 % eigenvalues - 前d+1个特征值(用于调试) if nargin < 2, k = 10; end if nargin < 3, t = 1; end if nargin < 4, d = 2; end [n, m] = size(X); % 1. 标准化数据(让距离更公平) X = (X - mean(X)) ./ std(X, 0, 1); % 2. 找到k近邻(加1是为了包含自身) idx = knnsearch(X, X, 'K', k+1); idx = idx(:, 2:end); % 3. 计算热核权重 D2 = zeros(n, k); for i = 1:n diff = X(i,:) - X(idx(i,:),:); D2(i,:) = sum(diff.^2, 2); end W = sparse(repmat((1:n)', 1, k), idx, exp(-D2/t), n, n); W = max(W, W'); % 对称化 % 4. 构造拉普拉斯矩阵 D = diag(sum(W, 2)); L = D - W; % 5. 求解广义特征值问题 [Y_eig, Lambda] = eig(full(L), full(D)); [~, ord] = sort(diag(Lambda)); Y_eig = Y_eig(:, ord); % 6. 取第2到第d+1个特征向量 Y = Y_eig(:, 2:d+1); eigenvalues = diag(Lambda); eigenvalues = eigenvalues(ord); % 7. 归一化坐标 Y = bsxfun(@rdivide, Y, sqrt(sum(Y.^2, 2) + eps)); end

这段代码可以应对一般规模的数据集,但有几个前提:样本数不能太大,否则eig(full(L), full(D))会内存爆炸。大规模数据需要改成eigs和稀疏矩阵。另外std(X,0,1)如果某列方差为0会得到NaN,需要提前删掉零方差特征。

3.2 在瑞士卷数据集上的效果演示

瑞士卷(Swiss Roll)是流形学习的经典数据集。三维空间中的点服从一个卷曲的平面结构,用PCA投影到二维会看到一团混叠,而LE能把卷曲展开成规则的条带。我们用MATLAB生成瑞士卷数据,测试上面的函数:

% 生成瑞士卷数据 n = 2000; t_roll = 3*pi/2 * (1 + 2*rand(n, 1)); height = 30 * rand(n, 1); X = [t_roll.*cos(t_roll), height, t_roll.*sin(t_roll)]; % 用LE降到二维 [Y_le, ev] = laplacian_eigenmap(X, 12, 1, 2); % 可视化 figure; subplot(1,2,1); scatter3(X(:,1), X(:,2), X(:,3), 10, t_roll, 'filled'); title('原始瑞士卷'); subplot(1,2,2); scatter(Y_le(:,1), Y_le(:,2), 10, t_roll, 'filled'); title('LE降维结果');

运行后,第二张图上颜色(对应原始主变量t_roll)会沿着一条平滑的条带展开,说明LE成功恢复了数据的内在参数坐标。如果用PCA,你会看到投影后的点挤在一起,无法展平整个卷曲,这就是流形学习相对线性降维的核心优势。

这里有一个要点:我生成数据时把t_roll做了非线性映射,实际流形的内在维度是1(卷曲参数)加上1(高度),总共是2。LE在降维时并不使用标签,因此能发现嵌入结构,属于无监督方法。

3.3 与其他降维方法对比的实测感受

很多人喜欢把LE和PCA、t-SNE放一起比较。我的经验是,LE在“保持流形全局拓扑”方面强于PCA,在“可视化聚类边界”方面弱于t-SNE。但t-SNE对超参数特别敏感,每次运行结果都不一样,而且计算量大。LE稳定、可复现、有明确的谱意义,适合做特征提取和预处理。

对比实验可以这样写:

[coeff, score, ~] = pca(X); pca_2d = score(:, 1:2); figure; subplot(1,2,1); scatter(pca_2d(:,1), pca_2d(:,2), 10, t_roll, 'filled'); title('PCA降维'); subplot(1,2,2); scatter(Y_le(:,1), Y_le(:,2), 10, t_roll, 'filled'); title('LE降维');

从图上能明显看到PCA把瑞士卷压成“扇形”重叠,LE则铺成连贯条带。这是因为PCA优化的是全局方差最大,而不是局部几何保持。LE的图拉普拉斯惩罚的是近邻点嵌入后距离变大,所以卷曲的拓扑被拉伸开。

4. 参数调优、常见问题与避坑指南

4.1 关键参数:近邻数k、热核宽度t、目标维度d

近邻数k是LE最重要的超参数。k太小,图可能不连通,特征向量会出现多个零特征值,嵌入坐标会断裂;k太大,局部结构被平滑掉,等于把整个数据集近似成全连接图,LE退化成类似MDS的结果。我的经验是:k取ln(n)左右作为下界,然后看看特征值光谱。如果第2个特征值和第1个特征值差距很小,说明图可能不连通,需要增大k。

热核宽度t控制权重衰减速度。t远大于数据尺度的平方时,所有边权都接近1,等于0-1权重;t太小时,只有极近的点有非零权重,图可能退化成树状结构。一个相对稳的经验值是对所有近邻距离的平方取中位数,然后把t设成这个中位数。代码里可以这样调:

dist_sq = D2(:); t = median(dist_sq(dist_sq > 0));

目标维度d一般根据应用设定。可视化取2或3,特征提取可以先用LE降维到较低维,再结合分类器看效果。如果d太大,特征向量后面的方向噪声明显,反而不利于泛化。

4.2 特征向量符号不定和坐标翻转问题

LE的特征向量有个数学性质:如果y是特征向量,那么-y也是特征向量,对应相同的特征值。这意味着每次跑出来的嵌入可能整体镜像翻转,不同维度的符号也可能随机。这不是错误,但如果后续要用嵌入坐标与标签做回归,或者直接算距离,符号翻转不影响距离(因为整体翻转不会改变点间距离),可是如果要做分量解释,就得注意。

我在多个数据集上遇到过这类问题:第一次运行嵌入坐标在某个维度为正偏差,第二次全部取反。解决方法是在算法内部对特征向量做符号约定,比如让每个特征向量的最大绝对值位置为正,或者让向量和某个参考向量内积为正。函数里可以加一行:

for col = 1:d if sum(Y(:, col)) < 0 Y(:, col) = -Y(:, col); end end

这样至少保证坐标总和为正,便于复现。不过严格讲,这种符号约定没有理论依据,只是为了实验可重复。

4.3 大数据集的计算效率优化

当样本数达到几万甚至几十万时,构造稠密的D2和L是灾难。我试过一万样本,全矩阵pdist2已经接近卡死,eig(full(L), full(D))直接内存溢出。优化思路有三个:

第一,用knnsearch的KD树算法,只计算近邻距离,避免全距离矩阵:

[idx, dist] = knnsearch(X, X, 'K', k+1, 'NSMethod', 'kdtree'); dist = dist(:, 2:end); idx = idx(:, 2:end);

第二,用eigs替代eig,只求解少量特征向量。但是eigs需要指定求解器,而且对稀疏矩阵求解广义特征问题收敛性依赖参数设置。我常用的调用:

[Y_eig, Lambda] = eigs(L, D, d+1, 'smallestabs', 'Tolerance', 1e-8, 'MaxIterations', 300);

第三,如果样本量特别大,可以先用粗粒度采样跑LE,然后用插值方法(例如Nyström方法)把嵌入扩展到全体样本。这个思路在很多谱方法里都有应用,MATLAB里可以自己实现:先对子集求特征向量,再用核矩阵获得新样本的嵌入近似。

4.4 常见报错与问题速查表

我整理了一个速查表,覆盖LE实现中的常见问题,基本都亲自踩过:

问题现象可能原因解决方式
eig报错矩阵必须是方阵X有缺失值或特征维度为0检查数据预处理,删除NaN、零方差列
特征值里有负数数值误差或图权重不对称检查W是否对称,改用max(W,W')强制对称
前几个特征值都接近0图不连通,存在孤立点增大k,或删除离群点,或用epsilon构图
eigs不收敛矩阵病态,初始向量不合适减小Tolerance,增大MaxIterations,或转用eig
降维结果出现明显“带状断裂”近邻数k过小增大k,让图连通
标准化后出现NaN某特征方差为0删除该特征或加eps
内存不足用了稠密矩阵改用稀疏sparse,或采样降样本数
嵌入坐标整体反复翻转特征向量符号不定做符号约定,如图坐标和为正

解决这些问题时,最有效的调试手段是检查特征值光谱。你可以在函数里顺手画一下特征值:

figure; plot(eigenvalues(1:min(20, length(eigenvalues))), 'o-');

如果第二个特征值极小且和第三个差一个数量级,说明图基本连通,嵌入质量有保证。如果前5个特征值都挤在一起,那就该回头调整构图了。

最后分享一个经验:LE不是万能的,如果你的数据本来就不满足流形假设,强行降维只会得到看不懂的结果。判断是否该用LE,可以先对数据做一次MDS或PCA,看看方差解释率。如果前两个主成分已经能解释大部分方差,LE可能不会带来额外收益。反之,如果数据是高度非线性的(例如图像、光谱、卷曲几何数据),LE往往比线性方法更适合做探索性分析。

这篇内容里给出的代码和调参方法,都是从实际工程里沉淀下来的。一开始别指望跑一次就出完美效果,多试几组k和t,把特征值光谱打出来看看,你很快就能找到适合自己数据的参数组合。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询