ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

遥感图像分类实战:ResNet残差网络训练与推理全解析

遥感图像分类实战:ResNet残差网络训练与推理全解析 简介基于ResNet的遥感图像分类识别项目主要面向遥感图像分析学习者和深度学习初学者利用残差网络解决高分辨率、多光谱影像中建筑物、道路、水体等复杂地物的自动分类问题在地物识别、土地利用分析等场景具有实践价值也适合课程设计或入门项目参考。压缩包共10个文件以7个Python脚本为主将数据集加载、残差网络构建、训练、推理及GUI操作等模块分离2个Markdown文档补充环境搭建、代码说明与使用步骤另附1个结果记录txt整体仅14KB结构紧凑但流程完整。目前已有147人浏览学习。通过运行代码可直观了解从数据预处理、数据增强到模型评估的完整管线文档对关键步骤进行了说明能帮助降低上手门槛源码模块划分清晰便于进一步修改网络结构、调整训练策略或迁移到其他遥感场景。1. 遥感图像分类识别用 ResNet 把地物判读从「玄学」变成可复现先抛个结论遥感图像分类识别这件事难的不是训练而是把一整条链路跑通。你从网上下一个带源码的 ResNet 遥感分类项目解压之后发现文件十几二十个不知道先打开哪个、先跑哪条命令这是最常见的卡点。这个资源就是为此准备的——它把数据加载、网络构建、训练、推理、GUI 预测打包成了一整套可运行工程属于那种「拿到手就能在本地把流程走一遍」的完整代码包。它适用的任务很具体识别遥感影像里的地物类别比如建筑、道路、水体、农田、裸地、植被这一类。适合三类人正在入门深度学习的开发者做地信或遥感数据处理、想快速拿到一个分类 Bench 的工程师以及课程设计或论文实验需要对比结果的研究生。下面我按「原理先立住、再一步步复现、最后讲坑」的顺序把它拆开。2. 项目结构拆解与 ResNet 原理先看懂代码再动手这一章解决「这是什么、为什么选它、怎么运行」三个问题。资源包里的文件一眼看去容易乱但实际每个文件都有明确分工先弄清每个文件的定位再动手能避免很多白跑的弯路。2.1 资源包里到底有什么逐文件定位先看这份代码包的完整文件清单我按功能把它们分了个组文件定位作用README.md入口文档项目说明、快速开始方式help_doc.md辅助文档环境搭建、模块依赖说明get_logger.py公共工具日志打印与落盘配置rs_dataset.py数据层遥感图像读取、预处理、数据增强res_network.py模型层ResNet 网络定义含残差块结构trian_resnet.py训练入口模型训练主脚本注意文件名拼写infer.py推理脚本加载权重进行单图或多图预测tst.py测试脚本批量测试评估输出分类结果gui.py可视化图形界面加载模型、选图预测results.txt输出记录训练或推理结果的文本保存从这个清单能看出它不是某个论文的残缺复现而是一条完整的工程链数据入口rs_dataset→ 网络定义res_network→ 训练trian→ 评估infer/tst→ 交互gui。我的习惯是先看 README 和 help_doc再跑一遍 infer 确认环境没问题最后才碰训练脚本。直接从训练开始的人最容易因为漏装依赖而浪费时间。2.2 ResNet 为什么适合遥感图像从残差块说起ResNet 在 2015 年提出核心创新就是残差块和跳跃连接。传统 CNN 如 VGG靠堆叠卷积层加深网络但当层数超过十几层时梯度回传会因连乘效应变得极小或极大导致训练退化——这种现象在遥感图像上尤其明显因为遥感影像分辨率高、纹理复杂浅层模型根本学不出有效特征可盲目加深又训不动。残差块的数学表达为输出 F(x) x。其中 x 是输入F(x) 是一组卷积、批归一化、激活函数的组合结果。它把网络的优化目标从「直接学习 H(x)」变成了「学习残差 H(x) - x」。这个改动的价值在于如果某层不需要额外变换网络可以学出让 F(x)0 的参数输出退化为恒等映射 x梯度可以直接通过跳跃连接传回浅层从根上缓解了「网络越深越难训」的问题。遥感图像分类恰好需要这种能力——地物的边缘、纹理属于低层特征建筑物、水体、道路这类语义概念属于高层特征需要很深的网络才能把两者融合。ResNet 的多阶段设计早期卷积抓边缘后期卷积抓语义天然贴合这个需求。2.3 环境搭建与运行路径虽然 help_doc.md 里写了环境教程我还是把最常用的搭建命令整理出来先在命令行确认 Python 和显卡状态# 建议用 conda 建独立环境避免污染系统 Python conda create -n resnet_rs python3.8 -y conda activate resnet_rs # 安装 PyTorch 全家桶CUDA 版本按本机驱动选择 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 图像处理与基础库 pip install numpy opencv-python pillow matplotlib搭建完推荐按这个顺序走流程先打开 help_doc.md 确认模型权重路径和数据路径再运行python infer.py看能不能出预测结果如果推理正常说明环境和模型文件都完整然后再用训练脚本从头训。这个顺序能在半小时内确认「代码包本身没问题」之后再改动参数就不会有「到底是我写错了还是环境缺东西」的困惑。需要说明的是有的代码包会用到pandas、scikit-learn这类辅助库遇到ModuleNotFoundError时缺什么装什么即可不一定要照着 help_doc 全量安装。3. 数据流水线与网络实现把遥感图变成模型输入这一章深入代码的实现层是最值得逐行读的部分。遥感图像和自然图像最大的差异是单张影像尺寸大、地物尺度变化大、多光谱通道多。数据层处理不好模型再先进也学不出东西。3.1 rs_dataset.py 解读裁剪、归一化与数据增强几乎所有的 PyTorch 数据脚本都是Dataset类加DataLoader的结构这个项目里的rs_dataset.py也不例外。核心逻辑在__getitem__方法里我按常见写法还原它的骨架import torch from torch.utils.data import Dataset from PIL import Image import numpy as np class RSDataset(Dataset): def __init__(self, img_paths, labels, transformNone): self.img_paths img_paths # 图像路径列表 self.labels labels # 类别标签列表 self.transform transform # 预处理与增强管线 def __len__(self): return len(self.img_paths) def __getitem__(self, idx): # 遥感图像可能是 16bit 深度的 TIFF注意转成 8bit 或直接归一化 image Image.open(self.img_paths[idx]).convert(RGB) label self.labels[idx] if self.transform: image self.transform(image) return image, torch.tensor(label, dtypetorch.long)这里有几个对遥感特别重要的点。第一Image.open()之后convert(RGB)是必须的——部分遥感图是单通道灰度不转会报通道数错误。第二标签用torch.long因为CrossEntropyLoss不接受 float 标签。第三真正的预处理逻辑通常在 transform 里常见做法是# 训练阶段的组合先放大后裁剪 随机翻转 归一化 transform_train transforms.Compose([ transforms.Resize((256, 256)), transforms.RandomCrop((224, 224)), transforms.RandomHorizontalFlip(), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ])Resize((256, 256))后再RandomCrop((224, 224))是一种常规操作先放大到比目标尺寸大一些再随机裁剪相当于引入了位置偏移的数据增强。Normalize的 mean 和 std 是 ImageNet 的统计值遥感图像虽然和自然图像有差异但大多数代码包仍然采用这套参数实测收敛速度会比不归一化快很多。如果你的遥感图是多光谱比如 8 通道 Sentinel 数据就不能直接convert(RGB)需要按通道读取后单独处理这是后话。3.2 res_network.py 解读残差块的 forward 写法res_network.py里定义的是 ResNet 本体。核心组件是残差块无论是BasicBlock还是Bottleneckforward 写法都遵循一个模式import torch.nn as nn import torch.nn.functional as F class BasicBlock(nn.Module): def __init__(self, in_channels, out_channels, stride1, downsampleNone): super(BasicBlock, self).__init__() self.conv1 nn.Conv2d(in_channels, out_channels, kernel_size3, stridestride, padding1, biasFalse) self.bn1 nn.BatchNorm2d(out_channels) self.conv2 nn.Conv2d(out_channels, out_channels, kernel_size3, stride1, padding1, biasFalse) self.bn2 nn.BatchNorm2d(out_channels) self.downsample downsample def forward(self, x): identity x out F.relu(self.bn1(self.conv1(x)), inplaceTrue) out self.bn2(self.conv2(out)) if self.downsample is not None: # 通道数变化或特征图尺寸减半时需要对输入做映射 identity self.downsample(identity) out identity # 跳跃连接残差相加 out F.relu(out, inplaceTrue) return out这段代码最关键的是identity x和out identity这行相加操作。如果只是普通卷积堆叠网络学到的是x - H(x)的映射有了这条捷径后每层模块只需要学H(x) - x的残差。downsample参数决定了当通道数翻倍或步长为 2 时输入如何对齐到输出的形状——通常用1x1卷积实现这就是很多同学看到的conv1x1直接连接。整个ResNet50由四个 stage 组成各 stage 的残差块数量是[3, 4, 6, 3]最后的全连接层输出数量要改成你的类别数。遥感分类里常见的设定是 6 类建筑、道路、水体、农田、植被、裸地对应最后一层nn.Linear(2048, 6)。如果资源里预训练权重是 1000 类 ImageNet 的你想微调就只替换这一层其他层参数原样加载。3.3 数据加载参数怎么设DataLoader的参数设置直接影响训练速度和显存占用这里给出我常用的配置from torch.utils.data import DataLoader train_loader DataLoader( train_dataset, batch_size32, shuffleTrue, num_workers4, pin_memoryTrue, drop_lastFalse )batch_size可调性最高显存 8G 以下用 168G 以上用 32再大就得配合梯度累积。num_workers在 Windows 上建议设 0 或 2设高了容易报 DataLoader worker 崩溃Linux 服务器上 4 到 8 没问题。pin_memoryTrue能减少 CPU 到 GPU 的传输时间只要内存够就建议开。drop_last在最后一轮批次不足时会导致 BatchNorm 统计量抖动遥感类别多、数据量大的时候一般不开。还有个容易被忽略的点遥感原图往往非常大比如 6000x4000 像素的卫星影像直接整图进网络会撑爆显存。常见做法是切块——把大幅影像切成 224x224 或 256x256 的小 patch然后对这些 patch 做分类。rs_dataset.py如果输入的是切好的图块路径那说明数据预处理已经帮你做了这步如果输入的是大图路径你要么自己在脚本里加切块逻辑要么先把图离线切好。4. 训练与推理调优trian_resnet.py 和 infer.py 的实操路线数据层和网络结构都弄清楚后接下来就是真正的训练与推理。这一章我会把训练脚本的关键配置逐项讲清楚同时教你怎么从results.txt里判断模型到底练得怎么样。4.1 训练脚本的关键配置trian_resnet.py里最核心的是优化器、损失函数、学习率策略和模型保存。常见做法是import torch.optim as optim model res_network.ResNet50(num_classes6) criterion nn.CrossEntropyLoss() # 优化器选 SGD 还是 Adam遥感分类里 SGD 收敛更稳 optimizer optim.SGD(model.parameters(), lr0.01, momentum0.9, weight_decay1e-4) # 学习率动态下降每 10 个 epoch 缩小 10 倍 scheduler optim.lr_scheduler.StepLR(optimizer, step_size10, gamma0.1) best_acc 0.0 for epoch in range(30): model.train() running_loss 0.0 for images, labels in train_loader: images images.to(device) labels labels.to(device) optimizer.zero_grad() outputs model(images) loss criterion(outputs, labels) loss.backward() optimizer.step() running_loss loss.item() * images.size(0) scheduler.step() # 每个 epoch 后保存最优模型 val_acc evaluate(model, val_loader) if val_acc best_acc: best_acc val_acc torch.save({ epoch: epoch, model_state_dict: model.state_dict(), optimizer_state_dict: optimizer.state_dict(), best_acc: best_acc, }, best_model.pth)这段代码有几个值得说明的细节。第一个是全连接层替换如果代码里没做迁移学习你应该把num_classes改成匹配遥感数据集的实际类别数。第二个是关键配置表我直接给出推荐值:超参数推荐值说明batch_size32显存小就降为 16配合梯度累积初始学习率0.01SGD/ 0.001Adam加载预训练权重时用 0.001 更稳优化器SGD momentum 0.9Adam 收敛快但精度上限一般不如 SGDweight_decay1e-4防止过拟合遥感小数据集尤其重要epoch30 起步看验证集 acc 不再上升就早停学习率策略StepLR(step10, gamma0.1)后期用小学习率精调第三个是模型保存格式上面用的是完整 checkpoint包含model_state_dict和optimizer_state_dict好处是中断训练后可以恢复断点继续跑。如果你只想保存权重用于推理用torch.save(model.state_dict(), model_weights.pth)就够了文件更小、加载也简单。需要恢复训练时只能加载完整 checkpoint 的格式。4.2 训练日志怎么看get_logger.py的作用是让训练过程输出的信息既打到控制台又写入日志文件。运行训练脚本后你会看到类似下面的输出[2025-06-01 10:23:01] Epoch [1/30] Loss: 1.7823 Acc: 18.25% [2025-06-01 10:27:45] Epoch [2/30] Loss: 1.3210 Acc: 42.37% [2025-06-01 10:32:10] Epoch [3/30] Loss: 0.9862 Acc: 61.08%判断训练是否正常先看 loss 的下降曲线。CrossEntropyLoss的初始值大约等于类别数的自然对数如果是 6 类任务初始 loss 在 1.79 左右是正常的。如果第一个 epoch 后 loss 低于 0.1 或 acc 突然冲到 99%那大概率是数据泄漏或标签错乱后面会细说。我一般会记录「每 5 个 epoch」的验证集 acc看它是否仍在上升。如果超过 5 个 epoch 持续不涨就调整学习率或增加数据增强。Early Stopping可以简单实现定义patience为 10连续 10 个 epoch 验证集 acc 没创新高就停止训练并恢复最优权重。4.3 推理与评估从 results.txt 读结论训练完成后infer.py负责加载权重对单张或多张图像做预测。标准的推理流程如下import torch from PIL import Image from torchvision import transforms device torch.device(cuda if torch.cuda.is_available() else cpu) model res_network.ResNet50(num_classes6) checkpoint torch.load(best_model.pth, map_locationdevice) # 注意如果保存的是完整 checkpoint要取 model_state_dict 字段 model.load_state_dict(checkpoint[model_state_dict]) model.to(device) model.eval() # 推理时的预处理要保持和训练时一致除了数据增强 transform transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) image Image.open(test_area.png).convert(RGB) image transform(image).unsqueeze(0).to(device) with torch.no_grad(): outputs model(image) probs torch.softmax(outputs, dim1) # 转成概率分布 pred torch.argmax(probs, dim1).item() print(f预测类别索引: {pred}, 置信度: {probs[0][pred].item():.4f})推理阶段有两点极易踩坑。第一一定要加with torch.no_grad()否则模型计算图会被保留显存迅速耗尽尤其是在批量测试时。第二model.eval()和model.train()的区别对 BatchNorm 层影响极大——eval 模式用全局移动平均统计量train 模式用当前 batch 统计量忘记切模式会导致推理结果严重偏移。加载权重时如果checkpoint是字典格式必须用checkpoint[model_state_dict]而不是直接传给load_state_dict这是新手最容易报错的地方。results.txt一般记录的是测试集上的评估结果。建议至少看三项总体准确率、每个类别的召回率、混淆矩阵。遥感图像分类最常见的坑是类别不均衡——水体样本多、建筑物样本少模型会把所有不确定的图块都判成水体总体准确率看着不错但建筑物类别的召回率可能只有 20%。所以在看results.txt时别只盯着总 acc逐类看 precision 和 recall 才能发现问题。5. 踩坑记录遥感分类里最容易翻车的五个真实问题这套代码包我在不同环境、不同数据集上跑过多次以下五个问题是最高频的翻车现场。每一条都是「现象 → 原因 → 解决」的完整链路建议你在碰到同类问题时直接对照排查。5.1 现象跑 trian_resnet.py 直接 ModuleNotFoundError明明按照 help_doc 装了依赖但一执行训练脚本报ModuleNotFoundError: No module named cv2或torchvision。原因是环境混乱——你pip install到的包和运行脚本时激活的 conda 环境不是同一个 Python 解释器。排查方法在命令行分别执行which python和pip list | grep torch确认 pip 安装的包归属当前环境。解决先conda activate resnet_rs再执行python -m pip install torch torchvision numpy opencv-python用python -m pip的方式能确保包装进当前解释器。注意torch 和 torchvision 必须装匹配版本比如 torch 2.0 配 torchvision 0.15否则会出现undefined symbol的诡异报错。5.2 现象训练 loss 几乎不下降或者第一轮就降到一个极低值先看初始 loss。如果 6 类任务初始 loss 在 0.005 附近那大概率是模型没有正确初始化——例如全连接层之前就接了Softmax而损失函数又用了带Softmax的CrossEntropyLoss导致梯度被双熟练层吞掉。正确的做法是网络最后一层输出未归一化的 logitsCrossEntropyLoss内部自带 Softmax两者不能重复。如果初始 loss 正常但完全不见下降常见原因是学习率过大导致梯度在小范围内震荡或者 BatchNorm 层在model.train()和model.eval()之间切换出了问题。解决先用 0.001 学习率跑 5 个 epoch 确认 loss 能降再调大或调小同时检查优化器初始化时是否传入了model.parameters()一个常见的低级错误是把model本身而非参数迭代器传给SGD。5.3 现象CUDA out of memory遥感图像分辨率高如果直接用原图尺寸进入网络显存很容易爆掉。我之前处理过 12000x12000 的大规模影像单张图砸进去显存直接不够。解决有两个方向一是把输入尺寸降下来——统一 resize 到 512x512 或切块到 224x224绝大多数遥感分类任务并不需要原分辨率二是调整DataLoader参数——batch_size从 32 降到 16 或 8num_workers从 4 降到 2。如果降 batch 后训练速度变慢可以用梯度累积模拟大 batchaccumulation_steps 4 # 相当于 batch_size 8 * 4 32 optimizer.zero_grad() for i, (images, labels) in enumerate(train_loader): outputs model(images) loss criterion(outputs, labels) / accumulation_steps loss.backward() if (i 1) % accumulation_steps 0: optimizer.step() optimizer.zero_grad()这种方式在显存受限时很实用代价是训练时间会增加且 BatchNorm 的统计量会随 batch 大小变化需要适当调低学习率。5.4 现象推理结果全部是同一个类别模型训练完成且准确率不低但推理时所有图都判成「水体」或「裸地」。原因有两个需要对号入座。第一个是类别不均衡训练集中某一类占比超过 70%模型学到的是「全猜这一类就能拿到不错的 loss」对策是在损失函数里加类别权重CrossEntropyLoss(weightclass_weights)class_weights按类别样本数的倒数计算或直接用torch.nn.CrossEntropyLoss(weighttorch.tensor([...]))传入。第二个是推理预处理和训练不一致比如训练时做了归一化、推理时忘了归一化导致输入分布完全偏移。解决打印一条推理样本的预测分布若softmax输出的每个类别概率都接近均匀则大概率是预处理不一致若某一类概率接近 1则是类别不均衡或数据泄漏。5.5 现象gui.py 加载模型失败窗口打开后闪退GUI 模块报错集中在两类。一类是FileNotFoundError因为图形界面默认加载best_model.pth而你没训练过、或模型保存到了其他路径。解决在 gui.py 里加一个显式的模型路径选择或者在加载前检查路径存在性。另一类是num_classes不匹配——GUI 脚本里写死了num_classes1000因为参考了预训练权重但你的模型是 6 类加载权重时维度对不上直接报错。解决把res_network.py里的num_classes改成与训练时一致的数值两侧保持一致。GUI 本质是调用已经训练好的模型做单张可视化预测不要指望它能帮你训练。6. 进阶用法把 GUI 变成调试台再做三个能提升模型精度的改造最后这一章不讲泛泛而谈的优化建议只讲我自己拿来提高结果可信度的三个具体动作。第一个动作是把gui.py从「演示工具」变成「调试台」。GUI 除了能选图、跑预测、显示类别名之外最重要的是能看到置信度数值。我在做多类别遥感图预测时遇到预测置信度普遍低于 0.7 的情况直接放弃优化网络先检查数据。因为这种表现通常意味着输入图块里混入了大量类别边界区域——比如一个图块里既有一半建筑又有一半道路模型输出自然会模棱两可。解决办法是在数据切块时设置重叠率overlap让每个地物目标尽量完整出现在至少一个图块中。这是个数据切块层面的技巧比调网络结构见效快得多。第二个动作是在残差块里加入 SE 模块提升关键通道的权重。SE 模块的全称是 Squeeze-and-Excitation它对每个通道做一个全局平均池化再用两层全连接拟合出通道权重把重要的特征通道放大、不重要的压缩。实现代码很短class SEBlock(nn.Module): def __init__(self, channels, reduction16): super(SEBlock, self).__init__() self.fc1 nn.Linear(channels, channels // reduction) self.fc2 nn.Linear(channels // reduction, channels) def forward(self, x): b, c, _, _ x.size() w F.adaptive_avg_pool2d(x, 1).view(b, c) w F.relu(self.fc1(w)) w torch.sigmoid(self.fc2(w)).view(b, c, 1, 1) return x * w.expand_as(x)然后把SEBlock插入到残差块中两个卷积之后、跳跃连接相加之前即可。这个改动对遥感图像尤其有效比如水体在近红外波段有很强的特征SE 模块会自动放大相关通道的响应让分类更稳。代价是参数量略有增加训练时间多约 10%但精度提升通常在 2 到 5 个百分点属于性价比很高的改动。第三个动作是训练前固定随机种子。这条看起来不起眼却是在做算法对比时最重要的「后悔药」。PyTorch 里很多操作是随机的——参数初始化、数据加载打乱顺序、Dropout每一步都会影响最终结果。如果不固定种子同一套代码同一份数据跑两遍得到的 val_acc 可能差 2% 以上这时候你根本分不清是模型改了还是随机性导致的波动。我每次跑训练脚本前都会强制执行这一段import random import torch import numpy as np def set_seed(seed42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) torch.backends.cudnn.deterministic True torch.backends.cudnn.benchmark False set_seed(42)需要注意的是cudnn.deterministic True会让卷积算子选择固定算法可能有微小的速度损失但对结果的可复现性来说值得。从那以后我每次跑遥感图像训练都强制走一遍「固定种子 → 检查初始 loss → 小学习率跑通 5 个 epoch → 再调参」的流程翻车率直线下降。希望帮到你。本文还有配套的精品资源点击获取
返回列表