
简介这份资源面向深度学习入门者与计算机视觉方向的在校学生提供一套可直接上手的花卉图像五分类实战资料帮助解决从数据集准备到模型训练全流程的落地问题。压缩包共约2000个文件、596.75MB其中1972张jpg花卉图片构成五类样本主体14个py脚本承载TensorFlow模型搭建与训练逻辑另有txt说明、xml标注、md笔记与pdf教程辅助理解数据组织方式。资源配套作者录制的B站讲解视频已有14841人学习下载热度较高。读者可借助脚本与教程完成数据读取、模型构建、训练评估的完整闭环并通过标注文件与说明文档理解类别划分与目录结构适合作为课程设计、入门练手或迁移学习实验的基础素材。1. 花卉识别数据集5类从拿到压缩包到跑出第一张预测图你手上有一个叫「花卉识别数据集5类-提供代码和教程.zip」的压缩包解压之后大概率是五个文件夹每个文件夹里塞满同一种花的照片外加一份训练脚本和一份说明文档。这件事的核心价值不在于数据集本身有多大而在于它是一条完整的、可以端到端跑通的图像分类流水线——从读图、切分训练验证集、搭建卷积网络、训练、评估到最后拿一张新照片做推理。适合谁适合刚学完 Python 基础、想找一个真实可复现的小项目练手的人也适合需要快速验证某个 backbone 或数据增强策略是否有效的工程师。五类花卉意味着类别数少、数据量可控单卡甚至 CPU 都能在可接受时间内跑完一轮这是它最大的优势。但「能跑」和「跑得好」之间隔着数据清洗、类别不均衡、过拟合这几道坎后面几章会把这些坑一个个拆开讲。2. 五类花卉数据集的目录结构与加载方式2.1 先看清压缩包里的目录长什么样拿到压缩包后不要急着写模型代码先把目录结构摸清楚。常见的组织方式有两种一种是flower_photos/下面直接放五个类别文件夹每个文件夹名就是类别标签另一种是train/和val/已经帮你切好各自下面再分五个类别文件夹。这两种结构决定了你后面用ImageFolder还是自己写Dataset。先执行一条命令看结构# 查看解压后的目录树只看两层避免输出太长 find ./flower_dataset -maxdepth 2 -type d | sort如果输出类似下面这样说明是第一种结构./flower_dataset ./flower_dataset/daisy ./flower_dataset/dandelion ./flower_dataset/rose ./flower_dataset/sunflower ./flower_dataset/tulip每个类别文件夹里的图片数量往往不一样这是第一个需要记录的信息。用一条命令统计# 统计每个类别文件夹下的图片数量 for dir in ./flower_dataset/*/; do echo -n $dir: ls $dir | wc -l done逻辑说明for dir in ./flower_dataset/*/遍历所有子目录ls | wc -l统计文件数。参数上注意如果图片格式混杂jpg、png、jpeg这条命令统计的是所有文件包括可能存在的隐藏文件或说明文件。更严谨的做法是只统计图片后缀# 只统计常见图片格式的数量 for dir in ./flower_dataset/*/; do count$(find $dir -maxdepth 1 -type f \( -iname *.jpg -o -iname *.jpeg -o -iname *.png \) | wc -l) echo $dir: $count done这一步的意义在于如果五个类别数量差距超过 3 倍后面训练时就要考虑类别权重或者重采样否则模型会偏向样本多的类。我一般会把这个统计结果记下来作为后面调整WeightedRandomSampler的依据。2.2 用 ImageFolder 还是自定义 Dataset如果目录结构是「一个大文件夹下面五个类别文件夹」PyTorch 的torchvision.datasets.ImageFolder可以直接用它会自动把文件夹名映射成 0 到 4 的标签。这是最省事的做法代码量最少。import torch from torchvision import datasets, transforms from torch.utils.data import DataLoader, random_split # 定义基础变换统一尺寸、转张量、归一化 data_transform transforms.Compose([ transforms.Resize((224, 224)), # 统一缩放到 224x224 transforms.ToTensor(), # 转成 [0,1] 的张量 transforms.Normalize( # 按 ImageNet 统计量归一化 mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225] ) ]) # 加载整个数据集ImageFolder 会自动按文件夹名排序生成标签 full_dataset datasets.ImageFolder( root./flower_dataset, transformdata_transform ) # 打印类别到索引的映射确认顺序 print(full_dataset.class_to_idx) # 典型输出{daisy: 0, dandelion: 1, rose: 2, sunflower: 3, tulip: 4} # 按 8:2 切分训练集和验证集 train_size int(0.8 * len(full_dataset)) val_size len(full_dataset) - train_size train_dataset, val_dataset random_split(full_dataset, [train_size, val_size]) # 构建 DataLoader train_loader DataLoader(train_dataset, batch_size32, shuffleTrue, num_workers2) val_loader DataLoader(val_dataset, batch_size32, shuffleFalse, num_workers2)逻辑说明ImageFolder要求根目录下每个子文件夹是一个类别子文件夹里直接放图片。class_to_idx的顺序是按文件夹名字母序排列的这一点必须确认因为后面推理时输出的索引要能对应回类别名。random_split是随机切分如果数据集本身已经分好 train/val就不要再用它直接对两个ImageFolder分别加载即可。参数说明Resize((224, 224))是为了适配大多数预训练模型的标准输入如果你打算从头训练一个小网络可以改成 128 或 96 以加快速度。Normalize的均值和方差用的是 ImageNet 的统计量这是迁移学习场景下的常规做法如果完全从头训练可以改成自己数据集的均值和方差但影响通常不大。num_workers在 Windows 上如果报错就改成 0这是血泪经验。如果目录结构是已经切好的train/和val/那就写两个ImageFoldertrain_dataset datasets.ImageFolder(root./flower_dataset/train, transformtrain_transform) val_dataset datasets.ImageFolder(root./flower_dataset/val, transformval_transform)注意训练集和验证集的变换要分开定义训练集加随机翻转、随机裁剪等增强验证集只做 Resize 和归一化。这是最容易被忽略的一点很多人直接复用同一个 transform导致验证结果波动很大。2.3 数据增强到底加在哪一步数据增强是提升小数据集泛化能力最便宜的手段。五类花卉数据集通常每类几百张总量几千张这个量级下不做增强很容易过拟合。常见的增强操作包括随机水平翻转、随机旋转、颜色抖动、随机裁剪。# 训练集专用的增强变换 train_transform transforms.Compose([ transforms.RandomResizedCrop(224, scale(0.7, 1.0)), # 随机裁剪并缩放 transforms.RandomHorizontalFlip(p0.5), # 一半概率水平翻转 transforms.RandomRotation(15), # 随机旋转 ±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]) ]) # 验证集只做确定性变换 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]) ])逻辑说明RandomResizedCrop的scale(0.7, 1.0)表示随机裁剪出原图 70% 到 100% 的区域再缩放到 224这个参数不要设得太激进否则花蕊等关键特征可能被裁掉。RandomRotation(15)的 15 度是经验值花卉图像旋转太多会引入不自然的背景。ColorJitter的三个参数都设 0.2 是保守做法再大可能让颜色成为噪声而不是特征。参数说明增强的强度需要根据验证集准确率来调。如果训练准确率远高于验证准确率说明过拟合严重可以加大增强如果两者都低说明欠拟合应该先检查学习率和模型容量而不是继续加增强。3. 用迁移学习在五类花卉上跑通训练与评估3.1 选 ResNet18 还是自己搭一个小卷积网络五类花卉、几千张图片这个规模下最稳妥的方案是迁移学习。ResNet18 在 ImageNet 上预训练过的权重拿来微调五分类通常十几轮就能到 90% 以上的验证准确率。自己从头搭一个三四层的卷积网络也能跑但需要更多轮次和更仔细的超参调整最终精度往往还不如迁移学习。import torch.nn as nn from torchvision import models # 加载预训练 ResNet18 model models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1) # 替换最后的全连接层输出改为 5 类 num_features model.fc.in_features model.fc nn.Linear(num_features, 5) # 冻结前面的卷积层只训练最后的分类层第一阶段 for param in model.parameters(): param.requires_grad False for param in model.fc.parameters(): param.requires_grad True # 把模型放到 GPU如果有 device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device)逻辑说明models.resnet18(weights...)加载预训练权重这一步需要联网下载如果网络不通可以提前把权重文件放到~/.cache/torch/hub/checkpoints/目录下。替换fc层是因为原模型输出 1000 类我们要改成 5 类。冻结卷积层是迁移学习的第一阶段策略先让随机初始化的分类层收敛避免一开始就把预训练好的特征破坏掉。参数说明ResNet18_Weights.IMAGENET1K_V1是 torchvision 新版的权重枚举写法旧版写pretrainedTrue也可以但会有弃用警告。如果显存不够可以把resnet18换成resnet18但减小 batch size或者换成更小的mobilenet_v3_small。3.2 两阶段训练先冻结再解冻第一阶段只训练分类层通常 5 轮左右验证准确率就趋于稳定。然后解冻所有层用更小的学习率做微调这是提升精度的关键一步。import torch.optim as optim # 第一阶段只训练 fc 层学习率可以大一点 optimizer_stage1 optim.Adam(model.fc.parameters(), lr1e-3) criterion nn.CrossEntropyLoss() # 训练循环第一阶段5 轮 for epoch in range(5): model.train() running_loss 0.0 for images, labels in train_loader: images, labels images.to(device), labels.to(device) optimizer_stage1.zero_grad() outputs model(images) loss criterion(outputs, labels) loss.backward() optimizer_stage1.step() running_loss loss.item() print(fStage1 Epoch {epoch1}, Loss: {running_loss/len(train_loader):.4f}) # 第二阶段解冻所有层用更小的学习率微调 for param in model.parameters(): param.requires_grad True optimizer_stage2 optim.Adam(model.parameters(), lr1e-4) # 学习率降一个量级 for epoch in range(10): model.train() running_loss 0.0 for images, labels in train_loader: images, labels images.to(device), labels.to(device) optimizer_stage2.zero_grad() outputs model(images) loss criterion(outputs, labels) loss.backward() optimizer_stage2.step() running_loss loss.item() print(fStage2 Epoch {epoch1}, Loss: {running_loss/len(train_loader):.4f})逻辑说明第一阶段学习率 1e-3 是因为分类层是随机初始化的需要快速下降。第二阶段学习率降到 1e-4是因为预训练权重已经很好大学习率会破坏它们。这个两阶段策略比直接端到端微调更稳尤其在小数据集上。参数说明optim.Adam的lr是最关键的参数。如果第二阶段 loss 震荡不降把lr再降到 5e-5。如果 loss 下降太慢可以试试optim.SGD加动量但 Adam 在大多数情况下够用。batch_size设 32 是显存和梯度的折中显存够可以加到 64。3.3 评估不能只看准确率五类花卉如果类别不均衡准确率会被多数类主导。必须同时看混淆矩阵和每类的精确率、召回率。from sklearn.metrics import classification_report, confusion_matrix import numpy as np model.eval() all_preds [] all_labels [] with torch.no_grad(): for images, labels in val_loader: images, labels images.to(device), labels.to(device) outputs model(images) _, preds torch.max(outputs, 1) all_preds.extend(preds.cpu().numpy()) all_labels.extend(labels.cpu().numpy()) # 打印分类报告 print(classification_report(all_labels, all_preds, target_namesfull_dataset.classes)) # 打印混淆矩阵 print(confusion_matrix(all_labels, all_preds))逻辑说明model.eval()会关闭 dropout 和 batch norm 的训练模式这是评估时必须做的。torch.no_grad()关闭梯度计算节省显存。classification_report输出每类的 precision、recall、f1-score能直接看出哪个类被混淆了。参数说明target_namesfull_dataset.classes把数字索引映射回类别名输出更可读。混淆矩阵的对角线是正确预测数非对角线是错误预测如果某两类之间互相混淆严重说明它们的特征在模型看来太接近需要考虑加数据或换更强的 backbone。4. 推理部署拿一张新照片验证模型到底学到了什么4.1 单张图片推理的完整代码训练完之后最直接的验证方式是拿一张模型没见过的花卉照片跑一遍推理。from PIL import Image def predict_image(image_path, model, transform, class_names, device): 对单张图片进行预测返回类别名和置信度 model.eval() image Image.open(image_path).convert(RGB) # 确保三通道 input_tensor transform(image).unsqueeze(0).to(device) # 增加 batch 维度 with torch.no_grad(): outputs model(input_tensor) probabilities torch.softmax(outputs, dim1) # 转成概率 confidence, predicted torch.max(probabilities, 1) class_name class_names[predicted.item()] conf confidence.item() return class_name, conf # 使用示例 class_names full_dataset.classes # [daisy, dandelion, rose, sunflower, tulip] name, conf predict_image(./test_flower.jpg, model, val_transform, class_names, device) print(f预测类别: {name}, 置信度: {conf:.4f})逻辑说明Image.open().convert(RGB)是为了处理灰度图或带 alpha 通道的 PNG统一转成三通道。unsqueeze(0)在第一个维度增加 batch 大小因为模型期望输入是[batch, channel, height, width]。torch.softmax把 logits 转成概率分布torch.max同时返回最大值和对应索引。参数说明推理时必须用验证集的 transform不能用训练集的增强 transform否则结果会不稳定。confidence低于 0.6 时建议人工复核这说明模型对这个样本不太确定。4.2 批量推理和结果导出如果有一批测试图片逐张调用效率太低可以写一个批量推理脚本把结果导出成 CSV。import os import pandas as pd def batch_predict(image_dir, model, transform, class_names, device): 对目录下所有图片批量预测返回 DataFrame results [] model.eval() for filename in os.listdir(image_dir): if not filename.lower().endswith((.jpg, .jpeg, .png)): continue image_path os.path.join(image_dir, filename) try: name, conf predict_image(image_path, model, transform, class_names, device) results.append({filename: filename, predicted: name, confidence: round(conf, 4)}) except Exception as e: results.append({filename: filename, predicted: ERROR, confidence: 0.0}) print(f处理 {filename} 失败: {e}) return pd.DataFrame(results) # 批量预测并保存 df batch_predict(./test_images, model, val_transform, class_names, device) df.to_csv(./predictions.csv, indexFalse, encodingutf-8-sig) print(df.head())逻辑说明os.listdir遍历目录用后缀过滤图片文件。try-except捕获单张图片读取失败的情况避免整个批次中断。encodingutf-8-sig是为了 Excel 打开 CSV 时不乱码这是实际交付时经常被忽略的细节。参数说明round(conf, 4)保留四位小数够用且不冗长。如果图片量很大可以把predict_image改成 batch 推理一次处理多张但要注意显存限制。5. 五类花卉识别避坑记录从数据到部署的五个翻车点5.1 类别文件夹里混进了非图片文件现象ImageFolder加载时报错FileNotFoundError或者某个类别数量异常多。原因压缩包里可能带了Thumbs.db、.DS_Store或者说明文档。解决加载前先清理或者在ImageFolder的is_valid_file参数里过滤。# 清理非图片文件的命令Linux/macOS find ./flower_dataset -type f ! \( -iname *.jpg -o -iname *.jpeg -o -iname *.png \) -delete5.2 验证集准确率远高于训练准确率现象训练时 loss 不降但验证准确率很高。原因验证集的 transform 用了训练集的增强导致验证结果不可信或者验证集太小恰好都是简单样本。解决确认验证集只用 Resize 和 Normalize并且验证集比例不低于 15%。5.3 显存溢出但 batch size 已经很小现象CUDA out of memory但 batch size 已经降到 8。原因图片分辨率太高或者num_workers太多导致内存泄漏。解决先把Resize降到 128 试试再把num_workers设为 0 排除多进程问题。5.4 推理时置信度全部接近 1.0 或全部接近 0.2现象所有预测的置信度要么极高要么极低。原因模型过拟合或者归一化参数和训练时不一致。解决检查推理时的Normalize是否和验证集完全一致并回看验证集的混淆矩阵如果某类召回率为 0说明模型没学到这个类。5.5 保存的模型加载后预测结果全乱现象训练时准确率 90%保存后重新加载预测全错。原因保存的是state_dict但加载时模型结构不一致或者忘了调用model.eval()。解决保存时同时存class_to_idx加载后先eval()再推理。# 正确的保存和加载方式 torch.save({ model_state_dict: model.state_dict(), class_to_idx: full_dataset.class_to_idx }, flower_model.pth) # 加载 checkpoint torch.load(flower_model.pth, map_locationdevice) model.load_state_dict(checkpoint[model_state_dict]) model.eval()6. 把五类花卉模型推到 95% 以上的三个微调技巧第一个技巧是分层学习率。解冻之后不要所有层用同一个学习率前面的卷积层学习率设小一点后面的层设大一点。ResNet18 可以按layer1到layer4分组学习率依次递增。# 分层学习率前面的层学习率小后面的层学习率大 params [ {params: model.conv1.parameters(), lr: 1e-5}, {params: model.layer1.parameters(), lr: 1e-5}, {params: model.layer2.parameters(), lr: 5e-5}, {params: model.layer3.parameters(), lr: 1e-4}, {params: model.layer4.parameters(), lr: 5e-4}, {params: model.fc.parameters(), lr: 1e-3}, ] optimizer optim.Adam(params)第二个技巧是余弦退火学习率调度。固定学习率在后期容易在最优解附近震荡余弦退火让学习率平滑下降通常能再涨 1 到 2 个百分点。from torch.optim.lr_scheduler import CosineAnnealingLR scheduler CosineAnnealingLR(optimizer, T_max20, eta_min1e-6) # 在每个 epoch 结束后调用 for epoch in range(20): # ... 训练代码 ... scheduler.step()第三个技巧是测试时增强TTA。推理时对同一张图片做多次变换原图、水平翻转、不同裁剪把多次预测的概率平均能显著降低单次预测的方差。def predict_with_tta(image_path, model, class_names, device): 测试时增强原图 水平翻转 中心裁剪 model.eval() image Image.open(image_path).convert(RGB) tta_transforms [ transforms.Compose([transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])]), transforms.Compose([transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(p1.0), transforms.ToTensor(), transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])]), transforms.Compose([transforms.Resize((256, 256)), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])]), ] probs_list [] with torch.no_grad(): for t in tta_transforms: tensor t(image).unsqueeze(0).to(device) outputs model(tensor) probs torch.softmax(outputs, dim1) probs_list.append(probs) avg_probs torch.mean(torch.stack(probs_list), dim0) confidence, predicted torch.max(avg_probs, 1) return class_names[predicted.item()], confidence.item()逻辑说明TTA 的核心思想是「同一个样本的多个视角应该得到一致的预测」。水平翻转和中心裁剪是最常用的两种视角计算开销小收益稳定。torch.stack把三次预测的概率堆叠成[3, 1, 5]torch.mean(dim0)在第一个维度平均得到[1, 5]的平均概率。参数说明TTA 的变换数量不要超过 5 个否则推理时间线性增长但收益递减。CenterCrop(224)配合Resize(256)是标准做法先放大再裁剪保留更多上下文。这三个技巧我一般按顺序上先分层学习率再余弦退火最后 TTA。每加一个都跑一次验证集确认有提升再保留。如果加了之后验证准确率反而降了说明当前模型还没到需要这些技巧的阶段先回去检查数据质量和增强策略。希望帮到你。本文还有配套的精品资源点击获取