
简介Matlab数据降维工具箱面向科研人员、高校师生和算法开发者适合高维数据特征压缩、可视化探索、分类聚类前处理以及多种降维方法横向对比。内容覆盖PCA、LDA、ICA、MDS等经典线性方法也包含Isomap、LLE、Laplacian、LTSA、DiffusionMaps等流形学习方案并延伸至核PCA、核LDA、SPCA、CCA、MVU与自编码器等方法谱系较完整。压缩包共84个文件约1011KB以57个.m源码为主体配合多平台mex二进制、C/C及头文件、少量可执行程序和说明文本兼顾直接调用与跨平台编译扩展。已有4285人学习适合快速搭建降维实验基线、比较算法表现或按需改造源码的研究与工程场景。1. 一张 200 多列的特征表这套 Matlab 降维工具箱解决哪个环节一张 200 多列的特征表丢进 MatlabPCA 跑完前两个主成分只解释了三成多的方差散点图糊成一团——这时候缺的不是再换一个算法而是一套能把预处理、降维、验证串起来的 Matlab 降维工具。这套 Dimensionality Reduction 工具箱把线性方法PCA、LDA、因子分析与非线性嵌入Isomap、LLE、t-SNE、UMAP收在相近的调用习惯下附示例脚本和小尺寸演示数据装好就能跑通从特征表到二维图、再到下游分类器的完整链路。适合做信号与图像特征分析的研究生、要把高维指标压成可视化报表的算法工程师也适合给课程设计找一份能改能调的实现。2. 算法选型PCA、LDA、Isomap、t-SNE 各自的适用边界降维翻车八成不是因为代码写错而是因为一开始就把方法用错了场景。把 t-SNE 的二维图当成“可分性证据”去做特征筛选把 PCA 用在一条卷起来的螺旋流形上最后得出的结论必然站不住。这一章先把选型的判断依据讲清楚再落到工具箱里各函数的调用约定上后面动手才有方向。2.1 先判断数据是不是躺在一张弯曲的薄片上选型的第一步不是看算法列表而是看数据的局部结构。我一般会抽 300500 个样本用原始特征的前三个主成分画一次三维散点再对同一批样本算一下每列特征的相关系数矩阵。如果前几个主成分就能吃掉大部分方差、相关系数矩阵里有明显的块状结构说明数据近似线性PCA 或因子分析够用如果三维图里出现明显的弯折、环状、卷曲主成分解释率却上不去那就说明数据落在一张非线性流形上得换 Isomap、LLE 或 t-SNE 这类基于近邻图的方法。第二件事是确认有没有标签。有标签且要做分类LDA 是有监督投影里最省事的选择它显式地最大化类间散度与类内散度的比值投影后的维度上限是“类别数减一”这一点必须提前算清楚别拿 10 类数据去要 20 维 LDA 结果。没有标签就只用无监督方法不要偷偷把标签喂进去调参数那是另一种形式的泄漏。第三件事是数据的量级。t-SNE 的复杂度大致随样本数呈平方增长样本过万时在普通工作站上会很吃力工具箱里那套 Barnes-Hut 近似实现能缓解一部分压力但内存峰值依然要看近邻图。Isomap 和 LLE 要先构 kNN 图再求特征分解样本数上万时稀疏矩阵的构造本身就会成为瓶颈。样本量大、只想要一个稳定的低维表示PCA 依旧是性价比最高的起点。2.2 工具箱统一的调用约定输入矩阵、输出得分、附加结构体这套工具箱在接口上做了一件很讨巧的事所有方法都遵循“输入样本×特征矩阵输出低维得分矩阵”的主约定附加信息放在第二个返回值里。PCA 的第二个返回值是主成分系数与解释方差Isomap 和 LLE 返回的是近邻图与残差t-SNE 返回的是收敛信息。这样一来第 3 章那条链路里替换算法只需要改一行调用后面的出图和验证代码完全不用动。调用前有两个约定必须记住。第一输入必须是数值矩阵table要先转成double类别型、时间型列一律提前处理掉工具箱不会替你猜。第二绝大多数方法默认把每一列当成一个特征、每一行当成一个样本如果你的数据是转置的很多传感器数据导出就是这样必须显式转置否则降维结果看上去“能跑”但每一个维度都在混样本属于最难排查的一类错误。注意工具箱里的示例脚本用的是自带的小尺寸演示数据直接换成自己的表之前先确认列方向与样本数别急着调参数。2.3 四类方法的对照与选择顺序把方法按“是否需要标签、是否线性、能否增量更新、可解释性”四个维度摆开选型就变成了查表。下面这张表是我自己常用的决策顺序从左往右逐项排除剩下的就是候选。方法需要标签线性可解释性典型用途主要代价PCA否是高载荷可回溯原始特征去冗余、压缩、可视化对非线性流形无能为力因子分析否是高有潜变量含义问卷、指标建模需要指定因子数并做旋转LDA是是中判别方向可解释有监督分类前的投影维度上限为类别数减一Isomap否否中测地距离可解释卷曲流形的展开对近邻数敏感图不连通会报错LLE否否低局部结构保持对新样本无自然映射t-SNE否否低簇间距离无意义高维特征的可视化检查慢参数敏感不可当特征用UMAP否否低可视化 粗略的降维特征依赖近邻数与最小距离参数一个常见的误用是把 t-SNE 的输出直接当特征送进分类器。t-SNE 只保留局部邻域概率结构簇与簇之间的距离、簇内方差都没有意义用它做特征分类器学到的是一张随机布局图。要降维后接下游模型优先 PCA、LDA 或 UMAP要给人看才用 t-SNE。3. 跑通第一条链路从原始特征表到可解释的二维图这一章把整条链路走一遍读表、清洗、标准化、PCA 定维、非线性嵌入出图、结果写回。每一步都给可抄的代码代码后面说明为什么这么写、参数改了会怎样。跑完这一遍你手上就有一份能替换成自己数据的骨架。3.1 装载与清洗把 ID 列摘出去很多特征表的第一列是样本编号第二列可能是采集时间直接table2array会把字符串列变成NaN或者干脆报错。稳妥的写法是先声明导入选项再把非数值列单独摘出来保存后面出图时还要用它们标注点。% 读入特征表保留原始列名避免中文列名被自动改写 opts detectImportOptions(features_demo.csv); opts.VariableNamingRule preserve; T readtable(features_demo.csv, opts); % 第一列当样本 ID其余列当特征非数值列在这里就被挡掉了 sampleId string(T{:, 1}); X table2array(T(:, 2:end)); if ~isa(X, double) X double(X); end % 缺失值处理先统计再决定别上来就填 0 missRate mean(isnan(X), 1); fprintf(含缺失的列数%d最高缺失率 %.2f%%\n, sum(missRate 0), 100*max(missRate)); X fillmissing(X, constant, 0);detectImportOptions比csvread稳它会推断每列类型VariableNamingRule设成preserve是为了保住原始列名后面看 PCA 载荷时要靠它对应回业务含义。缺失值那段先打印统计再填是因为“整列八成都是空”这种列直接补 0 等于往数据里灌噪声正确做法是整列删掉。填 0 只是为了让后面zscore不返回NaN如果缺失率超过 5%建议换成按列中位数填充或者干脆用工具箱里带缺失处理的变体。3.2 标准化与 PCA用累计方差定维数特征量纲不统一是 PCA 最常见的翻车点。一个以毫伏为单位的通道和一个以百分比为单位的通道放在一起前者方差大几十倍第一主成分几乎就是它自己。标准化之后再分解各列权重才可比。% 按列 z-score 标准化同时保留 mu/sigma 供新样本复用 [Xn, mu, sigma] zscore(X); % 已经中心化过这里关掉 pca 的内部中心化避免二次处理 [coeff, score, latent, tsq, explained] pca(Xn, ... Algorithm, svd, Centered, false); cumVar cumsum(explained); k find(cumVar 95, 1, first); fprintf(累计解释 95%% 方差需要 %d 个主成分共 %d 维\n, k, size(X, 2)); fprintf(前三个主成分解释率%.2f%% / %.2f%% / %.2f%%\n, explained(1:3));zscore的三个返回值要一起留着以后来了新样本必须用训练集的mu和sigma变换不能用新样本自己的统计量否则训练和推理的口径不一致这是第 5 章要展开的坑之一。Algorithm选svd在特征数远大于样本数时更稳eig在维度低时更快两者结果在数值上等价但符号可能相反PCA 主成分的正负号本来就没有绝对意义别因为两次跑出来图翻转了就以为代码有问题。k的取法这里用 95% 阈值是最省事的一种。实际项目里我一般会同时看碎石图的拐点两者差得多的时候以拐点为准因为方差阈值会被少数高方差噪声列带偏。如果后面要接分类器更靠谱的做法是直接把k当超参数用交叉验证选这一点放到 4.1 讲。3.3 非线性嵌入困惑度与近邻数怎么给初值PCA 出来的图如果仍然糊成一团就该上非线性方法了。t-SNE 的困惑度可以理解为“每个点预期的有效邻居数”经验初值取样本数的平方根再夹到 550 之间近邻图方法Isomap、LLE、UMAP的 k 一般从 1015 起再按图的连通性调整。rng(20240501); % 固定随机种子保证同一份数据每次出图一致 % 先用 PCA 降到 3050 维再喂 t-SNE既降噪也提速 Y tsne(score(:, 1:min(50, k)), ... NumDimensions, 2, ... Perplexity, 30, ... Exaggeration, 4, ... LearnRate, 500, ... Distance, euclidean, ... Standardize, false, ... Verbose, 1); % 近邻图方法k 从 12 起返回值里带残差可以判断是否退化 [Yiso, ~, info] isomap(Xn, NumNeighbors, 12, NumDimensions, 2); fprintf(Isomap 残差%.4f\n, info.reconstructionError(end));先 PCA 再 t-SNE 是常规做法把几百维降到 50 维既能压掉大部分噪声维度也能把 t-SNE 的耗时砍掉一大截。Perplexity小于 5 时结果会退化成局部小团大于 50 时计算量陡增且结构被抹平30 是个安全的起点。Exaggeration控制早期阶段的簇间斥力值大一些簇分得更开但太大会造出并不存在的空隙4 到 12 之间比较常见。Distance这一项容易被忽略特征都是正值的计数型数据比如词频、像素强度累计用cosine往往比euclidean更能反映相似性标准化后的连续特征就老实待着用欧氏距离。Standardize在已经做过 z-score 的情况下必须关掉否则相当于标准化了两遍各列权重会被再压一次。Isomap 返回的info.reconstructionError是个有用的诊断量它随维度增加应当单调下降如果在某个维度之后基本不动说明再加维度也榨不出结构了。另外isomap对图连通性很敏感k 太小时会出现孤立点函数会给出警告或者返回NaN这时候把 k 往上加两三个再试。3.4 出图与导出把坐标写回表里降维结果最终要么给人看要么给下游用两条路都得把坐标存成结构化数据别只留一个scatter。我通常把低维坐标和原始 ID、标签拼成一张表导出方便后面用别的工具复核。% 拼成交付用的表ID 标签 二维坐标 前若干个主成分 outTbl table(sampleId, Y(:,1), Y(:,2), ... VariableNames, {SampleId, Dim1, Dim2}); writetable(outTbl, embedding_tsne.csv); % 出图按标签着色点大小随局部密度微调 figure(Color, w); gscatter(Y(:,1), Y(:,2), label, [], ., 12); xlabel(t-SNE 1); ylabel(t-SNE 2); title(sprintf(perplexity30, k%d, k)); exportgraphics(gcf, embedding_tsne.png, Resolution, 300);gscatter比手动循环scatter省事它会自动分配颜色。exportgraphics比saveas清晰度高投稿或者写报告都能直接用。导出表里额外保留前若干个主成分是有意义的可视化靠 t-SNE下游建模靠 PCA两条线并行不冲突也方便后面对照“图上看着分开”和“数值上确实可分”是不是一回事。提示图上的簇间距不要当相似度读。t-SNE 只保证邻域概率结构两个簇在图上一左一右不代表它们在原始空间里离得远。4. 参数标定维数、近邻数、困惑度和随机种子参数乱给是降维结果的第二大杀手。同一份数据k 取 5 和取 50 出来的图能讲出两个完全相反的结论。这一章把四个关键参数拆开讲每个参数控制什么、初值怎么给、怎么用下游任务反过来验证。4.1 目标维数的三种定法方差阈值、平行分析、下游精度方差阈值最直观也最容易被噪声带偏。当数据里混着几个高方差的仪器漂移通道时前两个主成分可能就是这俩通道累计方差很快到 90%但结构一点没提出来。这种情况要先做异常通道筛查或者改用稳健的协方差估计。平行分析是更严谨的一种把原始矩阵的每一列各自独立打乱做一次 PCA得到一条“纯噪声下的解释方差曲线”真实曲线的拐点要高于这条噪声曲线才保留。实现只需要十几行跑一次几十秒比拍脑袋定阈值强。% 平行分析打乱各列内部顺序破坏列间相关性保留边缘分布 nPerm 20; noiseVar zeros(size(Xn, 2), nPerm); for p 1:nPerm Xp zeros(size(Xn)); for j 1:size(Xn, 2) Xp(:, j) Xn(randperm(size(Xn,1)), j); end [~, ~, ~, ~, expP] pca(Xp, Algorithm, svd, Centered, false); noiseVar(:, p) expP; end noise95 prctile(noiseVar, 95, 2); % 每个维度的噪声解释率上界 kParallel find(explained(:) noise95, 1, first) - 1; fprintf(平行分析建议保留 %d 维\n, kParallel);这段代码的关键在“按列独立打乱”列与列之间的相关性被破坏了但每列自己的分布没变所以噪声曲线反映的是“如果这些特征彼此无关PCA 能提出来多少”。真实解释率低于噪声上界的那一刻就说明再往后提的都是噪声。第三种也是最贴合业务的把维数当超参数用交叉验证后的分类或回归精度来选。做法是让 k 在 5 到 100 之间按步长扫一遍每个 k 都跑一次完整的“降维 训练 验证”取精度最高且方差最小的那个。这种定法计算量最大但它优化的是你真正关心的指标尤其是后面要接模型的时候前两种方法只当粗筛。4.2 近邻图参数k 太小碎片化太大抹平结构Isomap、LLE、UMAP 都依赖 kNN 图k 决定了“局部”有多局部。k 太小图变成一堆互不相连的小碎片测地距离算不出来LLE 的重构权重矩阵会病态k 太大邻域跨越了流形的弯折处测地距离把不同分支上的点串成一条直线展开后的结构全错。实操里我一般这样定先取 k 10画出图看有没有明显的孤立点或者整片区域被压成一条线。有孤立团就往上加每次加 5直到图连通如果加上去之后原本分开的两簇黏在一起说明已经过头了退回去取中间值。工具箱里的近邻图诊断函数能直接输出连通分量个数比肉眼判断准。注意UMAP 的n_neighbors和 t-SNE 的Perplexity不是一回事。前者直接控制近邻图规模后者是概率分布的熵目标两者虽然都在调“局部性”但敏感区间和调法完全不同别套用经验值。4.3 用下游分类器给降维结果打分降维好不好最终看下游任务吃不吃得下。最省事的评估是固定分类器我一般用线性 SVM 或 KNN 做基线换不同的降维方法和维数比 5 折交叉验证的精度。之所以用简单分类器是因为复杂模型会把降维的差异吃掉你调了半天以为降维有用其实模型自己在高维空间里也学到了。参数控制什么初值敏感区间失控表现目标维数 k保留信息量累计方差 95%5100太大引入噪声太小丢失判别信息近邻数 k图方法局部邻域尺度1015550太小图碎片化太大抹平流形Perplexity邻域概率熵30550太小出小团太大糊成一片Exaggeration早期簇间斥力4412太大造出假空隙随机种子初始化固定整数—不固定则图不可复现评估时有一个细节要注意降维和分类器必须在小折内重新拟合不能在全量数据上先降维再切折。这一点在 6 章用一整段代码展开这里先记住结论只要降维看到了验证折的数据精度就是虚高的。4.4 复现性种子、并行与浮点差异t-SNE 和 UMAP 都是随机初始化不固定种子你每次截图都不一样汇报的时候说不清是哪一版。固定rng是基本操作但还有两个细节一是并行循环里每个 worker 的随机流是独立的parfor里调 t-SNE 需要给每次迭代单独设种子二是不同 Matlab 版本、不同 BLAS 库的浮点累加顺序不同同一颗种子在 A 机器和 B 机器上仍可能有微小差异图会差一点点。% parfor 里保证每个任务独立可复现 seeds 20240501 (0:nRuns-1); results cell(nRuns, 1); parfor r 1:nRuns rs RandStream(Threefry, Seed, seeds(r)); RandStream.setGlobalStream(rs); results{r} tsne(score(:, 1:k), NumDimensions, 2, ... Perplexity, 30, Verbose, 0); endThreefry是支持并行独立流的生成器用它比默认的Mersenne Twister在parfor里更安全。多次运行不同种子还能干一件有用的事把多次结果的邻域保持度比如 KL 散度终值统计一下波动特别大的话说明参数落在了不稳定区间这本身就是个信号比单次跑出来的漂亮图可信得多。5. 避坑与排查五个把降维结果做废的细节下面五条都是我在实际项目里真踩过或者帮人排查过的每一条按“现象、原因、解决”写。看着琐碎但每一条都能让一整天的结果白跑。5.1 预处理顺序错位两个最隐蔽的坑现象一模型在训练集上精度 0.95换一批新数据掉到 0.6。原因是在全量数据上做了zscore和 PCA然后才切分训练测试集验证折的信息通过均值和主成分方向漏进了训练过程。这种做法在离线评估时看不出问题因为评估本身也被污染了。解决办法是把mu、sigma、coeff都当成模型的一部分只在训练折上拟合验证折用训练折的参数做变换具体写法见 6 章。任何时候你发现“离线指标好得不像话”先查这条。现象二PCA 载荷图里第一主成分几乎等于某一个通道其他通道权重接近 0。原因是没做标准化那列的原始量纲远大于其他列。解决办法是分解前先zscore同时把pca的Centered关掉避免二次中心化。如果那列本身就该占主导比如它确实是主信号那另说但大多数情况是量纲问题。5.2 参数与评估口径图好看不等于结论对现象三t-SNE 图上两类分得很开换成线性 SVM 一测精度只有 0.55。原因是把可视化当成了可分性证据。t-SNE 会放大局部密度差异随机标签跑出来的图照样有漂亮的簇。解决办法是任何“看起来分开了”的结论都必须用下游分类器的交叉验证兜底图只用来发现异常样本和检查数据质量。现象四换个随机种子重跑原本清晰的三簇变成了两簇。原因是Perplexity落在了不稳定区间或者样本里有一批近重复点。解决办法是把 perplexity 在 1050 之间扫一遍看簇结构在多大范围内保持稳定只在稳定区间里取参数近重复点先做去重或加微小扰动。一个只在特定种子下成立的结论不要写进报告。现象五Isomap 跑出来一片NaN或者图缩成一个点。原因是 k 太小导致近邻图不连通测地距离矩阵里出现无穷大特征分解自然崩。解决办法是逐步加大NumNeighbors直到连通分量为 1工具箱的近邻图诊断能直接给出这个数字。如果加到很大才连通说明样本本身在空间里分成了几个相距很远的团这时候该先怀疑是不是混进了不同来源的数据。5.3 内存与性能样本量上来之后的现实问题现象六五万条样本跑 t-SNE内存直接爆掉。原因是完整的相似度矩阵是 n×n 的五万样本就是 2.5e9 个元素双精度下 20GB 起步。解决办法有三条先用 PCA 降到 50 维以内减少距离计算开销打开近似算法选项把精确的近邻搜索换成近似搜索再不行就分层抽样先在一万条上把参数定下来再对全量做一次固定参数的嵌入。别指望在小样本上定好的参数直接搬到全量上还完全一致样本规模变了合适的 perplexity 也会变。一言以蔽之降维这条链路上报错信息从来都指向代码真正的问题几乎都藏在数据形态和评估流程里。看到异常先回头看数据比盯着函数签名强。6. 进阶把降维塞进交叉验证堵住标签泄漏前面反复提到“降维必须在折内做”这一段给一个能直接抄的完整写法。核心思路是把标准化、PCA、分类器打包成一条“在训练折上拟合、在验证折上只用 transform”的管道每一折都从头来一遍。cvp cvpartition(label, KFold, 5, Stratify, true); acc zeros(cvp.NumTestSets, 1); for i 1:cvp.NumTestSets tr training(cvp, i); te test(cvp, i); % 只用训练折估计 mu / sigma / 主成分方向 [Xtr, mu, sigma] zscore(X(tr, :)); [coeff, scoreTr, ~, ~, explained] pca(Xtr, ... Algorithm, svd, Centered, false); k find(cumsum(explained) 95, 1, first); Ztr scoreTr(:, 1:k); % 验证折用训练折的参数变换绝不重新拟合 Zte ((X(te, :) - mu) ./ sigma) * coeff(:, 1:k); mdl fitcecoc(Ztr, label(tr), Learners, linear); pred predict(mdl, Zte); acc(i) mean(pred label(te)); end fprintf(5 折精度 %.3f ± %.3f\n, mean(acc), std(acc));这段代码里有两处必须照抄而不能“优化”。第一Zte的计算用的是训练折的mu、sigma和coeff如果偷懒写成对X(te,:)再跑一次zscore和pca验证折的统计量就参与了变换精度会虚高几个百分点而且这个偏差在小数据集上特别明显。第二fitcecoc在每个折里重新训练不能在外面训好一个模型循环里只换数据。这两条加起来才叫真正的“折内降维”。如果要把降维方法当超参数一起搜用fitcecoc外面套一层bayesopt或者简单的网格扫把k和近邻数放进搜索空间。注意parfor和cvpartition一起用时随机流要单独设否则各折的划分虽然固定降维内部的随机初始化还是会飘。还有一个容易被忽略的点LDA 这类有监督降维标签必须只来自训练折。有人图省事在全量数据上跑一次 LDA 再切折这比 PCA 泄漏更严重因为判别方向直接把测试折的标签信息编码进去了精度能虚高十几个点而且离线怎么调都“很好”一上线就原形毕露。我现在的习惯是只要流程里出现zscore、pca、tsne这些词就先写一个空的折循环确认所有拟合动作都在tr的索引里再往里填模型。有次赶进度跳过了这一步回头查一个“训练集 0.98、线上 0.61”的模型翻了半天代码才发现标准化写在了切折之前血泪经验从此再没省过这五行循环。希望帮到你。本文还有配套的精品资源点击获取