ARTICLE DETAIL

资讯详情

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

遥感影像滑坡场景分类实战:从数据预处理到PyTorch训练评估

遥感影像滑坡场景分类实战:从数据预处理到PyTorch训练评估 简介一套基于Python的遥感影像滑坡场景分类完整实现面向毕业设计、课程设计与项目开发适合需要掌握机器学习处理遥感影像的初学者与开发者。压缩包共21个文件包含Python源码、模型文件、训练/测试文本数据及说明文档等其中4个py文件覆盖特征提取、样本生成与分类等核心流程4个pkl及model、state等文件用于保存聚类器、LDA模型与视觉词表整体仅1.86MB轻量易部署。项目按照“特征提取—K-Means聚类—LDA主题分析—Libsvm分类”流程组织提取光谱统计值和GLCM纹理特征作为视觉词袋再经LDA抽象为主题分布高级特征最终用SVM完成滑坡场景识别。已有56人学习下载。随包附有项目说明文档可帮助读者快速复现实验、理解遥感影像分类中的特征工程与模型训练细节并在此基础上扩展应用。1. 做毕业设计选到「基于 Python 实现的遥感影像滑坡场景分类」这个题目很多人第一反应是这不就是个图像分类吗跑个 ResNet 就完了。真上手才发现遥感影像不是普通照片滑坡也不是猫狗那样边界清晰的目标。滑坡场景分类要做的是给一张覆盖某片山区的遥感影像判断里面有没有发生滑坡。看起来是二分类但多光谱波段、云雾遮挡、季节差异、正样本稀缺会一个个跳出来卡住你。这套任务的价值在于它是少数能把深度学习、遥感数据处理和工程化文档串在一起的入门题目毕业设计、课程设计、小规模项目开发都能用同一套流程。下面按我做这类题目的顺序讲数据怎么准备、模型怎么选、指标怎么看、坑在哪、文档怎么写。2. 动手前先定任务边界滑坡场景分类的两种定义与遥感数据准备2.1 图级场景分类还是像素级分割先选一个代码量差一倍滑坡识别在遥感领域其实有两条路线。一条是图级场景分类输入一张影像图块输出一个类别标签判断这个区域有没有发生滑坡另一条是像素级语义分割输入一张影像输出每个像素属于滑坡还是背景的掩码。标题写的是「场景分类」所以默认走图级分类这也是绝大多数毕业设计和课程设计的选题范围。这两条路线的差别直接体现在代码量上。图级分类用 torchvision 的 ImageFolder 加 ResNet 就能跑加载数据、训练、评估加起来不到 200 行分割任务要自己写 Dataset 读掩码、改 U-Net 结构、算 IoU工作量和坑都翻倍。如果你所在的课程要求里写了「识别滑坡区域」「提取滑坡边界」那才需要走分割只写「判断是否存在滑坡」「滑坡场景识别」老老实实做图级分类。还有一个常见误解是把场景分类当成目标检测。检测要画框定位滑坡的具体位置需要多边形标注正样本标注成本高场景分类只需要给整张图打标签标注成本低而且和 Python 生态里的 ImageFolder、预训练模型、迁移学习能无缝配合适合短期出成果。2.2 遥感影像下载与预处理把多波段 GeoTIFF 切成 RGB 图块数据是这一题目最大的门槛。普通分类数据集从网上下载解压就能用遥感影像你得自己下载、合成、切片。常见的数据源包括 Sentinel-2、Landsat 系列、国产高分系列以及地理空间数据云这类面向国内用户的数据平台。做滑坡场景分类Sentinel-2 的 10 米分辨率在多数情况下够用而且免费开放复现门槛低。下载时注意两件事。第一文件格式基本都是 GeoTIFF一个文件里包含多个波段不是普通图片第二不同传感器的波段顺序不一样比如 Sentinel-2 真彩色合成用的是 B4红、B3绿、B2蓝而 Landsat 8 的 RGB 波段是 B4、B3、B2顺序恰好相同但编号体系不同。做预处理前先确认波段描述别想当然。拿到原始影像后第一步是切成固定大小的图块。遥感影像动辄上万像素宽不可能整张喂进模型。我一般用 rasterio 读波段然后按 256×256 或 512×512 的窗口切片存成 JPEG 供训练使用import os import numpy as np from PIL import Image import rasterio def tif_to_rgb_tiles(tif_path, output_dir, tile_size256, stride256): 把多波段 GeoTIFF 切成 RGB 图块直接供 PyTorch ImageFolder 使用 with rasterio.open(tif_path) as src: # 先打印 src.descriptions 确认波段顺序再决定读哪几个波段 print(波段顺序:, src.descriptions) # 以 Sentinel-2 为例B4 红、B3 绿、B2 蓝 red src.read(4) green src.read(3) blue src.read(2) rgb np.stack([red, green, blue], axis-1) # (H, W, 3) h, w, _ rgb.shape os.makedirs(output_dir, exist_okTrue) idx 0 for y in range(0, h - tile_size 1, stride): for x in range(0, w - tile_size 1, stride): tile rgb[y:y tile_size, x:x tile_size] # 跳过全黑或无数据区域减少无效样本 if tile.max() 0: continue Image.fromarray(tile.astype(np.uint8)).save( os.path.join(output_dir, ftile_{idx:05d}.jpg)) idx 1 print(f切片完成: 共 {idx} 个图块 - {output_dir})几个参数说明。stride 等于 tile_size 时切出的图块没有重叠图块数量最少适合数据量还够的情况stride 小于 tile_size 时产生重叠图块能扩充样本量但同一区域会出现在多张图里后面划分数据集时要注意数据泄漏这个坑在第 5 章详细讲。tile_size 从 256 起步比较稳图块太小滑坡特征看不全图块太大又浪费显存。波段读取用 src.read(4) 而不是 src.read(4)rasterio 的波段索引从 1 开始这一点经常有人搞混导致通道错乱。远感影像下载下来之后做不做大气校正取决于你的场景。滑坡识别靠的是地表形态和色调差异如果只是做课程设计拿到的是 L2A 级表面反射率产品直接用就行只有当你需要跨时相对比或做定量分析时才需要考虑辐射定标和大气校正。预处理阶段真正该花时间的是直方图拉伸很多影像原始数值范围很窄直接转 uint8 会整体发灰我一般用 2% 线性拉伸把对比度拉开再做切片。2.3 数据集划分与目录组织ImageFolder 结构让训练代码少写一半数据切完图块接下来是标注和划分。标注没有捷径只能靠人工目视判读逐个看切片有明显滑坡痕迹滑体、滑坡后壁、碎屑堆积的放进 landslide 文件夹没有的放进 non_landslide 文件夹。判读时有一个容易忽略的点植被已经恢复的老滑坡痕迹很淡这类样本要不要算正样本直接决定你的模型行为。建议在文档里写清楚你的正样本定义比如「目视可辨的滑坡体或滑坡痕迹」否则答辩时老师拿一张老滑坡影像问你「这算不算滑坡」你答不上来。标注完成后目录按 ImageFolder 约定组织训练代码几乎不用写数据加载逻辑data/ ├── train/ │ ├── landslide/ │ └── non_landslide/ ├── val/ │ ├── landslide/ │ └── non_landslide/ └── test/ ├── landslide/ └── non_landslide/划分脚本用 Python 写很直接关键是固定随机种子保证每次运行结果一致这也是实验可复现的基本要求import os import shutil import random from glob import glob def split_dataset(source_dir, output_dir, train_ratio0.7, val_ratio0.15): 按类别把已标注影像划分成 train/val/test 三份 random.seed(42) # 固定种子确保可复现 os.makedirs(output_dir, exist_okTrue) for cls in os.listdir(source_dir): cls_dir os.path.join(source_dir, cls) if not os.path.isdir(cls_dir): continue imgs glob(os.path.join(cls_dir, *.jpg)) glob(os.path.join(cls_dir, *.tif)) random.shuffle(imgs) n_train int(len(imgs) * train_ratio) n_val int(len(imgs) * val_ratio) parts { train: imgs[:n_train], val: imgs[n_train:n_train n_val], test: imgs[n_train n_val:], } for split_name, split_imgs in parts.items(): dst os.path.join(output_dir, split_name, cls) os.makedirs(dst, exist_okTrue) for img_path in split_imgs: shutil.copy(img_path, dst) print(f{cls}: {split_name} {len(split_imgs)} 张) # 用法 split_dataset(labeled_tiles, data)提示如果滑坡正样本总共只有几十张按 0.7/0.15/0.15 划分后测试集可能只剩几张不够评估。正样本少于 100 张时建议只分 train/val 两份把 val 当测试用或者做 K 折交叉验证别为了形式硬切三份。划分完的目录配合 torchvision 的 ImageFolder两行代码就把数据集和类别映射建好了from torchvision import datasets train_ds datasets.ImageFolder(data/train) print(train_ds.classes) # [landslide, non_landslide] print(train_ds.class_to_idx) # {landslide: 0, non_landslide: 1}注意 class_to_idx 是按文件夹名字母序排列的landslide 排在 non_landslide 前面所以正类是索引 0。后面写评估代码时别把正负类搞反混淆矩阵的解读全靠这个映射。3. 模型选型与训练骨架用 ResNet 迁移学习把基线跑起来3.1 为什么选 ResNet18 做基线参数量、复现难度与预期准确率滑坡场景分类的样本量通常在几百到几千张这种规模下模型选型的核心不是「谁准确率最高」而是「谁能在小数据上稳定收敛、容易复现、好解释」。几个候选模型放在一起对比就很清楚模型参数量几百张样本的预期复现难度建议ResNet18约 11M稳定 85% 以上低PyTorch 直接调用首选ResNet50约 25M容易过拟合低样本上千再考虑ViT-B/16约 86M需要大量数据容易崩中不推荐做基线自建 CNN不定难到 80%中不推荐ResNet18 的残差结构在遥感小数据集上有天然优势网络不深参数量适中配合 ImageNet 预训练权重做迁移学习几百张样本就能收敛到可用的准确率。ViT 虽然在新数据集上表现亮眼但 transformer 需要大量数据拟合归纳偏置滑坡正样本往往只有一两百张用它做基线纯属给自己添堵。ResNet50 比 18 深一倍数据不够时验证集损失先降后升过拟合会很典型。另一个选型理由是 PyTorch 生态对 ResNet 的支持最完整。torchvision.models 里 resnet18 一行调用预训练权重自动下载后续换 ResNet50 或 101 只需要改一行代码实验对比成本极低。答辩时老师问「为什么选这个模型」你可以回答残差结构缓解梯度消失适合中小规模数据集微调预训练权重提供了良好的初始化缓解遥感样本不足的问题。这个回答逻辑是完整的。3.2 train.py 最小骨架PyTorch 训练主循环与参数说明选型定了直接写训练脚本。下面这份 train.py 是完整的、能跑的骨架我按「数据增强 → 模型构建 → 训练循环 → 验证保存」四段组织方便你在答辩时对着代码讲思路import os import torch import torch.nn as nn from torch.utils.data import DataLoader from torchvision import datasets, transforms, models def get_model(num_classes2): 加载 ImageNet 预训练的 ResNet18替换最后分类头 model models.resnet18(pretrainedTrue) model.fc nn.Linear(model.fc.in_features, num_classes) return model def evaluate(model, loader, device): 验证集准确率 model.eval() correct, total 0, 0 with torch.no_grad(): for imgs, labels in loader: imgs, labels imgs.to(device), labels.to(device) preds torch.argmax(model(imgs), dim1) correct (preds labels).sum().item() total labels.size(0) return correct / total def main(): device cuda if torch.cuda.is_available() else cpu batch_size, epochs, lr 16, 30, 1e-4 # 训练集用增强验证集只用缩放归一化保证评估稳定 train_tf transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(p0.5), transforms.RandomVerticalFlip(p0.5), transforms.RandomRotation(degrees15), transforms.ColorJitter(brightness0.2, contrast0.2), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ]) val_tf transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ]) train_ds datasets.ImageFolder(os.path.join(data, train), train_tf) val_ds datasets.ImageFolder(os.path.join(data, val), val_tf) train_loader DataLoader(train_ds, batch_sizebatch_size, shuffleTrue, num_workers2) val_loader DataLoader(val_ds, batch_sizebatch_size, shuffleFalse, num_workers2) model get_model(len(train_ds.classes)).to(device) criterion nn.CrossEntropyLoss() optimizer torch.optim.Adam(model.parameters(), lrlr) best_acc 0.0 for epoch in range(epochs): model.train() total_loss 0.0 for imgs, labels in train_loader: imgs, labels imgs.to(device), labels.to(device) optimizer.zero_grad() loss criterion(model(imgs), labels) loss.backward() optimizer.step() total_loss loss.item() val_acc evaluate(model, val_loader, device) print(fEpoch {epoch1:02d}/{epochs} | loss{total_loss/len(train_loader):.4f} | val_acc{val_acc:.2%}) # 只在验证准确率创新高时保存权重防止最后几轮过拟合覆盖好模型 if val_acc best_acc: best_acc val_acc os.makedirs(checkpoints, exist_okTrue) torch.save(model.state_dict(), checkpoints/best.pth) print(f训练完成最佳验证准确率 {best_acc:.2%}) if __name__ __main__: main()几个参数的使用逻辑说清楚。学习率 1e-4 是迁移学习的常见起点直接从头训练才需要 1e-3 量级用 1e-4 微调 ImageNet 权重既能让新分类头学起来又不会把预训练特征冲掉。batch_size 16 在 224×224 输入下6GB 显存的显卡基本够跑显存不足优先减半而不是改模型。epochs 30 对小数据集偏多但配合「验证准确率创新高才保存」的逻辑多跑几轮也没有副作用反而是训练曲线更完整写实验报告时更有素材。有两个容易翻车的小点。第一Windows 上 DataLoader 的 num_workers 设为 0否则多进程加载会在 Jupyter 或某些 IDE 环境里反复报错Linux 服务器上设 2 或 4 能明显提速。第二Normalize 的均值和标准差用的是 ImageNet 统计值这是迁移学习的标准做法你不需要自己重新统计遥感影像的均值除非你打算完全从头训练。3.3 正样本不足和多波段输入两个必须提前处理的选型问题训练起来之后最先暴露的两个问题几乎必然出现正样本少导致模型偏置多波段影像和预训练权重不匹配。正样本少的典型表现是 loss 降得很顺但验证时 landslide 类的召回率极低。原因是交叉熵损失对多数类偏向模型发现全预测为非滑坡就能拿到很低的 loss。最常见的解法是给交叉熵损失加类别权重让少数类的错误付出更大代价# 按训练集样本数计算类别权重正样本少就给它更高的权重 class_counts [train_ds.targets.count(i) for i in range(len(train_ds.classes))] max_count max(class_counts) weights [max_count / c for c in class_counts] class_weights torch.tensor(weights, dtypetorch.float).to(device) criterion nn.CrossEntropyLoss(weightclass_weights)这段代码放在 get_model 之后、训练循环之前。注意 train_ds.targets 是 ImageFolder 按文件夹顺序生成的标签列表和 class_to_idx 对应。权重计算方法很朴素多数类权重为 1少数类权重 多数类样本数 / 少数类样本数。如果两类样本数是 500 比 50滑坡类权重就是 10相当于把滑坡样本的 loss 放大 10 倍。多波段的问题是另一类翻车现场。Sentinel-2 有 13 个波段很多人想保留近红外波段提升植被和裸地的区分度但 ImageNet 预训练的 ResNet 第一层卷积只接受 3 通道输入。两条路大多数情况直接取 RGB 三波段简单稳定配合合适的拉伸效果不差想用 4 波段RGBNIR需要修改第一层卷积并复制权重import torch.nn as nn def adapt_conv1_for_bands(model, in_channels4): 把 ResNet 首层卷积改成支持 4 波段输入保留预训练权重 old_conv model.conv1 # 预训练权重 shape: (64, 3, 7, 7) new_conv nn.Conv2d(in_channels, 64, kernel_size7, stride2, padding3, biasFalse) with torch.no_grad(): new_conv.weight[:, :3] old_conv.weight # 第 4 个通道用前 3 个通道的均值初始化而不是随机初始化 new_conv.weight[:, 3] old_conv.weight.mean(dim1) model.conv1 new_conv return model为什么要用均值初始化第四个通道而不是随机值随机初始化会在一开始给模型引入剧烈噪声破坏预训练权重的稳定性均值复制至少让前向传播的输出量级和原模型接近。不过说句实在话课程设计和毕业设计的样本量下RGBN 四波段带来的提升通常不显著还要额外写预处理逻辑。我一般建议先跑 RGB 基线如果 F1 卡在瓶颈再尝试加 NIR 波段把结论写进实验报告反而显得你考虑周全。4. 训练评估与推理闭环指标、调参顺序与 predict.py4.1 评估指标混淆矩阵、F1 与滑坡类召回率才是关键滑坡场景分类有一个典型陷阱Accuracy 虚高。假设验证集里 500 张非滑坡、50 张滑坡模型把所有图都判成非滑坡Accuracy 有 90.9%看起来「效果不错」实际上滑坡一张都没识别出来。所以训练脚本里的 evaluate 只能算中间检查项目交付时必须上混淆矩阵和分类报告from sklearn.metrics import confusion_matrix, classification_report, f1_score def detailed_evaluate(model, loader, device, target_names): 输出混淆矩阵、Precision/Recall/F1 分类报告 model.eval() all_preds, all_labels [], [] with torch.no_grad(): for imgs, labels in loader: imgs, labels imgs.to(device), labels.to(device) preds torch.argmax(model(imgs), dim1).cpu().numpy() all_preds.extend(preds) all_labels.extend(labels.cpu().numpy()) cm confusion_matrix(all_labels, all_preds) print(混淆矩阵 (行真实, 列预测):) print(cm) print(\n分类报告:) print(classification_report(all_labels, all_preds, target_namestarget_names)) print(f滑坡类 F1 分数: {f1_score(all_labels, all_preds):.4f}) return all_preds, all_labels # 调用示例 # detailed_evaluate(model, val_loader, device, [non_landslide, landslide])实际使用中我重点看三个数滑坡类的召回率漏检率直接关系应用价值、滑坡类的 F1精度和召回的综合、非滑坡类的精度误报率。混淆矩阵则帮你定位错误模式——如果非滑坡被误判为滑坡的样本大量集中在裸地、河流冲积扇、采石场这些视觉上与滑坡相似的地物这就是一个值得写进文档的发现。特别提醒一个阈值问题。模型的输出是 softmax 概率默认取 0.5 作为分类阈值但对滑坡这类「漏检代价高」的任务可以主动调低阈值提升召回率# 不用 argmax改用概率阈值滑坡召回率不够时把阈值从 0.5 降到 0.4 probs torch.softmax(outputs, dim1)[:, 1] # 索引 1 是滑坡类 preds (probs 0.4).long()阈值降多少看你对漏检和误报的权衡。答辩时能说出「我把阈值从 0.5 调到 0.4滑坡召回率从 82% 提到 91%代价是非滑坡误报率从 6% 升到 9%这个代价在实际应用中可接受」这种表述比单纯报一个准确率有说服力得多。4.2 训练参数范围与调参顺序先稳住 loss 再谈准确率调参不是玄学但乱调确实很浪费时间和算力。我的习惯是固定一套顺序先确认 loss 能下降再调学习率最后调数据增强和数据量模型结构尽量不动。超参数推荐范围失败时的排查方向学习率1e-4 ~ 5e-4loss 震荡则减半loss 不降则先冻结 backbonebatch size8 ~ 32显存不够就减半BN 层在小 batch 下不稳定epochs20 ~ 50连续 5 轮 val_acc 不涨就早停优化器Adam betas(0.9, 0.999)想提上限可换 SGD momentum0.9loss 迟迟不降时最常见的做法是先冻结 backbone只训练分类头几个 epoch让新分类头先适应预训练特征的分布再解冻全模型微调def freeze_backbone(model, freezeTrue): 冻结或解冻 ResNet 除 fc 外的全部参数 for name, param in model.named_parameters(): if name.startswith(fc): param.requires_grad not freeze else: param.requires_grad freeze # 先冻结训练 5 轮 freeze_backbone(model, freezeTrue) # ... 训练 5 个 epoch ... # 再解冻全部参数训练 freeze_backbone(model, freezeFalse)冻结 backbone 的阶段建议把学习率调到 1e-3因为此时只训练 fc 层参数量小收敛快解冻后再降回 1e-4 微调避免破坏预训练特征。这个「先粗后细」的调参顺序在遥感小数据集上非常管用也是答辩时能讲清楚的工程经验。4.3 模型保存与独立推理脚本predict.py 写得让答辩老师能直接跑训练产出的 best.pth 只是权重文件项目要能交付必须有一个独立的预测脚本。所谓独立指的是不依赖训练代码只要有 PyTorch 环境就能跑单张影像的推理。我写的 predict.py 会把模型路径、输入影像、置信度阈值都做成命令行参数答辩现场拿一张没见过的影像直接跑import argparse import torch import torch.nn as nn from PIL import Image from torchvision import transforms, models CLASS_NAMES [non_landslide, landslide] # 和 train_ds.classes 顺序一致 def load_model(model_path, num_classes2): 加载权重并切到推理模式注意 map_location 保证无 GPU 也能跑 model models.resnet18(pretrainedFalse) model.fc nn.Linear(model.fc.in_features, num_classes) model.load_state_dict(torch.load(model_path, map_locationcpu)) model.eval() return model def predict_single(image_path, model, threshold0.5): 对单张影像做推理返回类别和置信度 tf transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ]) img Image.open(image_path).convert(RGB) tensor tf(img).unsqueeze(0) with torch.no_grad(): prob torch.softmax(model(tensor), dim1)[0] landslide_prob prob[1].item() label CLASS_NAMES[1] if landslide_prob threshold else CLASS_NAMES[0] return label, landslide_prob def main(): parser argparse.ArgumentParser(description遥感影像滑坡场景分类推理) parser.add_argument(--image, requiredTrue, help待预测的影像路径) parser.add_argument(--model, defaultcheckpoints/best.pth, help权重路径) parser.add_argument(--threshold, typefloat, default0.5, help滑坡判定阈值) args parser.parse_args() model load_model(args.model) label, prob predict_single(args.image, model, args.threshold) print(f影像: {args.image}) print(f滑坡概率: {prob:.2%} | 判定结果: {label}) if __name__ __main__: main()命令行用法是python predict.py --image test_001.jpg --model checkpoints/best.pth --threshold 0.4。三个参数的意义分别对应影像路径支持 jpg/png/tiftif 会被 PIL 自动转 RGB、模型权重路径、置信度阈值。用map_locationcpu是因为演示机器大概率没装 GPU 版 PyTorch提前兼容能避免答辩时当场报 CUDA 错误。这个脚本的价值在于它把「训练」和「使用」解耦了。答辩时老师不会关心你怎么训练的但会关心「你训练出来的东西能不能用」predict.py 就是回答这个问题的最好证据。5. 滑坡场景分类五个常见翻车点现象、原因与排查手记下面这五个问题是我和身边人做遥感分类题目攒下来的血泪经验每一条都按「现象 → 原因 → 解决」的顺序写照着排查能省好几天。5.1 通道数不匹配4 波段影像直接喂进 ResNet 报错现象训练脚本在 DataLoader 加载第一个 batch 时报RuntimeError: Given groups1, weight of size [64, 3, 7, 7], expected input to have 3 channels, but got 4 channels instead或者类似 shape mismatch 的报错。原因GeoTIFF 默认保留了所有波段预处理时如果只做了切片没做波段选择读出来的图块就是 4 通道RGB NIR甚至 13 通道的数组而 ResNet 的第一层卷积只接受 3 通道。解决回到第 2.2 节的切片脚本确认用src.read(4), src.read(3), src.read(2)只取三个波段再合成 RGB如果坚持用 4 波段走第 3.3 节的adapt_conv1_for_bands改模型结构。排查时在切片脚本里加一行print(tile.shape)看到(256, 256, 4)就说明波段没选对。5.2 模型把所有影像都判成非滑坡正负样本失衡现象训练 loss 正常下降验证准确率能到 90% 以上但打开混淆矩阵发现滑坡类精度和召回率全为 0模型把所有输入都输出为非滑坡。原因滑坡正样本通常只有几十到一两百张非滑坡可能上千张交叉熵损失在样本比例悬殊时倾向多数类。准确率虚高就是被这类样本失衡骗了只盯着 Accuracy 根本发现不了问题。解决先加类别权重nn.CrossEntropyLoss(weightclass_weights)是最快的修正如果加权重后训练震荡再用 WeightedRandomSampler 对少数类过采样每次迭代有更高概率抽到滑坡样本。判断是否修好不看准确率看混淆矩阵里 landslide 行有没有非零值。5.3 验证集 95% 测试集 60%切块造成的数据泄漏现象训练时验证准确率一路涨到 95%自我感觉良好拿独立测试集一测只有 60%答辩现场直接翻车。原因第 2.2 节切片时如果 stride 小于 tile_size相邻图块有重叠更常见的是同一幅大影像的不同区域被随机分到了 train 和 val。模型在训练时见过来自同一场景的像素验证时当然「眼熟」一遇到真正没见过的影像就露馅。解决切块时记录每张图块来自哪幅原始影像在文件命名里加原始影像 ID划分数据集时按影像 ID 分组保证同一幅影像的所有图块进同一个划分。最简单的做法是切片时用tile_{image_id}_{idx:05d}.jpg命名划分脚本里按前缀分组而不是逐张 shuffle。这个坑最隐蔽也最致命一定要在文档里写明你的划分策略。5.4 loss 不降、验证准确率卡在 50%迁移学习在遥感影像上失效现象预训练权重加载成功但训练十几轮后 loss 还在 0.69 附近验证准确率稳定在 50% 左右跟随机猜一样。原因多数情况下不是模型坏了而是输入分布和预训练数据差异太大。常见诱因有三个RGB 波段选错导致颜色通道错乱、影像没有做拉伸对比度过低、学习率设太高把预训练权重冲坏了。解决按顺序排查。先保存一张预处理后的训练样本图片肉眼确认颜色正常再检查预处理里有没有做Resize((224, 224))原始影像分辨率太高直接进模型会触发缩放异常最后把学习率降到 1e-5 试几个 epoch如果 loss 开始下降就说明之前是学习率问题回到 1e-4 继续。冻结 backbone 只训练分类头也是一个有效的兜底手段能把问题范围缩小一半。5.5 文档和代码对不上答辩被问一句就穿现象答辩时老师问「你的学习率为什么是 1e-4」「loss 曲线为什么有毛刺」「这个阈值 0.4 怎么定的」学生翻代码翻半天最后说「我试出来的」然后就没有然后了。原因训练过程中参数随手改改完没记录代码里只留了最终版本中间过程没有存档。这是课程设计和毕业设计最常见的失分点不是技术问题是工程习惯问题。解决从第一次跑通基线开始维护一份实验记录表每改一个参数就记一行包含日期、模型、学习率、数据增强策略、验证准确率、F1、备注代码里每个关键参数旁边写注释说明为什么取这个值。答辩被问参数时直接翻实验记录表回答「第 3 轮实验从 1e-4 调到 5e-4验证 F1 下降 4 个点所以维持 1e-4」这个回答的杀伤力远大于「我试出来的」。6. 交稿前补齐三件事文档主线、实验记录表与答辩现场验证6.1 项目文档的一页式主线与四张必备图项目文档最常见的毛病是写成流水账先写环境安装再写代码结构最后贴几个结果老师看完不知道你解决了什么问题。更好的写法是沿着一条主线组织背景与问题定义 → 数据来源与预处理 → 方法选型理由 → 实验设计与结果 → 结论与不足。这条线就是你在第 2 到第 4 章做的事情文档只是把它用文字固化下来。文档里必须放四张图数据样本图正负样本各几张带标注说明、模型结构图用 PyTorch 的模型结构打印或手绘 ResNet 示意、loss 和验证准确率曲线、混淆矩阵热力图。这四张图对应数据处理、模型设计、训练过程、评估结果四个环节答辩 PPT 可以直接复用比大段文字高效得多。6.2 实验记录表让老师一眼看到你的调参过程实验记录表放在项目文档的附录里格式不用复杂一个表格就够实验编号模型输入波段学习率数据增强验证准确率滑坡 F1备注E01ResNet18RGB3e-4基础翻转87.2%0.81基线E02ResNet18RGB1e-4增强亮度89.5%0.85增强有效E03ResNet18RGBN1e-4增强亮度88.1%0.834 波段无提升E04ResNet50RGB1e-4增强亮度90.2%0.86有过拟合风险这个表本身就在回答问题为什么最终选 ResNet18 而不是 ResNet50因为 E04 验证集涨了但测试集没涨过拟合。为什么用 1e-4因为 E01 和 E02 对比说明 1e-4 更稳。老师的追问一张表全部兜住。6.3 答辩现场验证拿三张没见过的影像当场跑推理答辩前准备三张模型没见过的影像一张典型滑坡、一张典型非滑坡、一张边缘场景比如裸岩或采石场。现场跑python predict.py --image 那张图重点不是结果全对而是你能解释为什么。典型滑坡判对了说「滑坡体呈浅色调、边界不规则模型在滑坡类上召回率达到 91%」边缘场景如果判错就借机说「这类样本和裸岩视觉特征相似训练数据里这类负样本偏少这是当前方案的已知局限后续可以补充数据或加注意力机制」。把翻车现场变成方案讨论比假装模型完美无缺更让老师信服。我养成的习惯是每次实验先在记录表里补一行再动下一个参数。看起来每次只多花两分钟答辩前能省出大量返工时间。这套流程从数据到文档走完一轮你就知道遥感影像滑坡场景分类真正花时间的不是模型训练而是数据整理和实验管理——把这两块做扎实项目自然立得住。希望帮到你。本文还有配套的精品资源点击获取
返回列表