☰
高光谱分类实战:PaviaU 上 SVM、CNN、KNN 三条路线怎么选
2026/10/22 21:10:45 网站建设 项目流程

简介:本资源面向遥感图像处理与机器学习初学者及研究人员,提供在Matlab环境下对高光谱数据集PaviaU进行分类的完整实践方案,重点对比支持向量机、卷积神经网络与K近邻三种算法的分类效果。压缩包共35个文件,约35.42MB,以27个m脚本文件为主,辅以7个mat数据文件和1个txt说明文档,涵盖数据加载、PCA与LDA降维、模型训练测试及精度评估等环节。已有758人学习下载,适合希望系统掌握高光谱分类流程的读者。通过运行这些脚本,可直观比较SVM、CNN与KNN在PaviaU数据上的表现,理解核函数选择、卷积特征提取与距离度量对分类精度的影响,并借助降维技术缓解高维计算负担,为遥感、环境监测等场景下的地物分类任务提供可复用的代码框架与实验参考。

1. 高光谱分类选 SVM、CNN 还是 KNN:PaviaU 上的三条路线怎么选

高光谱图像分类这件事,很多人第一次上手就是拿 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 或 uint16;paviaU_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 到 CNN

3.1 KNN:最快能跑通,但参数最玄学

KNN 的优势是零训练成本,适合做基线。MATLAB 里用fitcknn或者knnclassify都行,我一般用fitcknn,因为可以显式控制距离度量和 k 值。PaviaU 上 KNN 的精度通常在 70% 到 85% 之间浮动,k 值取 3 到 7 比较稳,距离用欧氏距离。但 KNN 在高维空间里会遭遇维度灾难,所以前面做 PCA 是必要的。

% KNN 分类,k=5,欧氏距离 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% 以下。选哪条路线,取决于你手头有多少标注样本、能等多久、以及下游任务对小类别精度有多敏感。希望帮到你。

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

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

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

立即咨询