资讯动态

遥感图像分类识别实战:ResNet迁移学习与可复现落地路径

发布时间:2026/10/9 3:13:14 来源:尧图企业网站定制
简介这份资源面向具备一定Python与深度学习基础、希望上手遥感图像分类的开发者与研究者围绕ResNet残差网络解决高分辨率、多光谱遥感图像中地物特征复杂、类别区分困难的问题。压缩包共10个文件以7个py脚本为主辅以2个md说明与1个txt结果文件整体约14KB涵盖数据读取、网络定义、训练、推理与日志等模块结构紧凑便于按流程阅读。已有147人学习下载说明其在同类小型实战项目中具备一定参考价值。读者可据此了解ResNet残差块如何缓解深层网络梯度消失并借助跳跃连接提取从边缘纹理到建筑、道路、水体的多层次特征同时可参考数据预处理、模型构建、训练优化与精度、召回率等评估环节的代码组织方式作为遥感分类任务的练手模板或课程设计起点。1. 遥感图像分类识别从 ResNet 迁移学习到可复现的落地路径遥感图像分类识别这件事真正上手做过的人都知道难点从来不在“选哪个网络”而在“数据怎么喂、预训练权重怎么接、增强怎么配、指标怎么读”。我见过太多人拿到一份基于 ResNet 的遥感图像分类识别代码包跑通训练脚本就以为大功告成结果换一份数据集直接翻车——类别不均衡、影像波段数不对、验证集泄漏全是血泪经验。这篇笔记面向两类人一是刚接触遥感场景、想用 ResNet 快速搭出可用分类器的工程师二是已经跑过 ImageNet 分类、但没处理过遥感影像特殊性的熟手。我会把“为什么选 ResNet”“数据管线怎么搭”“参数怎么调”“坑在哪”按可复现的顺序讲清楚让你读完能直接动手改自己的数据集。2. ResNet 为什么仍是遥感分类的稳妥起点残差、预训练与选型边界遥感图像分类识别这个任务本质上和自然图像分类共享同一套骨架输入固定尺寸的影像块输出类别概率。但遥感影像有几个绕不开的特性——目标尺度差异大、类间相似度高比如不同农作物光谱接近、标注成本高导致样本量小。ResNet 能在这种场景下长期占据主流靠的不是“最新”而是残差连接带来的可训练深度和成熟的迁移学习生态。2.1 残差连接解决的到底是什么问题深层网络在反向传播时梯度会随着层数增加而衰减导致浅层参数几乎不更新。ResNet 的残差块把输入直接加到输出上形成y F(x) x的结构。这样梯度可以通过恒等映射路径直接回传即使F(x)学不到东西网络至少能保持恒等变换不会比浅层网络更差。对遥感分类来说这个特性意味着你可以放心用 ResNet-50 甚至 ResNet-101 去拟合复杂的地物边界而不必担心训练不收敛。实际项目中我一般从 ResNet-50 起步因为它在精度和显存占用之间比较平衡如果数据集类别少于 10 类且样本量在万级以下ResNet-18 或 ResNet-34 往往就够了再深反而容易过拟合。2.2 预训练权重怎么选、怎么接遥感领域没有 ImageNet 那样大规模的标注数据集所以几乎所有人都走迁移学习路线。常见做法是加载 ImageNet 预训练权重替换最后的全连接层然后分阶段微调。import torch import torch.nn as nn from torchvision import models def build_resnet(num_classes, backboneresnet50, pretrainedTrue): # 加载 torchvision 内置的 ResNet自动下载 ImageNet 预训练权重 if backbone resnet18: model models.resnet18(weightsmodels.ResNet18_Weights.DEFAULT if pretrained else None) elif backbone resnet34: model models.resnet34(weightsmodels.ResNet34_Weights.DEFAULT if pretrained else None) elif backbone resnet50: model models.resnet50(weightsmodels.ResNet50_Weights.DEFAULT if pretrained else None) else: raise ValueError(f不支持的 backbone: {backbone}) # 替换分类头原版输出 1000 类改成自己的类别数 in_features model.fc.in_features model.fc nn.Linear(in_features, num_classes) return model # 示例8 类遥感地物用 ResNet-50 model build_resnet(num_classes8, backboneresnet50, pretrainedTrue)这段代码的关键点有三个。第一weights参数用新版 API旧版pretrainedTrue在 torchvision 0.13 之后会走弃用警告虽然还能跑但建议统一改掉。第二只替换fc层前面的卷积层全部保留预训练参数这是迁移学习的标准操作。第三num_classes必须和你的标签映射一致后面做标签编码时要用同一套顺序。参数选择上如果你用的是多光谱影像比如 4 波段或 8 波段ResNet 第一层卷积默认接收 3 通道需要手动改。常见做法是把第一层卷积的权重在通道维度上做平均或复制再微调# 将 3 通道预训练权重适配到 4 通道输入 old_conv model.conv1 new_conv nn.Conv2d(4, old_conv.out_channels, kernel_sizeold_conv.kernel_size, strideold_conv.stride, paddingold_conv.padding, biasFalse) with torch.no_grad(): # 前 3 个通道复制预训练权重第 4 个通道取前三者均值 new_conv.weight[:, :3] old_conv.weight new_conv.weight[:, 3] old_conv.weight.mean(dim1) model.conv1 new_conv这样做的理由是预训练权重在 RGB 通道上学到的边缘、纹理特征对多光谱的前几个波段仍然有效直接随机初始化会浪费掉这部分先验。第 4 个通道用均值初始化比全零更稳定。2.3 什么情况下不该用 ResNetResNet 不是万能钥匙。如果你的遥感影像分辨率极低比如 10 米级 Sentinel-2 做城市地物分类目标在影像上只有几个像素深层卷积的感受野反而会引入过多背景噪声这时候浅层网络或轻量级模型可能更合适。另外如果类别极度不均衡且少数类样本只有几十张ResNet 的参数量容易过拟合需要配合强增强或改用度量学习思路。选型时先看数据再看模型别反过来。3. 从原始影像到 DataLoader遥感数据管线的四个关键环节遥感图像分类识别的数据管线比自然图像麻烦得多因为原始数据往往是大幅影像加标签矢量需要先切图、再对齐、再做增强。这一章按“切图 → 划分 → 增强 → 加载”的顺序把每个环节的可执行步骤和参数说清楚。3.1 大幅影像切块与标签对齐遥感影像通常是一整张大图配一份标签文件Shapefile 或 GeoJSON。分类任务需要把大图切成固定尺寸的小块同时保证每个小块有明确的类别标签。常见做法有两种按标签中心点切或按滑动窗口切后按重叠面积分配标签。import rasterio from rasterio.windows import Window import numpy as np def slide_crop(image_path, label_path, crop_size224, stride112): 滑动窗口切图返回影像块和对应标签 with rasterio.open(image_path) as src: img src.read() # 形状 (bands, H, W) H, W src.height, src.width patches [] coords [] for y in range(0, H - crop_size 1, stride): for x in range(0, W - crop_size 1, stride): patch img[:, y:ycrop_size, x:xcrop_size] # 跳过全黑或全透明的无效块 if patch.sum() 0: continue patches.append(patch) coords.append((y, x)) return np.array(patches), coordscrop_size一般取 224 或 256和 ResNet 的输入尺寸对齐。stride控制重叠率取crop_size // 2可以保证边界目标不被切碎但会产生更多样本训练时间相应增加。如果显存紧张可以把 stride 调大代价是边缘样本减少。标签对齐时要注意坐标系一致。影像和标签矢量必须在同一个 CRS 下否则切出来的块和标签对不上。我一般先用rasterio读影像的 CRS再用geopandas把标签转成同一 CRS然后做空间连接。3.2 训练集、验证集、测试集怎么分才不泄漏遥感数据有个隐蔽的泄漏风险相邻的切块来自同一片区域如果随机划分训练集和验证集里会存在高度相似的样本导致验证指标虚高。正确做法是按空间位置分块划分比如按经纬度网格把研究区切成若干子区整块子区划入训练或验证。from sklearn.model_selection import GroupKFold import numpy as np # 假设 coords 是每个样本的中心点坐标group 按空间网格编号 groups np.array([f{y//1000}_{x//1000} for y, x in coords]) gkf GroupKFold(n_splits5) for train_idx, val_idx in gkf.split(patches, labels, groupsgroups): X_train, X_val patches[train_idx], patches[val_idx] y_train, y_val labels[train_idx], labels[val_idx] # 用同一组划分训练和验证避免空间泄漏GroupKFold保证同一空间网格的样本只出现在训练或验证一侧。如果数据量足够我更推荐按地理区域手动划分比如用一条河流或道路作为边界这样更贴近实际部署时的泛化场景。3.3 遥感场景下的增强策略自然图像常用的随机裁剪、翻转、颜色抖动在遥感场景下要谨慎使用。翻转和旋转通常安全因为地物方向不敏感但颜色抖动会改变光谱特征对多光谱分类可能有害。我一般用以下组合import albumentations as A from albumentations.pytorch import ToTensorV2 train_transform A.Compose([ A.RandomRotate90(p0.5), # 90 度旋转保持光谱不变 A.HorizontalFlip(p0.5), A.VerticalFlip(p0.5), A.RandomResizedCrop(224, 224, scale(0.8, 1.0)), # 尺度增强 A.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), # ImageNet 统计量 ToTensorV2() ]) val_transform A.Compose([ A.Resize(224, 224), A.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ToTensorV2() ])RandomRotate90比任意角度旋转更安全因为不会引入插值伪影。RandomResizedCrop的scale下限不要低于 0.6否则小目标会被裁没。归一化用 ImageNet 统计量是因为预训练权重基于该分布如果换成自定义均值方差第一层卷积的响应会偏移需要更多轮次才能适应。3.4 DataLoader 的 batch 与 worker 设置from torch.utils.data import DataLoader, Dataset class RemoteSensingDataset(Dataset): def __init__(self, patches, labels, transformNone): self.patches patches self.labels labels self.transform transform def __len__(self): return len(self.labels) def __getitem__(self, idx): # patches 形状 (C, H, W)转成 (H, W, C) 给 albumentations img self.patches[idx].transpose(1, 2, 0) if self.transform: img self.transform(imageimg)[image] return img, self.labels[idx] train_loader DataLoader( RemoteSensingDataset(X_train, y_train, train_transform), batch_size32, shuffleTrue, num_workers4, pin_memoryTrue, drop_lastTrue )batch_size从 32 起步显存不够就降到 16 或 8同时把学习率按比例调小。num_workers设为 CPU 核数的 1/4 到 1/2太多会导致 IO 争抢。drop_lastTrue避免最后一个不完整 batch 影响 BatchNorm 统计。pin_memoryTrue在 GPU 训练时能加速数据传输。4. 训练、调参与评估让 ResNet 在遥感数据上真正收敛数据管线搭好后训练本身反而简单但遥感场景有几个参数需要特别关注。这一章讲学习率策略、类别不均衡处理、以及指标怎么读才不被误导。4.1 分阶段微调与学习率设置迁移学习的标准流程是先冻结卷积层只训练分类头再解冻全部层用小学习率微调。这样做的好处是避免随机初始化的分类头在初期产生大梯度破坏预训练权重。import torch.optim as optim from torch.optim.lr_scheduler import CosineAnnealingLR # 阶段一只训练 fc 层 for param in model.parameters(): param.requires_grad False for param in model.fc.parameters(): param.requires_grad True optimizer optim.Adam(model.fc.parameters(), lr1e-3) # 训练 5 个 epoch # 阶段二解冻全部层小学习率微调 for param in model.parameters(): param.requires_grad True optimizer optim.Adam(model.parameters(), lr1e-4) scheduler CosineAnnealingLR(optimizer, T_max50, eta_min1e-6)阶段一的学习率可以设 1e-3因为只更新一层。阶段二降到 1e-4 或 1e-5避免大梯度冲垮预训练特征。CosineAnnealingLR的T_max设为总 epoch 数eta_min设 1e-6让学习率平滑衰减到接近零。4.2 类别不均衡的三种处理方式遥感分类的类别不均衡很常见比如城市区域里“建筑”类占 70%“水体”类只有 5%。直接训练会导致模型偏向多数类。常见做法有三种方法实现方式适用场景加权损失给少数类更高权重类别数少、不均衡程度中等重采样对少数类过采样或多数类欠采样样本量充足数据增强对少数类做更强增强少数类样本极少加权损失最省事直接在 CrossEntropyLoss 里传weightfrom collections import Counter import torch class_counts Counter(y_train) total sum(class_counts.values()) # 权重与类别频率成反比 weights torch.tensor([total / (len(class_counts) * class_counts[i]) for i in range(num_classes)], dtypetorch.float32) criterion nn.CrossEntropyLoss(weightweights.cuda())注意权重不要设得过于极端否则模型会对少数类过拟合验证集上多数类指标暴跌。我一般把权重上限控制在 10 倍以内。4.3 评估指标总体精度之外还要看什么总体精度OA在类别不均衡时会严重虚高。一个把所有样本都预测为多数类的模型OA 可能也有 70%。所以必须同时看混淆矩阵、Kappa 系数和每类 F1。from sklearn.metrics import confusion_matrix, cohen_kappa_score, f1_score def evaluate(model, loader, device): model.eval() all_preds, all_labels [], [] with torch.no_grad(): for imgs, labels in loader: imgs imgs.to(device) outputs model(imgs) preds outputs.argmax(dim1).cpu().numpy() all_preds.extend(preds) all_labels.extend(labels.numpy()) oa (np.array(all_preds) np.array(all_labels)).mean() kappa cohen_kappa_score(all_labels, all_preds) f1 f1_score(all_labels, all_preds, averagemacro) cm confusion_matrix(all_labels, all_preds) return oa, kappa, f1, cmKappa 系数排除了随机一致性的影响比 OA 更可靠。宏平均 F1 对少数类敏感能暴露模型是否忽略了小类。混淆矩阵则帮你定位具体是哪两类容易混比如“草地”和“农田”在光谱上接近可能需要增加近红外波段或引入纹理特征。5. 避坑与排查遥感分类项目里最容易翻车的五个地方这一章记录我在实际项目里踩过的坑每条按“现象 → 原因 → 解决”写希望能帮你省下几天调试时间。5.1 验证集精度很高测试集一塌糊涂现象训练时验证集 OA 到 95%换测试集掉到 60%。原因最常见的是空间泄漏——相邻切块分到了训练和验证两侧模型记住了空间位置而不是地物特征。其次是归一化参数不一致训练用了 ImageNet 统计量测试用了自定义统计量。解决用GroupKFold按空间网格划分确保同一区域的样本只出现在一侧。归一化参数在训练和测试阶段必须完全一致建议把 mean/std 写进配置文件两边读取同一个值。5.2 多光谱影像直接套 3 通道模型第一层就报错现象RuntimeError: expected input[*, 4, 224, 224] to have 3 channels。原因ResNet 第一层卷积固定接收 3 通道4 波段影像直接输入会维度不匹配。解决按 2.2 节的方法改第一层卷积把预训练权重复制到前 3 个通道第 4 个通道用均值初始化。改完后要确认model.conv1.weight.shape是[64, 4, 7, 7]。5.3 训练损失震荡不下降学习率可能太大了现象loss 在 2.0 附近来回跳epoch 之间没有明显下降趋势。原因阶段二解冻全部层后如果学习率仍用 1e-3预训练权重会被大梯度破坏模型相当于从头训练。解决阶段二学习率降到 1e-4 或更低配合 warmup 策略前 3 个 epoch 从 1e-6 线性升到目标学习率。如果仍然震荡检查 batch size 是否太小BatchNorm 在小 batch 下统计量不稳定。5.4 少数类 F1 始终为零现象混淆矩阵里少数类全部被预测成多数类F1 为 0。原因类别权重没设或者设了但权重上限太低模型仍然偏向多数类。也可能是少数类样本在增强时被过度裁剪实际有效样本更少。解决先检查class_counts确认少数类样本数。如果少于 100优先做针对性增强旋转、镜像、加噪声而不是只调权重。权重上限可以放宽到 20 倍但要监控多数类指标是否下降超过 5%。5.5 推理时显存溢出batch 调小也没用现象训练时正常推理时CUDA out of memory把 batch 降到 1 仍然报错。原因推理时没有用torch.no_grad()中间激活值仍然保留计算图。或者模型没有切到eval()模式BatchNorm 仍在更新统计量。解决推理代码必须包在with torch.no_grad():里并且先调model.eval()。如果仍然溢出检查是否有残留的 tensor 引用未释放可以在每个 batch 后手动del输出并调torch.cuda.empty_cache()。6. 进阶技巧用测试时增强和模型集成把精度再推一档当单模型训练稳定后如果还想提升精度测试时增强TTA和模型集成是两个性价比最高的手段。TTA 不需要重新训练只在推理阶段对同一张图做多次变换把预测概率平均。模型集成则是训练多个不同初始化或不同深度的 ResNet投票或平均输出。TTA 的实现很简单对验证集做水平翻转、垂直翻转、90 度旋转各推理一次然后取平均def predict_tta(model, img_tensor, device): model.eval() transforms [ lambda x: x, lambda x: torch.flip(x, dims[3]), # 水平翻转 lambda x: torch.flip(x, dims[2]), # 垂直翻转 lambda x: torch.rot90(x, 1, dims[2, 3]) # 90 度旋转 ] probs [] with torch.no_grad(): for t in transforms: aug t(img_tensor).to(device) out torch.softmax(model(aug), dim1) probs.append(out.cpu()) return torch.stack(probs).mean(dim0)TTA 的收益通常在 1 到 3 个百分点代价是推理时间翻倍。如果业务对延迟不敏感这个投入很划算。模型集成方面我一般训练 3 个模型ResNet-50 两个不同随机种子ResNet-34 一个然后对 softmax 输出取平均。集成后的 Kappa 系数通常比单模型高 2 到 4 个点。还有一个容易被忽略的技巧把验证集上的混淆矩阵拿出来看哪两类最容易混然后针对这两类单独训练一个二分类器做后处理。比如“草地”和“农田”混淆严重可以在这两类样本上微调一个二分类 ResNet-18推理时先过主分类器如果预测为这两类之一再走二分类器确认。这种做法在类别数多、部分类光谱重叠的场景下效果明显。最后说一个我自己的习惯每次跑完实验把配置文件、随机种子、验证集指标和混淆矩阵存进一个带时间戳的目录哪怕这次没提升也留着。遥感数据标注成本高实验复现周期长没有后悔药可吃。我吃过一次亏调参调出最好结果后忘了记学习率后面重跑再也复现不出来只能从头试。希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑