自然天气图像分类数据集验收与PyTorch迁移学习实战

发布时间:2026/9/26 13:51:38
自然天气图像分类数据集验收与PyTorch迁移学习实战 简介面向计算机视觉入门与图像分类任务实践提供一套已划分好的4种自然天气图像数据集覆盖晴朗、多云等四类场景可直接用于训练与测试。数据按ImageFolder格式组织train与test目录分别存放901张和224张图片无需额外预处理即可接入PyTorch等框架目录命名直观类别标签与类型一一对应。压缩包共1128个文件以jpg图片为主体另含少量jpeg、png及一个可视化py脚本、一个json配置文件py脚本支持随机读取图片并保存展示结果方便快速检查数据质量json文件则可用于记录类别映射。整体大小约91.93MB轻量易下载目前已有172人学习使用。数据集类别均衡、划分明确适合课程作业、算法对比、天气识别项目起步自带可视化工具进一步降低上手门槛是一份可直接落地的分类数据集。1. 自然天气图片分类数据集到手后别急着写网络先花十分钟验收划分做图像分类的人最容易在数据集上翻车尤其是花了大把时间写模型最后发现准确率上不去的“元凶”是训练集和测试集根本不在同一个频道上。这份自然天气分类数据集一共 93MB训练集 901 张、测试集 224 张4 个天气类别已经按文件夹分好不用你手动拆分。它最大的价值并不是“图多”而是“划分干净”data/train、data/test 各自带着子目录直接用 PyTorch 的 ImageFolder 就能打开省掉了写自定义 Dataset 和拆分脚本的过程。如果你是刚入门图像分类、要做课程设计或者只是缺一份能快速验证训练流程的天气图片数据这套资源很合适。但有一点必须说在前面划分做完了不代表划分合理训练之前先验收。2. 先看懂 93MB 数据怎么组织的目录结构、类别映射与数量校验2.1 目录树里藏着答案父目录名才是真正的标签解压这份数据后你会看到 data 目录下面只有 train 和 test 两个子目录。train 里面的每一个子文件夹代表一个天气类别文件夹里放的是这个类别的图片test 目录结构完全一样只是图片数量更少。这种组织方式不是随便拍的它是 torchvision ImageFolder 能直接读取的基础前提。data/ ├── train/ │ ├── rain/ │ │ ├── rain22.jpeg │ │ ├── rain24.jpeg │ │ └── ... │ ├── sunrise/ │ │ ├── sunrise89.jpeg │ │ ├── sunrise7.jpg │ │ └── ... │ ├── cloudy/ │ │ └── ... │ └── shine/ │ └── ... └── test/ ├── rain/ ├── sunrise/ ├── cloudy/ └── shine/这里有个容易让新手迷惑的点文件名里有 rain、sunrise 这样的前缀但文件名本身不是标签。哪怕某张图叫 rain22.jpeg程序真正读到的类别也是它所在的父目录名。我之前见过有人拿文件名做 one-hot 编码结果下载回来图片名被批量重命名过标签全乱了。这个数据集的正确姿势是只看父目录名不要自己去 parse filenameImageFolder 在构造时就会把子目录名字按字母排序映射成 label。2.2 数量分布核查901 和 224 不是随便写的数字train 901 张、test 224 张加起来 1125 张测试集占比大概是 19.9%接近常见的 8:2 分割。但光看总数不够你得确认每个类别在 train 和 test 里都出现了而且数量没有严重失衡。尤其是天气数据雨天和晴天的数量经常差好几倍直接训练会让模型偏向样本多的类。import os from collections import Counter for split in [train, test]: root os.path.join(data, split) counter Counter() for cls in os.listdir(root): cls_path os.path.join(root, cls) if os.path.isdir(cls_path): counter[cls] len(os.listdir(cls_path)) total sum(counter.values()) print(f[{split}] 总数: {total}) for cls, count in counter.items(): print(f {cls}: {count} ({count / total:.1%}))这段脚本做的事情就是遍历 data/train 和 data/test 下的每一个类别文件夹统计每个文件夹里的图片文件数量。Counter 用来按类别计数total 是当前 split 下的样本总数。如果输出里某个类别在 test 中数量为 0说明这个类只有训练样本没有测试样本后续做评估时该类的准确率根本算不出来。org 目录结构有问题的时候os.path.isdir 会帮我们跳过非目录文件避免把 Mac 上的 .DS_Store 之类的隐藏文件误当成类别。2.3 为什么“已做数据集划分”能省掉一大半踩坑手动划分数据集的坑是很隐蔽的需要先收集所有图片路径做 shuffle再按比例切开还要保证每个类别在两个集合里的比例接近原分布。如果直接在全局文件列表上切一刀很可能某个类别全部进了训练集测试集里根本见不到它。这份数据已经帮你完成了 train/test 划分所以你至少不用写那套 split 脚本。但“已经划分”不等于“划分合理”你还需要做一道最基础的验收确认训练集和测试集的类别映射是否一致。from torchvision import datasets train_ds datasets.ImageFolder(data/train) test_ds datasets.ImageFolder(data/test) print(train classes:, train_ds.classes) print(test classes:, test_ds.classes) print(train index:, train_ds.class_to_idx) print(test index:, test_ds.class_to_idx) assert train_ds.class_to_idx test_ds.class_to_idx, 类别映射不一致ImageFolder 在初始化的时候会扫描 root 下的子目录自动生成 classes 列表和 class_to_idx 字典不需要手动读一遍文件名。这里直接实例化两个数据集做一个断言就能知道 train 和 test 的映射是否对齐。之所以这一步重要是因为后面训练时模型输出的是索引如果你用 train 的顺序去解释 test 的预测结果而两边字母排序不一致结果会完全错位。这种做法对作物病害图像、工业质检图像这类按文件夹组织的图像数据集同样适用目录结构一规范后面的流程就统一了。3. ImageFolder 加载与图片可视化两行代码背后的三个约定3.1 ImageFolder 运行前提子目录、扩展名、类别排序torchvision 的 datasets.ImageFolder 功能其实很薄它本质上是一个读取文件夹的 Dataset。它有三个约定需要你心里有数。第一root 下面必须直接是类别子目录不能中间再套一层第二图片后缀得是 ImageFolder 默认支持的 .jpg、.jpeg、.png 等如果你有一批大写的 .JPG 文件它不会自动识别第三类别索引不是按你写代码的顺序排列而是按文件夹名字母升序排列。这个排序是很多人第一批踩坑的地方我在第 5 章会展开讲。3.2 直接打开 train 并随机展示一张图片这份资源里自带一个可视化 py 文件作用就是随机抽一张图片展示并保存。如果你自己写核心逻辑是这样的import random import matplotlib.pyplot as plt from torchvision import datasets, transforms transform transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), ]) train_dataset datasets.ImageFolder(rootdata/train, transformtransform) print(类别索引:, train_dataset.class_to_idx) print(样本总数:, len(train_dataset)) idx random.randint(0, len(train_dataset) - 1) img_tensor, label train_dataset[idx] print(随机索引:, idx, 标签索引:, label) # tensor 是 CHW 顺序matplotlib 需要 HWC plt.imshow(img_tensor.permute(1, 2, 0)) plt.axis(off) plt.savefig(preview.png, bbox_inchestight)逻辑说明先定义 transformResize 把输入统一到 224×224ToTensor 把 PIL Image 转成浮点张量并将通道从 HWC 调整成 CHW同时把像素值从 0-255 缩放到 0-1。ImageFolder 构造完成后train_dataset[i] 会返回一个图像张量和一个整数标签。最后 plt.imshow 需要的是 HWC 布局所以要用 permute(1, 2, 0) 把通道维度挪回最后。这里有个容易忽略的参数Resize 的尺寸。如果你后续要接 ResNet 这类网络224 是最常见输入尺寸如果只是想看原图建议直接不传 transform 读原图保存避免被压缩变形干扰判断。3.3 把单图预览升级成逐类网格预览随机抽一张图只能证明数据能读不能证明每个文件夹里的图确实对应它的类名。更好的做法是每个类别各抽一张拼成一张网格图一眼就能看出有没有放错。import os import matplotlib.pyplot as plt from torchvision import datasets train_dataset datasets.ImageFolder(rootdata/train, transformNone) fig, axes plt.subplots(1, len(train_dataset.classes), figsize(12, 3)) for ax, cls in zip(axes, train_dataset.classes): cls_dir os.path.join(train_dataset.root, cls) img_file os.listdir(cls_dir)[0] img plt.imread(os.path.join(cls_dir, img_file)) ax.imshow(img) ax.set_title(cls) ax.axis(off) fig.savefig(class_grid.png, bbox_inchestight)这段代码的关键在于 transformNone也就是说 ImageFolder 不会对图片做任何预处理返回的仍然是最原始的样本。取每个类别文件夹的第一张图用 plt.imread 读进来直接画在子图里。figsize 里的 12 按类别数做了简单的宽度适配四类天气图刚好合适。如果你发现某个类别里混入了明显不是对应天气的图片别急着训练先把目录里的数据清理掉再跑实验。这个网格预览脚本也适合其他图像数据集比如病害叶片或者工业缺陷样本原则都一样先看图再看数。4. 训练前先验收重复图片、类别一致性与损坏图片的三类检查4.1 重复图片检查给所有文件算 SHA256训练与测试不应有交集数据泄漏是图像分类里最让人难受的问题。训练集和测试集里出现同一张图或者同一张图的不同压缩版本测试准确率会高得离谱一旦换成真实新图立刻现原形。这种问题用肉眼看目录很难发现最快的方式是对每个文件算哈希然后比较 train 和 test 的哈希集合。import os import hashlib def file_sha256(path, chunk_size1 20): h hashlib.sha256() with open(path, rb) as f: while chunk : f.read(chunk_size): h.update(chunk) return h.hexdigest() def build_index(split): index {} root os.path.join(data, split) for cls in os.listdir(root): cls_path os.path.join(root, cls) if not os.path.isdir(cls_path): continue for fn in os.listdir(cls_path): fp os.path.join(cls_path, fn) index[file_sha256(fp)] (split, cls, fn) return index train_idx build_index(train) test_idx build_index(test) dup set(train_idx.keys()) set(test_idx.keys()) print(重复图片数:, len(dup)) for h in list(dup)[:5]: print(train_idx[h], , test_idx[h])逻辑说明file_sha256 使用分块读取chunk_size 设成 1MB对 93MB 的数据集来说根本不用等。build_index 遍历某一个 split以文件哈希为 key记录它的 split、类别和文件名。最后用集合取交集找出两个 split 完全相同的图片。这里有个参数需要根据实际情况调如果图片很大chunk_size 可以调大减少读盘次数如果图片都很小直接一次读完也行。找到重复后处理原则只有一个那就是从一个集合里删掉重复项绝不能让同一张图同时出现在 train 和 test。4.2 类别索引一致性测试集的 class_to_idx 必须与训练集一致我在第 2 章已经提过一次断言但这里值得再强调一遍。ImageFolder 的类别排序是自动按字母升序生成的如果你某个类别的文件夹名有细微差别比如一个是sunrise另一个是sun_rise两边的 class_to_idx 顺序就会错开。一旦错开模型预测出的 0、1、2、3 和你想的类别对不上整个结果就成了黑匣子。from torchvision import datasets train_ds datasets.ImageFolder(data/train) test_ds datasets.ImageFolder(data/test) assert train_ds.class_to_idx test_ds.class_to_idx, 训练集测试集类别映射不一致 # 输出映射方便肉眼确认 for cls, idx in train_ds.class_to_idx.items(): print(f{idx}: {cls})这段代码最重要的一行就是断言失败时直接报错成功时打印出映射。如果两边类别数量一致但顺序不一致也可以手动重命名文件夹比如在类名前加0_、1_、2_前缀让字母排序直接变成你想要的排序。这个做法能避免后续每次都要去查 class_to_idx 的麻烦。4.3 损坏图片与尺寸异常PIL 能打开、能 load 才算过关jpg 文件并不是一定能把像素读出来。有时候下载过程丢字节文件头正常但中间数据损坏有时候文件后缀是 .jpg内容却是 PNG。ImageFolder 能扫描到它但训练时读取它就会报错或者产生奇怪的数据。最直接的检查方式是用 Pillow 打开并触发 load 解码。from PIL import Image import os bad [] sizes set() for split in [train, test]: root os.path.join(data, split) for cls in os.listdir(root): cls_path os.path.join(root, cls) if not os.path.isdir(cls_path): continue for fn in os.listdir(cls_path): fp os.path.join(cls_path, fn) try: with Image.open(fp) as im: im.load() sizes.add(im.size) except Exception as e: bad.append((fp, repr(e))) print(损坏图片数:, len(bad)) print(出现过的尺寸:, sizes) for item in bad[:5]: print(item)看代码时注意两点。第一Image.open 默认是惰性的只有执行 im.load() 才会真正解压像素数据所以异常要在 load 时才暴露。第二sizes 集合记录了整个数据集出现过哪些尺寸如果输出结果里尺寸种类特别多说明这批图是各种来源拼凑的。尺寸不统一本身不是致命的后面 transform 加 Resize 和 CenterCrop 就能解决但如果出现大量非 4 通道图或损坏图就要考虑清洗数据了。5. 避坑指南天气图像数据集训练前最常见的五个坑5.1 坑一类别索引被字母顺序打乱导致预测标签错位现象训练过程一切正常但打印预测结果时发现 sunny 对应的数字不是 0 也不是 3和数据集介绍里给的顺序完全不一致。原因torchvision 的 ImageFolder 内部用sorted(os.listdir(root))生成类别列表文件夹叫什么名字索引顺序就按名字排序。你脑子里的顺序是 sunny、cloudy、rain、snow但程序看到的顺序是 cloudy、rain、snow、sunny。解决第一次加载数据的时候立刻打印 class_to_idx把映射存下来。更稳妥的做法是给文件夹重命名加上数字前缀比如0_cloudy、1_rain、2_snow、3_sunny这样字母排序和语义顺序一致。从那以后我只要换新数据集第一步永远是打印映射而不是直接开训。5.2 坑二训练集和测试集重复图片验证指标虚高现象测试集准确率轻松到 95%但把几张拍好的新照片丢进去预测准确率直接掉到 60%。这种漏洞最常见于从网上下载的图片集因为整理数据时从同一个来源复制了多次。原因数据划分时没有做去重同一张图同时出现在 train 和 test 中。模型在测试时相当于开卷考试它已经见过这张图的精确像素。解决用第 4.1 节的哈希脚本跑一遍找出交集从训练集里删除重复项。这里尤其要小心那些做了缩放或加噪的“半重复”图片肉眼觉得不一样但语义内容一致这类问题哈希查不出来需要靠感知哈希或者人工抽检。对 93MB 的自然天气数据我是建议直接多花几分钟把所有图片全部过一遍因为图量不大。5.3 坑三matplotlib 中文标签变成方块现象用可视化脚本保存类别预览图图片上每个类别的 title 都是方块或者问号完全看不出写的是什么。原因matplotlib 默认字体里没有中文字形Windows 上的 SimHei、Linux 上的 WenQuanYi 都需要手动指定才能生效。这个坑不会让训练失败但会浪费你排查可视化结果的时间。解决在绘图前加两行配置plt.rcParams[font.sans-serif] [SimHei]同时把axes.unicode_minus设为 False 防止负号异常。如果运行环境是 Linux 服务器没有 SimHei可以直接用英文类名画图或者把类名映射先翻译成英文字母再展示。我的习惯是可视化代码默认用英文因为训练脚本跑在服务器上的概率很大。5.4 坑四Batch 训练时尺寸不一致报 stack 错误现象训练第一个 batch 没事第二个 batch 突然报错类似stack expects each tensor to be equal size。原因ImageFolder 本身不做任何尺寸统一它只是把图片读出来交给 transform。如果你 transform 里只写了 ToTensor没有 Resize每张图的宽高就是原始尺寸一个 batch 里存在不同 shape 的张量DataLoader 无法把它们堆成 batch。解决在 transform 里统一加入Resize((224, 224))如果想让更自然的裁剪可以Resize((256, 256))再加CenterCrop(224)。还有一个小技巧可以用default_collate之前做一个打印输出每个 sample 的尺寸快速确认问题源的路径。现在的主流网络对输入尺寸并不敏感只要固定一个值就行关键是别让尺寸不一致拖垮训练。5.5 坑五训练集和测试集用了两套 transform归一化对不上现象训练损失正常下降测试集上 Loss 异常高或者预测全是一个类。仔细检查后发现测试集 transform 里漏了 Normalize模型输入的分布和训练时完全不一样。原因写代码时图省事给 test set 单独写了一个 transform只做了 ToTensor没做 Normalize还把随机翻转写进了 test 流程。测试阶段任何随机变换都会让结果不稳定。解决定义一个函数统一生成 transform用trainTrue控制是否加入随机增强保证两套 transform 的基础部分一直同步。基础部分至少包含 ToTensor 和一样的 Normalize随机翻转、随机擦除只允许出现在 train。我自己现在复制代码时都会单独确认 test transform 里没有任何 random 操作宁可多写几行也不想在指标上吃亏。6. 进阶用法用迁移学习把 ImageFolder 数据集一次性跑成可复现的指标6.1 固定随机种子与统一数据流自然天气数据集不大1125 张图撑不起从零训练一个深度网络。常见做法是加载 ImageNet 预训练的 ResNet18把最后一层全连接换掉只微调分类头。为了让结果可复现训练前要固定 PyTorch、NumPy、Python random 三个随机种子如果用了 GPU还要设置torch.cuda.manual_seed_all。import torch from torchvision import datasets, models, transforms from torch.utils.data import DataLoader 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]) ]) train_ds datasets.ImageFolder(data/train, transformtransform) test_ds datasets.ImageFolder(data/test, transformtransform) train_loader DataLoader(train_ds, batch_size32, shuffleTrue) test_loader DataLoader(test_ds, batch_size32, shuffleFalse) model models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1) model.fc torch.nn.Linear(model.fc.in_features, len(train_ds.classes))代码逻辑是把整个数据流串成一条线ImageFolder 负责读图transform 负责统一尺寸和归一化DataLoader 负责 batch 化。Normalize 的 mean 和 std 用的是 ImageNet 预训练统计值因为 ResNet18 的预训练权重就是这么做的。最后一行的len(train_ds.classes)会得到 4正好替换成天气分类输出维度。6.2 用五个 epoch 验证数据集划分是否真的合理迁移学习不需要训练很久就能看出数据划分有没有问题。设置五个 epoch只在全连接层做训练前面的卷积冻结住这样几分钟就能跑完。device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device) optimizer torch.optim.Adam(model.fc.parameters(), lr0.001) criterion torch.nn.CrossEntropyLoss() for epoch in range(5): model.train() for images, labels in train_loader: images, labels images.to(device), labels.to(device) optimizer.zero_grad() loss criterion(model(images), labels) loss.backward() optimizer.step() model.eval() correct total 0 with torch.no_grad(): for images, labels in test_loader: images, labels images.to(device), labels.to(device) preds model(images).argmax(dim1) correct (preds labels).sum().item() total labels.size(0) print(fepoch {epoch 1}, test acc: {correct / total:.3f})这里注意一个细节optimizer 只传入了model.fc.parameters()也就是只更新最后那个新的线性层。这么做的原因是天气图片和 ImageNet 的场景差异不算特别大冻结卷积特征可以在小数据量下防止过拟合。如果你发现五个 epoch 后测试准确率还在 0.5 左右徘徊先别怀疑模型回头检查哈希重复和类别映射如果准确率能到 0.8 左右说明这个数据集的划分基本可靠。从那以后我每次拿到新的图片分类数据集都强制把“哈希去重、class_to_idx 断言、统一 transform”这三步走一遍确认没问题才把代码交给训练脚本。数据划分这类问题最隐蔽它不会像维度报错那样打断你只会让最终指标看起来不错却扛不住真实场景。希望这套流程能帮你在自然天气分类以及其它图像数据集上少走几个弯路。本文还有配套的精品资源点击获取