
1. 项目概述与核心思路拆解1.1 高光谱图像分类到底在做什么高光谱图像分类简单说就是给遥感影像里的每一个像元打标签。普通照片只有R、G、B三个通道而高光谱图像往往有几十上百个波段覆盖从可见光到近红外的连续光谱范围。每个像元拿到的不是三种颜色而是一条完整的光谱曲线这条曲线就像物质的“指纹”不同地物植被、水体、建筑、土壤的光谱响应完全不同。分类任务的核心就是让模型学会根据这些光谱-空间特征判断像元属于哪个地物类别。我拿到的这个项目标题叫“超简单基于PyTorch的高光谱图像分类2D_CNN网络实战”用户意图很明确不搞复杂的3D卷积、不堆Transformer就用最常见的2D卷积神经网络把高光谱图像分类这件事跑通。这里有个很容易误解的点——高光谱图像本身是三维数据块高度×宽度×波段数怎么用2D CNN处理答案就在数据预处理上先做降维或波段选择把高光谱数据压缩成几个关键通道再按普通图像分类的思路处理。这个思路不是最先进的但它是理解高光谱分类的“最短路径”特别适合第一次接触这个方向的初学者。1.2 为什么选择2D CNN而不是3D CNN很多教程一上来就推荐3D CNN理由是它能同时提取空间和光谱特征听起来更“完整”。但3D CNN的问题很明显参数量大、训练慢、对硬件要求高而且高光谱数据本身波段数多、样本量少3D卷积很容易过拟合。项目标题里既然强调“超简单”我理解的核心诉求就是快速跑通、理解全流程而不是刷SOTA精度。2D CNN的做法是先把高光谱立方体通过PCA主成分分析降维保留前3个或前若干个主成分相当于把几十上百个波段压缩成3个信息量最大的“伪彩色通道”之后整个任务就退化成标准的RGB图像分类问题。这样做的优势在于可以完全复用PyTorch里成熟的2D卷积模块、预训练权重和大量图像分类的经验调试门槛低训练速度快而且代码量比3D CNN少一个量级。当然代价也明显PCA压缩会丢失一部分光谱细节如果地物之间光谱差异特别细微2D CNN的精度上限就不如3D方法。但对于教学、原型验证和大部分中等难度数据集这个方案完全够用。1.3 项目整体流程速览整个项目可以拆成四个环节数据准备、预处理、模型搭建、训练评估。数据准备阶段最容易被忽视高光谱数据集的格式五花八门有的是.mat文件有的是.tif有的还带单独的标签图第一步就得统一读取逻辑。预处理阶段的核心是PCA降维和样本划分这里要特别小心训练集和测试集不能混在一起做标准化否则会有信息泄露。模型搭建阶段我建议用最简单的卷积堆叠结构先跑通再谈优化。训练评估阶段除了看准确率还要看每类地物的分类效果因为高光谱分类最常见的坑是整体准确率好看、小类别却完全没学会。我在这篇文章里会把每个环节的代码拆开讲参数选择给出理由还会把我实际踩过的坑拿出来说。适合人群主要是刚入门深度学习、想拿高光谱数据练手的学生以及做遥感应用但不太熟悉PyTorch的工程师。你不需要有很强的数学背景但最好已经会基础的Python和简单的PyTorch张量操作。2. 环境准备与工具选型2.1 PyTorch环境搭建的几个关键决定PyTorch是必须的但怎么装其实有讲究。我建议用Anaconda创建独立环境不要直接装在base环境里否则以后项目多了依赖会打架。创建环境的命令很简单conda create -n hs python3.10 -y conda activate hsPython版本方面3.9到3.11都可以PyTorch官方对3.10的适配最好。接下来装PyTorch这里有两个方向CPU版和GPU版。如果机器上没有NVIDIA显卡或者只有A卡那就老老实实装CPU版训练慢但能跑通流程如果有NVIDIA显卡装CUDA版。激活环境的命令pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118或者用conda安装conda install pytorch torchvision pytorch-cuda11.8 -c pytorch -c nvidia我实际测试下来pip方式更省事conda经常因为依赖解析卡很久。装完之后务必验证一下CUDA是否可用import torch print(torch.__version__) print(torch.cuda.is_available()) print(torch.cuda.get_device_name(0) if torch.cuda.is_available() else CPU only)如果打印出True说明GPU版本正常工作。有个容易被忽略的细节PyTorch版本和CUDA驱动版本是两回事。哪怕你系统里装了CUDA 12.x只要PyTorch是cu118版本它也能跑因为PyTorch自带CUDA运行时系统驱动够新就行。所以别在“到底装哪个CUDA版本”上纠结太久用nvidia-smi看一眼驱动支持的CUDA版本选一个比它低的PyTorch CUDA版本就对了。2.2 硬件配置与WSL方案高光谱图像分类的数据量其实不大主流数据集比如Indian Pines、Pavia University整个文件也就几MB到几十MB所以对内存和显存的要求都不高。我实测在8GB显存的GTX 1070上批大小32、图像块尺寸11×11训练50轮毫无压力。但如果你用的是AMD显卡比如最近讨论度很高的RX 7900 XTX情况就特殊了——PyTorch官方对ROCm的支持在Linux下才比较完整Windows下基本没法直接用。这时候有两个选择一是装WSLWindows Subsystem for Linux在WSL里安装ROCm版本的PyTorch二是干脆用CPU跑这个小项目。WSL的坑不少我简单说下要点先在Windows里启用WSL2然后装Ubuntu 22.04发行版接着在Ubuntu里安装AMD的ROCm驱动最后再用pip安装PyTorch的ROCm版本。整个过程对新手不太友好光是驱动版本匹配就能折腾半天。所以我给个务实的建议如果只是跑这个项目CPU版完全够用如果你以后要认真做深度学习要么换NVIDIA卡要么老老实实折腾WSLROCm。在WSL里创建环境和Windows里没有本质区别仍然是condaenv那一套只是显卡调用走的是ROCm而不是CUDA。另外提一句无论什么环境只要代码里用了.cuda()或者to(device)PyTorch都会自动尝试调用GPU。如果GPU不可用它会报错或者静默回到CPU所以代码里最好加一句设备判断的逻辑免得在别人机器上跑的时候出问题。2.3 数据读取的库选择处理高光谱数据除了PyTorch还需要几个辅助库scipy读.mat文件、numpy矩阵运算、scikit-learnPCA降维和评价指标、matplotlib可视化。这些都用pip一条命令装完pip install numpy scipy scikit-learn matplotlib这里我想强调scikit-learn的价值很多人只拿它当传统机器学习的工具包但它的decomposition.PCA和metrics.classification_report在深度学习项目里同样非常好用。PCA直接调包比自己写快得多而且它支持先fit再transform可以方便地把训练集学到的降维参数应用到测试集上这个特性我们后面会用到。3. 数据准备与预处理实操3.1 以Indian Pines数据集为例Indian Pines是高光谱分类最经典的benchmark由AVIRIS传感器在美国印第安纳州拍摄空间尺寸145×145包含200个有效波段原始224个波段去掉吸水带后剩200个地物类别共16类包括玉米、大豆、小麦、草地、树林等。这个数据集常用于论文对比网上很容易找到.mat格式的版本两个文件一个存图像数据一个存标签。读取方式很简单import scipy.io as sio import numpy as np data sio.loadmat(Indian_pines_corrected.mat)[indian_pines_corrected] labels sio.loadmat(Indian_pines_gt.mat)[indian_pines_gt] print(data.shape) # (145, 145, 200) print(labels.shape) # (145, 145)data的形状是(高度, 宽度, 波段数)labels是(高度, 宽度)。注意labels里0代表背景无标签1到16是16个地物类别。数据准备的第一步就是把背景像素剔除只保留有标签的样本这步做不好后面样本统计全乱。3.2 PCA降维与图像块采样如前所述200个波段不能直接塞进2D CNN先做PCA降维。这里有个细节PCA要把三维数据reshape成二维矩阵再计算。具体做法from sklearn.decomposition import PCA # 把三维展开成二维像素数×波段数 h, w, bands data.shape data_2d data.reshape(h * w, bands) # 只对有标签的像素做PCA拟合 mask labels.reshape(-1) 0 pca PCA(n_components3) pca.fit(data_2d[mask]) data_pca pca.transform(data_2d) # 恢复成三维并归一化 data_pca_3d data_pca.reshape(h, w, -1)为什么只用有标签的像素来拟合PCA因为PCA是无监督方法如果用所有像素拟合背景像素通常是零值或噪声会主导主成分方向干扰降维结果。n_components选3一方面是为了让可视化时能对应RGB三个通道另一方面前3个主成分通常能解释超过90%的方差。如果你发现前3个主成分解释的方差不够也可以适当增加到5-10个但那就需要调整模型第一层的输入通道数。下一步是构建图像块。2D CNN不能只看单个像素还要看它周围的邻域这样才能利用空间上下文信息。常见的做法是取每个像素周围p×p的小块比如11×11这样一个样本的形状就是(11, 11, n_components)。边界像素的邻域超出图像范围需要用padding处理我习惯用镜像填充reflect模式因为零填充会让边界样本带上明显的人工痕迹。3.3 训练集测试集划分与信息泄露陷阱高光谱分类有两种划分方式随机划分和按区域划分。随机划分是把所有有标签的像素随机分成训练集和测试集这样训练测试可能来自同一块地精度会偏高按区域划分是把图像分成几块某些块做训练、某些块做测试更接近真实遥感应用场景难度更大。初学阶段建议先用随机划分代码简单、指标好看等你理解整个流程后再挑战按区域划分。随机划分代码如下from sklearn.model_selection import train_test_split # X_all是图像块数组形状为(N, 11, 11, 3)y_all是对应标签 X_train, X_test, y_train, y_test train_test_split( X_all, y_all, test_size0.7, stratifyy_all, random_state42 )这里我设置了test_size0.7意思是70%的样本做测试30%训练。高光谱分类通常不需要太多训练样本因为每个像素都是一条光谱曲线信息密度高30%已经足够。stratifyy_all保证每个类别在训练集和测试集中的比例和总体一致防止某个小类别在训练集中恰好没被抽到。信息泄露陷阱要特别留意如果先对整个数据集做标准化减去全局均值、除以全局标准差再划分训练测试那么测试集的信息已经通过均值、方差混进了训练过程评估结果会虚高。正确做法是先划分再在训练集上计算均值方差用同一组的参数去归一化测试集。PCA也是同理上一节特意强调只用训练集有标签的像素拟合PCA就是为了避免信息泄露。4. 2D CNN模型搭建与训练4.1 模型结构设计与参数量分析2D CNN的结构不需要太复杂我用的是一个三层卷积加全连接的小网络参考了许多高光谱分类论文里的基础结构但把参数做了精简保证新手也能在普通显卡上快速训练。核心代码如下import torch import torch.nn as nn class HyperspectralCNN(nn.Module): def __init__(self, input_channels3, num_classes16, patch_size11): super(HyperspectralCNN, self).__init__() self.features nn.Sequential( nn.Conv2d(input_channels, 32, kernel_size3, padding1), nn.BatchNorm2d(32), nn.ReLU(inplaceTrue), nn.Conv2d(32, 64, kernel_size3, padding1), nn.BatchNorm2d(64), nn.ReLU(inplaceTrue), nn.Conv2d(64, 128, kernel_size3, padding1), nn.BatchNorm2d(128), nn.ReLU(inplaceTrue), ) self.classifier nn.Sequential( nn.AdaptiveAvgPool2d(1), nn.Flatten(), nn.Linear(128, 256), nn.ReLU(inplaceTrue), nn.Dropout(0.5), nn.Linear(256, num_classes) ) def forward(self, x): x self.features(x) x self.classifier(x) return x这个结构有三个设计要点值得解释。第一三个卷积层的通道数从32到64到128逐层翻倍这是图像分类网络的经典做法——浅层提取边缘、纹理等低级特征通道数少一点没关系深层提取语义特征需要更多通道来承载信息。第二每层卷积后面都跟了BatchNorm和ReLUBatchNorm能加速收敛、缓解梯度消失在高光谱这种数据分布差异大的任务上作用尤其明显。第三分类器用AdaptiveAvgPool2d(1)把特征图压缩成1×1这样不管输入图像块尺寸怎么变全连接层的输入维度都是128代码更灵活。我算一下这个模型的参数量。卷积层参数主要由权重和偏置组成第一层3×32×3×3864个参数第二层32×64×3×318432第三层64×128×3×373728。全连接层128×25632768256×num_classes4096以16类计。总计约13万个参数不到0.2MB相比动辄上千万参数的ResNet轻量太多。这个体量意味着单CPU也能较快训练GPU更是秒级迭代。4.2 训练流程与超参数选择训练流程里最核心的超参数是学习率、批大小、损失函数和优化器。我给出的配置是一套经过验证、适合小数据集的基础组合import torch.optim as optim from torch.utils.data import DataLoader, TensorDataset # 转换为PyTorch张量并调整通道顺序为 (N, C, H, W) X_train_tensor torch.FloatTensor(X_train).permute(0, 3, 1, 2) y_train_tensor torch.LongTensor(y_train) - 1 # 标签从1开始减去1变成0-15 X_test_tensor torch.FloatTensor(X_test).permute(0, 3, 1, 2) y_test_tensor torch.LongTensor(y_test) - 1 train_dataset TensorDataset(X_train_tensor, y_train_tensor) test_dataset TensorDataset(X_test_tensor, y_test_tensor) train_loader DataLoader(train_dataset, batch_size64, shuffleTrue) test_loader DataLoader(test_dataset, batch_size128, shuffleFalse) model HyperspectralCNN(input_channels3, num_classes16) device torch.device(cuda if torch.cuda.is_available() else cpu) model.to(device) criterion nn.CrossEntropyLoss() optimizer optim.Adam(model.parameters(), lr1e-3) scheduler optim.lr_scheduler.StepLR(optimizer, step_size20, gamma0.5)这里有几处新手容易忽略的细节。标签减一是因为原始标签从1到16而CrossEntropyLoss要求的类别索引必须从0开始不减的话模型会认为有17类最后一类永远没有梯度。通道顺序调整为(N, C, H, W)是PyTorch卷积层的硬性要求如果你之前的数组形状是(N, H, W, C)不转置会直接报错或者得到莫名其妙的结果。Adam优化器对学习率不那么敏感初始1e-3基本是万金油StepLR每20轮把学习率减半能让训练后期收敛更稳定。训练循环的代码是标准写法但有几个技巧可以让过程更可控epochs 50 for epoch in range(epochs): model.train() running_loss 0.0 for inputs, targets in train_loader: inputs, targets inputs.to(device), targets.to(device) optimizer.zero_grad() outputs model(inputs) loss criterion(outputs, targets) loss.backward() optimizer.step() running_loss loss.item() * inputs.size(0) scheduler.step() model.eval() correct 0 total 0 with torch.no_grad(): for inputs, targets in test_loader: inputs, targets inputs.to(device), targets.to(device) outputs model(inputs) _, predicted torch.max(outputs, 1) total targets.size(0) correct (predicted targets).sum().item() train_loss running_loss / len(train_dataset) test_acc 100.0 * correct / total print(fEpoch {epoch1:3d} | Loss: {train_loss:.4f} | Test Acc: {test_acc:.2f}%)训练循环里我习惯把model.train()和model.eval()分开写这是必须的因为BatchNorm和Dropout在训练和推理时行为不同。如果不切模式训练完直接做测试BatchNorm会用当前的batch统计量而不是训练时的滚动统计量结果会有偏差。torch.max(outputs, 1)返回每一行的最大值和对应索引预测类别就是索引位置。我用Indian Pines数据实际跑过这个配置50轮之后测试准确率一般能达到94%-97%整个训练过程在GTX 1070上大约需要3-5分钟CPU上大约15分钟左右。如果准确率明显低于这个区间多半是数据预处理或者标签对齐出了问题后面会专门说排查方法。4.3 模型评估与结果可视化高光谱分类不能只看总体准确率还要看每个类别的准确率和召回率否则小类别被牺牲了你都不知道。我推荐用scikit-learn生成分类报告from sklearn.metrics import classification_report, confusion_matrix # 收集所有预测结果 y_true_all [] y_pred_all [] model.eval() with torch.no_grad(): for inputs, targets in test_loader: inputs inputs.to(device) outputs model(inputs) _, predicted torch.max(outputs, 1) y_true_all.extend(targets.cpu().numpy()) y_pred_all.extend(predicted.cpu().numpy()) print(classification_report(y_true_all, y_pred_all, digits4))这份报告会列出每个类别的精确率、召回率、F1分数和样本数。我在实际项目中看到的典型情况是样本量多的大类如大豆、玉米精度很高样本量少的小类如石墙、苜蓿精度可能只有50%-60%这就是类别不平衡问题。对于高光谱分类最简单的缓解方案是在训练时给样本量少的类别加权CrossEntropyLoss支持传入权重向量import torch.nn.functional as F # 统计每个类别的样本数 counts np.bincount(y_train) weights 1.0 / (counts 1e-6) weights torch.FloatTensor(weights).to(device) criterion nn.CrossEntropyLoss(weightweights)加权之后小类别的损失权重变大模型会稍微更重视它们整体准确率可能略降但每类平均精度会提升。对于侧重分类均衡性的应用场景这个操作很有必要。分类结果的图像可视化同样重要可以把模型在整张图上的预测结果做成伪彩色图和真实标签图对比一眼就能看出模型在哪些区域出错。做法是先把整个图像的所有像素包括背景都提取图像块并预测然后把预测标签填回二维矩阵# 假设整个图像逐像素预测后得到pred_map形状为(145, 145) # 0表示背景1-16表示类别 import matplotlib.pyplot as plt fig, axes plt.subplots(1, 3, figsize(15, 5)) axes[0].imshow(data_pca_3d / data_pca_3d.max()) axes[0].set_title(PCA RGB) axes[1].imshow(labels, cmaptab20) axes[1].set_title(Ground Truth) axes[2].imshow(pred_map, cmaptab20) axes[2].set_title(Prediction) plt.show()这里有个细节PCA压缩后的三个通道最大值可能差异很大直接imshow会偏色所以先除以各自的最大值归一化。标签图用tab20色图因为类别数最多16tab20刚好有足够的颜色区分度。5. 典型问题排查与性能优化5.1 维度错误与数据类型问题2D CNN最常见的报错就是维度不匹配。PyTorth的Conv2d要求输入形状为(N, C, H, W)如果你传入的是(N, H, W, C)会提示“Expected 4D input”或者“got 3D input”。解决方法是记住一个口诀通道维永远在第二位batch之后。而PCA降维后的三维数据是(H, W, C)提取像素块时是(N, H, W, C)所以在送入模型前必须permute(0, 3, 1, 2)。另一个高频坑是数据类型。numpy数组默认是float64但PyTorch的Conv2d要求float32或float16如果不转换直接包裹成Tensor会报类型错误。用torch.FloatTensor而不是torch.Tensor就能避免这个问题前者强制float32。标签数据则用torch.LongTensor因为CrossEntropyLoss要求target为整数类型。还有一个小众但很隐蔽的问题如果训练集某个类别只有一两个样本DataLoader在按batch随机采样时可能恰好没抽到它导致这个类别的模型weights完全没学到东西。解决方法有几个一是用stratify划分保证每个类别都出现在训练集里二是加大batch size让采样更均匀三是干脆用过采样把稀有类别的样本重复复制几份。对于Indian Pines这类标准数据集前两种方法通常就足够了。5.2 显存不足与训练速度慢显存不足OOM在这个项目里不太容易出现因为图像块尺寸很小但如果你的patch_size设到了25×25以上batch_size又设到128小显存显卡可能吃不消。解决方法按优先级排列先减小batch_size比如从64降到32再减小patch_size最后才考虑减小模型通道数。实际项目中我一般不会为了省显存而牺牲模型容量因为Indian Pines的样本量摆在那里模型再大也大不到哪去。训练速度慢则要分清瓶颈在数据还是模型。如果是cpu训练每次迭代都要做numpy和tensor的转换瓶颈常在DataLoader的数据加载上。可以尝试设置num_workers2或4让数据加载并行化train_loader DataLoader(train_dataset, batch_size64, shuffleTrue, num_workers2)如果是在Windows上num_workers0可能会报错或者导致程序卡死这是Windows多进程的固有问题。我的建议是Windows下就用num_workers0Linux下再考虑并行。数据量小的情况下worker带来的加速很有限不值得为了这点速度而增加调试成本。模型训练慢还有一个隐性原因没把模型和数据放到GPU上。检查代码里是否有model.to(device)和inputs, targets inputs.to(device), targets.to(device)。漏掉前者模型在CPU上算forward漏掉后者数据在CPU上而模型在GPU上会反复做设备和内存之间的拷贝速度慢到怀疑人生。5.3 精度上不去的排查思路当模型的准确率始终在70%以下徘徊时问题通常不是模型结构而是前面的数据处理。我总结了一个排查顺序先看训练loss有没有下降如果loss一直不降说明模型没有学到东西可能是学习率太大或太小也可能是标签错误如果loss下降了但测试准确率低说明过拟合了加Dropout或数据增强如果训练和测试准确率都低但loss正常那大概率是数据问题。数据问题里又分两类。一类是数据预处理出了bug比如PCA只针对部分像素做了归一化导致输入分布不一致另一类是标签错位比如取图像块时用的坐标和标签坐标对不上。高光谱数据集的坐标系是(height, width)如果你在提取特征时用了(width, height)的顺序就会出现“图像块内容是一个位置标签却是另一个位置”的错位而这种错位从loss曲线上很难看出来。我自己调试时就踩过这个坑最后是通过可视化预测图和标签图的差异才定位到坐标顺序问题。还有一个精度陷阱是类别不均衡。Indian Pines里大豆类的样本数可能比石墙类多几十倍如果模型把所有像素都预测成大豆总体准确率也能达到70%以上但这种模型没有实用价值。所以一定要看classification_report重点关注小类别的F1分数。如果小类别确实学不动先用类别权重加权再不行就考虑换成3D CNN或引入更多光谱信息。5.4 数据增强在2D CNN里的作用边界很多做自然图像分类的人习惯性地加随机翻转、随机裁剪但在高光谱分类中数据增强要非常谨慎。高光谱图像是遥感场景地物有明确的方向性建筑、道路、农田的方向都包含语义信息随机旋转90度或180度在某些数据集上反而会损害精度。我建议只加随机水平翻转和随机垂直翻转并且要通过实验对比如果加了增强后精度明显下降果断去掉。更有效的一种增强方式是我在项目里常用的“光谱扰动”对图像块的每个通道加少量高斯噪声等价于对光谱曲线做轻微偏移能增强模型对光谱波动的鲁棒性。实现很简单if random.random() 0.5: noise torch.randn_like(inputs) * 0.01 inputs inputs noise0.01的噪声方差是我试出来的经验值太小没效果太大会把真实光谱特征淹没。对于Indian Pines这类干净的标准数据集不加噪声的baseline已经很高加噪反而可能干扰训练但如果你自己采集的数据带有噪声这个操作能带来明显提升。6. 项目扩展与进阶方向6.1 从2D CNN升级到混合CNN如果把2D CNN跑通了下一步自然是想办法进一步提高精度。我建议不要直接跳到3D CNN而是先试“空间-光谱双分支”结构一路分支用2D CNN提取空间特征另一路分支把每个像素的光谱曲线一维向量单独送入1D CNN提取光谱特征最后把两个分支的特征拼接在一起分类。这种结构代码量只多了几十行但精度通常比纯净的2D CNN高一两个百分点而且能直观展示空间和光谱信息各自的作用。实现思路空间分支输入的是PCA降维后的三通道图像块光谱分支输入的是原始200个波段在该位置的像素向量形状为(1, 200)。两个分支的输出都经过全局池化变成一维向量然后cat起来送入全连接分类器。训练时注意两个分支的学习率可以不同光谱分支收敛慢可以设大一点学习率空间分支收敛快设小一点但这种精细调节对初学者来说不是必须的先用同一套学习率也能跑出合理结果。6.2 注意力机制与Transformer的简单接法注意力机制近年来也杀进了高光谱分类领域。最朴素的接法是在2D CNN的深层特征图后面加一个通道注意力模块类似SENet的squeeze-and-excitation。通道注意力的思想是让网络自动学习哪些特征通道更重要从而放大有用特征、抑制无用特征。对于高光谱图像PCA降维已经做了一次通道筛选再叠加通道注意力相当于第二次筛选理论上能让分类器更聚焦于判别性特征。代码实现如下class ChannelAttention(nn.Module): def __init__(self, in_channels, reduction16): super(ChannelAttention, self).__init__() self.avg_pool nn.AdaptiveAvgPool2d(1) self.fc nn.Sequential( nn.Linear(in_channels, in_channels // reduction), nn.ReLU(inplaceTrue), nn.Linear(in_channels // reduction, in_channels) ) self.sigmoid nn.Sigmoid() def forward(self, x): b, c, _, _ x.size() y self.avg_pool(x).view(b, c) y self.fc(y).view(b, c, 1, 1) return x * self.sigmoid(y)把ChannelAttention插在每层卷积之后就能以很小的代价换取几个点的精度提升。至于Transformer我的建议是初学者先不要碰。ViT类模型需要大量数据才能训练好高光谱数据集的样本量通常只有几千到几万在标准数据集上很容易大幅过拟合。如果你想全用Transformer那必须扩充训练样本比如用生成数据或跨数据集预训练这对初学者来说是一条更长的路。6.3 把单模型做成分类器集成另一个简单有效的进阶方向是模型集成。训练3个不同的模型比如不同随机种子、不同patch_size、不同卷积核大小推理时对3个模型的输出概率取平均再判断类别。集成能平滑单模型的随机误差通常能把精度提升1-2个百分点。代价是训练时间变为原来的3倍但代码改动很小probs_list [] for model in models: model.eval() with torch.no_grad(): probs F.softmax(model(inputs), dim1) probs_list.append(probs) avg_probs torch.stack(probs_list).mean(dim0) _, predicted torch.max(avg_probs, 1)集成对于类别不平衡问题也有一定帮助因为不同模型在个别类别上的偏好可能不一样平均之后偏见会被削弱。缺点是需要保存多个模型文件推理速度变慢。对于小数据集来说集成的收益大于代价我比较推荐。6.4 部署到实际场景的注意事项如果这个项目要真正部署到遥感业务系统中有两个问题必须在训练阶段就考虑。第一个是推理效率实际遥感图像可能非常大比如10000×10000像素逐像素提取图像块并预测会非常慢。解决方案是使用滑窗批量推理或者直接用全卷积网络让模型一次处理一大块图像大幅减少重复计算。全卷积网络本质上是把最后的全连接层换成1×1卷积这样网络可以接受任意尺寸的输入输出对应的分割图。第二个是类别定义标准数据集Indian Pines的类别是固定的但实际业务中地物类别经常变化今天分的可能是农作物种类明天分的可能是建筑物损坏程度。设计模型时最好把分类头做成可配置的num_classes作为参数传入。另一个实用技巧是在训练阶段就把背景类纳入模型让模型学会区分“已知地物”和“未知区域”否则推理时模型会把每个像素都强行归到某个已知类别里。我在实际项目中还发现高光谱分类模型在实验室数据和野外真实场景数据之间的精度落差很大。原因很直接实验室数据经过严格的辐射定标和大气校正光谱干净野外数据受光照、云影、传感器噪声影响光谱曲线多了很多随机波动。如果需要部署建议在训练数据里混入一些真实场景的样本或者对实验室数据做更激进的光谱增强提高模型的泛化能力。7. 从实战中沉淀的几点体会这个项目做下来最大的感触是“高光谱图像分类”名字听着高大上拆开看每一步都是通用深度学习的组合应用。PCA降维是sklearn的标准操作2D CNN是PyTorch的入门组件训练流程和任何图像分类项目没有本质区别。真正的难点从来不在模型而在数据理解你得知道波段是什么意思、标签分布什么样、空间上下文有多重要这些领域知识比调参更能决定最终效果。最后分享一个小技巧训练过程中把每个epoch的测试准确率和loss记录下来画成曲线图观察变化趋势能帮你做出很多判断。如果训练集准确率稳定上升、测试集准确率开始下降说明开始过拟合了应该提前停止或者在更早的epoch取checkpoint。如果准确率曲线震荡很大可能是学习率太高降一个量级试试。这些曲线比任何复杂的自动化调参工具都直观也最适合新手建立对模型训练过程的“手感”。对刚接触这个方向的朋友我建议不要一开始就追新模型新技术先把这个2D CNN流程从头到尾跑明白改一改patch_size看效果怎么变加一加噪声看鲁棒性怎么变。等你亲手感觉到每一个操作带来的变化高光谱图像分类的大门就算真正迈进去了。