资讯动态

主成分分析MATLAB源代码:带注释可运行的降维方案

发布时间:2026/10/13 16:08:39 来源:尧图企业网站定制
简介数据降维是机器学习和数据分析中常用的预处理手段主成分分析PCA通过协方差矩阵的特征值分解将高维数据映射到方差最大的低维空间在保留核心信息的同时消除特征间的相关性。对于使用MATLAB的工程师和学生而言理解PCA的原理与实现细节远比直接调用内置函数更重要。本文从标准化、特征值分解到贡献率计算层层拆解结合一份可直接运行的带注释MATLAB源码和模拟例题数据帮助你清晰掌握每个步骤的数学含义与代码逻辑。同时介绍特征向量符号统一、重构误差验证等避坑技巧并展示载荷图与双标图的可视化方法适用于论文写作、特征压缩和数据可视化等场景。1. 主成分分析MATLAB源代码能直接跑通、能看懂每行含义的降维方案如果你在找“主成分分析matlab源代码带注释带例题数据”大概率不是想看PCA的数学推导而是手里有一批多维数据想降维做可视化、去相关性、压缩特征或者写论文时需要一张贡献率图和一套可复现的处理流程。这套代码的价值不在于“实现了PCA”而在于把协方差矩阵、特征值分解、投影矩阵、贡献率计算这些环节拆成带注释的MATLAB脚本并配上一份可以直接喂进去跑的例题数据——拿到手就能看到每一行在算什么改哪一行就能换自己的数据。我见过太多人卡在同一个地方用MATLAB内置的pca函数三行跑完但说不清输出的score、coeff、latent分别代表什么也不知道为什么要对数据先做标准化。这套源代码就是用来解决这个问题的——它不追求极简而是把每个中间量都打印出来、写在注释里。适合两类人一是课程作业或论文需要给出计算过程的学生二是做数据分析时想对PCA结果有完全控制权的工程师而不是单纯拿黑匣子输出。下面从原理落地、代码结构、例题数据、避坑、验证到可视化技巧一层层把这条路径走完。2. PCA在MATLAB中的落地为什么先标准化、为什么用特征值分解这一章不是讲数学推导而是回答你在写代码时必须做的三个决定数据要不要标准化、用eig还是svd、降成几维。这三个决定直接决定你的代码能不能复现、结果能不能解释。2.1 标准化不是可选项量纲差异会直接扭曲主方向PCA的核心目标是找到方差最大的投影方向。问题在于“方差”这个概念对量纲敏感。举个例子如果一份数据里包含身高单位厘米数值在160-190和体重单位千克数值在50-90身高的方差天然比体重大很多PCA算出来的第一主成分会被身高的数值尺度主导而不是被数据的真实结构主导。这不是PCA的缺陷而是输入数据没有标准化时的必然结果。所以凡是你看到一套PCA代码里没有做标准化这一步只对原始数据直接算协方差矩阵那这套代码处理量纲差异大的数据时基本是翻车的。正确的做法是先对每列数据减去均值、除以标准差把每一维变成零均值、单位方差的标准化数据再在这个基础上计算协方差矩阵。标准化之后协方差矩阵就变成了相关系数矩阵每个特征对主方向的贡献才是等权重的。% 标准化数据每列减去均值除以标准差 mu mean(X); % 1 x p 均值向量 sigma std(X); % 1 x p 标准差向量 X_std (X - mu) ./ sigma; % n x p 标准化后的数据代码逻辑说明X是原始数据矩阵每行一个样本每列一个特征。mean(X)得到每个特征的均值std(X)得到每个特征的标准差两者做逐元素运算后X_std的每一列都是均值为0、标准差为1的标准化数据。参数说明这里的std默认使用N-1作为分母对应样本标准差如果你的数据是总体数据而非抽样样本可以用std(X, 1)改用N作为分母这会影响后续特征值的绝对大小但不会改变主成分的方向。我一般保持默认的N-1因为大部分场景下数据都是样本。2.2 eig和svd的选择结果等价但代码逻辑不同计算主成分有两条路直接对协方差矩阵做特征值分解eig或者对数据矩阵本身做奇异值分解svd。两条路在数学上等价但代码写起来差别很大。简单说eig的路径更直观适合教学和调试因为协方差矩阵是实实在在的p×p方阵特征值就是各主成分解释的方差直接对应贡献率svd的路径更数值稳定适合高维数据但需要多一步把奇异值和特征值做换算。这套带注释的源代码用的是eig路径原因很简单每一行代码都能对应到一个数学概念协方差矩阵、特征向量、特征值、投影读者调试时心里有数。如果你数据维度很高比如p 1000我建议自己改成svd路径否则eig在极端情况下的数值稳定性会让你想摔键盘。% 计算协方差矩阵 C cov(X_std); % p x p 协方差矩阵 % 特征值分解 [eigvec, eigval] eig(C); % eigvec的每列是一个特征向量eigval对角线为特征值 % 提取对角线特征值并按降序排序 latent diag(eigval); [latent_sorted, idx] sort(latent, descend); eigvec_sorted eigvec(:, idx);代码逻辑说明cov(X_std)算的是标准化数据的协方差矩阵由于数据已标准化这个矩阵实际上就是相关系数矩阵。eig(C)返回两个矩阵eigvec的每一列是一个特征向量eigval是一个对角矩阵、对角线上的元素就是特征值。注意eig返回的特征值不保证按大小排序所以需要sort做一个降序排列同时把特征向量的列顺序也对应调整否则后面投影时主成分的顺序是乱的。参数说明sort的第二个参数descend表示降序idx保留了原始列位置到排序后位置的映射关系这一步遗漏的话贡献率图和投影坐标全都会对不上。2.3 降维维度怎么定累计贡献率还是业务解释力代码写完之后下一个实际问题就是保留几个主成分。最常见的做法是看累计贡献率一般保留到累计贡献率达到80%或85%。但纯看百分比有个坑如果你的业务场景需要后续做回归、聚类或者你需要在某个主成分上做物理意义的解读那么即使第3个主成分贡献率只有10%只要它和某个业务标签有强相关性你也应该把它保留下来。% 计算每个主成分的贡献率和累计贡献率 explained latent_sorted / sum(latent_sorted) * 100; % 各主成分贡献率(%) cum_explained cumsum(explained); % 累计贡献率(%)代码逻辑说明因为特征值的大小直接代表该方向解释的方差大小所以把每个特征值除以特征值总和乘以100就是百分比贡献率。cumsum做的是累计求和得到第1个主成分到前k个主成分的累计贡献率。参数说明这里没有手动指定保留几个而是把贡献率全部算出来后续可以自己设定阈值去截取。注意特征值总和等于标准化后数据的总体方差等于p因为每个特征标准化后方差为1所以sum(latent_sorted)实际上等于p这个关系可以用来验证你的标准化和分解步骤是否正确。3. 带注释的MATLAB源代码完整脚本、调用方式与例题数据说明到了这一章直接给出一份能跑通的完整代码。这份代码的设计原则是每个中间结果都有变量输出每个关键步骤有注释说明为什么这样做而不是只注释“这一步计算协方差矩阵”这种废话。例题数据放在代码下方可以直接复制进MATLAB跑。3.1 主函数脚本从数据读入到投影输出的完整流程%% 主成分分析完整实现带注释 % 输入X为n行p列的数据矩阵每行一个样本每列一个特征 % 输出score为投影后的主成分得分coeff为特征向量矩阵latent为特征值 function [score, coeff, latent, explained] my_pca(X, num_components) % 步骤1数据标准化 % 为什么PCA对量纲敏感标准化可以消除不同特征的尺度差异 mu mean(X); sigma std(X); X_std (X - mu) ./ sigma; % 步骤2计算协方差矩阵 % 为什么标准化后协方差矩阵等于相关系数矩阵体现了特征间的线性相关性 C cov(X_std); % 步骤3特征值分解 % 为什么特征向量给出主方向特征值给出该方向解释的方差大小 [eigvec, eigval] eig(C); latent diag(eigval); % 步骤4按特征值降序排列 % 为什么eig函数不保证顺序必须先排序才能按重要性取前k个 [latent_sorted, idx] sort(latent, descend); coeff eigvec(:, idx); % 步骤5计算贡献率 explained latent_sorted / sum(latent_sorted) * 100; % 步骤6投影到主成分空间 % score的每一列是一个主成分每一行对应一个样本在新坐标系下的坐标 score X_std * coeff; % 步骤7按指定维度截取 if nargin 2 num_components size(X, 2); end score score(:, 1:num_components); coeff coeff(:, 1:num_components); end代码逻辑说明这份代码把PCA拆成了7个步骤每个步骤的变量都可以在MATLAB工作区里检查。score的维度是n×kcoeff的维度是p×k其中k是保留的主成分个数。注意步骤6用的是矩阵乘法X_std * coeff不是逐元素乘法这个计算实际是把每个样本向量投影到特征向量张成的子空间。参数说明num_components控制输出维度默认保留全部分量。在调用时如果你只想要前两维做可视化就传2比如[score, coeff, latent, explained] my_pca(X, 2)。3.2 例题数据一组带业务含义的模拟数据及调用示例例题数据我常用一份模拟的“学生考试成绩与学习行为”数据它包含15个样本、4个特征课堂出勤率、课后练习完成度、实验操作得分、期末笔试成绩。这份数据的特点是第一维和第四维高度正相关第二维和第三维也有相关性——人造出相关结构才能看出PCA压缩效果。%% 例题数据学生成绩与学习行为15个样本4个特征 % 每行出勤率(%), 练习完成度(%), 实验得分(满分100), 笔试成绩(满分100) X [... 92 85 78 88; ... 88 90 82 85; ... 95 92 88 94; ... 78 70 65 72; ... 84 88 80 86; ... 98 95 90 96; ... 65 60 58 62; ... 80 82 75 78; ... 90 91 85 92; ... 70 68 62 70; ... 86 84 79 83; ... 75 72 68 71; ... 96 93 87 95; ... 82 86 78 84; ... 60 55 50 58]; %% 调用主函数保留前2个主成分 [score, coeff, latent, explained] my_pca(X, 2); %% 输出结果 disp(贡献率(%)); disp(explained); disp(累计贡献率(%)); disp(cumsum(explained)); disp(前2个主成分的投影坐标每行一个样本); disp(score);代码逻辑说明数据是4列意味着最多有4个主成分。前2个主成分的累计贡献率通常超过85%达到降到2维做可视化的条件。调用时传了2所以score是一个15×2的矩阵每一行对应一个学生的二维坐标可以直接用scatter(score(:,1), score(:,2))画散点图观察样本分布。参数说明X矩阵的分号是行间隔空格是列间隔注意保证每行数据个数一致。如果你要用自己的数据只需要把X替换成你自己的n×p矩阵不需要改任何后续代码。4. PCA代码避坑标准化顺序、特征向量符号、数据维度方向的常见错误这一章专门写踩坑。PCA的代码看起来短但实际操作中翻车点极其密集。每一条都是我见过真实发生的问题按照“现象 → 原因 → 解决”来梳理。4.1 特征向量符号方向不稳定两次运行结果镜像翻转现象同一份数据代码一行没改第一次运行出来第一主成分的系数是正的第二次运行出来变成了负的投影坐标的散点图左右镜像翻转。这不是代码bug但如果你没有意识到这一点可能会得出完全相反的商业结论。原因特征值分解得到的特征向量其符号方向本来就是不确定的。eig函数返回的特征向量乘以-1仍然是特征向量特征值不变。MATLAB的线性代数库在某些版本或某些矩阵元素顺序下可能随机返回符号方向不同的特征向量。解决在排序之后、投影之前加一个符号统一处理逻辑——检查每个特征向量第一个绝对值最大的元素强制让符号为正。这样无论底层库怎么变输出结果都是一致的论文里也能交代清楚。% 符号统一确保每个特征向量的最大绝对值分量为正 for i 1:size(coeff, 2) [~, max_idx] max(abs(coeff(:, i))); if coeff(max_idx, i) 0 coeff(:, i) -coeff(:, i); end end代码逻辑说明循环遍历每个特征向量找到该向量中绝对值最大的那个元素的位置如果这个元素是负的就把整个特征向量取反。这样做的依据是特征向量乘以-1不改变其作为子空间基的性质但能让结果的符号方向固定下来。参数说明max_idx是该特征向量中绝对值最大元素的行位置。注意这只影响方向和投影坐标的正负号不影响特征值和贡献率。4.2 忘记标准化导致贡献率虚高或主成分失真现象有同学直接对原始数据做协方差矩阵跑出来第一主成分贡献率高达95%以上看起来效果极好但画出来的散点图所有的点基本都分布在一条对角线上毫无结构可言。原因和解决这就是量纲差异导致的。最大的那个特征列数值范围远大于其他特征PCA的第一主成分几乎完全被它带走。解决方法是回到2.1节所有特征都必须先经过标准化再算协方差矩阵。事后检查技巧如果第一主成分的特征向量系数里有一个绝对值接近1、其余接近0那基本可以断定是没做标准化。4.3 数据矩阵维度方向搞反每行是样本还是特征现象写代码时矩阵维度方向弄反PCA跑出来的结果看起来合理——贡献率正常、散点图能看但投影坐标的含义完全错了每一行对应的不是样本而是特征。原因很多人读数据时习惯把每列当作一个样本——尤其是从表格软件复制数据时列方向更容易贴反。解决在函数入口加一个维度检查如果行数大于列数则按样本×特征处理否则转置。% 维度检查确保每个样本占一行每个特征占一列 if size(X, 1) size(X, 2) disp(检测到数据维度可能反转已自动转置); X X; end代码逻辑说明这个检查逻辑简单但极其有效。如果行数比列数少多半是样本被放到了列上。转置之后继续执行后续代码即可。参数说明size(X, 1)是行数size(X, 2)是列数。这个判断适用于通常情况如果你确实有“样本数少于特征数”的数据也能落在这个判断里正确执行因为它已经自动转置了。4.4 特征值排序后特征向量没同步排序投影结果错乱现象投影出来的散点图第一维方差确实最大但第二维方差急剧缩小画出来的图是一条线。原因很多人排序只用sort(latent)形成了新的特征值数组但coeff矩阵的列没有跟着同步重排。结果就是投影时用的特征向量顺序和特征值顺序对不上。解决永远用[sorted_val, idx] sort(latent, descend)这种带第二个输出参数的写法然后用idx去索引特征向量矩阵。这一点在2.2节的代码里我已经显式处理过但自己在改代码时极易遗漏。4.5 用内置pca函数和这套脚本结果不一致时的排查思路现象两套代码跑出的score坐标一模一样但coeff矩阵某些列的符号相反或者贡献率完全一致但coeff和score都不同。原因内置pca函数内部还会对数据做中心化并默认使用SVD路径数值上和你手写的eig路径会有微小差异符号方向也不保证和你的统一逻辑一致。解决对比时不要盯coeff的符号要盯投影后数据的相对位置。运行一次内置pca用gscatter画分组散点图和你的手写代码散点图对比只要每个样本点的相对位置一致允许镜像翻转就说明你的代码正确。如果相对位置不一致再检查你的标准化和排序步骤。5. 验证PCA结果正确性的三个方法特征值之和、重构误差、与内置函数交叉比对5.1 特征值总和等于数据总方差PCA一个不太为人注意的数学事实是协方差矩阵的迹对角线元素之和等于所有特征值之和而标准化后的数据协方差矩阵的迹等于pp是特征数量。这个关系是内建验证逻辑如果你计算sum(latent_sorted)得到的结果应该非常接近p误差来自浮点数精度。如果偏离很大说明标准化或协方差计算出了问题。% 验证1特征值之和是否等于特征数量 fprintf(特征值之和: %.6f\n, sum(latent_sorted)); fprintf(特征数量: %d\n, size(X, 2));代码逻辑说明因为标准化使得每个特征的方差为1总方差就是p。协方差矩阵对角线元素反映各个特征自身的方差特征值之和等于迹所以理论上两者相等。参数说明%.6f是格式化输出保留6位小数。正常误差应该在1e-10量级如果出现明显偏差bug大概率出在标准化步骤。5.2 用重构误差检查降维损失降维的本质是信息压缩保留前k个主成分后再投影回原空间和原始数据必然有误差。重构误差既能用来验证代码正确性——如果kp时存在明显误差说明实现有bug——也能用来帮你寻找合适的k值随着k增加重构误差单调下降拐点处对应最佳维度。% 重构并计算误差 score_all X_std * coeff_all; % 使用全部分量投影 X_reconstructed score_all * coeff_all; % 投影回原空间 % 注意重构后是标准化数据的近似需要反标准化才能和原始X比较 X_recon_orig X_reconstructed .* sigma mu; % 逐列乘以标准差再加回均值 reconstruction_error mean(sum((X - X_recon_orig).^2, 2)); fprintf(全分量重构误差: %.6e\n, reconstruction_error);代码逻辑说明score是样本在新空间的坐标乘以coeff的转置就映射回原特征空间。由于我们做了标准化映射回来的是标准化空间的值必须逐列乘以标准差再加回均值才能与原数据对齐。参数说明coeff_all是完整的特征向量矩阵不截断。mean(sum(...))是先对每个样本求各维度误差平方和再对所有样本取平均得到均方重构误差。如果这个误差在kp时不是1e-14量级说明你的投影或反标准化步骤有bug。5.3 与MATLAB内置pca函数交叉比对这是最实用也最容易被接受的验证方式。手写代码总是对还是错和MATLAB官方实现做对比一分钟就能确认。% 使用MATLAB内置pca函数 [coeff_builtin, score_builtin, latent_builtin] pca(X); % 比较前两维得分注意符号方向需要对齐 score1_norm score(:,1) / norm(score(:,1)); score_builtin1_norm score_builtin(:,1) / norm(score_builtin(:,1)); % 检查相关性接近1或-1 corr_check corr(score1_norm, score_builtin1_norm); fprintf(与内置pca第一主成分相关系数: %.6f\n, abs(corr_check));代码逻辑说明由于符号翻转的存在直接比较score数值没有意义。先对每个得分向量做归一化再用相关系数的绝对值来判断方向是否一致——相关系数接近1或-1都代表方向对齐。参数说明norm是向量的欧几里得范数corr计算两个向量间的皮尔逊相关系数。这个验证方法也适用于后续你自己的修改版本建议把这段验证脚本保留为固定测试代码。6. 让PCA结果可解释的进阶技巧载荷图、双标图与贡献率阈值的选择逻辑代码跑通不是终点能用PCA结果支撑业务判断才是。这一章分享两个在你写代码之后最常用的进阶技巧怎么用载荷图看清原始特征与主成分的关系以及怎么根据业务场景调整贡献率阈值而不是机械地卡85%。6.1 载荷图把一个主成分拆回原始特征的贡献coeff矩阵的每一列是一个特征向量向量中的每个元素代表原始特征在这个主成分中的权重。权重绝对值越大说明这个原始特征对该主成分的影响越强。把第一主成分和第二主成分的载荷权重直接画成散点图就是所谓载荷图。每个原始特征在图上的坐标是它在两个主成分中的权重方向相近的特征说明它们在降维空间中高度相关。% 绘制载荷图每个特征在PC1-PC2平面上的权重坐标 figure; quiver(zeros(size(coeff,1),1), zeros(size(coeff,1),1), ... coeff(:,1), coeff(:,2), 0, r); hold on; text(coeff(:,1)*1.1, coeff(:,2)*1.1, ... {出勤率,练习完成度,实验得分,笔试成绩}, FontSize, 10); xlabel(第一主成分载荷); ylabel(第二主成分载荷); axis equal; grid on;代码逻辑说明coeff的第1、2列分别是各特征在第一、第二主成分中的权重。quiver函数以原点为起点画有向箭头0作为缩放参数表示不自动缩放宽高比text用来给每个特征标注名称。轴设为equal保证箭头夹角看起来不失真。参数说明coeff(:,1)*1.1的作用是在箭头末端偏移10%的位置写文字避免文字压住箭头。axis equal对载荷图尤其重要因为载荷权重的绝对值大小之比才是有意义的不等比例缩放会扭曲夹角信息。6.2 贡献率阈值选择85%不是万能金标准很多人从教材里记下一个“累计贡献率大于85%”的经验数字但这个数字并非在所有场景都适用。如果你的数据本身噪声很大比如传感器原始采集信号85%的累计贡献率可能需要保留8个甚至10个主成分这种情况下应该看特征值碎石图的拐点而不是硬套85%。反之如果数据有强结构前两个主成分就已经达到90%以上强行保留更多分量只会让你画出的散点图徒增噪声。我的判断习惯是分场景做可视化时固定取前2或前3个主成分不管贡献率是多少因为图纸上的轴就那么多做后续回归或聚类时用碎石图拐点选k再验证一下选出的主成分和业务标签的相关性做特征压缩时才用85%或90%的累计贡献率阈值。你自己的代码只负责输出——它已经给出了全部贡献率和累计贡献率数组阈值怎么设取决于你要拿压缩后的数据做什么。6.3 双标图Biplot把样本和特征画在同一张图里最后一个值得掌握的技巧是把投影后的得分样本位置和载荷向量特征方向画在同一张图上一目了然地看出每个样本在哪个特征方向上更突出。MATLAB里通常还是用散点图和quiver叠加实现。% 绘制双标图 figure; scatter(score(:,1), score(:,2), 40, filled); hold on; % 对载荷向量做缩放使其和样本坐标区间匹配 scale max(max(abs(score))) / max(max(abs(coeff))); h quiver(zeros(size(coeff,1),1), zeros(size(coeff,1),1), ... coeff(:,1)*scale, coeff(:,2)*scale, 0, k, LineWidth, 1.5); text(coeff(:,1)*scale*1.1, coeff(:,2)*scale*1.1, ... {出勤率,练习完成度,实验得分,笔试成绩}, FontSize, 12); xlabel(第一主成分); ylabel(第二主成分);代码逻辑说明关键参数是scale。coeff的载荷权重数值范围通常在-1到1之间而score的坐标可能很大两者不在同一尺度上。scale是通过样本坐标最大绝对值除以载荷最大绝对值得到的缩放因子把载荷箭头放大到与样本点可比较的比例。参数说明quiver最后一个参数0表示不自动缩放LineWidth控制箭头粗细。观察双标图的技巧是样本点沿着某个特征箭头方向分布得越远说明该样本在这个特征上取值越极端这个信息在原始数据里要翻很多行才能看出来在双标图上一眼就能识别。说实话当初我第一次看到自己写的PCA脚本跑出的双标图时被它的解释能力震撼到了——不是因为它复杂而是因为它让一个四维数据表变得像一张地图每个学生都成了图上的一个点。也是从那次之后我意识到写PCA代码最大的陷阱不在数学也不在代码在于你没有一套系统的方法去验证和理解输出结果。希望这套注释清晰、带例题数据的方案能让你少走这些弯路。也希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑