☰
花卉识别数据集与训练代码:从零跑通分类模型
2026/10/2 8:41:27 网站建设 项目流程

简介:这份资源面向深度学习图像分类的学习者与开发者,提供一套可直接上手的花卉识别训练方案,解决从数据到模型落地的完整链路问题。数据集中包含64种花卉、共32000张224×224彩色图像,按25600张训练集与6400张测试集划分,全部为手机实地采集,非网络爬虫图,类别均衡且贴近真实拍摄场景。配套训练代码针对图像分类任务设计,实现了resnet、vgg、inception、mobilenet、densenet、efficientnet、squeezenet等37种主流模型,可自由切换骨干网络进行训练与对比实验。压缩包共2000个文件,以1919张jpg图像为主体,辅以39个txt说明、20个py训练脚本及22个pyc缓存文件,整体约194.92MB,目录结构清晰,便于按数据集与代码模块分别查阅。目前已有1646人学习下载,适合希望快速复现花卉分类基线、开展模型选型对比或完成课程与竞赛项目的读者参考使用。

1. 花卉识别数据集与训练代码:从零跑通一个能用的分类模型

手里有一批花朵照片,想训练一个能认出品种的模型,这件事听起来简单,真正动手时最卡人的往往不是网络结构,而是数据集怎么组织、标签怎么对齐、预训练权重怎么接。花卉识别数据集、深度学习花卉识别、花卉识别模型训练代码、花朵数据集这几个词,本质上指向同一条链路:把散落的图片整理成标准目录,用迁移学习在有限样本上快速收敛,最后拿到一个能对单张图输出类别的权重文件。这套方案适合两类人:一类是刚入门深度学习、想找一个视觉任务练手的工程师;另一类是手头有垂直场景图片、需要快速验证分类可行性的开发者。花卉分类是个好靶子,类别间差异明显、样本量适中,跑通一次就能把数据清洗、增强、训练、评估的完整流程走一遍,后面换成其他细粒度分类任务,骨架基本不用改。

2. 花朵数据集怎么选、怎么组织才不返工

2.1 常见花朵数据集的类别分布与选用判断

公开的花朵数据集里,被引用最多的是 Oxford Flower 系列,常见版本有 102 类,每类样本数从几十到几百不等,图片以自然场景为主,存在大量遮挡、光照变化和同类不同姿态的情况。另一个常被拿来练手的是 TensorFlow 官方教程里用的花朵数据集,规模小、类别少,适合先验证流程。选哪个取决于你的目标:如果只是跑通训练代码,小规模数据集半小时内能出结果;如果要评估模型在真实拍摄条件下的泛化能力,102 类版本更有参考价值,但训练轮次和显存占用都会上去。

判断一个花朵数据集能不能直接用,看三个点。第一,类别是否互斥,有些数据集把「玫瑰」和「月季」分成两类,视觉上极难区分,模型准确率上不去不一定是代码问题。第二,每类样本数是否均衡,长尾分布会让模型偏向多数类,需要靠采样策略或损失函数权重来补。第三,图片分辨率是否一致,不一致时要在数据加载阶段统一缩放,否则同一个 batch 里张量形状对不上,直接报错。

2.2 目录结构与标签映射的落地写法

绝大多数训练框架读数据都遵循同一个约定:每个类别一个文件夹,文件夹名就是类别名。这个结构看起来简单,但实际项目里翻车最多的就是这一步。常见错误是把所有图片平铺在一个目录里,靠文件名前缀区分类别,结果数据加载器读出来只有一个类。正确做法是先建好类别目录,再把图片移动进去。

# 假设原始图片都在 raw_images/ 下,文件名格式为 rose_001.jpg # 按前缀拆分到 train/ 下对应类别目录 mkdir -p dataset/train for f in raw_images/*.jpg; do name=$(basename "$f") label=$(echo "$name" | cut -d'_' -f1) # 取第一个下划线前的部分作为类别 mkdir -p "dataset/train/$label" mv "$f" "dataset/train/$label/" done

这段脚本的逻辑是按文件名前缀归类,cut -d'_' -f1取第一个下划线之前的内容作为类别名。参数上要注意,如果你的文件名里类别名本身带下划线,比如morning_glory_001.jpg,这个切法会把类别截断成morning,需要改成按最后一个下划线切或者用固定分隔符。执行前先拿几条文件名验证一下,别直接对全量数据跑,移错了再恢复很麻烦。

划分训练集和验证集时,我一般按 8:2 切,并且保证每个类别在验证集里至少有 10 张,否则评估指标波动会很大。切分脚本要固定随机种子,不然每次跑出来的验证集不一样,模型对比就没有意义了。

import os, random, shutil random.seed(42) src_root = "dataset/train" val_root = "dataset/val" val_ratio = 0.2 for cls in os.listdir(src_root): cls_dir = os.path.join(src_root, cls) imgs = os.listdir(cls_dir) random.shuffle(imgs) n_val = max(10, int(len(imgs) * val_ratio)) for img in imgs[:n_val]: dst = os.path.join(val_root, cls) os.makedirs(dst, exist_ok=True) shutil.move(os.path.join(cls_dir, img), os.path.join(dst, img))

random.seed(42)保证每次切分结果一致,max(10, ...)是防止小类别验证集样本过少。这段代码会把图片从训练目录移动到验证目录,执行一次就够了,重复执行会把验证集越切越小。跑完之后检查一下dataset/val下每个类别的文件数,确认没有空目录。

注意:移动图片之前先备份一份原始数据,切分脚本写错路径是高频事故,图片移丢了只能重新下载。

3. 用迁移学习训练花卉识别模型:代码、参数与收敛判断

3.1 为什么花卉识别优先用预训练模型而不是从零训

花卉分类属于细粒度视觉任务,类间差异小、类内差异大,从零初始化训练需要大量标注数据才能收敛。实际项目里样本量通常只有几千张,从零训的准确率很难超过 60%。用 ImageNet 预训练权重做迁移学习,相当于模型已经学会了边缘、纹理、颜色分布这些底层特征,你只需要微调高层语义部分,几百张图就能把准确率推到 85% 以上。

选哪个骨干网络,看你的部署环境。ResNet34 和 ResNet50 是稳妥选择,权重好找、结构成熟、社区问题多,遇到报错容易搜到答案。如果要在树莓派这类边缘设备上跑,MobileNetV3 或 EfficientNet-B0 更合适,参数量小、推理快,代价是准确率略低几个点。我一般先用 ResNet34 跑一版基线,确认数据和流程没问题,再根据部署约束换骨干。

3.2 训练脚本的关键参数与逐段说明

下面是一个基于 PyTorch 的最小训练脚本,覆盖数据加载、模型构建、训练循环和验证。代码不追求工程完备,但每个参数都对应一个实际决策点。

import torch import torch.nn as nn from torch.utils.data import DataLoader from torchvision import datasets, transforms, models # 数据增强:训练集用随机裁剪和翻转,验证集只做缩放和归一化 train_tf = transforms.Compose([ transforms.RandomResizedCrop(224, scale=(0.7, 1.0)), transforms.RandomHorizontalFlip(), transforms.ColorJitter(brightness=0.2, contrast=0.2), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) val_tf = transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) train_ds = datasets.ImageFolder("dataset/train", transform=train_tf) val_ds = datasets.ImageFolder("dataset/val", transform=val_tf) train_loader = DataLoader(train_ds, batch_size=32, shuffle=True, num_workers=4) val_loader = DataLoader(val_ds, batch_size=32, shuffle=False, num_workers=4) # 加载预训练 ResNet34,替换最后的全连接层 model = models.resnet34(weights=models.ResNet34_Weights.IMAGENET1K_V1) num_classes = len(train_ds.classes) model.fc = nn.Linear(model.fc.in_features, num_classes) model = model.cuda() criterion = nn.CrossEntropyLoss() optimizer = torch.optim.AdamW(model.parameters(), lr=1e-3, weight_decay=1e-4) scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=20) for epoch in range(20): model.train() for imgs, labels in train_loader: imgs, labels = imgs.cuda(), labels.cuda() optimizer.zero_grad() loss = criterion(model(imgs), labels) loss.backward() optimizer.step() scheduler.step() # 验证阶段 model.eval() correct, total = 0, 0 with torch.no_grad(): for imgs, labels in val_loader: imgs, labels = imgs.cuda(), labels.cuda() preds = model(imgs).argmax(dim=1) correct += (preds == labels).sum().item() total += labels.size(0) print(f"epoch {epoch+1}, val_acc {correct/total:.4f}")

RandomResizedCrop(224, scale=(0.7, 1.0))是花卉任务里最有效的增强之一,因为花朵在画面中的占比变化很大,随机裁剪能让模型适应不同尺度。ColorJitter用来对抗拍摄时的色温差异,但亮度对比度别调太大,否则花瓣纹理会被抹掉。归一化用的均值方差是 ImageNet 的统计值,只要用预训练权重就必须保持一致,换成自己算的均值会让预训练特征失效。

优化器选 AdamW 而不是 SGD,是因为迁移学习场景下 AdamW 对学习率不敏感,初始 lr 设 1e-3 通常能直接工作。weight_decay=1e-4是轻量正则,防止全连接层过拟合。余弦退火调度让学习率从 1e-3 平滑降到接近 0,比固定学习率更容易在后期稳定收敛。

batch_size 设 32 是显存和梯度稳定性的折中,显存不够就降到 16,同时把学习率按比例降到 5e-4。num_workers=4在 Linux 上能加速数据加载,Windows 上如果报多进程错误就改成 0。

3.3 训练过程中的收敛判断与中断处理

判断模型有没有在正常学习,看两个信号。训练 loss 在前几个 epoch 快速下降,然后缓慢震荡下行,这是正常的。如果 loss 一直不降或者剧烈跳动,先检查标签有没有对错、学习率是不是太大。验证准确率在 10 个 epoch 内应该能到 70% 以上,如果卡在随机猜测的水平(比如 102 类时约 1%),大概率是数据加载出了问题,比如所有图片被读成了同一个类别。

训练中断时不要从头再来,把每个 epoch 的模型权重存下来,恢复时加载最近一次权重继续。存权重用torch.save(model.state_dict(), f"ckpt_epoch{epoch}.pth"),恢复时先构建同样的模型结构再load_state_dict。注意保存的是state_dict而不是整个模型对象,后者在不同环境加载时容易因为类路径不一致报错。

4. 花卉识别模型训练避坑:五条血泪经验

4.1 验证集准确率异常高但实际预测全错

现象:训练日志里验证准确率到了 95%,但拿一张新图预测,输出类别和肉眼判断完全不符。原因通常是验证集和训练集有重叠,或者验证集的图片在预处理时用了训练集的增强。解决方法是检查两个目录下有没有同名文件,并确认验证集用的是val_tf而不是train_tf。另一个隐蔽原因是类别映射错位,ImageFolder按文件夹名排序生成类别索引,如果训练和推理时类别顺序不一致,预测结果就会整体偏移。

4.2 显存溢出但 batch_size 已经调到很小

现象:batch_size 降到 8 还是报 CUDA out of memory。原因可能是图片分辨率没统一,某些图片尺寸特别大,缩放后仍然占用大量显存。解决方法是先用脚本统计所有图片的尺寸分布,把超过 2000 像素的图片单独处理,或者在Resize之前加一步强制缩放。另外,num_workers过大也会占用额外显存,调小到 2 试试。

4.3 训练 loss 正常但验证 loss 持续上升

现象:训练 loss 稳定下降,验证 loss 从第 5 个 epoch 开始反弹。这是典型过拟合,花卉数据集样本少的时候特别常见。解决方法是加大数据增强强度、增加 weight_decay、或者冻结骨干网络的前几层只训练高层。如果这些都不管用,说明数据量确实不够,需要考虑收集更多图片或者用更强的预训练权重。

4.4 类别不均衡导致小类别完全识别不出来

现象:整体准确率看着还行,但混淆矩阵里某几个类别的召回率接近零。原因是这些类别样本太少,模型学会了直接预测多数类。解决方法是在损失函数里加类别权重,nn.CrossEntropyLoss(weight=class_weights),权重按类别样本数的倒数计算。另一个办法是对小类别做过采样,在 DataLoader 里用WeightedRandomSampler。

4.5 推理时预处理和训练时不一致

现象:训练时验证准确率 90%,部署到服务里预测准确率掉到 60%。原因是推理时的预处理流程和训练时不一样,比如训练用了归一化但推理忘了,或者缩放方式不同。解决方法是在训练脚本里把预处理参数单独存成一个配置文件,推理时直接读同一份配置,不要手写两套。

5. 把花卉识别模型推到可用:评估、导出与一个提点技巧

模型训练完只是半成品,要判断它能不能用,得看混淆矩阵而不是整体准确率。整体准确率会被多数类拉高,掩盖小类别的失败。用sklearn.metrics.confusion_matrix跑一遍验证集,重点看对角线以外的高值区域,那些就是模型真正分不清的类别对。如果某些类别对混淆严重,先回去看图片,很多时候是标注本身就有问题,比如把两种相似的花混在了一个文件夹里。

导出模型时,如果目标是服务端部署,直接存state_dict就够了。如果要在移动端或边缘设备跑,用torch.jit.trace导出 TorchScript,或者转 ONNX。导出后一定要用几张验证集图片跑一遍,对比导出前后的输出是否一致,数值误差超过 1e-3 就说明导出过程有问题。

# 导出 ONNX 并验证输出一致性 dummy = torch.randn(1, 3, 224, 224).cuda() torch.onnx.export(model, dummy, "flower_model.onnx", input_names=["input"], output_names=["output"], dynamic_axes={"input": {0: "batch"}, "output": {0: "batch"}}) import onnxruntime as ort sess = ort.InferenceSession("flower_model.onnx") img = val_ds[0][0].unsqueeze(0).numpy() onnx_out = sess.run(None, {"input": img})[0] torch_out = model(torch.from_numpy(img).cuda()).detach().cpu().numpy() print("max diff:", abs(onnx_out - torch_out).max())

dynamic_axes让导出的模型支持可变 batch,部署时不用固定 batch_size。验证输出一致性是必须做的一步,我见过太多导出后精度掉点但没人发现的案例。

最后一个提点技巧:如果验证准确率卡在某个水平上不去,试试把验证集的图片也做一次轻量增强,比如只做中心裁剪的多个位置,然后对多次预测结果取平均。这个做法在花卉分类上通常能涨 1 到 2 个点,代价是推理时间翻倍。另一个更省事的办法是调低学习率再跑 5 个 epoch,余弦退火后期学习率已经很小,再给一点微调空间往往能挤出一部分性能。

我自己做这类项目的习惯是,训练脚本和数据目录结构固定下来之后就不再改,每次换数据集只动配置不动代码。这样踩过的坑不会重复踩,模型对比也有基线。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询