
简介面向遥感场景图像分类与迁移学习入门者这份资源提供了基于EfficientNet轻量级网络的完整图像识别项目。项目覆盖b0至b7共8种模型可通过参数选择是否加载官方预训练权重、是否冻结特征层并集成Adam、SGD、AdamW优化器与余弦退火学习率策略训练过程兼顾灵活性与收敛效果。资源内含约1.5万张25类遥感场景数据及对应标签配套3个Python脚本、标签txt和readme说明总计2000个文件压缩包约297MB可直接一键运行训练与评估。评估环节输出训练/验证集的loss与准确率曲线并生成混淆矩阵、recall、precision、F1 score等详细指标到json文件方便量化模型性能。当前已有71人学习适合想快速上手EfficientNet迁移学习并完成遥感识别实战的开发者也可据readme更换数据集复用。1. 遥感场景图像识别为什么需要轻量级网络和迁移学习遥感场景图像识别与自然图像分类有一个本质差异拍摄视角垂直向下地物没有语义上的正方向同一类场景在不同分辨率、季节、光照下纹理差异极大。以 NWPU-RESISC45 这类公开基准为例45 类里既有结构规整的机场、立交桥也有纹理高度近似的森林和灌木丛类间距离小、类内方差大。手里只有几千张标注切片随机初始化训练深层 CNN很快就会在训练集上满分过拟合。迁移学习加轻量级网络是当前性价比最高的解法EfficientNet 的 MBConv 结构把参数压在同等精度模型的几分之一B0 约 5.3M 参数遥感任务输入普遍 224 或 256一张消费级显卡就能跑完整个流程。下面的路线很直接先搞清 EfficientNet 为什么适合迁移到遥感数据再给出预处理、训练循环、参数调优到混淆矩阵与 ONNX 导出的可照抄方案。新手能跟步骤跑通熟手可以直接看第 4 章的参数边界和第 5 章的验证技巧。2. EfficientNet 迁移学习原理与遥感场景数据准备2.1 复合缩放与 MBConvEfficientNet 轻量省在哪EfficientNet 的核心贡献是复合缩放compound scaling用一组系数 φ 同时放大深度、宽度和输入分辨率而不是像 ResNet 那样只加深。自动搜索得到的基线 B0 由 MBConv 倒残差块堆叠每个块内部嵌了 SESqueeze-and-Excitation通道注意力。SE 只增加少量参数却能重新校准通道权重这对遥感场景尤其关键——森林和灌木丛在 RGB 直方图上高度重叠靠通道间相关性建模才能把它们分开。从 B0 到 B4 精度逐步上升参数量和显存占用也随之增长。遥感场景识别我一般只用 B0 到 B3这类任务输入分辨率本来就小B4 以上的增益大多来自更高分辨率输入单卡训练的压力会翻倍。表 2-1 列出常用变体规模方便选型时对照。表 2-1 EfficientNet 变体规模与遥感任务选型参考变体输入尺寸参数量ImageNet Top-1单卡训练建议B02245.3M77.1%快速基线首选B12407.8M79.1%中等数据规模B22609.2M80.1%数据量充足的主力B330012.2M81.6%先确认显存余量和 ResNet50 放在一起看更直观ResNet50 约 25.6M 参数精度和 B3 相当参数量却是 B3 的两倍多。迁移学习场景下参数量小还意味着另一层好处冻结主干后只需更新分类头反向传播显存占用低可以开更大的 batch。顺带提一句最新的图像分类模型里也有 ViT 这类 transformer 结构ImageNet 上精度更高但迁移到只有几千张图的遥感数据集时全局自注意力的数据需求反而成为负担预训练权重迁移的效果常常不如轻量 CNN 稳定所以 EfficientNet 仍是小样本遥感分类的稳妥开局。2.2 归纳式微调与直推式迁移学习遥感场景怎么选迁移学习在遥感场景识别里有两条路线。第一种是特征提取加微调也就是把 ImageNet 预训练的 EfficientNet 主干当作特征器替换分类头先冻结主干训分类头再解冻部分层统一微调。这是遥感分类最主流的做法属于归纳式迁移学习目标函数一致、工程上最可控。第二种是直推式迁移学习transductive transfer learning它不假设源域与目标域共享同一个分类函数而是直接对未标注的目标域数据做分布适配或标签传播。遥感业务里大量无标注影像天然契合这个思路看起来很有吸引力但它对每一批新场景都要重新做适配和验证流程成本远高于微调。实操建议是先做微调建立基线如果发现某一类精度明显偏低再用伪标签做半监督扩充而不是一开始就上直推式方案。基线的存在能让后续一切改进都有对照。2.3 遥感图像预处理参数归一化不准自己算旋转增强必须上EfficientNet 预训练权重在 ImageNet 上训练得到输入分布对应均值 [0.485, 0.456, 0.406]、标准差 [0.229, 0.224, 0.225]。遥感影像的原始强度范围和自然图像不同但既然用了预训练权重就得迁就它的统计分布不要自己统计数据集均值再归一化——那等于把预训练特征的分布底座换掉属于从零训练的玩法。import timm from torchvision import transforms # 与 ImageNet 预训练权重配套的归一化常数不要自行修改 IMAGENET_MEAN [0.485, 0.456, 0.406] IMAGENET_STD [0.229, 0.224, 0.225] train_tf transforms.Compose([ # scale 下限取 0.5遥感切片裁剪太小会丢失地物上下文 transforms.RandomResizedCrop(224, scale(0.5, 1.0)), transforms.RandomHorizontalFlip(p0.5), # 遥感图像没有固定朝向90 度整数倍旋转不破坏语义 transforms.RandomRotation(90, expandFalse), transforms.ColorJitter(brightness0.2, contrast0.2), transforms.ToTensor(), transforms.Normalize(IMAGENET_MEAN, IMAGENET_STD), ]) val_tf transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(IMAGENET_MEAN, IMAGENET_STD), ])逻辑说明训练侧用 RandomResizedCrop 加 RandomRotation遥感影像里同一栋建筑从不同角度拍都算正样本90 度旋转不破坏语义RandomRotation(90) 只旋转整个倍数避免边界插值产生大面积黑边。验证侧不做任何随机增强Resize 到 256 再 CenterCrop 到 224是为了保持和训练一致的分辨率并裁掉无关边缘。参数说明scale(0.5, 1.0) 比 ImageNet 常用的 (0.08, 1.0) 范围窄很多因为遥感地物识别依赖上下文裁剪过小会丢失邻接关系。如果显存有富余、数据多为高分辨率切片可以把输入统一提到 300 并换 B3精度通常还能上 1 个点左右。2.4 数据目录组织与 tif 输入的坑遥感数据集多数按类别分目录torchvision 的 ImageFolder 可以直接用。有两个坑需要先处理一是公开数据集文件名常带空格和中文Linux 下没问题Windows 下要统一重命名为 label_id_filename.jpg二是目录里不能有隐藏文件比如 .DS_Store 会被当成图片解析报错。我习惯在数据装载脚本里先扫一遍扩展名只保留 .jpg、.jpeg、.png、.tif再统一转成 8 位 RGB——16 位 tif 直接给 torchvision 会读出全黑图先用 PIL 或 OpenCV 转码再进 DataLoader 才是正确姿势。提示新版 timm 的 pretrainedTrue 仍可用但会提示改用 weights 参数两种写法加载的权重一致不必纠结版本告警。3. 用 timm PyTorch 搭 EfficientNet 遥感分类训练主流程3.1 加载预训练权重与分类头替换timm 把 EfficientNet 的全系列变体和预训练权重都收在 create_model 里替换分类头只需传 num_classes。遥感公开数据集类别数一般是 21UC Merced、30AID或 45NWPU-RESISC45和 ImageNet 的 1000 类完全不同。import timm import torch.nn as nn def build_model(num_classes21, variantefficientnet_b0, freeze_backboneTrue): # timm 的 create_model 传入 num_classes 会自动替换分类头 model timm.create_model( variant, pretrainedTrue, num_classesnum_classes, ) if freeze_backbone: # 冻结除 classifier 以外的全部参数只训分类头 for name, param in model.named_parameters(): if classifier not in name: param.requires_grad False return model逻辑说明传入 num_classes 后timm 会把最后的全连接层替换成对应输出维度不需要手动改 model.classifier 再对齐 in_features。冻结主干后反向传播只经过分类头那一小段显存占用很低可以开大 batch 观察初始损失下降是否正常。参数说明variant 换成 efficientnet_b1、efficientnet_b2、efficientnet_b3 即可升级8G 显存以下固定用 B0。需要保留原 1000 类输出时不要传 num_classes但迁移学习场景下基本都要换掉。3.2 三段式训练循环冻结主干、解冻 stage、整体微调遥感场景识别迁移学习的典型节奏是三个阶段先只训分类头 5 到 8 个 epoch再解冻最后一个 stage 微调最后全量解冻用更低学习率整体微调。第一阶段的意义是让随机初始化的分类头先适应预训练特征分布避免一开始就反向传播大梯度把主干冲坏。import torch from torch.optim import AdamW from torch.optim.lr_scheduler import CosineAnnealingLR def train_one_epoch(model, loader, criterion, optimizer, device): model.train() total_loss, correct, total 0.0, 0, 0 for images, labels in loader: images, labels images.to(device), labels.to(device) logits model(images) loss criterion(logits, labels) optimizer.zero_grad() loss.backward() # 轻量网络梯度范数本来就小clip 防离群样本破坏主干 torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm5.0) optimizer.step() total_loss loss.item() * images.size(0) correct (logits.argmax(dim1) labels).sum().item() total images.size(0) return total_loss / total, correct / total逻辑说明交叉熵损失加 AdamW 是迁移学习的默认组合weight_decay 取 1e-4遥感数据集小权重衰减过强会把预训练特征压得过度平滑。clip_grad_norm_ 设 5.0 是防御性手段防止个别难样本产生的大梯度破坏主干。参数说明第一阶段学习率可以放宽到 3e-3因为只更新分类头不碰主干第二、第三阶段必须分别降到 1e-4 和 5e-5 级别。切换阶段时要重新构造 optimizer只把 requires_gradTrue 的参数传进去否则 AdamW 的状态里会残留冻结层的动量解冻后第一次更新方向会被旧状态污染。# 第二阶段解冻最后一个 stagelr 降到 1e-4 for name, param in model.named_parameters(): if blocks.7 in name or classifier in name: param.requires_grad True optimizer AdamW( [p for p in model.parameters() if p.requires_grad], lr1e-4, weight_decay1e-4 ) scheduler CosineAnnealingLR(optimizer, T_max10)这段代码的逻辑是EfficientNet-B0 的 features 包含 8 个 blocks 阶段blocks.7 是最后一个阶段解冻它加分类头一起训练。T_max 设为 10 表示 10 个 epoch 内余弦退火到 0配合前面 5 到 8 个 epoch 的分类头训练整个流程 15 个 epoch 左右收尾。3.3 三个必调参数速查表 3-1 迁移学习训练参数速查参数B0 推荐值B3 推荐值取值依据分类头阶段 lr3e-32e-3主干冻结时可偏大全微调阶段 lr1e-48e-5解冻主干必须压低batch size6432显存允许的最大值不要小于 16每阶段 epoch8~108~10超过 15 容易过拟合输入分辨率224300与显存成正比batch size 的边界要单独强调小于 16 时 BatchNorm 统计量抖动明显EfficientNet 的 MBConv 依赖 BN遥感数据类内方差又大batch 分布不稳会直接反映在 loss 曲线上。显存不够时优先降分辨率而不是降 batch。4. EfficientNet 迁移学习在遥感数据上的收敛与过拟合排查4.1 学习率与 warmup先把损失曲线读对迁移学习最常见的失败现象是损失一开始就震荡。原因通常有两个分类头随机初始化后第一轮正向输出方差大步子太大导致梯度爆炸或者数据集小一个 batch 恰好集中了难分样本。常见做法是加 2 到 3 个 epoch 的线性 warmup把学习率从 0 缓升到目标值。from torch.optim.lr_scheduler import LambdaLR def warmup_lr(epoch, warmup_epochs3): # warmup 阶段线性从 0 升到 1 if epoch warmup_epochs: return (epoch 1) / warmup_epochs return 1.0 scheduler_warmup LambdaLR(optimizer, lr_lambdawarmup_lr)逻辑说明LambdaLR 按 epoch 返回学习率缩放因子warmup 阶段因子从 1/warmup_epochs 逐步升到 1之后再交给余弦退火。整个学习率曲线是缓升快降对轻量网络特别友好因为 EfficientNet 的收敛窗口本来就窄前期不稳定很容易把它带偏。参数说明warmup_epochs 取 2 到 5数据集越小 warmup 越长。如果第一阶段冻结主干只训分类头warmup 可以省略分类头梯度相对温和不需要缓升。4.2 类别不平衡遥感场景里的难分类对遥感场景类别数大体均衡但模型容易把语义相近的类互相混淆比如河流、湖泊、湿地三者再比如密集住宅区和中等住宅区。混淆矩阵里这类错误不是随机噪声而是系统性特征混淆。按优先级处理先提升输入分辨率再看是否需要按类别重采样。from torch.utils.data import WeightedRandomSampler def build_sampler(labels, num_samples_per_epoch4000): # 按类别频率倒数作为采样权重稀有类被抽到的概率更高 class_counts torch.bincount(torch.as_tensor(labels), minlength21).float() class_weights 1.0 / (class_counts 1e-6) sample_weights class_weights[torch.as_tensor(labels)] return WeightedRandomSampler(sample_weights, num_samples_per_epoch)逻辑说明WeightedRandomSampler 每个 epoch 按权重有放回采样稀有类每轮被多采。num_samples_per_epoch 必须固定成常量否则每个 epoch 迭代步数不一致学习率调度和日志对比都会乱。参数说明类别数量本身均衡时不要用采样器保持原始分布训练强行加权会压低多数类精度整体准确率反而下降。4.3 过拟合实时判据训练集与验证集差值遥感场景类内方差大过拟合往往比自然图像分类来得慢但一旦出现就是灾难训练集接近满分验证集停在 85% 左右不再动。我每两个 epoch 记录一次训练与验证的差值差值超过 8 个点就介入手段按顺序是加 CutMix 增强、去掉 ColorJitter遥感影像颜色抖动会引入伪纹理、冻结最后两个 stage 的表征层。与其等到过拟合再补救不如一开始就把 timm 提供的 drop_path_rate 从 0 提到 0.1。EfficientNet 内置随机深度遥感影像背景连续性强随机深度等于隐式集成对验证集抖动有明显的抑制作用。这个参数在通用图像分类里常被忽略但在遥感小数据集上收益比 Dropout 更直接。4.4 失败模式对照表表 4-1 迁移学习遥感分类常见问题速查现象原因处理方式训练损失不下降学习率过大或分类头没替换学习率降到 1e-4 以下检查 num_classes验证精度波动大BN 统计量不稳增大 batch 或降分辨率保 batch个别类完全分不出类间纹理重叠提分辨率用 Grad-CAM 看关注区域解冻主干后掉点学习率过高破坏预训练权重全微调阶段 lr 不超过 5e-5全微调阶段要对主干和分类头分开设学习率这是经常被忽略的细节。常见的做法是用参数组主干参数 lr 乘 0.1分类头保持原值。原因是分类头从零训练需要相对大的学习率收敛而主干特征是预训练来的只能小步调整统一学习率的结果往往是主干被冲坏或分类头欠拟合。5. EfficientNet 遥感分类模型的精度验证与部署混淆矩阵、Grad-CAM 与 ONNX 导出5.1 混淆矩阵定位系统误差训练完不要只看 top-1 准确率。把验证集的预测和真实标签排成混淆矩阵重点观察哪些类两两互混。比如河流和湖泊大量误分说明模型学到的是大片蓝色区域而不是形状与邻接关系这时候提分辨率比调损失函数更有效。用 sklearn 的 confusion_matrix 配合 seaborn 的 heatmap 画图把对角线之外的错误按类别对聚合排序优先处理数量最大的三个错误对。5.2 用 Grad-CAM 验证判别区域遥感场景识别里模型可能学到无关线索比如某类训练样本边缘恰好都有道路。对 features 中最后一个 MBConv block 的输出注册 forward hook 拿特征图用类别最大 logit 对特征图的梯度做全局平均加权就能得到热力图。热力图如果集中在图像边缘说明训练集划分有泄漏集中在目标地物内部才是真正学到了语义。timm 的 efficientnet 各层有固定命名hook 路径直接引用 model.features[-1] 即可。5.3 ONNX 导出与推理验证通过后导出 ONNX。EfficientNet 的 SE 模块包含全局平均池化和逐点卷积ONNX Runtime 的优化算子覆盖得很好。导出时把模型切到 eval 模式、固定输入尺寸遥感推理切片是任意大小的导出固定尺寸反而省事推理前用 OpenCV 把切片按长边缩放即可。导出前删掉分类头的 softmax归一化和后处理都留给外部脚本ONNX 的输入输出保持原始张量语义。import torch model.eval() dummy torch.randn(1, 3, 224, 224) # 输出不带 softmax归一化与后处理都在外部脚本做 torch.onnx.export( model, dummy, efficientnet_b0_rs21.onnx, input_names[input], output_names[logits], opset_version13, dynamic_axes{input: {0: batch}, logits: {0: batch}}, )逻辑说明dynamic_axes 只放开 batch 维度输入尺寸保持 224 固定这样 ONNX Runtime 能走静态 shape 优化路径遥感切片推理按长边缩放再中心裁剪不需要动态分辨率。opset 13 覆盖 SE 模块里的所有算子兼容性最稳。5.4 验证阶段的 TTA 技巧最后给一个性价比最高的验证分数提升做法TTA。对每张验证图做中心、水平翻转、垂直翻转、双翻转四次预测再取平均遥感场景类间边界往往就在零点几个百分点的置信度差距上四路平均能把这类边际预测拉回正确侧。实现上只需要在 DataLoader 阶段构造四个 transform 的副本分别前向推理后对 logits 取均值不需要改模型和训练代码验证集上通常能带来 0.5 到 1.5 个点的提升。这是部署前最后一道不出错就能拿到的精度红利。本文还有配套的精品资源点击获取