☰
花卉识别数据集与训练代码:64类32000张图,37种模型一键切换
2026/10/2 8:41:24 网站建设 项目流程

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

1. 花卉识别数据集与训练代码:64 类 32000 张图能直接跑出什么

手机拍的花和网络爬虫图混在一起训模型,是花卉识别翻车率最高的场景之一。这份资源恰好绕开了这个坑:64 种花卉、32000 张 224×224 彩色图像,全部手机实地采集,训练集 25600 张、测试集 6400 张,按 8:2 切好。配套代码实现了 37 种主流图像分类网络,resnet、vgg、inception、mobilenet、densenet、efficientnet、squeezenet 七个系列全覆盖,换模型只改一个字符串。适合两类人:想入门深度学习图像分类但缺干净数据的新手,以及需要快速对比 backbone 效果、不想自己洗数据的从业者。下面按「数据长什么样 → 怎么跑起来 → 怎么换模型调参 → 坑在哪 → 怎么验证」的顺序拆开讲。

2. 数据集结构与训练代码的目录约定:先对齐路径再谈训练

2.1 文件名编码规则与类别映射

先看项目正文里那串文件名:017-001-03549.jpg、024-001-03397.jpg、035-001-02985.jpg。这不是随机命名,常见做法是「类别 ID - 采集批次 - 序号」三段式。017代表第 17 类花卉,001是采集批次,03549是批次内序号。这种命名方式的好处是类别信息直接编码在文件名里,不需要额外的 label 文件,写个正则就能生成标注。

import os import re from collections import defaultdict # 按文件名前缀解析类别,适配 "类别ID-批次-序号.jpg" 格式 def build_label_map(img_dir): pattern = re.compile(r'^(\d+)-(\d+)-(\d+)\.jpg$') label_map = defaultdict(list) for fname in os.listdir(img_dir): m = pattern.match(fname) if m: cls_id = int(m.group(1)) # 第一段是类别 ID label_map[cls_id].append(fname) return label_map label_map = build_label_map('./flowers/train') print(f'类别数: {len(label_map)}') print(f'总图片数: {sum(len(v) for v in label_map.values())}') # 预期输出: 类别数 64, 总图片数 25600

这段代码的逻辑很直白:用正则把文件名拆成三段,第一段转成整数当类别标签,后面两段只做唯一性保证。参数上唯一需要注意的是正则里的\d+要跟实际文件名段数对齐,如果拿到手的文件名是017_001_03549.jpg这种下划线分隔,把-换成_即可。跑完这步你会得到 64 个类别、25600 张训练图的统计,跟摘要里的数字对得上,说明数据完整。

2.2 训练/测试目录的两种组织方式

图像分类数据集在磁盘上通常有两种摆法,这份资源大概率是第一种:

组织方式目录结构适用场景
按类别分文件夹train/017/xxx.jpgImageFolder 直接读,最省事
按文件名编码train/017-001-03549.jpg需要自定义 Dataset 解析文件名

如果是第一种,PyTorch 的ImageFolder一行就能加载;如果是第二种,就得像上面那样自己写 Dataset。我一般会先跑一段探测代码确认到底是哪种,避免后面 DataLoader 报「Found 0 files」这种低级错误。

import os def detect_layout(root): subdirs = [d for d in os.listdir(root) if os.path.isdir(os.path.join(root, d))] files = [f for f in os.listdir(root) if f.endswith('.jpg')] if len(subdirs) > 10 and len(files) == 0: return 'folder_per_class' # 按类别分文件夹 elif len(files) > 100: return 'filename_encoded' # 文件名编码类别 return 'unknown' print(detect_layout('./flowers/train'))

判断依据是子目录数量和根目录下 jpg 文件数量的对比。64 个类别的话,子目录数会接近 64;如果是文件名编码,根目录下会直接躺着上万张图。这一步花 10 秒,能省掉后面半小时的调试。

2.3 训练代码的模型注册机制

37 种模型能自由切换,背后一定有个模型注册表。常见做法是用字典把模型名映射到构造函数,或者用装饰器注册。不管哪种,你只需要知道改哪个字段能换模型。典型入口长这样:

# 伪代码示意,实际字段名以资源内代码为准 MODEL_ZOO = { 'resnet18': build_resnet18, 'resnet50': build_resnet50, 'vgg16': build_vgg16, 'mobilenet_v2': build_mobilenet_v2, 'efficientnet_b0': build_efficientnet_b0, # ... 共 37 种 } parser.add_argument('--model', type=str, default='resnet50', choices=list(MODEL_ZOO.keys()))

参数说明:--model控制 backbone,choices限制了合法值,传错会直接报错而不是静默失败,这点比很多野代码强。--num_classes要设成 64,--img_size保持 224,--batch_size看显存,8GB 卡上 resnet50 大概能跑 32~64。这些参数在资源内的训练脚本里应该都有默认值,先按默认跑通再调。

3. 从零跑通一次训练:环境、命令与日志解读

3.1 环境依赖与版本对齐

深度学习项目最玄学的问题就是版本不兼容。这份代码涉及 torch、torchvision、timm(如果用了预训练权重库)、numpy、Pillow 这几个核心包。我一般会先建虚拟环境再装,避免污染系统 Python。

conda create -n flower_cls python=3.9 -y conda activate flower_cls # 按 CUDA 版本装 torch,下面以 CUDA 11.8 为例 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install numpy pillow tqdm tensorboard scikit-learn

参数说明:python=3.9是兼容性最好的版本,3.10+ 有时会遇到某些包没轮子。cu118要跟你nvidia-smi显示的 CUDA 版本匹配,不匹配会装成 CPU 版,训练速度差几十倍。装完跑一句python -c "import torch; print(torch.cuda.is_available())",输出True才算对。

3.2 启动训练与关键参数

假设资源内主训练脚本叫train.py,典型启动命令:

python train.py \ --data_root ./flowers \ --model resnet50 \ --num_classes 64 \ --img_size 224 \ --batch_size 32 \ --epochs 50 \ --lr 0.001 \ --weight_decay 1e-4 \ --pretrained \ --output_dir ./runs/resnet50_exp1

逐个说:--pretrained决定是否加载 ImageNet 预训练权重,花卉识别这种中等规模数据强烈建议开,收敛快且精度高。--lr 0.001是 Adam 的常用值,如果换 SGD 要降到 0.01 并加 momentum。--epochs 50对 25600 张图、resnet50 来说大概能收敛,mobilenet 这种轻量模型可以加到 80。--output_dir分开存,方便对比不同 backbone。

3.3 训练日志里该盯哪几个数

跑起来之后终端会刷 loss 和 acc,但真正要盯的是这四个:

  • train_loss:持续下降是正常,震荡说明 lr 偏大或 batch 太小
  • val_acc:第 5 个 epoch 还没超过 60% 就要查数据加载对不对
  • lr:如果用了 cosine 或 step 调度,看它有没有按预期衰减
  • gpu_mem:接近显存上限时降 batch_size,别等 OOM 崩掉
# 用 tensorboard 看曲线,比盯终端直观 tensorboard --logdir ./runs --port 6006

如果 val_acc 卡在 1/64 附近(约 1.5%),基本可以断定标签没对上,回去查 2.1 的类别解析。如果 train_loss 不降,先确认--pretrained有没有生效,再检查 lr 是不是被设成了 0。

4. 换模型与调参:37 种 backbone 怎么选、怎么改

4.1 七个系列的选型逻辑

37 种模型不是让你全试一遍,而是按场景挑。我把七个系列的核心差异列成表:

系列代表模型参数量适用场景224 输入下的精度倾向
resnetresnet18/5011M/25M通用首选,稳高
vggvgg16138M精度够但慢,显存杀手中高
inceptioninception_v327M多尺度特征,适合花朵高
mobilenetmobilenet_v2/v33.4M/5.4M端侧部署,速度快中
densenetdensenet1218M特征复用强,小数据友好高
efficientnetefficientnet_b05.3M精度/参数量比最优高
squeezenetsqueezenet1_01.2M极致轻量,精度妥协中低

花卉识别的特点是类间差异小(比如不同品种的玫瑰)、类内差异大(同一朵花不同角度),所以多尺度特征和强特征复用更吃香。我的经验是:先跑 resnet50 和 efficientnet_b0 各一轮做 baseline,再根据部署需求决定要不要换 mobilenet。

4.2 换模型时最容易漏改的三处

换 backbone 不是改个字符串就完事,有三处经常被忽略:

# 1. 分类头输入维度要跟 backbone 输出对齐 # resnet 系列输出 2048,mobilenet 输出 1280,写死会报错 num_features = model.classifier.in_features # 动态获取 model.classifier = nn.Linear(num_features, 64) # 2. 输入尺寸归一化参数不同 # ImageNet 均值方差对大多数预训练模型通用,但 inception 要求 299 transform = transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), # inception 要改成 299 transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ]) # 3. 学习率要跟着模型大小调 # 大模型用小 lr,小模型可以大一点 lr_map = {'resnet50': 1e-3, 'mobilenet_v2': 3e-3, 'vgg16': 5e-4}

第一处是维度对齐,动态获取in_features最保险。第二处是输入尺寸,inception 系列默认 299×299,硬塞 224 会掉点。第三处是 lr,大模型参数量大,lr 大了容易震荡。

4.3 冻结与微调的策略

如果数据量不大或者想快速验证,可以先冻结 backbone 只训分类头,几个 epoch 后再解冻全量微调:

# 阶段一:冻结 backbone for param in model.parameters(): param.requires_grad = False for param in model.classifier.parameters(): param.requires_grad = True # 训 5 个 epoch,lr 可以设大点 1e-2 # 阶段二:解冻全量 for param in model.parameters(): param.requires_grad = True # 换小 lr 1e-4 继续训

这种两阶段策略在花卉这种中等规模数据上通常比直接全量微调快 20%~30% 达到相同精度。参数上,阶段一的 lr 可以大胆用 1e-2,因为只训分类头;阶段二必须降到 1e-4 量级,否则预训练权重会被冲掉。

5. 避坑与排查:训练不收敛、精度虚高、显存爆炸的五个现场

5.1 现象:val_acc 一直卡在 1.5% 不动

原因:标签映射错了,所有图被当成同一类,或者类别 ID 解析时把017当成了字符串导致排序错乱。解决:跑 2.1 的统计代码,确认len(label_map) == 64,且每个类别的图片数大致均衡(25600/64 = 400 张左右)。如果某个类别只有几十张,说明文件名解析漏了。

5.2 现象:train_acc 99% 但 val_acc 只有 40%

原因:训练集和测试集有重叠,或者测试集被污染。这份资源是官方切好的 25600/6400,但如果你自己重新划分时用了随机切分且没固定种子,可能把同一张图的不同增强版本分到了两边。解决:检查训练集和测试集的文件名有没有交集,用集合运算一秒确认。

train_files = set(os.listdir('./flowers/train')) test_files = set(os.listdir('./flowers/test')) overlap = train_files & test_files print(f'重叠文件数: {len(overlap)}') # 必须是 0

5.3 现象:跑到第 3 个 epoch 突然 OOM

原因:显存碎片累积,或者某个 batch 的图片尺寸异常大。虽然数据集标称 224×224,但手机采集图可能有少数没 resize 干净。解决:在 Dataset 的__getitem__里强制 resize,并加一句尺寸断言。

def __getitem__(self, idx): img = Image.open(self.paths[idx]).convert('RGB') img = img.resize((224, 224)) # 强制统一尺寸 assert img.size == (224, 224) # ... 后续 transform

5.4 现象:换 mobilenet 后精度掉 10 个点

原因:mobilenet 的深度可分离卷积对 lr 更敏感,沿用 resnet 的 lr 会欠拟合。解决:把 lr 提到 3e-3,并把 warmup 加上,前 3 个 epoch 线性升温。

5.5 现象:tensorboard 曲线正常但最终测试精度对不上

原因:训练时用的是 val_acc,最终报告用的是 test_acc,两者如果差太多说明验证集和测试集分布不一致。解决:确认资源里的 6400 张测试集是独立采集的,不要拿验证集当测试集报数。我一般会在训练结束后单独跑一遍evaluate.py,用测试集出最终数字。

6. 验证与进阶:用混淆矩阵和 TTA 把精度再压榨 2 个点

训练跑完拿到模型只是开始,真正判断这份资源好不好用,得看它在 64 类上的细粒度表现。花卉识别里最容易混的是同属不同种的花,比如各种月季和玫瑰,整体 acc 高不代表每个类都好。我习惯先出一张混淆矩阵,找出最差的 5 个类,再针对性处理。

import torch import numpy as np 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.cuda() outputs = model(imgs) preds = outputs.argmax(dim=1).cpu().numpy() all_preds.extend(preds) all_labels.extend(labels.numpy()) cm = confusion_matrix(all_labels, all_preds) print(classification_report(all_labels, all_preds, digits=4)) # 画出最差的 10 个类 per_class_acc = cm.diagonal() / cm.sum(axis=1) worst = np.argsort(per_class_acc)[:10] print('最差类别:', worst, '对应精度:', per_class_acc[worst])

这段代码的逻辑是先跑完整个测试集收集预测结果,再用 sklearn 出分类报告和混淆矩阵。classification_report里的f1-score比单纯看 acc 更有参考价值,因为 64 类如果某类样本少,acc 会被大类带偏。参数上digits=4保留四位小数,方便对比不同模型的细微差异。

找到弱类之后,有两个不用重训就能提点的技巧。第一个是 TTA(测试时增强),对同一张图做水平翻转、多尺度裁剪,把多次预测平均:

def tta_predict(model, img, n_crops=5): model.eval() preds = [] with torch.no_grad(): # 原图 preds.append(torch.softmax(model(img.unsqueeze(0).cuda()), dim=1)) # 水平翻转 preds.append(torch.softmax(model(torch.flip(img, [2]).unsqueeze(0).cuda()), dim=1)) # 多尺度 for scale in [0.9, 1.1]: h, w = img.shape[1:] resized = torch.nn.functional.interpolate( img.unsqueeze(0), size=(int(h*scale), int(w*scale)), mode='bilinear', align_corners=False) resized = torch.nn.functional.interpolate( resized, size=(h, w), mode='bilinear', align_corners=False) preds.append(torch.softmax(model(resized.cuda()), dim=1)) return torch.stack(preds).mean(dim=0)

TTA 在花卉这种对翻转和尺度敏感的任务上通常能涨 1~2 个点,代价是推理时间翻几倍,适合离线评估不适合实时服务。第二个技巧是类别权重平衡,如果混淆矩阵显示某几个类互相混得厉害,可以在 loss 里给这些类加权:

# 根据混淆矩阵给难分类别更高权重 class_weights = torch.ones(64).cuda() for cls in worst: class_weights[cls] = 2.0 # 难类权重翻倍 criterion = nn.CrossEntropyLoss(weight=class_weights)

这两个技巧叠加,在 resnet50 baseline 上一般能把 top-1 acc 从 85% 左右推到 88%~90%。但要注意别过拟合测试集,调完权重后最好再留一份从未看过的数据做最终验证。

从那以后我每次拿到新数据集,都强制先跑一遍类别统计和训练/测试重叠检查,再开始训模型。这两个检查加起来不到 5 分钟,但能挡掉后面几小时的无效训练。希望帮到你。

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

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

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

立即咨询