资讯动态

高光谱遥感全链路解析:Python+AI实现地物分类实战指南

发布时间:2026/10/19 5:53:49 来源:尧图企业网站定制
做高光谱这行时间长了最深的感受是这个东西的高不光是波段数高更是门槛高、坑多、数据量大。早年我们用传统机器学习做高光谱分类样本少、特征多每次调SVM的核函数参数都要折腾好久泛化效果还经常被波段噪声毁掉。这几年AI技术全面渗透进来配合Python的生态工具链整条链路明显顺了不少——从数据读取、预处理、降维到模型训练、分类制图基本可以在一个脚本里跑完。今天我想结合自己做过的模拟项目X和几个实际接触过的案例场景把这条全链路从头到尾拆一遍重点讲讲哪些地方容易被忽略、哪些参数卡过我的脖子以及AI模型进了高光谱之后到底改变了什么。这篇文章适合三类人看刚接触高光谱遥感的学生或开发者想弄清整套处理流程和关键技术点已经在用ENVI等传统软件、想转向PythonAI路线的从业者以及做农业、地质、环境方向需要把高光谱数据落到实际业务里的工程师。我不会堆术语但该给的公式、参数、代码思路都会给到尽量让新手能跟着走完一遍让有基础的人也能拿到一些能直接用的经验。1. 全链路思路与设计拆解1.1 高光谱为什么一定要和AI走到一起先把基础掰扯清楚。高光谱成像采集的是几十到几百个连续窄波段的光谱信息每个像元都是一条完整光谱曲线。相比多光谱的十几个波段高光谱能把地物的指纹精细记录下来理论上能做到对植被、矿物、水体成分的精准判别。但这也带来了一个绕不开的问题波段数远远大于训练样本数也就是典型的维度灾难。传统方法处理这种高维小样本数据时通常会先做降维再用SVM、随机森林这类分类器。这套路有效但非常依赖人工特征工程而且光谱曲线的细微差异很难被线性方法抓住。深度学习不一样它能在训练过程中自动学习光谱特征的表征尤其是1D-CNN对光谱曲线波段间局部相关性的提取能力天然契合高光谱数据的结构。这两年Transformer结构也进来了通过自注意力机制捕捉波段之间的长程依赖比卷积能看得更远。我的建议是不要盲目追求炫酷模型先想清楚业务目标。是做精细地物分类还是做目标探测是像素级分类还是端到端的回归估算目标不同模型选型差别很大。比如一个农业地块分类任务随机森林可能就够用且有可解释性但如果要做矿物的亚像元识别复杂场景下3D-CNN或混合注意力网络才会占据优势。链路设计的第一件事是让方法适配问题而不是反过来强行套模型。1.2 Python在高光谱链路里的角色定位以前做高光谱处理很多人习惯用ENVI、ERDAS这类商业软件操作界面点几下就能完成大气校正、MNF变换确实方便。但问题也很明显批量处理难、算法定制难、和后续机器学习流程割裂严重。我见过不少人大气校正结果出来之后还要手工导出数据再喂给Matlab或R做分类来回倒腾非常崩溃。Python这几年能成为高光谱的粘合剂靠的是整个生态的完整性。底层有GDAL和rasterio负责读写几乎所有主流栅格格式开箱即用读ENVI格式的.hdr和.dat文件中层有numpy、scipy、scikit-learn处理数值计算和传统算法高层有PyTorch等深度学习框架提供自动微分和GPU加速再加上spectral、pysptools这类专业库把高光谱的常见操作都封装好了。可以说从原始DN值矩阵一直到最终分类结果图和精度验证报告全程可以代码化实现可复现性比GUI操作强一个量级。不过也别忽略Python生态再全也不是万能。大气校正的核心模块像辐射传输模型6S或者MODTRAN大多数时候还是要商业软件或专门工具完成Python能做得更多的是调用封装好的接口。一个务实的链路是重物理的环节用成熟工具重统计和学习的环节用Python加AI框架各取所长。1.3 全链路的关键环节地图我自己习惯把一条完整的高光谱AI处理链路分成六个环节每个环节都有明确的输入输出和核心指标数据获取与质量审查包含传感器参数确认、数据格式解析、坏波段检测、辐射异常检查。预处理辐射定标、大气校正、几何校正、图像裁剪、噪声抑制。特征工程与降维PCA/MNF变换、波段选择、光谱指数构建为模型减少冗余。样本构建感兴趣区域标注、光谱信息提取、数据增强与样本划分。模型训练与调优网络结构设计、损失函数选择、训练策略、超参数搜索。评估与制图混淆矩阵、Kappa系数、逐像元推理、专题图输出、不确定性分析。每个环节都不是孤立的前面的误差会一层层传导到后面。我经常和团队的人说高光谱项目最后的精度天花板往往在预处理阶段就定死了。模型再强也只能在给定数据质量的范围内发挥。所以设计链路时一定要留出质量检验节点比如在预处理后目视检查几条典型地物的光谱曲线是否合理而不是一股脑往下跑。2. 数据读取与预处理实操要点2.1 高光谱数据格式和Python读取方式高光谱数据的存储格式千奇百怪但国内最常见的主要是三类ENVI标准格式、HDF5格式、GeoTIFF堆栈。ENVI格式虽然老但依然是很多国产成像光谱仪默认输出格式包括.hdr头文件和.dat二进制数据文件.hdr里面记录了波段数、波长列表、数据精度、字节序、存储方式这些关键信息。用Python读ENVI格式最省事的办法是用spectral库的envi.open函数import spectral img spectral.open_image(example.dat) # 自动关联example.hdr data img.load() # 得到 (rows, cols, bands) 的 numpy 数组这里有个易错点load()出来的数组默认是按BIP或BIL排列的如果后续要用PyTorch得先把维度排列调整成(C, H, W)或者序列形式同时注意数据类型是uint16还是float32辐射定标之后记得转成浮点再运算。另一个常见坑是字节序有些传感器数据是大端序spectral会根据hdr自动处理但如果你用numpy的fromfile直接读就很容易字节序错乱导致熵值爆炸。如果项目格式比较杂我一般先统一转成GeoTIFF或NPY后续所有脚本都基于统一格式省得每个环节都要写一遍解析逻辑。2.2 辐射定标与大气校正的标准思路从传感器拿到的一般是量化后的DN值或相对反射率并不能直接用于跨时间、跨区域的对比分析所以辐射定标是第一步。定标的核心就是乘增益加偏置公式很简单L gain * DN offset但实际操作时你必须从数据说明里找到准确的光谱响应函数和定标系数。有些数据分波段给系数有些给的是整体查找表千万别想当然用一套系数通用处理所有波段那样子波段和蓝波段的反射率计算会差很多。大气校正则是把传感器入瞳辐亮度转换为地表反射率。物理原理是太阳辐射穿过大气会被散射和吸收路径辐射叠加在信号上削弱了地表信息。我们常用的FLAASH是基于MODTRAN模型开发的界面工具另外6S模型也常被作为底层算法。回到Python这边实际情况是完全开源的精确大气校正库还不算成熟但你可以用py6s这个库调用6S模型做单点或多点的大气校正from py6s import * s SixS() s.atmos_profile AtmosProfile.PredefinedType(AtmosProfile.MidLatitudeSummer) s.aero_profile AeroProfile.PredefinedType(AeroProfile.Maritime) s.wavelength Wavelength(470) # 单位nm s.ground_reflectance GroundReflectance.HomogeneousLambertian(0.2) s.run() transmittance s.outputs.transmittance_total这里要注意6S是针对单波段的逐点计算高光谱几百个波段做全尺寸图像大气校正时计算量非常大一般会抽稀波段算系数再插值。经验之谈如果你的研究区域相对平坦、且目标是做分类而不是定量反演有时候不做完整大气校正是可以接受的直接使用经过辐射定标的相对反射率也能得到不错的分类效果。但一旦涉及跨传感器、跨时间的光谱对比或者要把光谱曲线拿去建光谱库大气校正这一步就不能省。2.3 坏波段剔除与噪声抑制的实战细节高光谱数据不是每个波段都可靠。受大气强吸收影响水的吸收带附近约1400 nm、1900 nm附近信噪比极低传感器自身也可能存在坏像元或响应异常的波段。这些波段如果不剔除不但增加计算负担还会让分类器学到错误的噪声模式。怎么判断坏波段两个简单标准一是看整幅图像的波段均值是否异常高或异常低二是计算波段之间的相关性如果一个波段和相邻波段的相关系数极低很可能就是坏波段的信号。import numpy as np # data形状为 (H, W, B)把图像拉成像素矩阵 pixels data.reshape(-1, data.shape[-1]) band_mean np.mean(pixels, axis0) bad_bands np.where(np.abs(band_mean - np.median(band_mean)) 3 * np.std(band_mean))[0]噪声抑制方面高光谱常用的是Savitzky-Golay滤波用于平滑光谱曲线同时保留峰谷特征。窗口大小和多项式阶数的选择很关键窗口太小去噪不干净太大又会把光谱吸收特征磨平。我自己常用的参数是窗口11、多项式3阶但还是要根据光谱分辨率微调。另一个常用的环节是光谱维度的MNF变换本质上是一种考虑噪声协方差的主成分分析可以把噪声分离到尾部分量。做完MNF再取前若干分量能显著提升后续分类的稳定性。这个我们下一节展开。3. 特征降维与波段选择的核心方法3.1 为什么必须降维以及几种思路的对比高光谱数据动辄上百个波段但相邻波段之间高度相关实际的有效信息维度远低于波段数。如果不降维直接扔给分类器不但计算量大更容易出现过拟合到噪声的现象——训练集精度极高验证集一塌糊涂。降维大概有三条路线。第一条是线性变换类最经典的就是主成分分析PCA和MNF。PCA找最大方差方向MNF在PCA之前先做噪声白化本质上是最大化信噪比而不是方差。第二条是波段选择直接挑出最有区分性的物理波段保留光谱的物理可解释性。第三条是基于深度学习的方法比如自编码器学习紧凑的光谱特征表示。具体选哪种取决于后续任务类型。如果只是常规地物分类PCA/MNF基本够用如果要做物理量反演波段选择保留可解释性更重要如果要追求极致精度且样本充足自编码器的非线性特征可能会有额外收益。3.2 PCA和MNF的计算过程与Python实现PCA的数学过程不复杂对像素光谱矩阵做标准化计算协方差矩阵然后特征值分解取前k个特征向量对应的投影。关键是k怎么定一般看累计方差贡献率。我通常保留贡献率超过99%的分量高光谱数据通常前10~30个主成分就能达到这个水平。MNF的实现稍微绕一点但原理很清楚先把数据分成信号部分和噪声部分用噪声协方差矩阵对数据做白化再对白化后的数据做PCA。噪声怎么估计常用的方法是相邻像元差值法——认为真实地物在极小邻域内变化不大相邻像元的差异主要来自噪声。Python里用spectral库的mnf接口很方便import spectral mnf spectral.mnf(MNF(data)) # 维度自适应 mnf_data mnf.reduce(num20) # 保留前20个MNF分量实际项目中MNF还有一个隐性价值它能把随机噪声隔离到后面的分量里所以你只取前若干个分量本身就是一个优秀的降噪步骤。做完MNF后光谱曲线的平滑度往往肉眼可见地提升分类精度也会跟着涨两个点左右。3.3 波段选择比变换更物理的做法有些场景下我们希望保留物理波段。比如农业监测里红边位置680~750 nm附近对叶绿素含量非常敏感这个物理含义不能丢。这时候变换类方法就不太合适因为PC分量是波段线性组合已经失去了原波段对应的物理意义。波段选择策略有很多基于可分性判据如J-M距离、基于信息熵、基于随机森林的特征重要性或者基于稀疏表示的方法。我比较常用的是随机森林特征重要性排序因为实现简单、结果稳定而且能直观知道哪些波段对分类最重要。from sklearn.ensemble import RandomForestClassifier rf RandomForestClassifier(n_estimators200) rf.fit(X_train, y_train) importance rf.feature_importances_ top_k np.argsort(importance)[-30:] # 取前30个波段序号一个容易犯的错是直接在原始波段上算重要性如果波段间相关性太强随机森林的特征重要性会有偏向。稳妥做法是先按相关性聚类粗选一批波段再在粗选子集上排序。这事没有绝对最优解需要多试几个方案再用验证集精度定。注意波段选择必须在训练集内部完成绝对不能用全量数据计算重要性再划分训练测试集否则就是信息泄露后面评估的精度全是虚的。4. AI模型的构建、训练与优化4.1 从经典机器学习到深度学习的过渡高光谱分类里随机森林和SVM至今没有过时。它们在小样本场景下表现稳定训练快结果可解释。我早期用RBF核SVM做林木树种分类配合PCA降维到30维在样本只有几百个的情况下精度能达到85%左右。传统方法的优势是稳定性劣势是特征表达上限不高。深度学习的转折点在于1D-CNN把光谱曲线当作一维信号去做卷积这个视角特别契合高光谱光谱吸收特征往往体现在局部波段区间内卷积核能自动捕捉这些局部模式。1D-CNN的网络结构通常不深一个典型架构是卷积层BNReLU→最大池化→再卷积→全局池化→全连接分类层。输入是每个像元的光谱向量长度等于波段数输出是地物类别概率。我自己在实验里验证过同样的训练数据1D-CNN和SVM对比整体精度提升约3~5个百分点在处理混合光谱时的稳健性更好。但1D-CNN用不了空间信息直接忽略了像元邻居的关系。为了利用空间上下文人们设计了2D-CNN和3D-CNN。2D-CNN每个波段取一个特征图相当于把多个通道叠加成图像输入3D-CNN则在空间和光谱两个维度同时做卷积3D卷积核可以同时捕捉相邻像元和相邻波段的联合模式。3D-CNN的问题是参数膨胀容易过拟合所以要用正则化和数据增强来配合。4.2 1D-CNN、3D-CNN与Transformer的取舍以我接触过的实际任务来看三者适用范围差异明显1D-CNN适合样本少、任务是纯光谱分类的场景训练快硬件要求低。2D/3D-CNN适合图像空间结构明显的场景比如城区精细分类、农作物地块提取能显著降低椒盐噪声但需要块状训练样本。Transformer光谱Transformer或混合模型适合长序列光谱依赖建模和海量数据的预训练场景表达能力上限高但小数据集容易欠收敛。一个操作建议当你手里样本少于每类几百个时别急着上3D-CNN或Transformer先用1D-CNN加传统分类器做一个baseline确定精度的地板然后逐级增加模型复杂度而不是一步到位否则很难判断精度提升到底来自模型结构还是数据运气。近几年也出现了不少预训练大模型应用到遥感分类的方案思路是先用大量无标注的高光谱影像做对比学习预训练再在特定任务上微调。这个方向在小样本场景下确实能把精度抬高一截但需要强大的算力支撑普通项目组可以先观望或者直接用归一化后的数据搭配迁移学习代价小效果也不错。4.3 训练策略、损失函数与样本不平衡处理高光谱分类样本往往是严重不平衡的。比如一个矿区地物分类任务裸岩样本占了80%植被只有零星几处直接训练会让模型把所有像元都预测成裸岩。处理办法有三板斧类别重加权、过采样、以及从损失函数层面解决。类别重加权最简单计算每个类别样本数的倒数并归一化作为权重让少数类的损失占比变大from sklearn.utils.class_weight import compute_class_weight weights compute_class_weight(balanced, classesnp.unique(y), yy)更进阶一点可以引入Focal Loss这个最初是目标检测里解决正负样本极端不平衡的思路核心是在交叉熵的基础上给难分类样本更大的权重让模型注意力集中在容易分错的少数类上。如果你用PyTorch自定义Focal Loss也就十几行代码我放在后面实操章节给出实现片段。还有一个经常被忽略的细节是样本划分策略。遥感场景里相邻像元高度空间相关如果随机划分训练测试样本两个像素可能来自同一区域造成空间自相关导致评估精度虚高。务必使用分层抽样的块状划分或者至少确保训练和测试样本来自不同地块才能得到可信的泛化指标。4.4 数据增强与归一化的常见做法高光谱数据增强和自然图像不一样不能随便做翻转、裁剪因为遥感地物的方向敏感性有时很致命。更合理的光谱增强方式包括三类光谱扰动在光谱曲线上加微小的随机噪声、随机缩放或偏移模拟光照变化和传感器噪声。混合增强把两个不同类别样本的光谱按随机比例混合标签也按比例线性混合类似图像领域的Mixup。空间裁剪在不改变光谱数值的前提下从大图里随机裁剪不同位置的块状样本增加空间多样性。归一化一定要逐像元还是逐波段要想清楚。如果目的是消除光照强度差异一般逐像元做归一化把每个像元的光谱向量缩放到零均值单位方差或者按最大值归一化到0~1。但要注意逐像元归一化会部分抹掉光谱的绝对反射率信息对依赖吸收深度的任务反而不利。所以我的习惯是分类任务用逐像元归一化通常没问题定量反演任务尽量保留绝对辐射信息只做全局标准化。5. 典型应用案例拆解5.1 农业场景作物病虫害早期识别这是我做过最多的类型。病虫害对作物的影响在肉眼可见之前叶片叶绿素含量、水分含量已经发生变化这些变化会在光谱曲线上留下痕迹。典型症状是红边位置蓝移、近红外反射率下降、叶绿素吸收深度变浅。我们当时的模拟项目X是在某试验田做小麦条锈病早期识别。数据是机载高光谱空间分辨率约1米波段范围400~1000 nm共150个波段。预处理后我们在叶片尺度上标注了健康、轻度感染、重度感染三类样本。训练了一个3D-CNN模型输入是7×7空间邻域×150波段的块状数据输出三分类。最终精度达到了91.7%Kappa系数0.88比传统SVM提高了约7个百分点。这里最有价值的发现是模型在轻度感染阶段的识别召回率最高能达到85%而人工田间诊断只能发现约50%。这就是高光谱加AI的核心价值——把发现窗口提前了将近7~10天为植保作业争取了时间。但要提醒的是农业模型的跨区域迁移非常难不同品种、不同生育期、不同土壤背景都会让模型失效落地时必须做领域自适应或者持续微调。5.2 地质场景矿区矿物填图与蚀变信息提取矿物填图是高光谱遥感最经典的应用。每种矿物都有特征光谱吸收谷比如高岭石在2200 nm附近有Al-OH吸收特征碳酸盐矿物在2300 nm附近有吸收特征。传统方法是基于光谱库做匹配但匹配算法对噪声和混合光谱敏感。我们处理过一个矿区的高光谱数据目标是识别多种蚀变矿物。数据是机载高光谱波段覆盖范围含短波红外。这里我做了一个关键决策没有直接用原始光谱做分类而是先用MNF降维到15个分量再结合野外验证样本和参考光谱库做光谱角匹配验证标签最后用1D-CNN做逐像元分类。结果上看模型成功区分了三种外观相似的含铁蚀变矿物而这些矿物在RGB合成影像上几乎无法区分。这个案例也暴露出一个问题矿物识别的训练样本获取成本极高很多区域不可能有地面验证点。应对策略是先聚类后专家打标先用K-Means对光谱空间聚类在每个簇中心选少量样本交给地质专家判别再用专家标签训练模型能用极少的标注量覆盖最大的光谱变异性。5.3 水环境场景水质参数反演水体的高光谱信号微弱且复杂但藻类浓度、浊度、叶绿素a等指标和光谱特征之间有明显相关性。水质反演一般不走分类路线而是回归估算去预测水体组分的浓度数值。我们做过的例子是用星载高光谱影像估算叶绿素a浓度。核心波段选择在近红外和红边区域因为叶绿素a在675 nm附近有吸收峰700 nm附近出现反射峰随着浓度升高反射峰会向长波方向移动。我们没有用物理模型而是直接训练了一个小型全连接网络输入是经过大气校正和水体掩膜后的光谱向量输出是叶绿素a浓度对数值。在验证集上R²达到了0.86RMSE约3.2 μg/L表现优于传统的三波段比值经验模型。这个案例最有价值的经验是掩膜先行。做水体反演前必须先准确提取水体范围把陆地像元剔除干净。如果混入了土壤或植被像元模型会学到非常离谱的映射损失函数看起来在下降但实际水体的可信度很平庸。6. 常见问题与排查技巧实录我把这些年被问得最多也最容易栽跟头的问题整理成一张速查表每条都是实际项目中遇到过的情况问题现象排查思路解决方案训练精度奇高但验证精度很差是否信息泄露样本是否空间自相关重新按地块划分样本检查特征选择是否用了全量数据分类图充满椒盐噪声逐像元分类没有用空间约束增加空间邻域输入、使用条件随机场后处理、或者用3D-CNN光谱曲线严重抖动大气校正不彻底或传感器坏波段未剔除重新检查坏波段列表对光谱做Savitzky-Golay平滑在MNF域截断噪声分量两个类别的光谱几乎重叠类别定义不清晰或混合像元过多检查训练标签是否有误标考虑用软分类/亚像元分解而不是硬分类模型在A区域可用换B区域失效数据的域偏移如太阳高度角、水分条件不同做全局归一化、直方图匹配或用无监督域自适应方法做校准GPU显存跑不动3D-CNN输入的块状数据太大、Batch size太大减小空间邻域到5×5减少波段数到100混合精度训练还有一个高频踩坑点是数据格式混乱。我做数据管理时习惯在项目启动的第一天就建立一个标准的目录结构raw/、processed/、samples/、models/、results/并且每个环节输出的数组都单独保存为.npy格式外加一个记录波长和头信息的JSON文件。这个习惯看起来简单但能从根本上杜绝做到一半发现上一步数据叠加错位的崩溃局面。再说几个排查思路的心得。如果你的模型验证精度始终卡在某个值上不去先不要再调网络结构了去做三件事第一把训练样本的光谱曲线画出来看看不同类别的均值和方差有没有区分度第二把模型预测错误的样本收集起来逐一对照原始影像看是否存在标错第三检查前处理管道是否引入伪影比如裁剪范围错位、波段顺序颠倒。这三件事做完80%的问题都能定位。另外一个常见问题是样本标注的主观性。不同人标定的样本标准不一致会让模型在决策边界附近摇摆。我的办法是双人独立标注、不一致样本人工仲裁虽然增加耗时但能显著提升标签质量。一个干净数据的分类实验往往比带噪声大数据的实验结果更有说服力。7. 实操环节从数据到模型的完整代码流程这是我做模拟项目X时总结出来的一套最小可用流程分为四个步骤覆盖从读取到推理成图的全过程。以下代码片段基于实际项目精简但保留了关键参数和注意事项。7.1 环境准备与核心依赖基础环境建议是Python 3.9以上版本搭配如下核心库numpy、rasterio、spectral、scikit-learn、PyTorch。如果条件允许建议准备一块NVIDIA显卡训练速度提升明显显存8GB以上就可以跑小规模的3D-CNN。安装命令只需要一行pip install numpy rasterio spectral scikit-learn torch torchvision7.2 数据读取、裁剪与真值提取这一步骤做的事情是读原始影像、根据地面真值区矢量裁剪出训练样本、整理成numpy数组。import numpy as np import spectral from osgeo import gdal # 有时用来处理矢量转栅格 img spectral.open_image(scene.dat) data img.load() # (rows, cols, bands) # 假设polygon_labels是一个二维掩膜数组数值0表示背景1/2/3表示三类地物 x_all data[polygon_labels 0, :] y_all polygon_labels[polygon_labels 0] print(有效像元数:, x_all.shape[0], 波段数:, x_all.shape[1])这里要特别提醒很多文件的长宽行列数和你想象的顺序不一致取数之前先打印backing shape确认避免把转置的矩阵当成正常数据后面所有结果全是错的。7.3 数据划分与归一化按空间块划分训练和验证集。最简单但有效的做法是先给像素坐标打一个行号列号利用网格索引划分保证训练和验证像素不在同一个空间块。rows, cols polygon_labels.shape # 生成随机块编号这里用简单的模3分块模拟空间划分 block_id (np.arange(rows)[:, None] // 10 * 100 np.arange(cols)[None, :] // 10) block_train block_id[polygon_labels 0] % 3 ! 0 block_val block_id[polygon_labels 0] % 3 0 x_train, y_train x_all[block_train], y_all[block_train] x_val, y_val x_all[block_val], y_all[block_val] # 逐像元归一化 x_train_norm (x_train - x_train.mean(axis1, keepdimsTrue)) / (x_train.std(axis1, keepdimsTrue) 1e-8) x_val_norm (x_val - x_train.mean(axis1, keepdimsTrue)) / (x_train.std(axis1, keepdimsTrue) 1e-8)注意归一化参数必须从训练集上计算验证集和测试集都用训练集的均值方差绝不能用全量数据算。这个细节是我曾经犯过错的地方当时验证集精度很完美一上真实全图就崩了根源就在归一化参数泄漏。7.4 1D-CNN训练与评估核心代码一个轻量1D-CNN足够作为强大baseline结构包含两个一维卷积层和一个全连接分类头import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import TensorDataset, DataLoader class SpecCNN1D(nn.Module): def __init__(self, in_channels150, num_classes3): super().__init__() self.conv1 nn.Sequential( nn.Conv1d(1, 32, kernel_size7, padding3), nn.BatchNorm1d(32), nn.ReLU(), nn.MaxPool1d(2)) self.conv2 nn.Sequential( nn.Conv1d(32, 64, kernel_size5, padding2), nn.BatchNorm1d(64), nn.ReLU(), nn.MaxPool1d(2)) self.head nn.Sequential( nn.AdaptiveAvgPool1d(1), nn.Flatten(), nn.Linear(64, 128), nn.ReLU(), nn.Dropout(0.4), nn.Linear(128, num_classes)) def forward(self, x): x x.unsqueeze(1) # (batch, 1, bands) x self.conv1(x) x self.conv2(x) return self.head(x) model SpecCNN1D(in_channels150, num_classes3)训练主循环的重点是损失函数。类别不平衡时我强烈建议优先用带权重的交叉熵class_weights torch.tensor([1.0, 2.0, 3.0], devicecuda) criterion nn.CrossEntropyLoss(weightclass_weights) optimizer optim.Adam(model.parameters(), lr3e-4) # 训练循环 train_ds TensorDataset(torch.tensor(x_train_norm, dtypetorch.float32), torch.tensor(y_train, dtypetorch.long)) train_loader DataLoader(train_ds, batch_size256, shuffleTrue) model.train() for epoch in range(50): for data, label in train_loader: data, label data.cuda(), label.cuda() optimizer.zero_grad() out model(data) loss criterion(out, label) loss.backward() optimizer.step() if (epoch1) % 10 0: print(fEpoch {epoch1}, Loss: {loss.item():.4f})这个网络训练一轮非常快几十个epoch就能收敛。评估阶段我用验证集精确率、召回率和Kappa三个指标一起看单看准确率很容易被不平衡类别带偏。7.5 全图推理与专题图输出训练完成后的推理是全图逐像元预测要把整幅图的像素reshape成二维矩阵喂给模型再把预测类别reshape回原始图像形状model.eval() H, W, B data.shape flat_pixels data.reshape(-1, B).astype(np.float32) flat_norm (flat_pixels - mean_vec) / (std_vec 1e-8) with torch.no_grad(): tensor_pixels torch.tensor(flat_norm, dtypetorch.float32).cuda() pred_logits model(tensor_pixels).cpu().numpy() # 全图一次性推理可能显存不够按块循环更稳 pred_labels np.argmax(pred_logits, axis1).reshape(H, W)大图一定不要一次性全图推理显存会直接爆炸。我的做法是把整幅图切成512×512的瓦片逐块推理最后用拼贴函数合回原尺寸顺便处理边界重叠区域取中心结果能有效避免瓦片接缝处的分类跳变。7.6 混淆矩阵与Kappa统计量计算评估指标不能只看总体精度。我习惯输出一个完整混淆矩阵观察具体的错误模式比如A类是否被系统性误判成B类。用sklearn的confusion_matrix和cohen_kappa_score非常方便from sklearn.metrics import confusion_matrix, cohen_kappa_score, accuracy_score acc accuracy_score(y_val, y_pred) kappa cohen_kappa_score(y_val, y_pred) cm confusion_matrix(y_val, y_pred) print(OA:, round(acc, 4), Kappa:, round(kappa, 4)) print(cm)看到混淆矩阵后重点看对角线以外最突出的单元格那是最值得优化的类别关系。如果两类之间的混淆不可控可能需要重新审视类别定义或者补充波段。8. 写在最后的经验与建议真要给新手一个最高优先级建议我会说先把数据吃透再谈模型。我见过太多人把时间花在换网络结构上最后发现问题是坏波段没剔干净或者大气校正参数用错。高光谱这条链路很长任何一个环节的粗糙都可能让前面的努力归零。所以我每次开始新项目时都会强制自己先花两天时间做数据质量审查宁可前面慢一点也不让后面返工。另一个建议是多用传统方法给深度学习兜底和照镜子。比如训练一个随机森林作为baseline如果1D-CNN打不过随机森林说明问题多半不在模型能力而在数据或训练策略上。这样定位问题的速度比盲目调网络快得多。我自己习惯把传统方法和深度学习都跑一遍再做模型融合很多项目最后的精度提升恰恰来自这种不花哨的融合策略。最后想分享一个小技巧高光谱分类项目里一定要保留一份未经过任何平滑处理的原始光谱副本。因为平滑、降维都是不可逆操作一旦发现处理后的光谱出现了奇怪特征你还能回到原始数据排查是哪个环节引入的问题。这个习惯帮我避过好几次数据管道bug。高光谱数据分析的乐趣和折磨都在这里——它给你足够多的信息也给你足够多的犯错机会。希望这篇拆解能帮你少走几步我以前走过的弯路。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑