首页/新闻资讯/正文详情

小样本遥感图像分类实战:WHU-RS数据集与PyTorch落地指南

发布时间:2026/9/23 3:40:33 来源:云帆数科 栏目:资讯中心
小样本遥感图像分类实战:WHU-RS数据集与PyTorch落地指南
简介WHU-RS19种土地利用类型遥感卫星图像分类数据集是一份面向遥感影像分类与深度学习实验的已标注数据集适用于计算机视觉方向的开发者与学生可用于图像分类、语义分割等模型的训练和评估尤其适合土地利用类型识别场景。压缩包内含1016个文件其中1006张jpg遥感图像、1个json类别说明文件、1个Python可视化脚本及8个db辅助文件整体约100.06MB。数据集已按类别划分训练集与测试集可直接投入模型训练与测试免去手动整理样本的步骤已有199人学习或下载可作为入门基准数据使用。json文件具体定义了airport、beach、bridge、commercial、desert、farmland等19个类别覆盖机场、海滩、桥梁、商业区、沙漠、农田等常见地物py脚本支持快速可视化图像样本便于预览与质量检查是开展遥感图像分类实验和数据增强研究时的实用基础数据。1. 1000 张带标注的遥感图够不够训练一个 19 类分类模型在别处折腾过十万张级别的遥感项目回头看到 WHU-RS19种土地利用类型遥感卫星图像分类数据集第一反应往往是“才 1000 张19 类这不随便训”。真把数据拿到手跑一遍才会发现小样本是另一种难法模型很容易把训练集背下来验证集上却把河流认成湖泊商业区认成工业区。这个数据集的价值恰恰在于它足够小、标注足够干净是验证遥感图像分类流程、比较骨干网络、排查训练链路毛病的理想试验场。它适合三种人想快速跑通“遥感图像 → 深度学习分类”完整流程的工程师用公开数据集做算法验证的学生以及需要在一天内评估一个模型结构值不值得继续投入的调研者。后续所有内容都围绕“拿到这个数据集之后如何一步步落地”展开不抄捷径不跳坑。2. 拆解 WHU-RS 的 19 类标签每类的像素特征与易混类在训练脚本里跑通一遍很容易真正让准确率上不去的是类别之间的边界。WHU-RS 的 19 类土地利用类型覆盖了自然地表与人工建筑两大方向公开流传的版本里类别集合基本包含飞机、公路、桥梁、商业区、密集住宅、沙漠、农田、森林、工业区、草地、山区、港口、停车场、河流、湖泊、住宅区、稀疏住宅、运动场、火车站。这个类别清单很像把谷歌地球截图逐块做语义理解的任务。2.1 19 类标签的判别特征与易混关系遥感图像分类里的易混通常不是颜色完全一样而是颜色相近加纹理相近再加形状被降采样抹掉。在约 1000 张图、每类几十张的情况下模型只能靠非常弱的纹理线索来区分。我把最容易出问题的几组先列出来类别视觉识别要点最容易混淆的对象河流细长弯曲的蓝色条带有连续走向湖泊、港口湖泊大面积连续水面边缘自然河流、港口港口水面加规则灰色码头泊位河流、湖泊公路深灰色长线条互相交叉成网桥梁、停车场桥梁跨过水面的长条结构两侧有引桥公路草地均匀浅绿色纹理细腻农田、运动场农田有明显畦垄条纹草地、稀疏住宅森林深绿色颗粒感强树冠阴影多草地、山区山区明暗变化大山脊线硬朗森林商业区密集屋顶白色红色块状整齐工业区、密集住宅工业区大型厂房蓝白色顶棚体量大商业区、停车场密集住宅屋顶连排密度极高商业区、住宅区住宅区中等密度屋顶颜色多样有院落稀疏住宅稀疏住宅房屋间距大中间穿插绿植草地、住宅区停车场黑底白线整齐的车位格子公路、工业区运动场绿色场地加红色跑道或几何轮廓草地、农田火车站长条形月台与铁轨几何感强工业区、桥梁沙漠大面积均匀黄色几乎无纹理农田收割后飞机停机坪上的亮色机身形状细长工业区、停车场表格里的重点不是背熟每类长什么样而是理解模型为什么会错。卫星图像分辨率通常在 0.5m 到 2m一棵树占几个像素、一辆车占一个像素细长结构在缩放到 224 时直接变成十几像素的短线。CNN 在小尺寸输入下更容易抓住颜色和纹理而很难抓住“这条蓝线是连续弯曲的所以要判成河流”这种全局形状语义。这就是为什么港口和湖泊都带大块水面模型在低分辨率下经常翻车。判断一个类是否稳定还有个更务实的办法看这个类的样本里有没有大量环境无关噪声比如云影、卷帘门、阴影。云影会让森林变成山区让商业区变成工业区。后面第 6 章用混淆矩阵把这类错误拉出来会比人肉看图片快得多。人肉看图适合建直觉不适合做精细 Debug。2.2 数据集目录组织与标注文件解析多数公开流传版本的 WHU-RS 是按类别分文件夹组织的没有单独的 JSON 或 CSV 标注表。这对加载数据挺友好但也带来一个隐患文件名和文件夹名的解析规则决定了标签顺序任何一个下划线切分错误都会让整批标签错位。下面这段脚本把文件夹名作为类别标签读进来顺便统计每个类别的样本数tree -L 2 data/WHU-RS/import os from collections import Counter data_root data/WHU-RS all_items [] # 每个元素是 (class_name, image_path) for class_name in sorted(os.listdir(data_root)): class_dir os.path.join(data_root, class_name) if not os.path.isdir(class_dir): continue # 跳过 README、.DS_Store 等非目录文件 for fname in os.listdir(class_dir): if fname.lower().endswith((.jpg, .jpeg, .png, .tif)): all_items.append((class_name, os.path.join(class_dir, fname))) label_names sorted(set(name for name, _ in all_items)) label2id {name: i for i, name in enumerate(label_names)} counter Counter(name for name, _ in all_items) print(总样本数:, len(all_items)) print(类别数:, len(label_names)) print(每类样本数:, dict(counter))这段代码有几个参数值得说明。sorted(os.listdir(data_root))必须保留否则在部分文件系统上目录顺序不固定同一份脚本两次运行得到的label2id可能不同直接导致训练标签错位。endswith的小写判断要用在fname.lower()上实际包里图片后缀混合了.jpg与.JPG的情况并不少见。.tif也要写进去不少流传版本的 WHU-RS 里混有 TIFF 格式的航拍图。输出里最值得看的是“每类样本数”。这个数据集每类大多在 30 到 70 张之间少数类可能明显偏少。如果某类只有 20 张左右后续训练就必须靠增强或类别权重来兜底不然后面验证集上的个别分类会很惨。先在这里记下一笔第 4 章和第 5 章还会反复回到这个问题。2.3 数据可视化与类别分布先看图再动手写训练脚本之前先用一张拼接图把所有 19 类过一遍。这一步不是仪式感是为了发现两类问题标注是否干净以及哪些类别的视觉差异本来就很小。import matplotlib.pyplot as plt from PIL import Image import random random.seed(0) fig, axes plt.subplots(5, 4, figsize(16, 20)) axes axes.flatten() for idx, name in enumerate(label_names): paths [path for cls, path in all_items if cls name] if idx len(axes): img Image.open(paths[random.randrange(len(paths))]).convert(RGB) axes[idx].imshow(img) axes[idx].set_title(f{idx}: {name}, fontsize10) axes[idx].axis(off) for idx in range(len(label_names), len(axes)): axes[idx].axis(off) plt.tight_layout() plt.savefig(whu_rs_preview.png, dpi120)参数说明5, 4的网格是为了放下 19 类再加一个空位如果你拿到的是 19 类版本这个网格最合适figsize(16, 20)对应每张缩略图约 4:5 的长宽比能基本保留原图比例避免因为显示拉伸而误导判断。random.seed(0)固定抽样保证每次运行抽查的是同一批样本方便和同事对照讨论。更值得顺手做的是画一张类别分布条形图。用上一节的counternames list(label_names) counts [counter[n] for n in names] plt.figure(figsize(10, 5)) plt.bar(range(len(names)), counts) plt.xticks(range(len(names)), names, rotation45, haright) plt.ylabel(sample count) plt.savefig(whu_rs_class_dist.png, dpi120)如果看到某几个类别的柱子明显矮一截这是全流程第一个需要记录的坑这类在验证集里天然吃亏。训练脚本里是否要给它更高权重第 5 章会给出处理方案。先知道它存在比到时候对着混淆矩阵猜原因要省时间得多。3. 用 PyTorch 组织 WHU-RS 数据从路径到 DataLoader 的最小实现WHU-RS 的类别目录结构和大多数 torchvision 示例数据集很像第一反应是用datasets.ImageFolder一把梭。实践下来发现这个做法在约 1000 张的小数据集上容易踩三个暗坑图像尺寸不一致导致 batch 报错RGBA 图或灰度图导致通道数错乱以及标签派生规则不透明导致标签错位。下面给出一个更可控的自定义 Dataset。3.1 为什么不用 ImageFolder 一把梭ImageFolder的核心逻辑是子文件夹名就是类别名读取时会自动建立classes和class_to_idx映射这对组织良好的数据集确实省事。但它默认的loader直接调用PIL.Image.open后不做通道规整遇到带 alpha 通道的 PNG 或者 16bit 的 TIFF后续ToTensor会报错。你还需要把图像尺寸变换全部塞进transform但遥感图长宽比差距大直接Resize((224, 224))会把本来细长的河流、桥梁拉得面目全非。我一般会写一个自定义 Dataset 来完成三件事读取时统一通道为 RGB在__getitem__里做等比缩放和裁剪以及把类别名到标签 ID 的映射暴露成属性。这样训练脚本里只出现dataset[i]的用法任何一次变换出了问题都能单独调试而不是在黑匣子里打转。3.2 自定义 Dataset 的完整实现import os from PIL import Image import torch from torch.utils.data import Dataset import torchvision.transforms.v2 as T class WHU19Dataset(Dataset): def __init__(self, root, img_size256, transformNone): self.img_size img_size self.transform transform self.samples [] # (path, label) self.label2id {} self.id2label [] for cls_name in sorted(os.listdir(root)): cls_dir os.path.join(root, cls_name) if not os.path.isdir(cls_dir): continue label len(self.id2label) self.label2id[cls_name] label self.id2label.append(cls_name) for fname in os.listdir(cls_dir): low fname.lower() if low.endswith((.jpg, .jpeg, .png, .tif)): self.samples.append((os.path.join(cls_dir, fname), label)) def __len__(self): return len(self.samples) def __getitem__(self, idx): path, label self.samples[idx] img Image.open(path) if img.mode ! RGB: if img.mode in (I;16, I): img img.convert(L).convert(RGB) else: img img.convert(RGB) w, h img.size scale self.img_size / max(w, h) new_w, new_h max(1, round(w * scale)), max(1, round(h * scale)) img img.resize((new_w, new_h), Image.BILINEAR) if self.transform is not None: img self.transform(img) else: img T.ToTensor()(img) return img, label需要先安装pillow。代码在几个容易失败的点都做了防护I;16是 16bit 灰度 TIFF 的模式直接convert(RGB)在部分 pillow 版本上会得到纯黑图必须先转L再转RGBscale img_size / max(w, h)是等比缩放而非拉伸Resize((224, 224))虽然也能训练但会让桥梁、河流这类长条目标的比例失真max(1, ...)防止短边缩到 0。使用时的调用方式train_transform T.Compose([ T.RandomCrop(224), T.RandomHorizontalFlip(), T.ToTensor(), T.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ]) dataset WHU19Dataset(data/WHU-RS, img_size256, transformtrain_transform) loader torch.utils.data.DataLoader(dataset, batch_size32, shuffleTrue, num_workers4)这里img_size256和RandomCrop(224)的组合是一个常用策略先把长边缩放到 256再随机裁出 224相当于在保留比例的前提下引入了平移扰动。如果你的显存紧张可以把img_size降到 224、RandomCrop降到 160效果会下降但训练速度快很多。mean和std沿用 ImageNet 统计值是起步做法第 6 章会讲怎么用 t-SNE 验证这个假设是否成立。3.3 划分训练/验证集随机划分在遥感图上可能太乐观遥感图像存在空间自相关同一块区域的相邻图像地物分布和光照都很相似。如果只按文件名随机划分训练集和验证集里可能同时出现同一地块的不同截图模型在验证集上的成绩会被高估。这个问题的严重程度取决于你拿到的那份 WHU-RS 数据的来源如果图像来自均匀网格截图编号相近的图往往位置也相近。import numpy as np from sklearn.model_selection import train_test_split, GroupShuffleSplit all_paths [p for p, _ in dataset.samples] all_labels np.array([lb for _, lb in dataset.samples]) # 方案一按类别比例分层划分保证每类在验证集中都有 tr_idx, va_idx train_test_split( np.arange(len(all_labels)), test_size0.2, random_state42, stratifyall_labels ) # 方案二如果文件名里有栅格编号按编号分组划分 # groups [os.path.basename(p).split(_)[0] for p in all_paths] # gss GroupShuffleSplit(n_splits1, test_size0.2, random_state42) # tr_idx, va_idx next(gss.split(all_paths, groupsgroups))stratifyall_labels会让训练集和验证集里 19 类的比例一致避免某类样本只进训练集不进验证集导致验证指标忽高忽低。方案二用了GroupShuffleSplit它的核心是保证同一组编号的图像只会出现在训练集或验证集的一侧。如果你能确认文件名里的编号含义方案二更贴近真实验证如果只是普通文件名直接用方案一即可。两个方案都要传random_state否则每次运行划分结果不同后面所有对比实验都会失去意义。这里补充一句验证集最好只留 15% 到 20%。WHU-RS 总共约 1000 张验证集留 30% 的话每类只剩 40 多张训练连 ResNet 都容易欠拟合。4. 训练配置与参数batch、学习率、输入尺寸怎么调数据管道准备好后很多人会直接把公开分类任务的训练参数抄过来。一个常见配置是ResNet18 输入 224 batch 32 Adam 学习率 1e-3 50 个 epoch。这套配置在 CIFAR 上没问题在 WHU-RS 上却容易看到 loss 快速收敛到很低、验证集准确率却卡住不动的现象。原因在于遥感图像的尺度分布和 ImageNet 不一样目标不一定居中纹理细节容易被小尺寸输入抹掉。这一章把模型选择、输入尺寸、增强参数三个最关键的旋钮拆开讲。4.1 用什么模型打底小模型与预训练骨干的取舍约 1000 张图像、19 个类别每类平均 50 张这个数据量撑不起从头训练的大网络。常见做法是使用 ImageNet 预训练骨干然后把最后的全连接层换成 19 分类输出在几个轻量骨干里做对比。模型参数量输入 224 时显存占用batch 32单 epoch 时间量级适合场景ResNet18约 11M约 1.5GB快默认首选调试流程最快ResNet50约 25M约 3.5GB中追求精度显存允许时EfficientNet-B0约 5.3M约 1GB中移动端部署前评估MobileNetV3-Large约 5.4M约 1GB快端侧落地验证ViT-Small约 22M约 4GB慢不推荐数据量撑不起加载预训练模型的写法需要留意 torchvision 的新接口。以与旧版写法区分的代码为例import torch.nn as nn import torchvision.models as models model models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1) num_ftrs model.fc.in_features model.fc nn.Linear(num_ftrs, 19) # 如果换 MobileNetV3 # model models.mobilenet_v3_large(weightsmodels.MobileNet_V3_Large_Weights.IMAGENET1K_V1) # in_features model.classifier[-1].in_features # model.classifier[-1] nn.Linear(in_features, 19)weights是老版本里pretrainedTrue的新写法传一个具体的权重枚举而不是布尔值避免在代码审查时被追问用的是哪一版预训练权重的哪次命名。fc.in_features不要手写死 512不同 ResNet 变体的最后输入维度不一样手写死之后换骨干模型容易漏改。选择建议第一轮用 ResNet18 只跑 20 个 epoch确认整条链路没有 bug再去试 EfficientNet-B0 和 ResNet50。不要在第一天并行跑五个模型这会带来一堆同样错误的复现问题。小数据集上骨干网络的差异通常没有数据划分方式和增强策略的差异大先用最轻的模型把基线锚定。还有一个隐性选择是否冻结预训练骨干只训练全连接层。在数据量这么小的情况下冻结前几层往往能减少过拟合。常见做法是骨干学习率乘 0.1或者先冻结骨干训练 head 50 个 epoch 再解冻。我在 WHU-RS 上尝试过全量微调结果验证精度比冻结骨干时低几个点原因是接近满容量的参数在小数据上收敛太快直接记住了训练集。所以第一条建议是先冻结骨干训练 head等验证集指标稳定了再解冻骨干做微调。4.2 输入尺寸选 224 还是 512分辨率与尺度的权衡遥感图像分类里输入尺寸对精度的影响比一般物体识别更大。车辆、树冠、房屋这类小地物在 224×224 输入下只有几个像素模型很难捕捉到足够纹理。但输入越大显存和时间成本成倍上涨所以一般分两个阶段来试输入尺寸每 batch 32 时的显存量级单 epoch 时间量级主要问题160约 0.6GB极快小地物几乎消失只能抓大块颜色224约 1.5GB快细长结构纹理开始可辨384约 4GB中等显存压力显现需要配合梯度累积512约 6GB慢适合精调阶段增强强度需同步加大一个稳妥的落地路径先 224 跑通流程确认数据划分、增强、损失函数都正确如果验证准确率离你的目标差 2 到 3 个点再换 384 精调。直接把输入换成 512 不是不行但要检查RandomCrop的裁剪位置是否导致每张图看到的内容过少并考虑把 batch 降到 16 加梯度累积来保住批量统计量。梯度累积的做法很简单每个 batch 只做前向和 backprop 的梯度累加攒够若干步再更新参数。比如 batch 16、累积 2 步等价于 batch 32 的效果但显存占用只有一半。optimizer.zero_grad() for i, (images, labels) in enumerate(loader): loss criterion(model(images), labels) loss.backward() # 梯度累积 if (i 1) % 2 0: optimizer.step() optimizer.zero_grad()注意BatchNorm 在梯度累积下统计的是每个小 batch 的均值与真正的大 batch 仍有差别但遥感分类任务对这一点不太敏感可以接受。需要特别强调的是增大输入尺寸并不等价于看到更多细节因为卷积核的感受野没有变。如果只是把图放大、网络不变模型学到的依然是同样大小的纹理模式。真正有效的做法是配合更强的随机裁剪增强让模型在不同尺度上都见过目标也就间接学会了尺度不变性。遥感图像分类里很多收益其实都来自这个组合。4.3 数据增强策略翻转、裁剪、颜色抖动与鲁棒性对 WHU-RS 而言一个合理的增强管线应该同时包含几何变换和颜色变换但强度不能大到把地物语义改掉。下面这套配置是我在这个数据集上的常用起点import torchvision.transforms.v2 as T train_transform T.Compose([ T.RandomResizedCrop(size(224, 224), scale(0.6, 1.0), ratio(0.8, 1.2)), T.RandomHorizontalFlip(p0.5), T.RandomVerticalFlip(p0.3), T.RandomRotation(degrees10), T.ColorJitter(brightness0.2, contrast0.2, saturation0.1, hue0.02), T.ToTensor(), T.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ])RandomResizedCrop的scale(0.6, 1.0)意思是裁剪区域占原图面积的 60% 到 100%比 ImageNet 常用的(0.08, 1.0)温和得多。遥感图上的目标本来就小裁剪太狠会把整块地物裁成局部纹理模型学的就不是“这是河流”而是“这是蓝色条纹”。RandomVerticalFlip我只给了 0.3因为 WHU-RS 的截图大多是近似正射视角垂直翻转会改变河流上下游方向但影响不大如果是带倾斜视角的航拍图建议关掉这一项。ColorJitter的参数值得单独说亮度 0.2 和对比度 0.2 可以模拟不同时段、不同传感器成像这是遥感数据的真实噪声来源。饱和度 0.1、色调 0.02 必须给小否则整片农田的绿色会被抹成灰色模型的语义判断就崩了。增强强度不是越大越鲁棒我见过有人把ColorJitter开到 0.5最终训练准确率掉到 70% 以下这不是增强的锅是颜色语义被破坏。注意验证集和测试集的变换里只保留Resize、ToTensor、Normalize绝不能出现随机翻转和随机裁剪。验证时把增强带上会在每个 epoch 得到不同准确率没法判断模型是否真的学会。5. 常见的五个坑排查WHU-RS 训练翻车与血泪经验给这个数据集做落地最花时间的不是模型架构而是排错。以下五条是从实战里总结出来的踩坑记录按“现象 → 原因 → 解决”的方式整理每一条都直接影响最终能否得到一个可复现的实验结果。5.1 标签全部错位模型居然还能收敛现象训练集准确率稳步上升到 90% 以上可视化预测结果时却发现类别整体偏移比如真实的住宅区被模型预测成商业区而且所有预测都偏向标签列表中靠后的类别。原因脚本用str.split(_)[0]解析文件名来生成标签但部分类别文件夹名里带下划线比如sparse_residential被切成sparse或者railway_station被切成railway标签映射和真实类别对不上。更隐蔽的是目录清单顺序在不同操作系统上的排序不一致导致同样的代码在 Windows 和 Linux 上产生不同标签 ID。解决写一个一次性校验脚本把label2id的键值对打印出来再随机取 10 张图把类别名画到图上目检。代码可以很简单for i, (path, label) in enumerate(dataset.samples[:10]): img Image.open(path) print(f{i}: {os.path.basename(path)} - id{label}, name{dataset.id2label[label]})不要用split(_)[0]这种写法改成维护一张显式映射表label_map {agriculture: 0, airplane: 1, ...}读取文件夹时查表查不到就抛异常。越早发现标签错位越少浪费后面所有实验。5.2 训练到一半突然崩掉batch 尺寸对不齐现象训练前几个 epoch 很正常第五个 epoch 的某个 batch 突然报错RuntimeError: stack expects each tensor to be equal size重跑一次有时又没问题非常随机。原因数据集中混有不同尺寸和不同通道数的图像。Dataset 里如果只对部分路径做了 resize或者某张 RGBA 的 PNG 在ToTensor()之后变成 4 通道拼 batch 时就会冲突。时好时坏的原因是DataLoader的shuffleTrue每次都随机重排撞到那张特殊图才崩。解决在自定义__getitem__的返回前加一段断言把异常图像找出来img self.transform(img) assert img.shape[0] 3, fbad channels at {path}: {img.shape} assert img.shape[1] img.shape[2], fnot square at {path}: {img.shape}同时把num_workers设为 0 跑一次全量扫描定位那些宽高异常的图片再做一次性批量修复。图像的通道模式在convert(RGB)之后一定检查img.mode不要假设PIL会把所有格式都转对。5.3 训练集准确率 97%验证集只有 74%是过拟合还是没收敛现象训练曲线一路向下验证曲线在 74% 附近震荡再往后训练集继续涨、验证集不动。新手容易以为是还没收敛继续调学习率实际上模型已经开始背训练集了。原因WHU-RS 只有约 1000 张模型容量远大于数据量。ResNet50 加全连接层有 2500 万个参数而每类只有 50 张训练图模型完全可以把训练样本的局部纹理背下来根本学不到泛化特征。解决先加早停验证集准确率连续 10 个 epoch 不涨就停止训练把最优 epoch 的权重存下来。然后做两件事一是加weight_decay1e-4 起步和Dropout二是把增强强度调大尤其是RandomRotation和RandomResizedCrop的比例。还有一个容易被忽略的动作把验证集固定下来用一个固定的random_state划分数据否则每次重启脚本验证集都在变你根本无法判断改动是否有效。5.4 类别不平衡少数类被模型当噪声现象整体准确率 85%但混淆矩阵里沙漠、火车站这类类别的召回率只有 30% 左右模型把它们的图都判成了其他类。查了数据量发现这两类样本数只有其他类的一半。原因交叉熵损失默认对每个类别一视同仁但 WHU-RS 的类别样本数量并不均匀。多类样本 60 张少类只有 25 张时模型为了降低整体 loss会优先学好多类、直接把少数类当成噪声忽略。解决两个办法二选一或者结合使用。第一个是给CrossEntropyLoss传入类别权重import numpy as np from sklearn.utils.class_weight import compute_class_weight labels_arr np.array([label for _, label in dataset.samples]) class_weights compute_class_weight(balanced, classesnp.unique(labels_arr), ylabels_arr) class_weights torch.tensor(class_weights, dtypetorch.float32).to(device) criterion torch.nn.CrossEntropyLoss(weightclass_weights)compute_class_weight的balanced模式会自动把小类权重调高省去手动计算。第二个是用WeightedRandomSampler让小类样本在采样时出现概率更高from torch.utils.data import WeightedRandomSampler sample_weights 1.0 / np.bincount(labels_arr)[labels_arr] sample_weights torch.tensor(sample_weights, dtypetorch.float64) sampler WeightedRandomSampler(sample_weights, num_sampleslen(sample_weights), replacementTrue) loader DataLoader(dataset, batch_size32, samplersampler)注意两个方案不要同时开到最强否则少数类会严重过采样模型可能把它们背下来。常见起手式是只用损失函数权重如果少数类召回率仍然过低再叠加采样器并且把num_samples设为原始样本数的 1.5 倍左右。5.5 明明改了代码验证结果却对不上现象前后两次跑同一个训练脚本验证准确率差了好几个点甚至连 loss 曲线的形状都不一样。检查代码发现什么都没改只是机器负载不同。原因深度学习的随机性来源比大多数人想象得多DataLoader的shuffle、模型的权重初始化、GPU 上非确定性算法、预处理里的随机翻转。如果划分数据集时没有固定random_state每次运行验证集本身就在变那实验结果自然对不上。解决在训练脚本开头把所有随机种子固定住import random, numpy as np, torch 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)固定种子之后如果两次实验结果仍对不上优先怀疑DataLoader的num_workers是否大于 0。多进程下存在线程级随机种子问题严格可复现的实验需要把num_workers设成 0或者直接固定数据增强的随机种子。虽然这会拖慢训练但在调试阶段值得等流程稳定了再放开num_workers提速。6. 用混淆矩阵与 t-SNE 验证分类边界找出模型真正没学会的那几类训练完一个模型只看验证集准确率远远不够。WHU-RS 的 19 类之间存在大量模糊地带真正决定你要不要继续投入的是模型在哪几对类别上翻车是数据问题还是模型容量问题下面两个工具能帮你看清楚而不是靠感觉调参。6.1 混淆矩阵把易混类找出来用 sklearn 快速生成一张 19×19 的混淆矩阵把对角线以外的热点找出来from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay y_true, y_pred [], [] model.eval() with torch.no_grad(): for images, labels in val_loader: outputs model(images.to(device)) preds outputs.argmax(dim1).cpu().numpy() y_pred.extend(preds) y_true.extend(labels.numpy()) cm confusion_matrix(y_true, y_pred, labelsrange(19)) disp ConfusionMatrixDisplay(cm, display_labelsdataset.id2label) disp.plot(xticks_rotation45)行是真实类别列是预测类别矩阵里非对角线的亮点就是易混类。比如港口被大量预测为湖泊、商业区被预测为工业区说明模型在“水体加人工结构”“顶棚颜色加屋顶密度”这两组语义上没有区分开。这个现象在小数据集上极其常见接下来要做的不是盲目加模型容量而是回到 4.3 节的增强强度或者 5.4 节的类别权重去调整。6.2 t-SNE看特征空间是否真的可分混淆矩阵只能告诉你错了多少t-SNE 能告诉你为什么错。把模型倒数第二层的特征抽出来投影到二维空间观察 19 类的点是否形成清晰团簇from sklearn.manifold import TSNE from torchvision.models.feature_extraction import create_feature_extractor feature_model create_feature_extractor(model, return_nodes{avgpool: feat}) features, labels2 [], [] model.eval() with torch.no_grad(): for images, lbls in val_loader: out feature_model(images.to(device))[feat] features.append(out.flatten(1).cpu().numpy()) labels2.extend(lbls.numpy()) tsne TSNE(n_components2, perplexity30, random_state0) proj tsne.fit_transform(np.concatenate(features))return_nodes{avgpool: feat}是 torchvision 提供的 feature extraction 接口比手动挂 hook 少写很多模板代码也比“随便取某一层”更像一个可复现的特征基准。如果发现河流、湖泊、港口三个类的点完全叠在一起说明模型学到的主要是“这是一片蓝色”而不是“蓝色区域里有规则码头结构”特征空间没有分离度。常见的补救动作是把输入尺寸从 224 提高到 384强化对结构的感知。6.3 针对混淆对做增强而不是全图加复杂度跑完混淆矩阵后挑出错得最多的那一对类别比如“河流 vs 湖泊”。单独给这两类做针对性增强河流类加强RandomRotation的旋转角度湖泊类保持原增强不变再看混淆矩阵是否朝着对的方向改善。这个小技巧比盲目调大所有增强参数更有效因为数据量就这么多每一步干预都要有明确的证据说“这个改变是好的”。我现在拿到 WHU-RS 这种小数据集第一件事不是追求验证集 99%而是先跑通一个可复现的完整流程数据检查 → 数据划分 → 小模型基线 → 混淆矩阵 → 针对性增强。每个环节都盯着验证结果走不靠瞄一眼 loss 就下判断。这个习惯帮我避开了不少自作聪明的翻车希望帮到你。本文还有配套的精品资源点击获取

相关推荐

Learn Harness Engineering 实战:构建可观测、可调试、可基准测试的完整 Agent Harness(Project 06 Capstone 全解析)
Learn Harness Engineering 实战:构建可观测、可调试、可基准测试的完整 Agent Harness(Project 06 Capstone 全解析)

【免费下载链接】learn-harness-engineering Harness engineering beginner tutorial, from 0 to 1 项目地址: https://gitcode.com/gh_mirrors/le/learn-harness-engineering 点击查看 免费下载 本篇技术指南聚焦 Learn Harness Engineering 课程的收官项目&#… · 2026/9/23 3:40:33

Claude Code Haha v0.4.6 版本技术解析:技能市场、会话活动面板与发布链路加固
Claude Code Haha v0.4.6 版本技术解析:技能市场、会话活动面板与发布链路加固

Claude Code Haha v0.4.6 版本技术解析:技能市场、会话活动面板与发布链路加固 【免费下载链接】cc-haha Local-first cross-platform desktop workspace for Claude Code / agents: multi-agent, Git worktrees, code diffs, skill marketplace, multi-model, Comp… · 2026/9/23 3:40:33

不确定性感知知识追踪:从单点估计走向可信的学习状态建模
不确定性感知知识追踪:从单点估计走向可信的学习状态建模

做知识追踪(KT)的同学应该都有同感:一个学生连续做错两道同类题,模型给出一个结论——“他对这个知识点的掌握度是0.73”。但0.73和0.71之间的差别到底意味着什么?这个判断本身有多可信?大多数时候没人知道… · 2026/9/23 3:40:26

Perplexica:本地部署的AI搜索引擎,开启隐私保护的智能搜索新体验
Perplexica:本地部署的AI搜索引擎,开启隐私保护的智能搜索新体验

项目概述:Perplexica 到底是什么如果你平时喜欢逛 GitHub,大概率见过那种“今日热榜”类的速递帖子,而 Perplexica 几乎是最近几个月里热度蹿升最快的一个 AI 搜索项目。简单说,它是一个开源的、可以完全本地部署的 AI 搜索引擎&a… · 2026/9/23 4:19:55

GTA5增强版460报错终极排查:BattlEye反作弊服务修复与实测
GTA5增强版460报错终极排查:BattlEye反作弊服务修复与实测

最近GTA5增强版上线之后,Steam社区和各个游戏群里问得最多的不是画质设置,而是460报错。我自己的情况是:游戏下完点启动,先是Rockstar logo短暂出现,然后屏幕直接弹一个BattlEye相关提示,代码定位到460&… · 2026/9/23 4:19:55

横扫沙漠避坑指南:版本升级API全变?保姆级教程教你稳过
横扫沙漠避坑指南:版本升级API全变?保姆级教程教你稳过

横扫沙漠避坑指南:版本升级API全变?保姆级教程教你稳过 版本升级后 API 全变了,代码跑不起来,面试被问懵了。 这不是玄学,是你在【横扫沙漠】这个典型技术场景中踩了坑。 这篇保姆级教程,直接带你从现象到根源,彻底搞懂。… · 2026/9/23 4:19:55

基于Python单通道脑电信号自动睡眠分期:毕业设计源码、模型与数据集实战
基于Python单通道脑电信号自动睡眠分期:毕业设计源码、模型与数据集实战

简介:这份资源是面向计算机相关专业学生与项目实战学习者的单通道脑电信号自动睡眠分期研究完整方案,源自经导师指导并通过评审的高分毕业设计,可解决睡眠分期课题中数据预处理、模型搭建与结果验证等核心问题,也适合作为课程设计… · 2026/9/23 4:19:55

Easydict 中基于 Agent Skill 的 PR 审查报告结构规范与实现解析
Easydict 中基于 Agent Skill 的 PR 审查报告结构规范与实现解析

Easydict 中基于 Agent Skill 的 PR 审查报告结构规范与实现解析 【免费下载链接】Easydict 一个简洁优雅的词典翻译 macOS App。开箱即用,支持离线 OCR 识别,支持有道词典,🍎 苹果系统词典,🍎 苹果系统翻译… · 2026/9/23 4:19:49

EverOS 日志与可观测性规范实战指南:从 structlog 结构化日志到 OpenTelemetry 追踪
EverOS 日志与可观测性规范实战指南:从 structlog 结构化日志到 OpenTelemetry 追踪

人工智能AI AgentAgent 记忆RAG 【免费下载链接】EverOS One portable memory layer for every AI agent: local-first, Markdown-native, user-owned, and self-evolving across apps, tools, and workflows. 项目地址: https://gitcode.com/gh_mirrors/ev/EverOS … · 2026/9/23 4:19:49

3招搞定手机怎么下载微信面试难题实战项目解析
3招搞定手机怎么下载微信面试难题实战项目解析

3招搞定手机怎么下载微信面试难题实战项目解析 面试被问“手机怎么下载微信”背后的原理,90%的人答不上来。别笑,这看似弱智的问题,实则是考察你对移动应用分发机制、安全校验及网络协议理解的试金石。我带过不少校招新人,他们背了八股文,却连一个A… · 2026/9/23 0:00:03

你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型
你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型

你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 面试被问“高并发下如何保证消息不丢失”,你张口就是“用Redis”,结果面试官追问“如果Redis宕机了怎么办”,你瞬间卡壳。这种场景太常见了,很多新手在背八股文时,只记住了技术名词… · 2026/9/23 0:00:29

Win7无线热点配置工具源码解析:解决API失效的3个实战技巧
Win7无线热点配置工具源码解析:解决API失效的3个实战技巧

Win7无线热点配置工具源码解析:解决API失效的3个实战技巧 Win7无线热点配置工具在Win10/11上跑不动?不是你的问题,是版本升级后 API 全变了。很多老项目里的 netsh wlan… · 2026/9/23 0:00:36

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码