高光谱分类实战:PaviaU 上 SVM、CNN、KNN 三条路线怎么选
简介本资源面向遥感图像处理与机器学习初学者及研究人员提供在Matlab环境下对高光谱数据集PaviaU进行分类的完整实践方案重点对比支持向量机、卷积神经网络与K近邻三种算法的分类效果。压缩包共35个文件约35.42MB以27个m脚本文件为主辅以7个mat数据文件和1个txt说明文档涵盖数据加载、PCA与LDA降维、模型训练测试及精度评估等环节。已有758人学习下载适合希望系统掌握高光谱分类流程的读者。通过运行这些脚本可直观比较SVM、CNN与KNN在PaviaU数据上的表现理解核函数选择、卷积特征提取与距离度量对分类精度的影响并借助降维技术缓解高维计算负担为遥感、环境监测等场景下的地物分类任务提供可复用的代码框架与实验参考。1. 高光谱分类选 SVM、CNN 还是 KNNPaviaU 上的三条路线怎么选高光谱图像分类这件事很多人第一次上手就是拿 PaviaU 数据集跑一遍。它场景简单、类别清晰、标注干净特别适合把 SVM、CNN、KNN 三条路线都走一遍看清楚它们各自吃什么样的特征、在什么样本量下会崩。PaviaU 是 610×340 像素、103 个波段、9 类地物的机载高光谱数据空间分辨率 1.3 米去掉噪声波段后通常用 103 个波段做输入。这个数据集最典型的坑是样本极度不均衡部分类别只有几百个标注像素而多的类别有上万像素。所以同一套分类器换一种采样方式OA 能差十几个点。这篇文章面向的是已经拿到数据、想用 MATLAB 把三条路线都跑通、并且想知道每条路线边界在哪的从业者。下面从数据读取、特征构造、三个分类器的实现、参数怎么调、坑在哪一路讲到我个人推荐的验证习惯。2. 数据准备与特征构造从 .mat 到可训练矩阵2.1 PaviaU 的数据结构和你需要先确认的三件事PaviaU 的原始文件通常包含两个变量paviaU是三维数据立方体尺寸 610×340×103数据类型多为 double 或 uint16paviaU_gt是二维标签图尺寸 610×340背景为 0地物类别 1 到 9。拿到数据后先做三件事确认波段数是否已经去掉噪声波段、确认标签类别编号是否连续、确认数据是否已经归一化。这三件事任何一件没做后面分类精度都会莫名其妙地低。常见做法是先做可视化检查把第 1、50、100 波段分别拉出来看确认没有大面积坏线。如果某个波段整列都是零或饱和值直接剔除。PaviaU 官方版本一般已经做过辐射校正但不同来源的副本可能被二次处理过所以这一步不能省。% 读取 PaviaU 数据假设文件名为 PaviaU.mat load(PaviaU.mat); % 得到 paviaU load(PaviaU_gt.mat); % 得到 paviaU_gt % 确认尺寸 [rows, cols, bands] size(paviaU); fprintf(数据尺寸: %d x %d x %d\n, rows, cols, bands); % 确认标签类别 labels unique(paviaU_gt); fprintf(标签值: ); fprintf(%d , labels); fprintf(\n); % 检查每个类别的样本数 for i 1:length(labels) if labels(i) 0 continue; end cnt sum(paviaU_gt(:) labels(i)); fprintf(类别 %d: %d 个像素\n, labels(i), cnt); end这段代码的逻辑很直接先确认数据维度再确认标签分布。参数上唯一需要注意的是load之后的变量名不同来源的 .mat 文件变量名可能不一样如果报错就先用whos看一下文件里到底存了什么。类别样本数打印出来之后你会看到类似类别 1 有 6631 个像素、类别 7 只有 1330 个像素这种分布后面采样策略就要围绕这个不均衡来设计。2.2 把三维立方体展成二维矩阵reshape 的顺序陷阱SVM 和 KNN 在 MATLAB 里吃的是二维矩阵每行一个样本、每列一个特征。所以要把 610×340×103 的立方体展成 (610×340)×103 的矩阵。这里有一个非常经典的翻车点reshape的顺序。MATLAB 是列优先如果你直接reshape(paviaU, [], 103)得到的是按列排列的样本而标签图paviaU_gt(:)也是按列展开两者顺序一致这是对的。但如果你先转置了数据再 reshape顺序就对不上标签和特征错位训练出来的模型精度会低到离谱而且你很难发现。% 将三维立方体展成二维矩阵列优先顺序 X reshape(paviaU, rows * cols, bands); Y reshape(paviaU_gt, rows * cols, 1); % 去掉背景像素标签为 0 idx Y 0; X X(idx, :); Y Y(idx); % 归一化到 [0,1]按列做 min-max X double(X); X (X - min(X(:))) / (max(X(:)) - min(X(:))); fprintf(有效样本数: %d, 特征维度: %d\n, size(X,1), size(X,2));逻辑说明reshape之后 X 的第 i 行对应 Y 的第 i 个元素这个对应关系必须保持。去掉背景像素是因为背景不属于任何地物类别留着会干扰训练。归一化用全局 min-max 而不是按列是因为高光谱不同波段量纲差异大全局归一化能保留波段间的相对关系。参数上如果你用的是 uint16 数据先转 double 再归一化否则 MATLAB 会做整数除法结果全是 0 和 1。2.3 降维不是必选项但 PCA 能帮你省一半时间103 个波段直接扔给 KNN计算量会大到让你怀疑人生。常见做法是先做 PCA 降到 20 到 30 维保留 95% 以上方差。PCA 在 MATLAB 里一行就能跑但要注意PCA 必须只在训练集上拟合然后应用到测试集否则就是数据泄露。很多人图省事在全量数据上做 PCA精度虚高换一批数据就崩。% 只在训练集上拟合 PCA % 假设 trainIdx 是训练集索引testIdx 是测试集索引 [coeff, score, latent] pca(X(trainIdx, :)); % 保留 95% 方差的成分数 cumVar cumsum(latent) / sum(latent); nComp find(cumVar 0.95, 1); % 变换训练集和测试集 X_train_pca X(trainIdx, :) * coeff(:, 1:nComp); X_test_pca X(testIdx, :) * coeff(:, 1:nComp);参数说明nComp是保留的主成分数PaviaU 上通常落在 15 到 25 之间。coeff是特征向量矩阵训练集和测试集必须用同一个coeff。如果你发现 PCA 之后精度反而降了大概率是nComp太小把判别性强的波段扔掉了可以手动设成 30 再试。3. 三个分类器的 MATLAB 实现从 KNN 到 CNN3.1 KNN最快能跑通但参数最玄学KNN 的优势是零训练成本适合做基线。MATLAB 里用fitcknn或者knnclassify都行我一般用fitcknn因为可以显式控制距离度量和 k 值。PaviaU 上 KNN 的精度通常在 70% 到 85% 之间浮动k 值取 3 到 7 比较稳距离用欧氏距离。但 KNN 在高维空间里会遭遇维度灾难所以前面做 PCA 是必要的。% KNN 分类k5欧氏距离 knnModel fitcknn(X_train_pca, Y(trainIdx), ... NumNeighbors, 5, ... Distance, euclidean, ... DistanceWeight, equal); % 预测 Y_pred_knn predict(knnModel, X_test_pca); % 计算精度 acc_knn sum(Y_pred_knn Y(testIdx)) / length(testIdx); fprintf(KNN 精度: %.2f%%\n, acc_knn * 100);逻辑说明fitcknn的NumNeighbors就是 k 值DistanceWeight设成equal是标准 KNN设成inverse会按距离加权通常能提升一两个点。参数上k 值太小对噪声敏感太大则边界模糊。我一般会跑一个 k 从 1 到 15 的循环看精度曲线拐点在哪选拐点附近的 k。注意 KNN 不需要训练但预测时要计算测试样本和所有训练样本的距离样本量大时非常慢PCA 降维在这里是刚需。3.2 SVM小样本下的首选核函数和惩罚系数是命门SVM 在高光谱分类里长期是强基线尤其适合 PaviaU 这种标注样本有限的场景。MATLAB 里用fitcecoc做多分类底层是 SVM或者用fitcsvm做二分类再自己组合。我一般直接用fitcecoc省事。核函数选 RBF惩罚系数 C 和核尺度 gamma 用网格搜索找。PaviaU 上 SVM 的 OA 通常能到 88% 到 94%比 KNN 稳。% SVM 多分类RBF 核网格搜索 C 和 gamma C_list [0.1, 1, 10, 100]; gamma_list [0.01, 0.1, 1, 10]; bestAcc 0; bestC 1; bestGamma 0.1; for c C_list for g gamma_list t templateSVM(KernelFunction, rbf, ... BoxConstraint, c, KernelScale, 1/sqrt(2*g)); model fitcecoc(X_train_pca, Y(trainIdx), Learners, t); pred predict(model, X_test_pca); acc sum(pred Y(testIdx)) / length(testIdx); if acc bestAcc bestAcc acc; bestC c; bestGamma g; end end end fprintf(SVM 最优 C%.2f, gamma%.2f, 精度%.2f%%\n, bestC, bestGamma, bestAcc*100);逻辑说明templateSVM里BoxConstraint就是 C控制间隔和误分类的权衡KernelScale和 gamma 的关系是KernelScale 1/sqrt(2*gamma)MATLAB 的接口用KernelScale而不是直接给 gamma这里容易搞混。网格搜索虽然慢但 PaviaU 样本量不大跑一遍几分钟能出结果。参数上C 越大越容易过拟合gamma 越大决策边界越复杂。如果精度在某个组合突然掉到 60% 以下大概率是 gamma 太大导致过拟合或者 C 太小导致欠拟合。3.3 CNN需要重构数据维度小样本下要加正则CNN 在高光谱分类里通常用 1D 卷积处理光谱维或者 3D 卷积同时处理空间和光谱。PaviaU 上如果只做光谱分类1D-CNN 就够输入是 103×1 的光谱向量。MATLAB 的 Deep Learning Toolbox 里用sequenceInputLayer或者imageInputLayer都行。但 CNN 参数量大PaviaU 标注样本少必须加 dropout 和 L2 正则否则训练集精度 99%、测试集 70% 是常态。% 1D-CNN 用于光谱分类 layers [ sequenceInputLayer(bands) convolution1dLayer(7, 32, Padding, same) batchNormalizationLayer reluLayer maxPooling1dLayer(2, Stride, 2) convolution1dLayer(5, 64, Padding, same) batchNormalizationLayer reluLayer globalAveragePooling1dLayer dropoutLayer(0.5) fullyConnectedLayer(9) softmaxLayer classificationLayer]; options trainingOptions(adam, ... MaxEpochs, 50, ... MiniBatchSize, 64, ... InitialLearnRate, 1e-3, ... L2Regularization, 1e-4, ... Shuffle, every-epoch, ... Verbose, false); % 注意CNN 输入需要是 cell 数组每个样本一个序列 X_train_cnn num2cell(X(trainIdx, :), 1); Y_train_cnn categorical(Y(trainIdx)); net trainNetwork(X_train_cnn, Y_train_cnn, layers, options); % 预测 X_test_cnn num2cell(X(testIdx, :), 1); Y_pred_cnn classify(net, X_test_cnn); acc_cnn sum(Y_pred_cnn categorical(Y(testIdx))) / length(testIdx); fprintf(CNN 精度: %.2f%%\n, acc_cnn * 100);逻辑说明sequenceInputLayer(bands)表示输入是 103 维光谱序列。两个卷积层提取局部光谱特征globalAveragePooling1dLayer替代全连接降维减少参数量。dropoutLayer(0.5)是关键PaviaU 小样本下不加 dropout 几乎必过拟合。L2Regularization设 1e-4 是常用起点。参数上MiniBatchSize设 64 比较稳太小训练震荡太大内存吃紧。MaxEpochs设 50 通常够如果验证精度还在涨可以加到 100。注意 CNN 输入格式是 cell 数组每个 cell 是一个bands×1的序列这个转换容易写错num2cell(X, 1)是正确写法。4. 避坑与排查PaviaU 分类里最容易翻车的五件事4.1 精度虚高训练集和测试集混在一起做 PCA 或归一化现象第一次跑出 98% 的 OA换一组随机种子掉到 80%。原因在全量数据上做 PCA 或归一化测试集信息泄露到训练过程。解决所有预处理归一化、PCA、特征选择只在训练集上拟合然后 transform 测试集。归一化的 min/max 也要用训练集的不能全量算。4.2 类别不均衡导致小类别全错现象OA 看着有 90%但类别 7 和类别 9 的精度只有 40%。原因训练集里大类别样本多分类器偏向多数类。解决要么对训练集做分层采样每个类别取相同数量要么在损失函数里加类别权重。MATLAB 的fitcecoc可以用Prior, uniform让先验均匀CNN 可以在classificationLayer里设ClassWeights。4.3 reshape 顺序错位标签和特征对不上现象训练 loss 不下降精度在随机水平附近。原因reshape时数据立方体和标签图的展开顺序不一致。解决统一用列优先X reshape(paviaU, [], bands)和Y reshape(paviaU_gt, [], 1)配对使用。如果数据来源做过转置先permute回标准方向再 reshape。4.4 CNN 输入维度搞错报错或精度异常现象trainNetwork报维度不匹配或者训练完精度只有 30%。原因sequenceInputLayer期望输入是bands×1的序列但传进去的是1×bands的行向量。解决用num2cell(X, 1)转成列向量 cell 数组。另外注意classify的输出是 categorical和数值标签比较时要先转categorical。4.5 KNN 在高维下计算爆炸跑一晚上没结果现象KNN 预测阶段卡死内存爆满。原因103 维、几万样本距离矩阵是 N×M 的内存 O(NM)。解决先 PCA 降到 20 到 30 维再用fitcknn的NSMethod, kdtree加速。如果样本量超过五万考虑用ExhaustiveSearcher或者分批预测。5. 进阶技巧用混淆矩阵和交叉验证把结论钉死跑完三个分类器拿到三个 OA 数字这只是开始。真正能说明问题的是混淆矩阵和交叉验证。OA 会被大类别的表现拉高混淆矩阵能看出哪个类别被系统性错分。比如 PaviaU 上类别 4 和类别 7 经常互相混淆因为光谱曲线接近。这时候你就要回去看是不是波段选择有问题或者要不要加空间特征。% 混淆矩阵 cm confusionmat(Y(testIdx), double(Y_pred_svm)); figure; confusionchart(cm); title(SVM 混淆矩阵); % 五折交叉验证用 SVM 做例子 cvModel fitcecoc(X, Y, Learners, t, KFold, 5); cvAcc kfoldLoss(cvModel, LossFun, ClassifError); fprintf(五折交叉验证误差: %.2f%%\n, cvAcc * 100);逻辑说明confusionmat的输入是真实标签和预测标签输出是 9×9 矩阵对角线是正确分类非对角线是错分。confusionchart直接可视化一眼能看出哪两类在互相混。交叉验证用KFold参数kfoldLoss返回平均分类误差。参数上折数取 5 或 10 都行PaviaU 样本量下 5 折足够。注意交叉验证要在全量数据上做但内部的预处理仍然要在每折的训练集上独立拟合fitcecoc的KFold会自动处理但如果你自己写循环别偷懒。我个人的习惯是任何一次分类实验OA、AA平均精度、Kappa 系数三个指标一起看。OA 高但 AA 低说明小类别被牺牲了Kappa 低于 0.8说明分类结果和随机猜测的区分度不够。PaviaU 上 SVM 加 PCA 通常能到 OA 92%、AA 90%、Kappa 0.91 左右CNN 调好了能到 94% 但训练时间翻十倍KNN 最快但 OA 通常卡在 85% 以下。选哪条路线取决于你手头有多少标注样本、能等多久、以及下游任务对小类别精度有多敏感。希望帮到你。本文还有配套的精品资源点击获取