
简介一份面向图像分类与植物识别训练的花类数据集适合深度学习初学者、算法开发者及计算机视觉课设。图片采集自多个网络来源覆盖洋甘菊、郁金香、玫瑰、向日葵、蒲公英五个常见类别每类约八百张照片图像分辨率约320×240且比例不统一接近自然拍摄状态可用于从照片中识别植物或训练分类模型。数据集以真实拍摄照片为主尺寸虽小但类别清晰适合快速验证模型效果。zip压缩包约449.82MB文件总数约2000主体为JPG图片另附少量Python脚本与TXT说明便于数据预览、统一更名与批量划分。当前已有607人浏览学习适合图像分类、迁移学习等实践。下载后可直接获得按类别整理的花卉图像集配合脚本可快速完成数据预处理与训练集/测试集拆分省去手动爬取和整理时间也可作为算法验证与课程设计的可靠数据支撑。1. 花类识别数据集五分类图像识别从哪里入手做图像分类最烦的不是模型选型而是数据到手时一堆烂摊子类别没归档、图片尺寸千奇百怪、标签要手工敲。这份花类识别数据集 zip 解压后是 4242 张真实花朵照片按洋甘菊、郁金香、玫瑰、向日葵、蒲公英五类归档每类约 800 张单张分辨率约 320x240比例不统一。它解决的是“跑通流程”这个刚需五分类、样本够、标注明确直接拿来验证迁移学习、跑 baseline、做课程设计都很顺手。不管你是刚入门想完整走一遍图像分类流程还是调参阶段缺一块干净的数据做对比实验这份资源都值得先下下来铺开看看。2. 数据盘清楚再动手五个类别、图片规格与文件名里的信息2.1 五个类别的构成每类约 800 张足够撑起一个分类任务拿到 zip 先别急着解压训练。我习惯先把数据盘一遍类别数量、每类张数、图片尺寸分布这三个数直接决定后面模型怎么选。这份数据一共 4242 张分成五个类别每类大约 800 张。放到图像分类任务里这是一个相当友好的规模类别少样本量均衡不存在一类 3000 张另一类 200 张那种让人头疼的长尾分布。类别大概数量典型外观特征洋甘菊约 800 张白色花瓣、黄色花心花型较小郁金香约 800 张杯状花型颜色从红到黄都有玫瑰约 800 张多层花瓣红色粉色为主带刺茎向日葵约 800 张大花盘、黄色舌状花花心明显蒲公英约 800 张黄色头状花序或白色绒球状这个均衡分布有个直接好处训练时不用为类别权重做太多额外处理。我用过不少数据集类别不均衡时 loss 会被大类别主导需要给少数类加权。这份数据五个类别都在 800 张上下浮动先不用考虑这个问题可以把精力集中在模型结构和训练技巧上省掉一整套类别均衡的前置工作。数据来源是数据流、Google 图像、Yandex 图像这几个渠道的聚合。这意味着图片不是某个固定相机拍的光照、角度、背景都非常杂反而更像真实场景。用它训练出来的模型放到自然场景里泛化性会比用单一来源图库训练的效果更可信。这一点在你后期做实测的时候会感受很明显同一个模型在网上下载的实拍照片上预测准确率不会掉太多。2.2 文件名规律与图片规格320x240 小图的真实价值原始文件名的格式类似 2431737309_1468526f8b.jpg 这种前半段是数字 ID后半段是图片的哈希串。这类命名方式在爬取类数据集里很常见它本身不带类别信息所以分类必须靠目录结构或标签文件来确定这一点在后面整理目录时要特别注意不要指望从文件名里读出类别来。摘要里写得很清楚照片不是高分辨率的大约 320x240 像素而且不会缩小为单一尺寸比例各不相同。我第一次做图像分类时以为分辨率越高越好后来发现这是误解。320x240 的图意味着输入尺寸可以设小一点比如 224x224 甚至 160x160训练速度明显更快显存占用更低。对小数据场景来说与其追求高清大图不如在合理的输入尺寸内保留更多训练轮次。比例不统一这件事很多人会忽略但它一定会在训练时给你颜色看。不同比例的图直接塞进 batchtensor 形状对不齐框架直接报错就算强行堆叠也会让卷积核学到畸变特征。常规做法是在数据加载阶段做 resize 加中心裁剪先把所有图统一到同一个尺寸再进网络。拿到数据后我建议先跑一段脚本扫一遍尺寸分布确认横图和竖图的比例跨度心里有个底# inspect.py from PIL import Image import os roots [ ./flowers/daisy, ./flowers/tulip, ./flowers/rose, ./flowers/sunflower, ./flowers/dandelion, ] for root in roots: sizes [] for fname in os.listdir(root): path os.path.join(root, fname) try: w, h Image.open(path).size sizes.append((w, h)) except Exception: print(f无法读取: {path}) if not sizes: continue ws [s[0] for s in sizes] hs [s[1] for s in sizes] print(f{root}: 数量{len(sizes)}, 宽度{min(ws)}-{max(ws)}, 高度{min(hs)}-{max(hs)})这段脚本遍历五个类别目录用 PIL 读取每张图的宽高汇总出每个目录的尺寸范围。输出的价值在于让你直观看到数据里有多少横构图、多少竖构图、尺寸跨度有多大后面设置 Resize 参数时就不会拍脑袋。脚本里加了 try/except个别读取失败的文件会打印路径但不会中断整个扫描。2.3 分类和检测别搞混这份数据不是目标检测数据集有人会问这数据能不能拿来训练检测模型。这里要先把任务类型说清楚像 CCPD 车牌检测、HRSC2016 遥感舰船检测、ReID 行人重识别这类数据集核心是定位、匹配、输出 bounding box而这份花类识别数据集的核心是判别整张图属于五个类别中的哪一类没有框坐标信息属于图像分类任务。这一点先想清楚后面所有流程才不会跑偏。如果你想做目标检测比如在花田照片里框出每一朵花那需要先把这份数据转成检测格式。以 YOLO 系列为例要为每张图标注一个或多个目标框生成类别 ID 加归一化中心坐标和宽高的 txt 文件而且 320x240 的分辨率对检测任务来说偏低了小目标会非常难框准。这个转换不是不能做但工作量不小我更建议直接找带框的花卉检测数据集而不是拿这份硬转。顺便说一句对比一下你以前用过的数据集MNIST 是 28x28 灰度手写数字类别极度规整鸢尾花数据集是 4 维特征表格连图像都不是PHM2012 那种故障诊断数据集要按传感器通道去解析。这份数据的形态更接近真实照片分类任务所以用它练手迁移学习踩坑的含金量比 MNIST 高不少流程也更贴近工业落地场景。3. 把数据跑起来目录梳理、标签生成与训练集划分3.1 先做目录整理把散装图片按类别归档数据集 zip 解压之后图片是分散在一层目录里的文件名本身不带类别标记。第一步就是按类别建目录、把图归位。我建议用脚本批量处理而不是手动拖拽因为一旦类别多起来手分一次能忍分第二遍就想砸键盘。# 新建五个类别目录 mkdir -p flowers/{daisy,tulip,rose,sunflower,dandelion} # 按映射关系移动图片下面是单条命令的演示 mv ./raw_images/2431737309_1468526f8b.jpg ./flowers/daisy/上面 bash 命令只是演示目录结构和单文件移动动作。真实数据集的类别和文件对应关系通常由提供方给出或者你已经知道每个子目录对应哪个类别。如果 zip 解压后已经是按五个类别分好的子目录这一步可以直接跳过进入 3.2。更通用的做法是写 Python 脚本从映射表读取文件名和类别批量移动。这样即使类别从五个扩到二十个也只改配置不动代码。# organize.py import os import shutil # 文件名 - 类别 的映射实际使用中从 csv 或 txt 读取 mapping { 2431737309_1468526f8b.jpg: daisy, 4932735362_6e1017140f.jpg: tulip, 8717900362_2aa508e9e5.jpg: rose, } src_dir ./raw_images dst_dir ./flowers for fname, cls in mapping.items(): src os.path.join(src_dir, fname) dst os.path.join(dst_dir, cls, fname) if not os.path.exists(src): print(f跳过缺失文件: {src}) continue if os.path.exists(dst): print(f目标文件已存在: {dst}) continue shutil.move(src, dst)这段脚本逐个处理映射项源文件不存在就跳过目标文件已存在也跳过避免重复移动覆盖数据。两个检查都很便宜但对大批量文件整理来说能少很多麻烦。映射表建议用 pandas 从 csv 读不要写死在代码里后面加数据时不用改脚本。3.2 训练集与验证集划分8:2 分层抽样固定随机种子目录归位之后下一步是数据划分。图像分类里最忌讳的是把所有图都拿去训练跑完一个准确率就说模型好了。没有独立验证集你根本不知道模型是背住了训练数据还是学到了真正可泛化的特征。我按 8:2 划分训练集和验证集并且每个类别内部独立抽样保证五个类别的比例在两边完全一致。每类 800 张的话训练约 640 张、验证约 160 张总量足够模型收敛。# split.py import os import shutil from sklearn.model_selection import train_test_split classes [daisy, tulip, rose, sunflower, dandelion] src_root ./flowers train_root ./flower_split/train val_root ./flower_split/val for cls in classes: cls_dir os.path.join(src_root, cls) imgs [f for f in os.listdir(cls_dir) if f.lower().endswith((.jpg, .jpeg, .png))] train_imgs, val_imgs train_test_split(imgs, test_size0.2, random_state42) os.makedirs(os.path.join(train_root, cls), exist_okTrue) os.makedirs(os.path.join(val_root, cls), exist_okTrue) for f in train_imgs: shutil.copy(os.path.join(cls_dir, f), os.path.join(train_root, cls, f)) for f in val_imgs: shutil.copy(os.path.join(cls_dir, f), os.path.join(val_root, cls, f)) print(f{cls}: train{len(train_imgs)}, val{len(val_imgs)})代码逻辑是逐类别循环把每类图片按 8:2 切分用 copy 而非 move 保留原始数据。这样后面想调整划分比例不用重新解压 zip 和归位重跑一次脚本就行。test_size0.2 表示验证集占 20%random_state42 固定随机种子保证每次运行结果一致这是复现实验的基本要求。严格按类别循环后train_test_split 在每个类别内部做划分效果等同分层抽样。划分完之后的目录结构是标准的 ImageFolder 格式flower_split/ ├── train/ │ ├── daisy/ │ ├── tulip/ │ ├── rose/ │ ├── sunflower/ │ └── dandelion/ └── val/ ├── daisy/ ├── tulip/ ├── rose/ ├── sunflower/ └── dandelion/这种结构是 torchvision.datasets.ImageFolder 直接支持的标签由子目录名自动生成省掉手工写标签文件的步骤。到这里数据准备工作就算完成了可以进入模型训练。提示train_test_split 在每个类别内部独立调用比一次性传入全部图片加 stratify 参数更直观也更容易在输出日志中核对每个类别的划分张数。4. 模型训练与参数选择用迁移学习跑出可用的五分类模型4.1 为什么选迁移学习320x240 小图、每类 800 张样本的现实选择先给结论对这种规模的五分类任务直接拿 ImageNet 预训练模型做迁移学习而不是从零训练一个 CNN。原因有三个。第一图像信息量有限。单张图只有 320x240从零训练一个 CNN 需要大量数据让浅层卷积学到通用的边缘和纹理特征每类 800 张不太够。第二迁移学习收敛快。预训练模型已经把通用视觉特征学好了我们只需要替换最后一层全连接在这份数据上微调十几轮就能稳定。第三训练成本低。一张普通显卡跑 ResNet18 或 MobileNetV2 微调几分钟到十几分钟一轮完整实验可以快速试错。很多人一上来就问“能不能用 YOLOv8 训练自己的数据集”。YOLO 解决的是目标检测问题输出是边界框加类别这份数据是图像分类整张图的主体就是花不需要框。拿检测模型硬套分类任务既慢又没必要。如果未来你的场景是花田里同时出现多种花、要逐朵框出来那才需要把这份数据转成 YOLO 格式并补标注框。就当前这个五分类任务而言分类网络是更直接的选择。4.2 训练脚本与关键参数输入尺寸、batch size、学习率与数据增强我用 PyTorch 写一个标准的迁移学习训练脚本Backbone 选 ResNet18。选它的理由是结构简单、收敛稳定、参数量适中残差连接在小数据集微调时不容易出现深层网络退化问题。MobileNetV2 也可以速度更快精度略低一点两者选哪个主要看你手里的显卡。# train.py import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms, models from torch.utils.data import DataLoader # 训练集增强 归一化 train_transform transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(p0.5), transforms.RandomRotation(15), 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_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_data datasets.ImageFolder(./flower_split/train, transformtrain_transform) val_data datasets.ImageFolder(./flower_split/val, transformval_transform) train_loader DataLoader(train_data, batch_size32, shuffleTrue, num_workers4) val_loader DataLoader(val_data, batch_size32, shuffleFalse, num_workers4) # 加载 ImageNet 预训练权重替换最后一层为 5 分类 model models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1) model.fc nn.Linear(model.fc.in_features, 5) criterion nn.CrossEntropyLoss() optimizer optim.Adam(model.parameters(), lr1e-4) scheduler optim.lr_scheduler.StepLR(optimizer, step_size5, gamma0.5)这里几个参数值得单独说。Resize((224, 224)) 把所有图统一到 ResNet 标准输入尺寸因为原始图片比例不一这一步会引入轻微形变但对这类花分类任务影响很小属于迁移学习里的常见做法。batch_size32 在 20GB 显存以下的卡上跑 ResNet18 都够显存小就降到 16梯度更新依然稳定。学习率用 1e-4 而不是默认的 1e-3因为预训练权重已经成熟lr 太大会把 ImageNet 学好的特征冲掉微调场景下小学习率更稳。scheduler 每 5 轮把学习率减半让训练后期收敛更细腻。device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device) best_acc 0.0 for epoch in range(15): model.train() running_loss 0.0 for images, labels in train_loader: images, labels images.to(device), labels.to(device) outputs model(images) loss criterion(outputs, labels) optimizer.zero_grad() loss.backward() optimizer.step() running_loss loss.item() model.eval() correct total 0 with torch.no_grad(): for images, labels in val_loader: images, labels images.to(device), labels.to(device) outputs model(images) _, predicted torch.max(outputs, 1) total labels.size(0) correct (predicted labels).sum().item() val_acc correct / total print(fEpoch {epoch1}/15, Loss: {running_loss/len(train_loader):.4f}, Val Acc: {val_acc:.4f}) if val_acc best_acc: best_acc val_acc torch.save(model.state_dict(), best_model.pth) scheduler.step()训练循环有两个习惯必须养成。第一每个 epoch 结束都在验证集评估一次而不是只训练完评估一次loss 和 val_acc 放在一起看才能及早发现过拟合。第二验证阶段包一层 torch.no_grad()否则会白白计算梯度占用显存不说还拖慢速度。best_acc 的判断会把验证集上表现最好的权重保存下来避免最后一轮权重恰好过拟合导致浪费一次训练。epoch 设 15 轮是给第一次跑的经验参考值。实际跑下来ResNet18 迁移学习在这个数据集上通常第 8 到第 12 轮就稳定15 轮足够看收敛趋势。如果第 15 轮准确率还在上涨就加到 20如果第 10 轮就开始掉说明过拟合了按第 5 章的方法处理。注意显存不足时先降 batch_size不要先降输入分辨率。分辨率降到 160x160 以下花瓣纹理信息会明显流失最终准确率反而上不去。5. 花类识别训练避坑记录小图、比例不一致与类别误判5.1 现象验证集准确率不升反降训练 loss 一路走低我第一次跑这份数据就遇到了这个坑。训练 loss 从 1.2 一路降到 0.15训练集准确率接近 98%看着一切正常但验证准确率从第 7 轮开始不涨反跌最后稳定在 82% 左右。那段时间我把调参当成玄学试过调大 batch size、换优化器都没用。原因模型过拟合。每类只有 800 张训练图ResNet18 的参数量足够把训练图“背”下来训练 loss 当然低但它没学到可泛化的特征遇到没见过的图就露馅。另一个推手是数据增强不够如果不做翻转、旋转、颜色扰动模型很容易去记背景颜色和构图位置。解决数据增强先加上。我在第 4 章脚本里已经写了 RandomHorizontalFlip、RandomRotation、ColorJitter这三项几乎零成本但非常有效。第二步引入第 4 章里的 best_acc 保存逻辑每个 epoch 验证一次只在刷新最高准确率时保存权重而不是训练结束无脑存最后一轮。如果验证准确率连续 3 轮不涨提前停止训练省时间也避免过拟合继续恶化。5.2 现象DataLoader 报错图片尺寸不匹配另一个高频翻车点出现在数据加载阶段。第一次我把 transform 写成只有 ToTensor 和 Normalize没加 Resize结果 DataLoader 在拼 batch 时报错大致意思是 tensor 尺寸对不上。原因是这套数据比例不统一直接转张量后每张图的空间维度不同PyTorch 没法把它们堆成一个 batch tensor。原因transform 缺了 Resize或者 Resize 和 ToTensor 写反了。Resize 必须放在 ToTensor 前面因为 Resize 处理的是 PIL ImageToTensor 输出的是张量。如果先 ToTensor 再 ResizeResize 拿到的输入类型不对要么报错要么产生奇怪的插值结果。解决transform 第一项固定 Resize((224, 224))顺序保持 Resize 到 ToTensor 再到 Normalize。如果不想牺牲原始构图比例也可以先 Resize 到短边 224、再 CenterCrop 成 224x224会丢失部分边缘内容但保留横竖比例。两种方案训练都能跑看你自己取舍。还有一类情况是图片文件本身损坏PIL 打开直接抛异常这种可以在加载时加 try/except 跳过from PIL import Image def load_image(path): try: img Image.open(path).convert(RGB) return img except Exception as e: print(f损坏图片: {path}, 错误: {e}) return None这段代码套在读取阶段遇到损坏文件直接打印路径并返回 None后续跳过。数据集是采集来的偶发一两个坏文件很正常别让一张坏图拖垮整个训练流程。5.3 现象蒲公英和洋甘菊互相误判别的类别都正常训练完第一次完整评估我发现五个类别里蒲公英准确率只有 71%洋甘菊 76%而玫瑰、郁金香、向日葵都在 85% 以上。看 val 集最后一个 epoch 的输出误判全部集中在这一对。原因视觉上确实难分。蒲公英是黄色头状花序洋甘菊是白色花瓣加黄色花心在 320x240 的小图里如果拍摄距离稍远两者的黄色花心区域高度相似。加上部分图主体占比太小模型实际上主要靠颜色分布判断撞车是必然的。解决两个方向。数据层面把这两类里明显模糊、主体太小的图筛掉保留特征清晰的样本重新训练测试集保留这些难例用来衡量模型的真实鲁棒性。模型层面不需要追求这两类精度做到 95%自然场景下这对类别本来就难分。想要更可靠的判断可以把这两类的训练图数量稍微往上提或者用 focal loss 强制模型关注难分类样本。不过对这份数据来说先做数据筛查性价比最高。用混淆矩阵能看到具体是哪些图在误判这一节和第 6 章的评估脚本可以联动使用。6. 进阶验证用混淆矩阵评估模型搭一个命令行识别小工具6.1 混淆矩阵一眼看出哪两类在互相打架训练完只盯着 val_acc 一个数字不够。五分类里如果有一对类别互相误判总准确率会把问题掩盖掉。用 sklearn 的 confusion_matrix 把验证集预测结果拉成矩阵比看十行日志都直观。# evaluate.py import torch from torchvision import datasets, transforms, models from sklearn.metrics import confusion_matrix classes [daisy, tulip, rose, sunflower, dandelion] 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]), ]) val_data datasets.ImageFolder(./flower_split/val, transformval_transform) val_loader DataLoader(val_data, batch_size32, shuffleFalse) model models.resnet18(weightsNone) model.fc torch.nn.Linear(model.fc.in_features, 5) model.load_state_dict(torch.load(best_model.pth)) model.eval() all_preds, all_labels [], [] with torch.no_grad(): for images, labels in val_loader: outputs model(images) preds torch.argmax(outputs, dim1) all_preds.extend(preds.cpu().numpy()) all_labels.extend(labels.cpu().numpy()) cm confusion_matrix(all_labels, all_preds) print(混淆矩阵行列对应, classes) print(cm)这段代码从验证集拿到每张图的预测类别和真实标签拼成两个一维数组后交给 confusion_matrix。矩阵第 i 行第 j 列表示“真实类别是 i 却被预测成 j”的样本数对角线越高越好非对角线值越大说明那一对类别越容易打架。6.2 命令行预测脚本任意一张图直接识别验证完不是终点模型要给人用。用 argparse 写一个最小预测脚本输入图片路径输出类别和置信度# predict.py import argparse import torch from torchvision import transforms, models from PIL import Image classes [daisy, tulip, rose, sunflower, dandelion] parser argparse.ArgumentParser(description花朵分类预测) parser.add_argument(--image, requiredTrue, help输入图片路径) parser.add_argument(--model, defaultbest_model.pth, help模型权重路径) args parser.parse_args() 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]), ]) model models.resnet18(weightsNone) model.fc torch.nn.Linear(model.fc.in_features, 5) model.load_state_dict(torch.load(args.model, map_locationcpu)) model.eval() img Image.open(args.image).convert(RGB) input_tensor transform(img).unsqueeze(0) with torch.no_grad(): outputs model(input_tensor) probs torch.softmax(outputs, dim1).squeeze() pred_idx torch.argmax(probs).item() print(f预测类别{classes[pred_idx]}置信度{probs[pred_idx].item():.3f})这个脚本把训练时的预处理原样搬过来加载保存的权重做一次前向推理softmax 之后取最大概率作为结果。我在自己机器上跑320x240 的原始图片直接识别效果和训练时没有明显落差偶尔有蒲公英被识别成洋甘菊跟第 5 章分析的情况一致。从那以后我每训完一个分类模型都会强制走一遍“混淆矩阵 单图实测”这套流程。单张图片实测尤其能发现评估报告里看不出的问题因为你终于能亲眼看模型到底在看什么。希望这套流程能帮到你。本文还有配套的精品资源点击获取