news 2026/10/11 5:22:39

9000样本天气分类实战:从数据划分到模型微调全流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
9000样本天气分类实战:从数据划分到模型微调全流程

简介:这份天气分类数据集面向计算机视觉入门与进阶学习者,以及需要开展图像分类实验的学生和开发者,可用于CNN模型训练、迁移学习对比与数据增强等场景。资源共包含2000个文件,以7987张jpg图像为主体,另附1个py脚本与1个png图片,压缩包约676.81MB,训练集7180张、测试集808张,覆盖cloudy、dew、fogsmog、frost、glaze、hail、lightning、rain、rainbow、rime、sandstorm、shine、snow、sunrise共14个类别,目录按train_data与test_data划分,便于直接加载训练与评估。已有1130人学习下载,作者使用CNN模型取得96.3%的最好结果,说明数据质量与类别区分度较好。读者可基于该数据集复现分类流程、调整网络结构与超参数,并借助脚本快速搭建训练入口,适合作为课程作业、竞赛练习或论文实验的基准数据。

1. 天气分类数据集共9000个样本:从零训练一个能用的天气识别模型

手上有个天气分类数据集,共9000个样本,四个类别——多云、雨天、晴天、日出。这个量级不大不小,刚好卡在一个尴尬的位置:从头训练容易过拟合,直接套用预训练模型又觉得"杀鸡用牛刀"。但实际工程里,这恰恰是最常见的场景——你不可能每次都拿到百万级数据,更多时候就是几千到一万张图,要求你在有限资源下把准确率做到能上线的水平。

这篇文章要解决的问题很具体:拿到这个9000样本的天气分类数据集后,怎么划分、怎么增强、选什么模型、参数怎么调、训练过程中怎么判断模型是不是在"假学"。适合有基本PyTorch基础、想快速跑通一个完整图像分类流程的读者。不涉及任何复杂部署,目标就是让你在单卡上把验证集准确率做到90%以上,并且知道每一步为什么这么做。

2. 数据划分与增强:9000样本怎么切才不翻车

2.1 按类别分层划分,别用随机切分

9000个样本如果按7:2:1划分,训练集6300、验证集1800、测试集900。听起来合理,但如果你直接random.shuffle然后切片,很可能出现某个类别在验证集里只有几十张的情况。天气分类四个类别理论上应该均衡,但实际数据集中"晴天"和"多云"往往偏多,"日出"可能偏少。

正确做法是按类别分层抽样。用sklearn.model_selection.train_test_split的stratify参数,保证每个子集的类别比例和原始一致。

import os import shutil from sklearn.model_selection import train_test_split # 假设数据结构:data/多云/*.jpg, data/雨天/*.jpg, ... data_dir = "data" classes = ["cloudy", "rainy", "sunny", "sunrise"] all_files, all_labels = [], [] for idx, cls in enumerate(classes): cls_dir = os.path.join(data_dir, cls) for fname in os.listdir(cls_dir): if fname.lower().endswith((".jpg", ".png", ".jpeg")): all_files.append(os.path.join(cls_dir, fname)) all_labels.append(idx) # 先切出测试集,再从剩余切验证集 X_train, X_test, y_train, y_test = train_test_split( all_files, all_labels, test_size=0.1, stratify=all_labels, random_state=42 ) X_train, X_val, y_train, y_val = train_test_split( X_train, y_train, test_size=0.2, stratify=y_train, random_state=42 ) print(f"训练集: {len(X_train)}, 验证集: {len(X_val)}, 测试集: {len(X_test)}")

这段代码的关键在stratify=all_labels,它确保每个子集中四个类别的比例与原始数据集一致。random_state=42是为了可复现,换个数结果会不同但分布规律一致。注意先切测试集再切验证集,避免测试集信息泄露到验证集。

2.2 增强策略要匹配天气场景

天气分类的图像增强不能照搬ImageNet那套。翻转、裁剪、颜色抖动都可以用,但有几个细节:

  • 水平翻转:可以,天气场景没有方向性
  • 垂直翻转:不建议,天空不会跑到下面
  • 颜色抖动:慎用,天气分类高度依赖颜色和亮度特征,过度抖动会让"晴天"和"多云"混淆
  • RandomResizedCrop:可以用,但scale范围别设太激进,0.7到1.0比较稳
from torchvision import transforms train_transform = transforms.Compose([ transforms.Resize((256, 256)), transforms.RandomResizedCrop(224, scale=(0.7, 1.0)), transforms.RandomHorizontalFlip(p=0.5), transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.1, hue=0.05), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) val_transform = transforms.Compose([ transforms.Resize((256, 256)), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ])

ColorJitter里hue只给了0.05,因为色相变化太大会把日出的暖色调变成冷色调。Normalize用的是ImageNet统计量,如果你用预训练模型就必须保持一致,从头训练的话可以用数据集自身的均值和标准差,但差别不大。

提示:增强只作用于训练集,验证集和测试集只用Resize+CenterCrop+Normalize。这个坑每年都有人踩。

3. 模型选型:9000样本该用ResNet还是EfficientNet

3.1 预训练权重几乎总是更好的起点

9000样本从头训练一个ResNet-50,训练集6300张,每个epoch只能看到6300次样本。ResNet-50有2500万参数,这个数据量根本喂不饱,训练loss能降下去但验证loss很快反弹——典型的过拟合。

用预训练权重是标准做法。ImageNet上预训练的模型已经学会了边缘、纹理、颜色分布等底层特征,你只需要微调高层语义部分。实际测试中,同样9000样本,预训练ResNet-18比从头训练的ResNet-50验证准确率高8到12个百分点。

import torch import torch.nn as nn from torchvision import models def build_model(num_classes=4, model_name="resnet18", pretrained=True): if model_name == "resnet18": model = models.resnet18(weights=models.ResNet18_Weights.DEFAULT if pretrained else None) in_features = model.fc.in_features model.fc = nn.Sequential( nn.Dropout(0.3), nn.Linear(in_features, num_classes) ) elif model_name == "efficientnet_b0": model = models.efficientnet_b0(weights=models.EfficientNet_B0_Weights.DEFAULT if pretrained else None) in_features = model.classifier[1].in_features model.classifier = nn.Sequential( nn.Dropout(0.3), nn.Linear(in_features, num_classes) ) return model

替换分类头时加了Dropout(0.3),这是小数据集上的常规操作。ResNet18_Weights.DEFAULT是新版torchvision的写法,旧版用pretrained=True,效果一样但新写法更明确。

3.2 冻结策略:先冻后解,分阶段微调

直接全量微调不是不行,但小数据集上容易把预训练学到的特征"冲掉"。更稳的做法是分两阶段:

第一阶段:冻结backbone,只训练分类头。学习率设1e-3,跑5个epoch。这一步让随机初始化的分类头先收敛到一个合理位置,不会产生大梯度回传破坏预训练特征。

第二阶段:解冻全部参数,用更小的学习率1e-4微调。跑15到20个epoch,配合余弦退火调度。

def set_backbone_grad(model, requires_grad): for name, param in model.named_parameters(): if "fc" not in name and "classifier" not in name: param.requires_grad = requires_grad # 阶段一 model = build_model() set_backbone_grad(model, False) optimizer = torch.optim.Adam(filter(lambda p: p.requires_grad, model.parameters()), lr=1e-3) # 阶段二(训练5个epoch后) set_backbone_grad(model, True) optimizer = torch.optim.Adam(model.parameters(), lr=1e-4) scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=20)

filter(lambda p: p.requires_grad, ...)这行很重要,否则优化器会把冻结参数也加进去,虽然不更新但浪费显存。阶段切换时重新构建优化器,因为参数组的requires_grad状态变了。

3.3 批次大小与学习率的搭配

9000样本,batch_size设32或64都行。32的话一个epoch约197步,64约98步。小数据集建议用32,梯度更新更频繁,收敛更稳。

学习率和batch_size的关系:batch_size翻倍,学习率可以适当放大1.5到2倍,但微调场景下不建议超过1e-3。我一般用1e-4起步,如果loss下降太慢再调到3e-4。

4. 训练循环与验证:怎么判断模型是不是在假学

4.1 训练循环里必须记录的东西

很多人训练时只看loss,这是不够的。至少记录:训练loss、训练准确率、验证loss、验证准确率、当前学习率。这四个指标能告诉你模型是在真学还是假学。

import torch from torch.utils.data import DataLoader, Dataset from PIL import Image class WeatherDataset(Dataset): def __init__(self, file_list, label_list, transform=None): self.file_list = file_list self.label_list = label_list self.transform = transform def __len__(self): return len(self.file_list) def __getitem__(self, idx): img = Image.open(self.file_list[idx]).convert("RGB") label = self.label_list[idx] if self.transform: img = self.transform(img) return img, label def train_one_epoch(model, loader, optimizer, criterion, device): model.train() total_loss, correct, total = 0, 0, 0 for imgs, labels in loader: imgs, labels = imgs.to(device), labels.to(device) optimizer.zero_grad() outputs = model(imgs) loss = criterion(outputs, labels) loss.backward() optimizer.step() total_loss += loss.item() * imgs.size(0) correct += (outputs.argmax(1) == labels).sum().item() total += imgs.size(0) return total_loss / total, correct / total @torch.no_grad() def evaluate(model, loader, criterion, device): model.eval() total_loss, correct, total = 0, 0, 0 for imgs, labels in loader: imgs, labels = imgs.to(device), labels.to(device) outputs = model(imgs) loss = criterion(outputs, labels) total_loss += loss.item() * imgs.size(0) correct += (outputs.argmax(1) == labels).sum().item() total += imgs.size(0) return total_loss / total, correct / total

model.train()和model.eval()必须成对出现,前者启用Dropout和BatchNorm的更新,后者固定它们。@torch.no_grad()在验证时关闭梯度计算,省显存也加速。

4.2 过拟合与欠拟合的判断信号

  • 训练loss降、验证loss也降:正常学习
  • 训练loss降、验证loss先降后升:过拟合,需要加正则或早停
  • 训练loss不降、验证loss不降:欠拟合,学习率太小或模型容量不够
  • 训练准确率很高、验证准确率很低且差距持续扩大:严重过拟合

9000样本用预训练ResNet-18,正常情况下第3到5个epoch验证准确率就能到85%以上,10个epoch内到90%。如果20个epoch还在80%徘徊,检查数据标签有没有错、增强是不是太狠、学习率是不是设错了。

注意:验证集准确率波动2%以内是正常的,别看到一次下降就调参。看趋势,不看单点。

4.3 早停与模型保存

best_acc = 0.0 patience = 7 counter = 0 for epoch in range(25): train_loss, train_acc = train_one_epoch(model, train_loader, optimizer, criterion, device) val_loss, val_acc = evaluate(model, val_loader, criterion, device) scheduler.step() print(f"Epoch {epoch+1}: train_loss={train_loss:.4f} train_acc={train_acc:.4f} " f"val_loss={val_loss:.4f} val_acc={val_acc:.4f}") if val_acc > best_acc: best_acc = val_acc torch.save(model.state_dict(), "best_model.pth") counter = 0 else: counter += 1 if counter >= patience: print(f"Early stopping at epoch {epoch+1}") break

patience=7意味着验证准确率连续7个epoch没提升就停。这个值别设太小,余弦退火过程中准确率可能先平后升。保存的是state_dict()而不是整个模型,加载时先构建模型结构再load_state_dict。

5. 避坑与排查:9000样本训练中最容易翻车的5个地方

5.1 验证准确率远高于测试准确率

现象:验证集92%,测试集只有78%。

原因:验证集和测试集划分时没有分层,或者验证集被重复用于调参导致信息泄露。更隐蔽的原因是数据集中存在同一场景的连拍图片,随机划分时相似图片同时进入训练和验证集。

解决:按类别分层划分,并且检查是否有重复或高度相似的图片。可以用感知哈希去重,或者按拍摄时间/地点分组划分。如果数据集里同一场景有多张图,用GroupShuffleSplit而不是普通train_test_split。

5.2 训练loss正常但验证loss是NaN

现象:前几个epoch正常,突然验证loss变成nan。

原因:验证集中有损坏图片,或者某张图片尺寸异常导致预处理出错。也可能是学习率太大导致权重爆炸。

解决:在Dataset的__getitem__里加try-except,跳过无法读取的图片。同时用torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)做梯度裁剪。检查学习率是否超过1e-3。

5.3 某个类别准确率特别低

现象:多云、雨天、晴天都在90%以上,日出只有60%。

原因:日出样本量太少,或者日出和多云在视觉上确实难分。也可能是标注错误,把日出标成了多云。

解决:先看混淆矩阵,确认是跟哪个类别混淆。如果是样本量问题,对少数类做过采样或加类别权重。如果是标注问题,人工抽查几十张。nn.CrossEntropyLoss(weight=class_weights)可以给少数类更高权重。

5.4 模型在验证集上表现好但实际用起来很差

现象:验证集90%,但拿手机拍的照片测试,准确率掉到60%。

原因:训练数据都是专业相机拍摄的高质量图片,手机照片的色调、分辨率、噪点分布完全不同。这是域偏移问题。

解决:在增强里加入模拟手机拍摄的变换——降低分辨率、加高斯噪声、调整白平衡。或者收集少量手机拍摄的图片做微调。没有这个条件的话,至少把Resize的插值方式从默认的bilinear改成bicubic,对低质量图片更友好。

5.5 训练速度慢得离谱

现象:一个epoch要跑十几分钟。

原因:num_workers设成了0,数据加载是单线程的。或者图片分辨率太大,224的输入实际读的是4000x3000的原图。

解决:DataLoader设num_workers=4或8,pin_memory=True。在Dataset里先做一次离线Resize,把原图缩到256x256存下来,训练时直接读小图。这个操作能把训练速度提升3到5倍。

train_loader = DataLoader( train_dataset, batch_size=32, shuffle=True, num_workers=4, pin_memory=True, drop_last=True )

drop_last=True在训练时丢弃最后一个不满batch的数据,避免BatchNorm在只有一两张图时统计量不准。

6. 进阶技巧:用混淆矩阵和Grad-CAM定位模型到底在看哪里

训练完模型,准确率90%以上,但你真的知道模型在学什么吗?我习惯做两件事:画混淆矩阵,跑Grad-CAM。

混淆矩阵能告诉你类别之间的混淆模式。比如"多云"和"雨天"互相混淆多,说明模型对云层厚度和亮度的区分不够敏感。这时候可以针对性加强这两类的数据增强,或者引入注意力机制。

from sklearn.metrics import confusion_matrix, classification_report import seaborn as sns import matplotlib.pyplot as plt model.eval() all_preds, all_labels = [], [] with torch.no_grad(): for imgs, labels in test_loader: imgs = imgs.to(device) outputs = model(imgs) all_preds.extend(outputs.argmax(1).cpu().numpy()) all_labels.extend(labels.numpy()) cm = confusion_matrix(all_labels, all_preds) sns.heatmap(cm, annot=True, fmt="d", xticklabels=classes, yticklabels=classes) plt.savefig("confusion_matrix.png") print(classification_report(all_labels, all_preds, target_names=classes))

Grad-CAM更直观,它把模型最后卷积层的梯度回传到特征图上,生成热力图叠加在原图上。如果模型判断"晴天"时关注的是天空区域,说明学对了;如果关注的是图片角落的水印,那就翻车了。

from pytorch_grad_cam import GradCAM from pytorch_grad_cam.utils.image import show_cam_on_image target_layers = [model.layer4[-1]] # ResNet18的最后一个卷积块 cam = GradCAM(model=model, target_layers=target_layers) grayscale_cam = cam(input_tensor=img_tensor.unsqueeze(0)) visualization = show_cam_on_image(img_np, grayscale_cam[0], use_rgb=True)

跑完Grad-CAM后我发现一个血泪教训:模型有时候会依赖图片的亮度均值来判断类别,而不是真正的天气特征。这意味着如果测试图片整体偏暗,模型会倾向预测"雨天"。解决办法是在增强里加入亮度归一化,或者用更激进的ColorJitter让模型学会忽略全局亮度。

最后一个习惯:每次训练完,把验证集里预测错误的图片单独存一个文件夹,肉眼过一遍。十次里有八次能发现标注错误或者数据质量问题。这个动作花不了几分钟,但比调参管用得多。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/11 5:22:05

机战钢铁巨舰|海底异风暴,探秘流转变幻的深海奇境

海底异风暴是一处动态变幻的深海秘境,区别于泰坦深海的静谧安稳,这片水下星域拥有持续流转的光影水流与浮动晶质景观,动态景致变幻无穷,是星际深海中最具灵动质感的特色漫游场景。整片深海空间的水体始终处于轻柔流转的状态&#…

作者头像 李华
网站建设 2026/10/11 5:20:36

技能容器化:构建可验证、可组合的个人能力操作系统

1. 项目概述:当“skills”不再只是简历上的关键词,而成为可验证、可组合、可进化的个人能力操作系统最近在多个技术社区、职业发展论坛和高校创新工坊里,“skills”这个词高频出现,但它的语义正在发生一次静默却深刻的迁移。它早已…

作者头像 李华
网站建设 2026/10/11 5:19:39

个人微信API二次开发:如何设计微信消息处理队列?

在处理微信消息时,如果每收到一条消息就立即执行完整的业务逻辑,系统很容易出现处理拥堵。例如,短时间内收到大量消息,每条消息都要写数据库、调用其他服务,甚至执行 AI 分析。所有操作都放在同一个流程里,…

作者头像 李华
网站建设 2026/10/11 5:18:21

InstallShield 2021安装包制作实战指南

自从开始帮别人做项目交付,我就意识到一个问题:把代码跑起来只是完成了一半,剩下的一半是让你的程序在别人的电脑上也能正常跑起来。你可能遇到过这种场景——辛苦写完的程序,拷给甲方双击,结果先是缺 DLL,…

作者头像 李华
网站建设 2026/10/11 5:16:50

线缆兼容性不只看质量:StarTech数据线选型与排坑实战

做硬件集成和现场调试这些年,StarTech这个牌子的线缆我反复接触过很多次。说实话,在成品线缆里它算靠谱的,做工、屏蔽、接头的可靠性都说得过去,价格也比普通工包线高出一截。但哪怕是这样,实际项目中还是躲不开兼容性…

作者头像 李华
网站建设 2026/10/11 5:16:47

C#上位机集成海康相机、雷赛运动控制卡与数据库实战解析

简介:面向工业视觉与运动控制场景的C#工程源码包,整合海康相机取像识别、雷赛运动控制卡联动与数据库上传查询,适合需要完成设备ID追溯与产线信息化的上位机开发、调试人员参考学习。压缩包共205个文件,约22.98MB,以39…

作者头像 李华