ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

天气图像分类数据集实战:从数据划分到PyTorch迁移学习全解析

天气图像分类数据集实战:从数据划分到PyTorch迁移学习全解析 简介对于需要训练图像分类模型的开发者自然天气图像分类数据集的获取与整理往往十分繁琐。这份资源已按训练集与测试集划分完毕涵盖4种常见天气类别如晴朗、多云、雨、日出等可直接通过PyTorch的ImageFolder加载适合课程实验、毕业设计或快速验证模型效果。数据包共1128个文件以jpg/jpeg图片样本为主体共1125张另附一个可视化展示Python脚本、一个json辅助文件和一个png示例图压缩包整体约91.93MB目录结构清晰train目录含901张图片、test目录含224张图片。附带可视化脚本无需修改即可运行随机传入一张图片便可在当前目录生成展示图方便预览各天气类别样本。数据集中的图片覆盖多种光线与气象条件部分场景存在相似背景与复杂纹理可用于评测分类模型的泛化能力该数据集已经过实际测试可作为图像分类任务的即用型数据源目前已有173人浏览学习适合需要开箱即用天气数据集的研究者或开发者使用。1. 4种自然天气图像分类数据集是什么一张图、一份划分解决多任务分类落地的数据基础做图像分类的第一道坎往往不是模型而是数据。很多开源图片分类数据集只给一堆原始图片划分、清洗、类别统计全都得自己来。这个「4种自然天气分类图像数据集」不同点是它已经把train/val/test三个子集划分好了类别固定是晴天、雨天、雪天、雾天四类。对刚接触图片分类、或者想快速跑通一条分类训练基线的人来说省掉了最繁琐的整理阶段。适合两类读者一是准备做智慧交通、户外监控等场景天气识别的新手二是需要一份干净数据验证自己数据增强或迁移学习方案的熟手。但“已划分”不等于“直接可用”划分的合理性、类别分布、样本质量仍然需要自己核实一遍这也是本文要讲清楚的事。2. 先看数据再动手天气分类数据集的目录结构、标注格式与类别平衡检查拿到一个“已做数据集划分”的图片分类数据集我一般不会直接开训练。先花半小时把目录结构、图片数量、类别分布摸清楚能省后面一整个星期的排错时间。这个数据集如果按常见做法组织目录会类似train/sunny、val/rainy这种类别名/图片的层级或者把划分信息放在 CSV 里。无论是哪种第一步都是把分布统计出来。2.1 拿到压缩包先做三件事解压、统计、可视化解压后第一件事不是看图片长什么样而是用脚本统计每个子集、每个类别的图片数量。一个简单的bash命令就能完成# 统计 train/val/test 下每个类别的图片数量 for split in train val test; do echo $split for class_dir in $split/*/; do cls$(basename $class_dir) cnt$(find $class_dir -type f | wc -l) echo $cls: $cnt done done这段脚本按train/sunny这种目录结构遍历输出每个类别下的文件数。执行后你会得到一张 4 行 3 列的数量表。逻辑说明find wc -l统计的是普通文件数量能帮你快速发现空的类别目录、或者数量明显异常的样本。参数说明如果图片是多级目录嵌套需要把find $class_dir -type f改成find $class_dir -type f | grep -E \.(jpg|jpeg|png)$否则会把隐藏文件、缩略图也数进去。统计完数量还要看图片本身。我会用 Python 把每个类别的样本拼成一张网格图直接肉眼确认四类天气的长相import os import matplotlib.pyplot as plt from PIL import Image data_root path/to/dataset splits [train, val, test] classes [sunny, rainy, snowy, foggy] fig, axes plt.subplots(len(classes), 3, figsize(12, 12)) for i, cls in enumerate(classes): for j, split in enumerate(splits): cls_dir os.path.join(data_root, split, cls) img_files [f for f in os.listdir(cls_dir) if f.lower().endswith((.jpg, .png, .jpeg))] if img_files: img Image.open(os.path.join(cls_dir, img_files[0])) axes[i, j].imshow(img) axes[i, j].set_title(f{cls} / {split}) axes[i, j].axis(off) plt.tight_layout() plt.savefig(weather_check_grid.png, dpi100)逻辑说明这段代码从每个子集的每个类别里取第一张图拼成网格用来直观检查类别标签是否和图片内容匹配。如果发现某张“晴天”图里明显有雨丝或者“雨天”图里是阴天没有雨就要考虑该样本是否算噪音。参数说明figsize(12, 12)是 4 行 3 列共 12 个子图图片太密可以调大这里只取第一张图做抽查更严谨的做法是每个类别随机抽 9 张用random.sample实现。2.2 四类天气的类间相似度哪些样本注定是噪音自然天气图像分类的难点不在类别多而在类间相似度高。晴天和雾天在远山、建筑背景下颜色分布可能很接近雨天的路面反光和雪天的白色区域都会让模型困惑。所以拿到数据集后我会做一个简单的颜色分布对比把每个类别的图片缩到 32×32统计 HSV 色相通道的直方图看四个类别的分布重叠程度。import cv2 import numpy as np from glob import glob def color_hist(folder): hists [] for f in glob(folder /*.jpg)[:200]: img cv2.imread(f) img cv2.resize(img, (32, 32)) hsv cv2.cvtColor(img, cv2.COLOR_BGR2HSV) hist cv2.calcHist([hsv], [0], None, [32], [0, 256]) hists.append(cv2.normalize(hist, hist).flatten()) return np.mean(hists, axis0) sunny_hist color_hist(train/sunny) foggy_hist color_hist(train/foggy) # 计算两类的直方图相关度 corr cv2.compareHist(sunny_hist, foggy_hist, cv2.HISTCMP_CORREL) print(fsunny vs foggy correlation: {corr:.3f})逻辑说明cv2.calcHist统计的是 HSV 色相通道的分布compareHist的 CORREL 返回 -1 到 1越接近 1 表示两个类别的颜色分布越像。这能帮你量化哪些类别容易混淆为后续调整模型输出或数据增强提供依据。参数说明抽样 200 张、缩到 32×32 是为了快速计算实际使用可以增大抽样数直方图的 bin 数用 32 已经能看出趋势过大反而容易受单像素噪音干扰。2.3 已划分数据的信任边界train/val/test 的比例与同源问题“已做数据集划分”听起来省事但划分策略直接影响模型评估的可信度。我遇到过一种情况划分是把同一个场景的视频抽帧分别放进了 train 和 val结果验证集和训练集里出现高度相似的连续帧准确率虚高到 0.98。真实场景下遇到新地点、新时间段准确率直接掉到 0.8 以下。所以拿到已划分数据先看 train/val/test 的数量比例和来源说明。常见比例是 7:2:1 或 8:1:1。如果 val 和 test 的数量明显少于 100 张/类随机上下波动就会很大。另外要确认划分是否按“图片文件”随机切分还是按“场景/拍摄批次”切分。后者更严谨能避免同源泄漏。若数据集没有提供划分来源信息我会自己抽一批图片做感知哈希去重import imagehash from PIL import Image from glob import glob def dhash(folder): hashes {} for f in glob(folder /*.jpg): h imagehash.phash(Image.open(f), hash_size8) hashes.setdefault(h, []).append(f) return hashes train_hash dhash(train/snowy) val_hash dhash(val/snowy) for h, files in train_hash.items(): if h in val_hash: print(f重复/近似图片: {files[0]} - {val_hash[h][0]})逻辑说明感知哈希把图片压缩成 64 bit 的指纹相同或近似图片的哈希值会一样。这段代码找出训练集和验证集里哈希冲突的图片提示可能存在的同源泄漏。参数说明hash_size8表示生成 8×8 的哈希值越大对微小变化越敏感这里用 8 是为了只查明显重复的近重复图如果想查更轻微的连续帧差异可以提高到 16。3. 把已划分数据集接入训练管线从文件夹到 DataLoader 的标准化写法确认数据没问题后下一步就是写训练代码。对于这种类别名/图片结构的图片分类数据集最省事的方案是torchvision.datasets.ImageFolder它自动按子目录名映射类别标签配合transforms就能直接构造 DataLoader。下面以 PyTorch 为例讲一条能跑通的最小链路。3.1 基于 torchvision ImageFolder 的最小读取代码先写一个完整的读取和预处理模块这是后面所有训练的基础import torch from torch.utils.data import DataLoader from torchvision import datasets, transforms data_root path/to/dataset batch_size 64 # 训练集增强 train_transform transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(p0.5), transforms.ColorJitter(brightness0.2, contrast0.2), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ]) # 验证集只做 resize normalize val_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_dataset datasets.ImageFolder(rootf{data_root}/train, transformtrain_transform) val_dataset datasets.ImageFolder(rootf{data_root}/val, transformval_transform) train_loader DataLoader(train_dataset, batch_sizebatch_size, shuffleTrue, num_workers4, pin_memoryTrue) val_loader DataLoader(val_dataset, batch_sizebatch_size, shuffleFalse, num_workers4, pin_memoryTrue) print(类名映射:, train_dataset.classes)逻辑说明ImageFolder会把train/sunny目录下的所有图片赋予标签 0按字母序foggy, rainy, snowy, sunny依次映射。train_transform在训练时做随机水平翻转和颜色抖动这两个增强对天气分类非常关键——水平翻转模拟拍摄方向差异ColorJitter让模型对亮度、色温变化鲁棒。Normalize用的是 ImageNet 的均值和标准差因为后面要加载 ImageNet 预训练权重输入分布保持一致效果更好。参数说明num_workers4适合 CPU 多核环境GPU 机器可以调到 8pin_memoryTrue能让显存拷贝变快但没 GPU 时设置它没有意义。训练集必须shuffleTrue验证集不需要打乱。Resize((224, 224))是分类网络最常见的输入尺寸如果你的显卡显存小可以改成 160但预训练模型一般推荐不低于 224。3.2 数据增强策略天气分类里哪些增强真正有效天气分类任务里有些增强用了反而帮倒忙。比如随机旋转雨滴和雪花的纹理方向是有意义的旋转 90 度会把“斜落的雨”变成“横着飘”可能干扰语义。我常用的增强分三档增强操作参数建议是否推荐原因水平翻转p0.5推荐拍摄方向无物理限制随机裁切 缩放0.8~1.0推荐模拟不同构图颜色抖动brightness0.2, contrast0.2推荐白天/黄昏/阴影下天气外观差异大随机旋转15° 以内谨慎大雨/雪的方向性特征可能被破坏随机灰度p0.1不推荐晴天和雾天在灰度下更难区分高斯模糊半径 1px可选可解决一部分“雾天是模糊”的假相关3.3 类别不均衡时如何调整采样统计完数量后如果发现某一类比另一类多 2 倍以上常见做法是用WeightedRandomSampler让少数类在每轮训练中被抽到更多次。实现很简单先算每个类别的样本权重from torch.utils.data import WeightedRandomSampler labels [s[1] for s in train_dataset.samples] class_counts torch.bincount(torch.tensor(labels)) class_weights 1.0 / class_counts.float() sample_weights torch.tensor([class_weights[label] for label in labels]) sampler WeightedRandomSampler(sample_weights, num_sampleslen(sample_weights), replacementTrue) train_loader DataLoader(train_dataset, batch_sizebatch_size, samplersampler)逻辑说明WeightedRandomSampler让每个样本被抽到的概率与class_weights[label]成正比少数类权重高被抽到的次数就更多。num_sampleslen(sample_weights)表示每个 epoch 抽样的总数和原训练集一样大。replacementTrue 允许多次抽到同一张图否则小数类不够抽。参数说明如果某个类别特别少可以调大num_samples到原来的 1.5 倍让少数类在一个 epoch 内被看到更多次。但要注意这种采样会让模型对少数类过拟合验证时仍按真实分布评估准确率会偏低但每类召回率会更均衡。4. 训练与评估ResNet 基线、学习率与早停的实际参数数据管线和增强确定后接着选模型和训练策略。图片分类数据集规模一般不大几百到几千张从零训练一个 VGG 级别的模型容易欠拟合。常见做法是加载 ImageNet 预训练权重做迁移学习把最后一层全连接换成 4 类输出。我用 ResNet18 作为基线因为它足够小、收敛快适合验证数据质量。4.1 从零微调一个分类器的命令与参数使用 PyTorch 官方预训练模型只需要替换最后一层import torch.nn as nn from torchvision import models model models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1) in_features model.fc.in_features model.fc nn.Linear(in_features, 4) device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device) criterion nn.CrossEntropyLoss() optimizer torch.optim.SGD(model.parameters(), lr0.001, momentum0.9, weight_decay1e-4) scheduler torch.optim.lr_scheduler.StepLR(optimizer, step_size5, gamma0.5)逻辑说明resnet18(weights...)加载在 ImageNet 上预训练好的权重前面的卷积层已经学会边缘、纹理、颜色等基础特征只需要微调最后的全连接层来适应天气分类。CrossEntropyLoss是单标签多分类的标准损失。SGD momentum比 Adam 更稳在数据量小的分类任务上通常泛化更好。StepLR每 5 个 epoch 把学习率减半避免后期震荡。参数说明lr0.001是微调常用的初始值如果想只训练最后两层可以把前面层的requires_gradFalse此时学习率可以提到 0.01。weight_decay1e-4是 L2 正则如果训练集只有几百张可以适当增加到 5e-4 压过拟合。4.2 评估不只是准确率混淆矩阵和每类召回率训练完成后的评估我几乎不看整体准确率而是看混淆矩阵和每类召回率。因为晴天样本多、雪天样本少时模型只要都猜晴天准确率也能有 70%这对实际应用完全没用。记一个展示混淆矩阵的脚本import torch import numpy as np from sklearn.metrics import confusion_matrix, classification_report model.eval() all_preds, all_labels [], [] with torch.no_grad(): for images, labels in val_loader: images images.to(device) outputs model(images) _, preds torch.max(outputs, 1) all_preds.extend(preds.cpu().numpy()) all_labels.extend(labels.numpy()) matrix confusion_matrix(all_labels, all_preds) print(混淆矩阵 (行真实, 列预测)) print(matrix) print(classification_report(all_labels, all_preds, target_namestrain_dataset.classes))逻辑说明confusion_matrix的第 i 行第 j 列表示真实类别 i 被预测成类别 j 的样本数。对角线越接近行总和说明该类越准。classification_report会给出每个类别的 precision、recall、f1-score对天气分类来说重点看雪天和雾天的 recall——这两个类别样本通常少最容易出现漏检。参数说明如果 val_loader 的batch_size很大保存所有结果会占内存可以用每 100 批打印一次当前 batch 的准确率来观察趋势但最终统计还是要跑完整验证集。4.3 迁移学习 vs 从零训练的选择很多人问这个数据集该从零训练还是迁移学习。答案取决于图片总量。如果每个类别只有几百张强烈建议迁移学习如果每个类别有 5000 张以上从零训练也能收敛但训练时间更长。一个折中方案是把预训练模型的前几层冻结只训练后面的 layer3、layer4 和 fcfor name, param in model.named_parameters(): if name.startswith(layer4) or name.startswith(fc): param.requires_grad True else: param.requires_grad False逻辑说明浅层卷积学到的是通用边缘和颜色特征这些特征在天气分类里一样适用冻结它们可以大幅减少训练参数降低过拟合风险。layer4和fc是 ResNet18 中语义表达最高层的部分针对天气的全局色彩和纹理特征这些层必须微调。如果发现验证集准确率不涨再逐层往下放开layer3。5. 避坑天气数据集划分中的 5 个常见问题与排查方法这部分是我做图片分类数据集划分和训练时踩过的真实坑每一个都对应一个具体的现象、原因和解决方式希望能帮你少走弯路。5.1 现象训练集准确率很高、验证集很低 —— 数据泄漏训练集能到 0.99验证集只有 0.75第一反应是过拟合但加正则根本不管用。后来检查发现数据集的划分脚本在切分时没有先打乱导致 train 里连续 200 张都是同一段监控视频的晴天帧val 里却全是另一个天气日报的图片。这就是典型的数据泄漏同源图片被分到了两个集合。原因按文件顺序切分没有考虑图片的实际拍摄场景或者划分时用了train_test_split但没有设置shuffleTrue。解决重新按“场景组”划分而不是按单张图片。如果数据集本身没有场景信息至少做一次感知哈希去重把相似图片归到同一个集合里。如果你只是拿来练手最快的办法是重新随机划分一次并固定随机种子。5.2 现象雪天类别总被预测成阴天 —— 亮度/颜色偏置有一版模型 val 的雪天 recall 只有 0.5点开错误样本发现雪天图片里大量是积雪被阴影遮住的暗色区域亮度上和阴天几乎没差别。而训练集里的雪天图片大部分是白茫茫的大雪场景模型学到的是“高亮度 雪天”而不是“雪的结构”。原因数据集的雪天图片构图单一模型被亮度特征带偏预训练模型的 ImageNet 权重对高亮区域本身有偏置。解决在训练时对雪天类别做更激进的ColorJitter降低 brightness 的随机范围到 0.0~0.1同时加入RandomGrayscale(p0.1)让模型不能只靠颜色。关键是从数据上补充阴影下的雪景但开源数据集很难改所以增强是唯一可行路。5.3 现象模型对雾天和晴天混淆 —— 特征重叠雾天图片里往往有白雾造成的低对比度晴天图片在强烈逆光下也会发白两者在视觉上确实很难分。我遇到最多的情况是模型把雾天的远景建筑预测成晴天。原因雾天本质是一个连续强度现象薄雾和晴天边界很模糊数据集的标注是把有薄雾的图标成雾天但人眼看到也会犹豫。解决评估时把“雾天 vs 晴天”单独看成二分类问题如果混淆严重可以修改标签策略——把薄雾样本从雾天里剔除或者归入“阴天/多云”类别如果数据集支持。另一个思路是输入前做对比度归一化让模型更关注局部纹理而非全局白雾。5.4 现象加载图片时内存溢出用ImageFolder加载几千张 4K 图片num_workers4时直接 OOM。这是很多新手在图片分类数据集上翻车的地方以为报错是显卡不够其实是系统内存被图片解码占满了。原因ImageFolder默认惰性读取但在预处理时Resize((224, 224))需要先把原图完整解码进内存4K 原图一张就有 20MB几千张并发读入直接撑爆。解决把num_workers降到 2并且在预处理里首先用Image.open(...).convert(RGB)后立即缩放到 512×512再进入后面的流程。更彻底的方法是把图片先离线缩放成 256×256 JPEG训练时只加载小图速度也快得多。5.5 现象已划分数据里出现重复图片训练集和验证集里出现完全一样的文件md5 相同这通常不是有意为之而是数据集打包时某个源目录被重复拷贝了。如果不处理你会以为模型泛化好实际它在作弊。原因数据采集时不同来源文件夹有相同命名文件打包脚本用了cp -r且没有去重或者划分脚本用了有放回抽样。解决用find -type f -exec md5sum {} 做一次全库去重。先在同一划分内去重再跨划分去重。如果 val 里有和 train 相同的图片优先删除 val 中的那份保证评估集独立。6. 扩展与进阶把单标签天气分类升级成多标签和域泛化这个天气数据集虽然是 4 类单标签但实际场景里“有雨又有雾”很常见单标签会强制模型二选一。我后来做监控项目时把输出层从 4 类换成多标签用BCEWithLogitsLoss训练每张图片可以同时有“雨”和“雾”两个标签召回率反而提升了因为模型不再需要强行丢掉一个合理特征。做法很简单最后一层改成nn.Linear(512, 4)激活换成 sigmoid损失换成BCEWithLogitsLoss评估用每类的 F1 分数。另一个值得做的方向是域泛化。自然天气数据的采集地域性很强在北方拍的雪天和南方拍的雪天差别巨大。如果希望模型能适应新地域一个技巧是在训练时用随机风格迁移把每个 batch 中的图片随机做一次颜色仿射变换模拟不同相机色温。我常用torchvision.transforms.functional.adjust_gamma配合随机的 gamma 值 0.8~1.2效果比加更多数据更稳。具体实现可以写成一个自定义 transform在__getitem__里针对每张图随机执行。关于验证方法建议不要只在 val 上跑一次而是对 val 做 3 次不同随机种子的重复抽样统计每类准确率的均值和方差。如果某个类别的方差超过 5%说明测试集太小或划分不稳定需要合并 val 和 test 重做交叉验证。我在做这个天气数据集时最后就用 5 折交叉验证替代了固定划分得到的每类召回率曲线才足够可信。这也是我的血泪经验别把“已划分”当成最终答案把它当成一个候选方案用自己的验证逻辑重新审一遍这比多调几个轮次更有用。希望这份笔记能帮你把这个数据集真正用到实处而不是只跑通一个准确率数字。本文还有配套的精品资源点击获取
返回列表