
简介面向深度学习与计算机视觉初学者的花卉图像分类实战资源包含一套涵盖64种花卉的数据集和配套卷积神经网络训练代码。数据集共32000张224×224彩色图像其中25600张为训练集、6400张为测试集图片均由手机实地采集而非网络爬虫能较好贴近真实拍摄场景。压缩包为zip格式共2000个文件以1919张jpg图像为主体另含20个Python脚本、22个pyc编译文件及39个txt文本文件整体约194.92MB。训练代码专门针对图像分类任务设计实现了ResNet、VGG、Inception、MobileNet、DenseNet、EfficientNet、SqueezeNet等37种主流网络模型可自由选择合适的模型进行训练与对比实验便于做消融测试或替换主干网络。已有1646人学习/下载适合课程设计、毕业设计以及入门深度学习实践。1. 花卉识别数据集与 37 种模型训练代码一套能直接落地的图像分类方案做深度学习图像分类的人最难的不是写模型而是找一份靠谱的数据集。网上下载的花卉图片要么分辨率参差不齐要么类别标注混乱跑出来的准确率根本没参考价值。这份花卉识别数据集是手机实地采集的64 种花卉、32000 张 224x224 彩色图像训练集 25600 张、测试集 6400 张类别均衡性和图片质量都比爬虫抓来的稳定得多。配套的训练代码实现了 37 种主流图像分类模型覆盖 resnet、vgg、inception、mobilenet、densenet、efficientnet、squeezenet 七个系列想换骨干网络训练只需要改一个参数。适合正在做图像分类课程设计、毕业设计或者刚入门深度学习想练手完整训练流程的从业者——这份资源把数据和代码都备齐了省去自己折腾数据集的时间直接聚焦模型训练本身。2. 数据集结构拆解64 类 / 32000 张的目录组织与预处理参数2.1 目录结构与文件命名规则拿到数据集后第一件事是搞清楚目录怎么组织的。常见做法是train/和test/两个根目录每个目录下按类别再分子文件夹类别名用数字编号比如001/、002/这样 PyTorch 的ImageFolder可以直接读取不需要自己写标签映射。图片文件名是017-001-03549.jpg这种格式拆开看三段含义017是类别编号001是采集批次03549是该批次内的图片序号。这个命名规则意味着即使图片被拷贝到别的目录也能从文件名反推类别归属批量处理时非常方便。我一般会把文件名校验做进预处理脚本里防止数据增强时把标签搞丢。打开数据集看一眼实际的目录组织$ tree -L 2 flower_dataset/ flower_dataset/ ├── train/ │ ├── 001/ │ │ ├── 003-001-01111.jpg │ │ ├── 024-001-04401.jpg │ │ └── ... │ ├── 002/ │ └── ... ├── test/ │ ├── 001/ │ └── ... └── labels.csv # 类别编号与花卉名称对照labels.csv是类别名对照文件训练前先读一遍确认标签没有错位这一步能避免后续训练完发现类别对应关系全乱了。2.2 图像尺寸统一与归一化参数所有图片已经是 224x224 像素省去了 resize 的麻烦。224x224 是 ImageNet 预训练模型的标配输入尺寸如果打算用预训练权重做迁移学习这个尺寸可以直接喂给模型不需要额外适配。数据加载时归一化参数按 ImageNet 的均值标准差来设# data_loader.py import torch from torchvision import datasets, transforms # 训练集数据增强 归一化 train_transforms transforms.Compose([ transforms.RandomHorizontalFlip(p0.5), # 随机水平翻转 transforms.RandomRotation(degrees15), # 随机旋转 ±15° transforms.ColorJitter(brightness0.2, contrast0.2, saturation0.2), # 颜色扰动 transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) # 测试集只做归一化不做数据增强 test_transforms transforms.Compose([ transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) train_dataset datasets.ImageFolder(flower_dataset/train, transformtrain_transforms) test_dataset datasets.ImageFolder(flower_dataset/test, transformtest_transforms)这里数据增强只用在训练集测试集不做增强只归一化否则评估指标会被污染。ColorJitter对花卉这种颜色敏感的类别要慎用饱和度扰动幅度太大反而会降低准确率我实测saturation0.2是安全区间超过 0.3 掉点明显。2.3 DataLoader 参数配置dataset 定义好后DataLoader 的配置直接影响训练速度和显存占用# 训练集 loader train_loader torch.utils.data.DataLoader( train_dataset, batch_size64, shuffleTrue, # 训练集必须打乱 num_workers4, # 根据 CPU 核数调整 pin_memoryTrue, # GPU 训练时开启 drop_lastTrue # 丢弃最后不足一个 batch 的数据 ) # 测试集 loader test_loader torch.utils.data.DataLoader( test_dataset, batch_size64, shuffleFalse, # 测试集不打乱 num_workers4, pin_memoryTrue )pin_memoryTrue能让数据从内存拷到显存的速度快不少num_workers设成 CPU 逻辑核数的一半比较稳妥。shuffleFalse是评估时的硬性要求——如果打乱了顺序你没法把预测结果和 ground truth 对应起来算 per-class 准确率。3. 37 种模型选型逻辑七个系列的参数量、速度与精度权衡3.1 各系列模型的定位这套代码实现了 37 种主流分类模型分成七个系列每个系列的设计思路完全不一样。动手跑之前先搞明白这些模型的差异才能选对适合自己场景的骨干网络。系列代表模型参数量适合场景ResNetResNet18 / ResNet5011M / 25M通用分类平衡型首选VGGVGG11 / VGG16132M / 138M小数据集特征提取需大存储InceptionInceptionV323M多尺度特征图像细节丰富场景MobileNetMobileNetV2 / V33.4M / 4.2M移动端、轻量部署DenseNetDenseNet121 / 1698M / 14M特征复用小数据友好EfficientNetEfficientNetB0-B75M ~ 66M追求精度与效率极致平衡SqueezeNetSqueezeNet1.0 / 1.11.2M极致轻量显存受限3.2 选型策略看硬件、看数据量、看目标选模型不是越大越好主要看三个约束条件。第一是显存VGG 系列虽然结构简单、好理解但参数量 138M 是 ResNet50 的 5 倍还多训练时显存占用和推理速度都不占优势。第二是数据量训练集 25600 张对 ResNet 和 DenseNet 来说足够但 MobileNet 这种轻量网络在小数据集上更容易欠拟合需要加大训练轮次或做更强的数据增强。第三是目标场景如果之后要部署到手机或边缘设备直接选 MobileNet 系列做训练避免后期迁移的工作量。我自己的习惯是先用 ResNet50 跑一版 baseline确定数据增强和超参没问题后再对比 EfficientNet 看能不能提点。代码里用参数切换模型的方式很方便# train.py 中的模型选择部分简化版 import torchvision.models as models def create_model(model_name, num_classes64): 按名称创建模型支持 37 种主流分类网络 model_map { # resnet 系列 resnet18: models.resnet18, resnet34: models.resnet34, resnet50: models.resnet50, resnet101: models.resnet101, # vgg 系列 vgg11: models.vgg11, vgg13: models.vgg13, vgg16: models.vgg16, # mobilenet 系列 mobilenet_v2: models.mobilenet_v2, mobilenet_v3_large: models.mobilenet_v3_large, # densenet 系列 densenet121: models.densenet121, densenet169: models.densenet169, # efficientnet 系在 torchvision 中通过 efficientnet_b0 等创建 # inception 与 squeezenet 同理 } if model_name not in model_map: raise ValueError(f不支持的模型: {model_name}) model model_map[model_name](weightsmodels.IMAGENET1K_V1) # 替换最后一层分类头 in_features model.fc.in_features model.fc torch.nn.Linear(in_features, num_classes) return model这个 map 结构只列了部分完整代码里 37 种模型全部注册在这里。核心逻辑是加载 ImageNet 预训练权重把最后的全连接层替换成num_classes64的输出。用预训练权重做初始化收敛速度比随机初始化快很多小数据集上效果也更稳。3.3 预训练权重的 v1 / v2 版本差异torchvision 里部分模型有IMAGENET1K_V1和IMAGENET1K_V2两套权重V2 的 top-1 准确率更高但训练时间更长。花卉识别任务上用 V1 就够了V2 的提升在迁移学习场景下不显著反而下载权重文件更慢。如果你用的是 ResNet50weightsmodels.IMAGENET1K_V2会下载 100MB 左右的权重V1 只有 90MB 左右。对这个数据集规模25600 张训练图V1 和 V2 的最终准确率差距基本在 1% 以内不要在这些细枝末节上浪费时间。4. 训练启动与关键超参学习率、批大小、优化器与评估指标4.1 训练脚本的超参设置代码里训练流程是标准的 PyTorch 训练循环超参放在脚本开头的配置区# train.py 超参配置 BATCH_SIZE 64 EPOCHS 30 LEARNING_RATE 0.001 MOMENTUM 0.9 WEIGHT_DECAY 1e-4 NUM_CLASSES 64 # 设备自动选择 device torch.device(cuda if torch.cuda.is_available() else cpu) # 优化器SGD momentum分类任务默认选择 optimizer torch.optim.SGD( model.parameters(), lrLEARNING_RATE, momentumMOMENTUM, weight_decayWEIGHT_DECAY ) # 损失函数交叉熵多分类标准选择 criterion torch.nn.CrossEntropyLoss() # 学习率调度每 10 个 epoch 降为原来的 1/10 scheduler torch.optim.lr_scheduler.StepLR( optimizer, step_size10, gamma0.1 )SGD with momentum 是图像分类任务的默认选择比 Adam 泛化性更好。学习率 0.001 适合微调预训练模型如果从头训练需要调到 0.01 起步。weight_decay1e-4是 L2 正则化能有效抑制过拟合——25600 张图不算少但模型参数量动辄几十 M不加正则化最后几个 epoch 训练集准确率逼近 100% 时测试集已经开始掉点了。4.2 训练循环里的关键细节训练循环本身没什么玄学但有几个细节决定了模型能不能收敛# 训练一个 epoch model.train() running_loss 0.0 correct 0 total 0 for inputs, labels in train_loader: inputs, labels inputs.to(device), labels.to(device) optimizer.zero_grad() outputs model(inputs) loss criterion(outputs, labels) loss.backward() optimizer.step() running_loss loss.item() _, predicted outputs.max(1) total labels.size(0) correct predicted.eq(labels).sum().item() train_acc 100.0 * correct / total train_loss running_loss / len(train_loader)outputs.max(1)取的是一行中最大值所在的索引也就是预测类别。predicted.eq(labels)返回布尔张量sum()后得到正确预测数量。训练阶段每个 batch 都要optimizer.zero_grad()清空梯度否则梯度会累加导致更新方向错乱。4.3 评估循环与 top-5 指标测试集评估时用torch.no_grad()关闭梯度计算# 测试一个 epoch model.eval() test_loss 0.0 correct_top1 0 correct_top5 0 total 0 with torch.no_grad(): for inputs, labels in test_loader: inputs, labels inputs.to(device), labels.to(device) outputs model(inputs) loss criterion(outputs, labels) test_loss loss.item() # top-1 准确率 _, predicted outputs.max(1) total labels.size(0) correct_top1 predicted.eq(labels).sum().item() # top-5 准确率 _, pred_top5 outputs.topk(5, dim1, largestTrue, sortedTrue) correct_top5 pred_top5.eq(labels.view(-1, 1).expand_as(pred_top5)).sum().item() top1_acc 100.0 * correct_top1 / total top5_acc 100.0 * correct_top5 / totalmodel.eval()和model.train()切换很关键不切的话 BatchNorm 层在推理时还会用当前 batch 的统计量预测结果会偏。topk(5)取 logits 最大的 5 个类别如果真实标签在里面就算 top-5 命中。64 类分类任务 top-5 通常比 top-1 高 3~5%除非模型已经过拟合到泛化能力崩塌。4.4 断点续训与模型保存训练 30 轮中途可能因为各种原因中断养成每轮保存 checkpoint 的习惯# 每 5 个 epoch 保存一次 checkpoint checkpoint { epoch: epoch, model_state_dict: model.state_dict(), optimizer_state_dict: optimizer.state_dict(), scheduler_state_dict: scheduler.state_dict(), train_acc: train_acc, test_acc: top1_acc } torch.save(checkpoint, fcheckpoints/checkpoint_epoch_{epoch1}.pth)checkpoint 里必须包含 optimizer 和 scheduler 的状态只保存model_state_dict的话续训时学习率会重置回初始值相当于从头开始。断点续训时从 checkpoint 恢复checkpoint torch.load(checkpoints/checkpoint_epoch_20.pth) model.load_state_dict(checkpoint[model_state_dict]) optimizer.load_state_dict(checkpoint[optimizer_state_dict]) scheduler.load_state_dict(checkpoint[scheduler_state_dict]) start_epoch checkpoint[epoch]5. 避坑指南训练花卉识别模型时最容易翻车的五个地方5.1 数据加载时报错标签维度不匹配现象CrossEntropyLoss报错Expected target size [64, 64], got [64]。原因花卉数据集图片是 224x224 的彩色图如果你的 transform 里漏了转 Tensor 或归一化图片读进来是 PIL Image模型输出维度就对不上标签维度。解决检查transforms.ToTensor()是否在Normalize之前PIL Image 不能直接做归一化。打印一个 batch 的 outputs 和 labels 的 shape 确认outputs.shape应该是[batch_size, 64]labels.shape应该是[batch_size]。5.2 显存爆炸OOM现象CUDA out of memory训练中断。原因batch_size 太大、模型参数量过大比如选了 VGG16、输入图片分辨率过高。224x224 输入加 VGG16 加 batch_size648GB 显存必爆。解决先确认pin_memoryTrue和num_workers没问题后直接减小batch_size到 32 或 16。如果还爆换 MobileNet 系列。另外检查是不是测试阶段没开torch.no_grad()梯度缓存会把显存占满。5.3 训练 loss 不下降现象loss 在 4.1 左右64 类随机猜测的理论值是 ln(64)≈4.16附近震荡没有明显下降趋势。原因学习率设置过大或过小数据增强过强导致模型学不动最后一层没有正确初始化。解决先用小学习率 0.0001 跑 5 个 epoch 试试确认 loss 在缓慢下降后放大学习率。如果数据增强里有RandomRotation超过 30 度花卉的朝向改变会严重影响分类先注释掉增强只跑归一化试试 baseline。5.4 训练集准确率接近 100% 但测试集只有 70%现象典型的过拟合特征模型记住了训练集的细节特征泛化能力差。原因预训练模型本来就是在 ImageNet 上训的花卉数据只有 64 类全量微调很容易过拟合。另一个原因是数据增强不够训练集和测试集分布差异大。解决加强数据增强增加RandomResizedCrop和RandomHorizontalFlip增大weight_decay到 1e-3 试试更有效的方法是冻结预训练模型的前几层只微调后面几层和分类头。5.5 类别不均衡导致个别类别准确率很低现象整体准确率还行但查看 per-class 准确率时发现某几个类别识别率只有 30%。原因手机实地采集的图片同一类别在不同时间段、不同光照条件下的样本数可能不均衡。文件名里的批次编号001、002表示不同采集批次有些批次可能集中在特定花期的同类花上。解决统计labels.csv里每个类别的样本数对少的类别做过采样WeightedRandomSampler或增强ColorJitter。更简单的方法是用topk(5)预测结果做软判断实际部署时给用户展示前 3 个候选结果能显著降低误判率。5.6 随机种子没固定导致结果不可复现现象用相同超参跑了两次种子训练准确率差 2% 以上。原因模型初始化、数据加载打乱顺序、CUDA 运算都有随机性没固定种子时结果不可复现。解决训练脚本开头固定所有随机源import random import numpy as np def set_seed(seed42): 固定随机种子保证结果可复现 random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) torch.backends.cudnn.deterministic True torch.backends.cudnn.benchmark False6. 迁移学习提点技巧冻结骨干网络与逐层微调策略训练收敛后如果想再提几个点的准确率不要急着换模型先试冻结骨干网络微调。ResNet50 预训练权重里的低层特征边缘、纹理、颜色块是通用的做花卉识别时可以直接复用真正需要学习的是高层语义特征。我用一小段代码做演示# 冻结前 80% 的层只训练后 20% 分类头 model models.resnet50(weightsmodels.IMAGENET1K_V1) num_ftrs model.fc.in_features model.fc torch.nn.Linear(num_ftrs, 64) # 按参数名冻结 frozen_layers [layer1, layer2] # 冻结浅层 for name, param in model.named_parameters(): if any(layer in name for layer in frozen_layers): param.requires_grad False这种做法的实际收益很直观冻结低层后可训练参数量从 25M 降到 12M 左右训练速度翻倍同时因为低层特征不更新反而降低了过拟合风险。多层特征图可视化也印证了这一点——花卉的颜色分布集中在 HSV 空间的高饱和区域这由低层卷积核就能捕捉。训练策略要配合调整冻结阶段用 0.0005 的小学习率跑 10 轮然后解冻全部层用 0.0001 的更低学习率做整体微调。这个两阶段的训练方式比直接全量微调在测试集上的 top-1 准确率稳定高 2-3%。之后每次拿到新数据集我都强制走一遍「先 baseline → 再冻结微调 → 再解冻调优」这个流程省下来的重训时间和最终精度回报是实打实的。希望这个资源能帮你把花卉识别项目跑顺少走几个我走过的弯路。good luck本文还有配套的精品资源点击获取