尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

高光谱分类Matlab实战:PLS降维与SVM/BP对比分析

高光谱分类Matlab实战:PLS降维与SVM/BP对比分析 简介面向高光谱遥感图像分析与分类任务的算法程序包聚焦偏最小二乘法PLS与支持向量机SVM两种主流方法适合有一定机器学习基础的研究人员或学生用于解决高光谱数据维度高、样本标记有限时的降维与地物分类问题。压缩包共22个文件包括14个MAT数据文件、6个M脚本和2个ASV自动保存文件大小约100KBM脚本覆盖PLS降维、SVM分类及自定义核函数等关键流程MAT文件提供训练集、测试集及标签数据程序体现了先PLS压缩维度、再用SVM分类的典型思路便于对比不同方法的精度与效率。目前已有213人学习下载可应用于环境监测、资源勘查、农业评估等场景也可用于教学演示或算法验证。通过阅读和运行代码能够掌握PLS主成分提取与SVM最大间隔分类的实现细节理解PLS在多重共线性场景与SVM在非线性场景下的优势为后续改进或工程落地提供可复用参考。1. 高光谱分类程序的三大件PLS、SVM、BP高光谱图像分类最大的痛点不是模型不够高级而是波段数太多、样本量太少直接丢给分类器经常得到完美的训练集准确率和糟糕的测试集结果。这个压缩包给了三条不同思路的 Matlab 实现偏最小二乘法PLS、支持向量机SVM、BP 神经网络。其中 PLS 做特征提取和降维SVM 做小样本高维分类BP 则展示了神经网络在光谱数据上的表现。整套代码能跑通的好处是你不用自己重新搭框架只要把.mat数据替换进去就能在 Matlab 里对比三种分类器的精度。适合做遥感课程设计、农业和环境遥感应用的同学也适合刚接触高光谱的工程师用来做基准测试。从文件列表看pls、svm、bp三个目录分别对应三套完整的训练和预测流程数据文件也都给了基本能做到开箱即用。2. 从 .mat 加载到标准化高光谱数据进分类器前的准备工作2.1 先弄清楚 dataTrain.mat、dataTest.mat 和标签文件的维度拿到压缩包解压后最常见的错误是直接运行main_PLS.m然后报错“Undefined function or variable”。正常流程是先load数据观察工作区里有哪些变量、每个变量什么形状。一般高光谱数据集的排列是“样本数 × 波段数”比如一景高光谱影像取若干个感兴趣区域ROI每个像素作为一行像素的每个波段作为一列。dataTrain.mat和dataTest.mat通常就是这个二维矩阵。label.mat或Y.mat存储类别标签一般是列向量值从 1 到类别数。常见变量结构如下表变量名常见维度含义dataTrain样本数 × 波段数训练集光谱反射率或辐射亮度dataTest样本数 × 波段数测试集光谱维度必须与训练集一致label样本数 × 1训练集样本的真实类别标签Y样本数 × 类别数经过独热编码后的标签矩阵项目正文里还有一堆a10.mat、d30.mat、d15.mat之类的文件看起来是按类别或区块拆出来的中间数据建议用whos(-file d10.mat)查看不必急着一股脑加载。很多时候这些小文件里存的是单个地物类别的光谱子集用来重新划分训练测试集用的。2.2 用 size、whos 核对数据写一个预处理脚本我一般会先在主程序里加一段检查代码避免后续维数不对%% 加载并检查数据维度 load(dataTrain.mat); load(dataTest.mat); load(label.mat); fprintf(dataTrain: %d samples x %d bands\n, size(dataTrain,1), size(dataTrain,2)); fprintf(dataTest: %d samples x %d bands\n, size(dataTest,1), size(dataTest,2)); fprintf(label: %d x %d\n, size(label,1), size(label,2)); % 如果标签是 one-hot转成列向量 if size(label,2) 1 [~, label] max(label, [], 2); end逻辑说明size(dataTrain,1)是样本数size(dataTrain,2)是波段数。高光谱数据波段数通常几百样本数可能是几千这个对比能直观看出是否面临“维数灾难”。如果label是独热编码直接用max转换成类别序号如果已经是列向量跳过这一步。这里的fprintf输出能让你第一时间发现数据文件是否加载正确比如明明有 5 类地物但label是 4×1那大概率是文件选错了。2.3 标准化必须放在分类器之前且只从训练集统计高光谱不同波段的反射率数值范围差异不大但经过大气校正后可能有异常值。SVM 和 BP 对输入特征尺度敏感尤其是 RBF 核依赖距离计算不标准化的结果就是低波段主导权过重。常见做法是 Z-score 标准化%% Z-score 标准化用训练集的均值和标准差 mu mean(dataTrain); sigma std(dataTrain); dataTrainNorm (dataTrain - mu) ./ sigma; dataTestNorm (dataTest - mu) ./ sigma;这段代码的关键是mu和sigma必须只从训练集计算然后把同样的参数应用在测试集上。很多新手用zscore(dataTest)单独标准化导致测试集分布被重新居中精度评估失去意义。参数说明./是逐元素除法因为mu是 1×波段数会自动广播到每个样本。如果数据方差为 0比如某个波段全是常数sigma会出现 0需要加eps避免NaN。另外标准化操作应当直接在原始光谱矩阵上做如果你打算先用 PLS 降维也可以先标准化再降维两种顺序对最终精度影响不大但绝对不能在标准化之后再做样本划分。3. 偏最小二乘法降维ncomp 的选取与 plsregress 的实际行为3.1 PLS 为什么能处理高光谱的多重共线性高光谱波段分辨率高相邻波段高度相关普通主成分分析PCA只做无监督投影不关心分类目标。偏最小二乘法PLS则同时考虑光谱矩阵 X 和标签向量 y寻找一个低维空间使 X 与 y 的协方差最大化。这个直觉比 PCA 更贴近分类任务降维后的每个成分不只是方差最大的方向而是对标签最有区分力的方向。在高光谱分类里PLS 能压缩出几十个成分替代原始几百个波段后续无论接 SVM 还是 BP计算量都会大幅下降。3.2 plsregress 主程序中的代码走读Matlab 自带的plsregress函数可以直接用。假设我们想把原始光谱从 200 维降到ncomp维%% PLS 降维取前 ncomp 个成分 ncomp 15; [Xloadings, Yloadings, Xscores, Yscores, beta] ... plsregress(dataTrainNorm, label, ncomp); % 降维后的特征矩阵 XTrainPls Xscores; XTestPls (dataTestNorm - mean(dataTrainNorm)) * Xloadings;逻辑说明plsregress返回的Xscores是训练样本在 PLS 成分上的得分也就是降维特征。测试集不能用plsregress重新跑必须用训练集得到的Xloadings投影过去先中心化再右乘载荷矩阵。这里有一个细节plsregress内部默认对 X 和 Y 都做了均值中心化所以Xscores已经是减去训练集均值后的结果投影测试集时也要先减训练集均值否则两个数据集的特征分布没有对齐。参数说明ncomp是 PLS 成分数它决定降维后特征数量取值范围必须小于等于训练样本数和波段数。Xloadings是波段数 × ncomp 的载荷矩阵可以把它理解为前 ncomp 个投影方向的权重Yloadings是标签载荷beta是回归系数矩阵如果后续只做分类特征提取beta可以忽略。3.3 交叉验证选 ncomp而不是拍脑袋ncomp太小会丢失信息太大又退化回原始维度。常见做法是 K 折交叉验证对每个候选值计算均方根误差RMSE或分类交叉验证误差。如果后续接 SVM更推荐直接以分类精度作为选择标准而不是回归的 RMSE。%% 交叉验证选择 PLS 成分数以分类精度为准 cvFolds 10; candNcomp 1:5:50; accList zeros(size(candNcomp)); for k 1:length(candNcomp) n candNcomp(k); % 5 折交叉验证 cvp cvpartition(label, KFold, cvFolds); accFold zeros(cvp.NumTestSets, 1); for i 1:cvp.NumTestSets trainIdx cvp.training(i); testIdx cvp.test(i); [~, ~, Xs, ~] plsregress(dataTrainNorm(trainIdx,:), label(trainIdx), n); % 用 PLS 投影得到测试折特征 XTestFold (dataTrainNorm(testIdx,:) - mean(dataTrainNorm(trainIdx,:))) * Xloadings; % 这里用简单分类器如线性判别做快速评估 accFold(i) classifyUsingPls(dataTrainNorm, label, trainIdx, testIdx, n); end accList(k) mean(accFold); end [~, bestIdx] max(accList); bestNcomp candNcomp(bestIdx);实际工程中直接对每个ncomp调用一次完整 SVM 网格搜索会很慢所以先用线性分类器或决策树粗选范围再在最优值附近精调。参数说明cvpartition能保证每折数据类别比例与总体一致避免某一折没有某个类别。代码里的classifyUsingPls需要自己封装核心就是内部再跑一次plsregress和训练/预测。3.4 降维后给 SVM 和 BP 用PLS 降维后的XTrainPls和XTestPls就是后续分类器的输入。注意这里有个容易踩的坑如果后续接 SVMplsregress里的标签只能用数值向量不能直接塞独热编码矩阵。很多代码报错 “Y must be a vector” 就是这个原因。同样plsregress对多分类标签的处理方式是按数值大小有序的如果你的类别标签是 0、1、2使用起来没问题但如果标签是随机 ID 如 10、20、30建议在进 PLS 前先grp2idx重映射成连续整数。4. 支持向量机分类自定义核函数与 C、gamma 网格搜索4.1 SVM 在高光谱小样本下的优势高光谱分类任务中有标签样本往往很少SVM 基于结构风险最小化目标是找到一个超平面使不同类别的间隔最大化。这个特性让它对高维小样本数据非常友好即使波段数上千只要间隔够大模型的泛化能力依然可控。对于非线性可分的地物类别需要引入核函数把原始空间映射到高维特征空间。高光谱中植被、土壤、水体之间的界限通常不是线性的所以 RBF 核几乎是默认选项。4.2 自定义核函数的形式.m 在做什么压缩包里有个文件叫自定义核函数的形式.m说明作者给了手动写核函数的示例。最常见的 RBF 高斯核function K myRBF(U, V, sigma) % U: 训练集得分矩阵, V: 测试集得分矩阵 % sigma: 高斯核宽度 K exp(-pdist2(U, V, euclidean).^2 / (2 * sigma^2)); end这段代码的计算逻辑pdist2计算 U 中每行与 V 中每行的欧氏距离得到矩阵平方后除以2*sigma^2再取指数。RBF 核中sigma越小核函数越“尖”模型越容易过拟合sigma越大核函数越平缓边界越平滑。如果自定义核函数里忘了做平方或者忘了取负SVM 的 Gram 矩阵就不是半正定的训练通常会失败。如果用fitcsvm不需要自定义核函数直接指定KernelFunction,rbf即可。多分类任务需要叠加fitcecoc一对多或一对一策略%% 多分类 SVM先降维再用 fitcecoc template templateSVM(KernelFunction, rbf, ... KernelScale, 1.2, BoxConstraint, 10, Standardize, false); model fitcecoc(XTrainPls, label, ... Learners, template, Coding, onevsone);逻辑说明templateSVM定义了基础二分类器fitcecoc使用错误纠正输出编码把二分类器组合成多分类器。参数说明KernelScale是 Matlab 里的 gamma 的另一种表示实际核函数是exp(-||x-y||^2 / KernelScale^2)所以KernelScale越大表示核越宽BoxConstraint对应惩罚因子 C控制误分类的容忍度。Coding设为onevsone在高光谱类别多时训练速度比onevsall更稳定。4.3 C 和 gamma 的网格搜索SVM 调参最常用的方法是对数网格搜索。先用较粗的网格定范围再在最优值附近细化%% 网格搜索 C 和 gamma CList 10.^(-2:2); GList 10.^(-3:1); bestAcc 0; bestC CList(1); bestG GList(1); for i 1:length(CList) for j 1:length(GList) template templateSVM(KernelFunction, rbf, ... KernelScale, 1/sqrt(2*GList(j)), ... BoxConstraint, CList(i)); model fitcecoc(XTrainPls, label, Learners, template, KFold, 5); acc 1 - kfoldLoss(model, LossFun, classiferror); if acc bestAcc bestAcc acc; bestC CList(i); bestG GList(j); end end end fprintf(best C%.2f, gamma%.2f, acc%.4f\n, bestC, bestG, bestAcc);注意这里KernelScale和gamma的关系在 Matlab 里gamma 1 / (KernelScale^2)所以代码里用1/sqrt(2*GList(j))还原出 KernelScale等价于自定义核里的sigma1/sqrt(2*gamma)。网格搜索的运行时间随着样本量和类别数增长很快建议先用 PLS 降到 10 维内再搜参数组合不超过 5×5 是比较稳妥的。4.4 SVM 预测与分类评估训练完成后用predict对测试集分类再计算混淆矩阵predLabel predict(model, XTestPls); C confusionmat(testLabel, predLabel); % 总体精度 OA sum(diag(C)) / sum(C(:)); % 平均精度 AA mean(diag(C) ./ sum(C, 2)); fprintf(OA%.2f%%, AA%.2f%%\n, OA*100, AA*100);参数说明confusionmat的第一个参数是真实标签第二个是预测标签标签编码必须一致。OA是分类正确的样本占总样本的比例AA是每个类别精度的平均值。高光谱论文里通常还会算 Kappa 系数Kappa 衡量的是剔除随机一致后的一致性比 OA 更稳健。Matlab 没有内置 Kappa 函数一般用kappa (OA - pe) / (1 - pe)其中pe根据混淆矩阵各行列和计算。5. BP 神经网络分类网络结构设计与分类精度评估5.1 BP 网络为什么也能用在高光谱上BP 神经网络通过反向传播调整权重理论上可以拟合任意非线性函数。高光谱分类中它的优势是不需要手工设计特征输入层可以直接吃原始光谱或 PLS 降维后的特征劣势是小样本下容易过拟合且训练时间远高于 SVM。BP 对数据量和数据分布的要求比 SVM 更苛刻所以在高光谱场景里它经常作为对比模型出现而不是默认首选。5.2 Bp.m 中的网络结构与训练参数Matlab 老版本用newff创建前馈网络新版本推荐feedforwardnet。打开Bp.m大概率能看到类似结构%% BP 神经网络训练 net feedforwardnet([15 8]); % 两个隐层各自 15、8 个神经元 net.layers{1}.transferFcn tansig; net.layers{2}.transferFcn tansig; net.trainFcn trainlm; % Levenberg-Marquardt net.trainParam.epochs 1000; net.trainParam.goal 0.001; net.trainParam.min_grad 1e-5; % 输入需要转置行特征列样本 net train(net, XTrainPls, labelVector); predProb net(XTestPls); [~, predLabelBp] max(predProb, [], 1); predLabelBp predLabelBp;逻辑说明feedforwardnet([15 8])创建了两个隐藏层隐藏层节点数直接影响模型复杂度。tansig是双曲正切激活函数输出层因为是分类问题默认用 softmax 或者线性加max取最大类别。trainlm是 Levenberg-Marquardt 算法收敛快适合中小规模数据集如果内存不足可以换成trainbr或trainscg。labelVector需要和训练样本数相同的行向量且类别从 1 开始连续编码。关键点train的输入参数要求“特征 × 样本”所以转置了XTrainPls。如果直接传原矩阵Matlab 会提示维度错误这是最常见的运行报错。预测后得到predProb是类别概率矩阵每列是样本max取最大值所在行作为预测类别。5.3 三种分类器的精度对比与评估指标分类评估不能只看总体精度还要关注每个类别的表现。这里可以做一个汇总表分类器输入特征主要参数特点PLS原始光谱ncomp降维保留与标签相关的信息SVMPLS 降维特征C、gamma、核函数小样本高维表现好BPPLS 降维特征或原始光谱隐层节点、学习率、epoch非线性强容易过拟合把三个分类器的预测结果放进同一个对比脚本里输出 OA、AA 和每类精度就能直接比较。注意 BP 网络对随机初始权重敏感建议训练 5 次取平均精度或者固定rng(0)保证可复现。另外BP 的训练集精度往往接近 100%如果测试集精度和训练集差距过大说明过拟合了需要减少隐层节点或增加训练样本。6. 进阶技巧联合调优与三个高光谱分类的常见坑6.1 PLS 降维与 SVM 参数联合调优单独做网格搜索的错误在于先固定 ncomp 再调 C、gamma但 ncomp 本身影响着 SVM 能够使用的判别信息。我一般的做法是把 PLS 的 ncomp 也放进网格搜索的外层循环形成一个三维搜索空间for ncompTest 5:5:30 [~, ~, Xscores, ~] plsregress(dataTrainNorm, label, ncompTest); for C CList for gamma GList % 交叉验证评估 end end end受计算时间限制ncomp 步长可以设为 5。另一种更高效的方案是用贝叶斯优化函数bayesopt来搜索连续参数但离散的 ncomp 建议还是用网格。联合调优最大的收益是当 ncomp 从 5 增加到 20 时SVM 的最佳 gamma 往往需要相应减小因为特征维度越高特征空间中的距离越稀疏核宽度需要调整以匹配这种稀疏性。6.2 三个最常见的坑第一个坑是数据泄漏。有的代码先把所有数据标准化再划分训练测试集实际上测试集的信息已经参与了训练导致精度虚高。正确顺序是划分样本 → 在训练集上计算均值和标准差 → 用同一组参数标准化测试集。前文第 2 章已经专门强调了这一点。第二个坑是 PLS 投影维度不匹配。训练时用Xscores预测时却直接用plsregress(dataTestNorm, ...)重新求得分导致特征分布错位。正确做法是固定Xloadings测试集只做中心化和投影。如果ncomp改变了Xloadings的列数也会变注意三处保持一致。第三个坑是标签编码不一致。高光谱分类任务里有的样本标签从 0 开始有的从 1 开始SVM 和 BP 使用的标签编号必须连续且有相同的语义。用unique(label)检查一下类别数确保混淆矩阵的行列对应正确。特别是 BP 的输出层节点个数必须等于类别数如果标签不连续训练误差会出现奇怪的平坦曲线。6.3 验证结果靠 Kappa 和逐类精度的稳定性最后一个小技巧即使 OA 高于 95%如果某一个农作物的类别召回率只有 60%这个模型在生产环境里可能不可用。建议输出每类精度并打印混淆矩阵热力图。用imagesc(C)加colorbar快速观察哪些类别互相混淆比如植被和裸土往往因为光谱相似而混淆。这个检查步骤能帮你决定是否需要采集更多对应类别的样本或者是否该对光谱做平滑预处理。本文还有配套的精品资源点击获取
返回列表