☰
MATLAB PCA主成分分析实战:从数据标准化到降维代码详解
2026/10/19 12:50:51 网站建设 项目流程

简介:这份资源面向学习数据分析与MATLAB编程的初学者及进阶者,提供一套完整的PCA主成分分析实践案例。内容以不同浓度混合物的拉曼光谱作为实验数据,演示如何通过正交变换将相关变量转化为线性无关的主成分,帮助读者掌握PCA的数学原理与MATLAB实现流程。压缩包共2个文件,包含1个m脚本文件和1个xlsx数据文件,脚本负责核心算法与可视化,表格文件存放训练集光谱数据,整体约2.26MB,结构简洁、开箱即用。目前已有1957人学习下载,说明该案例在同类教程中具有较好的参考价值。读者可借助现成代码与数据快速复现实验,理解数据降维、特征提取与主成分解释等关键环节,并在此基础上迁移到自己的光谱、图像或统计数据集上,适合作为课程作业、科研入门或自学练手的实操素材。

1. 从一份 MATLAB 数据包说起:PCA 主成分分析到底能帮你解决什么

如果你手头有一组十几个甚至几十个特征的数据,画散点图都画不过来,跑回归又发现变量之间高度相关,那 PCA 主成分分析大概率是你绕不开的一步。这份基于 MATLAB 的 PCA 实例资源,核心就是一套能直接跑通的降维流程:包含原始数据文件和完整的.m脚本,打开就能看到从数据标准化、协方差矩阵计算、特征值分解到主成分得分输出的全过程。它适合两类人:一类是刚接触降维、想拿真实数据练手的学生和初级工程师;另一类是做信号处理、图像压缩、光谱分析,需要快速验证 PCA 效果但不想从零推导公式的开发者。MATLAB 作为开发语言在这里的优势很直接——矩阵运算一行顶十行,eig和svd都是内置的,省去了大量底层实现。下面我就按“数据长什么样、代码怎么跑、每一步在算什么、想改怎么改”的顺序,把这份资源拆开讲清楚。

2. 数据与脚本结构:先看清目录里有什么再动手

2.1 资源包里的文件构成

拿到这份资源,解压后通常能看到三类文件:数据文件、主脚本、辅助函数。数据文件常见的是.mat格式或.csv格式,里面存的是一个二维矩阵,行代表样本,列代表特征。主脚本一般命名为PCA_main.m或类似名字,负责串联整个流程。辅助函数可能包括标准化函数、贡献率计算函数等。我一般会先不急着运行,而是用whos命令看一眼数据变量的维度,确认样本数和特征数,这一步能避免后面因为行列搞反而出现的玄学错误。

% 查看数据文件内容,假设文件名为 data.mat load('data.mat'); % 加载数据,变量名可能是 data 或 X whos % 列出工作区所有变量的名称、大小、类型

load负责把.mat文件读进工作区,whos则告诉你每个变量占多少内存、是几行几列。如果数据是.csv,就用readmatrix或csvread替代。这一步的关键是确认矩阵方向:PCA 默认按列是特征、按行是样本,如果你的数据反了,后面算出来的协方差矩阵维度会对不上。

2.2 主脚本的调用链路

主脚本通常按固定顺序执行:读数据、标准化、算协方差、特征分解、排序、投影、算贡献率、画图。我习惯在每一段后面加一个disp或fprintf输出中间结果的维度,这样一旦哪一步报错,能立刻定位是哪个环节的矩阵尺寸不匹配。比如标准化之后应该还是同样的行列数,协方差矩阵应该是特征数乘特征数,如果这里变成了样本数乘样本数,那基本就是转置搞反了。

% 假设原始数据变量为 X,行为样本,列为特征 X = data; % 重命名,方便后续引用 X_std = zscore(X); % 标准化,消除量纲影响 cov_mat = cov(X_std); % 计算协方差矩阵 [V, D] = eig(cov_mat); % 特征分解,V 是特征向量,D 是对角特征值矩阵

zscore对每一列做零均值单位方差处理,这是 PCA 前最常用的预处理。cov计算协方差矩阵,输入是标准化后的矩阵。eig返回的特征向量矩阵V每一列对应一个特征向量,特征值在对角矩阵D上。注意eig不保证排序,所以后面必须手动按特征值从大到小重排,否则你取的前几个主成分可能对应的是方差最小的方向,那就完全反了。

2.3 数据标准化为什么不能省

很多人拿到数据直接丢进pca函数,结果发现第一主成分的载荷几乎全压在某一个量纲特别大的特征上,其他特征像没参与一样。这不是算法错了,而是量纲在作怪。比如一个特征是电压范围 0 到 1000,另一个是温度范围 0 到 1,协方差矩阵里电压的方差会碾压温度,PCA 自然优先降电压那个方向。标准化之后每个特征方差都是 1,大家站在同一起跑线上,主成分才能反映真实的变量间相关性。当然也有例外:如果所有特征本来就是同一量纲、同一量级,比如都是像素灰度值,那也可以不标准化,直接对原始数据做 PCA。我一般会先标准化跑一遍,再对比不标准化的结果,看载荷分布有没有剧烈变化,以此判断标准化是否必要。

3. 核心代码逐段拆解:从协方差矩阵到主成分得分

3.1 特征值分解与主成分排序

上一节算出了特征向量和特征值,但顺序是乱的。下面这段代码做两件事:把特征值从大到小排,同时把对应的特征向量列按同样顺序重排。排序后的特征值就是每个主成分的方差贡献,特征向量就是投影方向。

% 提取特征值向量并降序排列 eigenvalues = diag(D); % 把对角矩阵的对角线元素取出来变成列向量 [eigenvalues_sorted, idx] = sort(eigenvalues, 'descend'); % 降序排列,idx 是原始索引 V_sorted = V(:, idx); % 按 idx 重排特征向量列 % 计算每个主成分的方差贡献率和累计贡献率 contribution = eigenvalues_sorted / sum(eigenvalues_sorted) * 100; cumulative = cumsum(contribution);

diag(D)把对角矩阵转成列向量,sort的'descend'参数保证从大到小排,第二个返回值idx记录了排序前的位置,用这个索引去重排V的列,才能保证特征值和特征向量一一对应。contribution是每个主成分占总方差的百分比,cumsum算累计值。通常累计贡献率达到 85% 以上,就说明前几个主成分已经保留了大部分信息,后面的可以丢掉。

3.2 选择主成分个数并投影

选几个主成分没有绝对标准,常见做法是看累计贡献率拐点,或者直接设定一个阈值比如 90%。下面代码演示按累计贡献率自动选个数,然后把原始数据投影到选出的主成分上,得到降维后的得分矩阵。

% 按累计贡献率 90% 自动确定主成分个数 threshold = 90; num_pc = find(cumulative >= threshold, 1, 'first'); % 找到第一个达到阈值的索引 fprintf('选择前 %d 个主成分,累计贡献率 %.2f%%\n', num_pc, cumulative(num_pc)); % 取前 num_pc 个特征向量构成投影矩阵 V_selected = V_sorted(:, 1:num_pc); % 投影得到主成分得分 scores = X_std * V_selected;

find(cumulative >= threshold, 1, 'first')返回第一个满足条件的索引,也就是达到 90% 累计贡献所需的最少主成分数。V_selected是特征向量矩阵的前num_pc列,每一列是一个投影方向。X_std * V_selected就是整个 PCA 的核心运算——把标准化后的数据乘上投影矩阵,得到降维后的得分矩阵scores,它的行数不变,列数变成了num_pc。这个scores就可以拿去做后续的聚类、分类或可视化。

3.3 贡献率可视化与载荷分析

光有数字不够直观,通常要画两张图:一张是累计贡献率曲线,看拐点;另一张是前两个主成分的散点图,看样本分布。载荷矩阵则告诉你每个原始特征对主成分的贡献大小,对解释结果很重要。

% 绘制累计贡献率曲线 figure; plot(1:length(cumulative), cumulative, '-o', 'LineWidth', 1.5); xlabel('主成分个数'); ylabel('累计贡献率 (%)'); title('主成分累计贡献率'); grid on; % 绘制前两个主成分的散点图 figure; scatter(scores(:,1), scores(:,2), 30, 'filled'); xlabel('第一主成分'); ylabel('第二主成分'); title('PCA 得分散点图'); grid on; % 输出前两个主成分的载荷 loadings = V_selected(:, 1:2); disp('前两个主成分的载荷:'); disp(loadings);

plot画累计贡献率曲线,横轴是主成分个数,纵轴是累计百分比,拐点位置就是比较合适的截断点。scatter画前两个主成分的得分,如果样本有类别标签,还可以用不同颜色区分,看类别是否自然分开。loadings就是特征向量矩阵的前两列,每一行对应一个原始特征,绝对值越大说明该特征对这个主成分的影响越大。正负号表示方向,正载荷和负载荷的特征在解释主成分含义时要分开说。

4. 避坑与排查:PCA 跑通前最容易翻车的几个地方

4.1 现象:第一主成分贡献率异常高,接近 100%

原因通常有两个:一是数据没标准化,某个量纲特别大的特征主导了全部方差;二是数据本身所有特征高度线性相关,比如存在完全共线性的列。解决办法是先检查每列的标准差,如果差异超过两个数量级,必须标准化。如果标准化后依然如此,用corrcoef看特征间相关系数,接近 1 或 -1 的列考虑删掉一列再跑。

4.2 现象:eig返回复数特征向量

协方差矩阵理论上是对称半正定的,特征值应该全是实数。如果出现复数,多半是因为数据里有 NaN 或 Inf,导致协方差矩阵计算出错。用sum(isnan(X(:)))和sum(isinf(X(:)))检查,把异常值处理掉再跑。另一个可能是用了eig而不是eigs或svd,但标准 PCA 用eig就够了,问题一般出在数据本身。

4.3 现象:投影后得分矩阵和预期维度对不上

最常见的原因是原始数据矩阵方向搞反了。PCA 默认行是样本、列是特征,如果你把样本放在列上,协方差矩阵会变成样本数乘样本数,投影结果完全不对。用size(X)确认维度,如果第一维远小于第二维,很可能需要转置。转置用X = X';即可,但转置后记得重新检查标准化和协方差计算是否一致。

4.4 现象:累计贡献率曲线在某个点突然跳变

这通常说明数据中存在几个方差极大的离群样本,它们把某几个方向撑得特别大。用zscore标准化后如果还有样本的得分超过 3 或小于 -3,就值得警惕。可以先用马氏距离或简单的箱线图筛一遍离群点,剔除后再做 PCA。注意剔除离群点要谨慎,除非有明确业务理由,否则不要随便删样本。

4.5 现象:换一组数据后主成分符号全反了

特征向量的符号本身没有绝对意义,因为特征向量乘 -1 还是特征向量。所以不同次运行或不同数据下,第一主成分的载荷可能整体变号,但绝对值分布应该一致。这不是 bug,解释结果时只看绝对值大小和相对正负关系,不要纠结绝对正负。如果非要统一符号,可以约定每个特征向量中绝对值最大的那个元素为正,手动翻转。

5. 二次开发与改造:把这套 PCA 脚本变成你自己的工具

5.1 封装成函数,支持任意数据集

原始脚本通常是写死的,换数据要改路径和变量名。我一般会把它重构成一个函数,输入是数据矩阵和可选参数,输出是得分、载荷、贡献率和选定的主成分数。这样在多个项目里复用只需要改一行调用。

function [scores, loadings, contribution, num_pc] = my_pca(X, threshold) % my_pca 对输入矩阵 X 做 PCA,返回得分、载荷、贡献率和主成分数 % X: 行是样本,列是特征 % threshold: 累计贡献率阈值,默认 90 if nargin < 2 threshold = 90; end X_std = zscore(X); cov_mat = cov(X_std); [V, D] = eig(cov_mat); eigenvalues = diag(D); [eigenvalues_sorted, idx] = sort(eigenvalues, 'descend'); V_sorted = V(:, idx); contribution = eigenvalues_sorted / sum(eigenvalues_sorted) * 100; cumulative = cumsum(contribution); num_pc = find(cumulative >= threshold, 1, 'first'); V_selected = V_sorted(:, 1:num_pc); scores = X_std * V_selected; loadings = V_selected; end

这个函数把前面所有步骤串起来,输入输出清晰。threshold给了默认值 90,不传也能跑。调用时只需要[scores, loadings, contribution, num_pc] = my_pca(data);一行。注意函数里没有画图,画图属于展示层,应该和计算层分开,这样函数可以在批处理脚本里反复调用而不弹窗。

5.2 替换 eig 为 svd,提升数值稳定性

eig对协方差矩阵做分解在大多数情况下没问题,但当特征值非常接近时,数值误差可能让排序不稳定。更稳的做法是对标准化后的数据矩阵直接做 SVD,奇异值的平方除以样本数就是特征值,右奇异向量就是主成分方向。MATLAB 的svd底层实现更成熟,尤其在高维数据上表现更好。

% 用 SVD 替代 eig 做 PCA [U, S, V] = svd(X_std, 'econ'); eigenvalues = diag(S).^2 / (size(X_std,1) - 1); V_sorted = V; % SVD 已经按奇异值降序排列,无需再排 contribution = eigenvalues / sum(eigenvalues) * 100; cumulative = cumsum(contribution);

svd的'econ'参数表示经济型分解,只返回有效部分,节省内存。diag(S).^2得到特征值,除以样本数减一和协方差矩阵的定义一致。V已经是按奇异值降序排列的,省去了手动排序。如果你的数据特征数上万,SVD 的优势会更明显。

5.3 加入交叉验证,确定最优主成分数

按累计贡献率选主成分数虽然常用,但它是无监督的,不一定对后续分类或回归任务最优。更严谨的做法是结合下游任务做交叉验证:对不同的主成分数,分别训练一个简单分类器,看验证集准确率。下面是一个思路示例。

% 用交叉验证选择对分类任务最优的主成分数 max_pc = size(X_std, 2); cv_acc = zeros(max_pc, 1); for k = 1:max_pc scores_k = X_std * V_sorted(:, 1:k); % 假设有标签 y,用 5 折交叉验证训练 KNN mdl = fitcknn(scores_k, y, 'NumNeighbors', 3); cvmdl = crossval(mdl, 'KFold', 5); cv_acc(k) = 1 - kfoldLoss(cvmdl); end [~, best_k] = max(cv_acc); fprintf('交叉验证最优主成分数:%d,准确率 %.2f%%\n', best_k, cv_acc(best_k)*100);

这段代码对每个可能的主成分数训练 KNN 并做 5 折交叉验证,记录准确率,最后取最高的那个k。fitcknn是 MATLAB 自带的 KNN 分类器,crossval做交叉验证,kfoldLoss返回损失,用 1 减就是准确率。注意这需要你有标签y,如果没有标签就只能用累计贡献率。我一般会把这个循环跑一遍,看看交叉验证曲线和累计贡献率曲线的拐点是否一致,如果差很多,以交叉验证为准。

5.4 一个我踩过的坑

早期我图省事,把标准化和 PCA 写在一个脚本里,数据一换就手动改路径和变量名,结果有一次改漏了一处,标准化用了旧数据的均值和方差,投影出来的得分完全错位,排查了大半天才发现是变量覆盖。从那以后我每次做 PCA 都强制走一遍函数封装,输入输出明确,中间不产生全局变量。另外,特征向量符号翻转的问题也让我在解释载荷时翻过车,后来养成了只看绝对值、不纠结正负的习惯。希望这些经验能帮到你,少走点弯路。

本文还有配套的精品资源,点击获取

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

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

立即咨询