简介这是一份面向高校学生与初级开发者的花卉识别课程设计源码包基于深度学习技术聚焦图像自动分类这一典型任务。包内共49个文件压缩后仅3.36MB包含Python源代码、花卉图像样本、训练测试日志、热图与结果图等。源码实现从数据读取、数据集划分、卷积神经网络搭建到MobileNet迁移学习与模型评估的完整流程可帮助理解图像预处理、特征提取和分类器训练的关键环节并借助训练日志和可视化热图分析模型表现。附带的说明文档和按用途组织的目录便于快速复现并二次改造适合做课程设计或毕业设计基线。已有363人学习内容紧凑、代码结构清晰值得对照调试与深入研读。1. 基于Python深度学习的花卉识别这份课程设计源码到底值不值得跑把「基于Python深度学习的花卉识别」做成课程设计听起来是个被写烂的题目但实际拿到一套源码时大多数人第一周都耗在「跑不起来」上数据集路径不对、torchvision版本对不上、训练完准确率卡在30%。这套题目的真正难点不在于模型有多新而在于你怎么把数据、训练、评估这条链在一个晚上内完整跑通并且能在答辩现场讲清楚每个参数为什么这么设。这篇文章我会按课程设计最常遇到的落地路径来讲先选型再准备数据然后跑通训练最后把踩过的坑一次说完。适合正在做深度学习课程设计、想拿花卉识别练手、或者买到了源码但不知道怎么改的人。你会看到具体的命令、可抄的参数和失败时的排查思路而不是一篇泛泛的科普。2. 模型选型决定课程设计的性价比从ResNet到ViTCNN为什么仍是多数人的起点2.1 课程设计场景下CNN、MobileNet与ViT怎么选花卉识别本质上是一个图像分类任务输入一张花输出类别标签。深度学习做这件事的主流方案有三大类传统CNNResNet、VGG、轻量化CNNMobileNet、ShuffleNet、以及Transformer结构的ViT。很多人一上来就追ViT觉得新架构才配叫深度学习但放到课程设计这个场景里这个决定不一定划算。ViT的优势在大规模数据上才明显它需要海量数据或强大的预训练权重支撑训练轮次长、显存占用高一个本科课程设计往往没有那么多GPU时间来调参。而CNN里的ResNet因为引入了残差连接在中等规模数据集上收敛稳定PyTorch里直接调用预训练权重只需要几行代码是绝大多数课程设计项目最常见的打底模型。MobileNet则是另一个极端它用深度可分离卷积把参数压得非常小CPU上也能跑推理适合后续做Web或手机端演示。如果你的答辩需要现场演示实时识别MobileNet的推理速度会比ResNet从容很多。三种模型的取舍关系整理如下。模型参数量量级准确率上限CPU推理速度课程设计适配度ResNet18/50中等高中等最推荐容错高MobileNetV3小中高快适合做端侧演示ViT-Base大高需大数据慢不推荐新手如果你的源码包里默认训练脚本用的是ResNet那很正常这几乎是花卉识别课程设计的“标准答案”。如果你想在答辩里多一个亮点可以预留一个参数开关让模型在ResNet和MobileNet之间切换训练完对比两组准确率和参数量这张对比表比口头解释“我用了深度学习”有说服力得多。2.2 用预训练权重还是从头训练三种常见做法处理模型初始化的方式直接决定了你训练一晚上能拿到什么结果。常见做法有三种。第一种是使用ImageNet预训练权重然后微调。torchvision里一行models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1)就能加载训练时把最后一层全连接改成花卉类别数。第二种是从头训练即随机初始化权重这需要更大的数据量和更久的时间课程设计周期内往往只能得到一个不算理想的结果。第三种是冻结骨干网络只训练分类头这种方式训练速度快适合数据量小、机器配置普通的情况但准确率上限也会低一些。我一般建议课程设计选第一种微调20到30个epoch学习率设置在0.001附近效果远好过头训练。下面这段代码展示了ResNet18微调时的模型改造这是几乎所有花卉识别项目都会用到的一种做法。import torchvision.models as models import torch.nn as nn # 使用ImageNet预训练权重 model models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1) # 获取ResNet18最后一层全连接的输入维度原先是512 num_features model.fc.in_features # 替换全连接层5对应你的花卉类别数按实际数据集修改 model.fc nn.Linear(num_features, num_classes5)逻辑说明先加载在ImageNet上训练好的ResNet18然后取出原全连接层的输入维度把最后一层替换成适配当前数据集类别数的全连接层。预训练权重保留了前几层提取边缘、纹理、花瓣形状这些通用特征的能力我们只需要重新学习最后的分类边界。参数说明num_features不需要手动改成固定数字直接从model.fc.in_features读取更稳妥num_classes要和你数据集里的类别数严格一致否则训练时会报维度不匹配的错误。很多源码包里写死了num_classes5或num_classes17拿到后第一件事就是核对这个值。2.3 从ResNet到轻量化改进答辩能讲清楚才是真亮点模型选型和技术深度并不成正比答辩时老师更关心的是“你为什么这么选”和“效果差在哪里”。我见过不少学生在课程设计里塞了一个自己都讲不清的ViT结果被问到“为什么花卉边缘在某些类别上分错”就答不上来。如果你想让项目有点差异化建议在ResNet的基础上做一个具体改进换一个损失函数、加一个数据增强策略、或者在网络末尾加一个Dropout层。这些改动每一处都能讲出明确的动机和效果。比如在识别菊花和向日葵这类花瓣密集的类别时随机裁剪和遮挡增强能明显提升鲁棒性这个观察本身就比“我用了深度学习”更值分。3. 数据集准备与增广训练前先把数据安排明白3.1 常见花卉数据集长什么样目录结构怎么组织花卉识别这类课程设计最常用的数据集一个是公开的102类花卉数据集Oxford 102 Flowers另一个是Flower-17。它们都能直接按类别文件夹组织网上也有不少已经整理好的版本。但源码包里给的数据集结构往往很乱有的按train/val分好有的全部堆在一个文件夹里跑之前必须做一次规范化。PyTorch的torchvision.datasets.ImageFolder要求目录层级严格为根目录/类别名/图片文件这是最常见的约定。你拿到的源码如果启动就报Dataset not found八成是目录结构和代码里data_dir对不上。建议先手动打开文件夹看一眼把源码里写死的路径和实际路径对应起来。下面是一份标准的目录组织方式我通常会在拿到任何源码包后先按这个结构重建一遍。flower_data/ ├── train/ │ ├── daisy/ # 雏菊 │ ├── dandelion/ # 蒲公英 │ ├── rose/ # 玫瑰 │ ├── sunflower/ # 向日葵 │ └── tulip/ # 郁金香 └── val/ ├── daisy/ ├── dandelion/ ├── rose/ ├── sunflower/ └── tulip/这里的类别目录名就是标签名训练时ImageFolder会按字母序自动映射成数字标签不需要额外维护一个标签文件。建议类别名一律用英文字母中文目录名在Windows和Linux上可能引起编码问题这是源码运行阶段最容易踩的坑。如果拿到的数据集只有train一个目录自行按比例划分并搬到val目录里即可。3.2 单机CPU也能跑的训练配置epoch、batch与设备很多课程设计作者手头没有独立GPU这是很常见的情况。CPU训练时ResNet18跑一个epoch大约需要几分钟到十几分钟取决于图片尺寸和机器配置。源码里如果默认开了大批次和高分辨率CPU上可能要跑一整夜。建议先改三个参数把训练成本降下来batch_size设为16或32图片尺寸resize设为224×224epoch数设为20到30。在CPU训练时可以把torch.set_num_threads(4)加在训练脚本开头把CPU线程数显式限制住避免和其他程序抢资源导致卡顿。训练设备最好在代码里自动探测而不是写死cuda否则没有GPU的同学一运行就报错。常见的写法是先判断torch.cuda.is_available()拿到设备字符串再送给模型和数据。这种几行的改动能让团队里没有显卡的同学也能在同一套脚本上跑通。3.3 数据增广的参数怎么设缩放、翻转、颜色抖动增广不是为了把图片变好看而是为了让模型见过更多样的花瓣形态。花卉识别的特殊性在于拍摄角度、光照、背景差异极大同一朵花在不同照片里可能看起来完全不像同一类。一套合理的训练集增广通常包含随机翻转、随机裁剪和颜色抖动三个部分。下面这段transform配置可以直接替换源码里的预处理部分参数上我按照花卉数据的常见情况做了平衡。from torchvision import transforms data_transforms { train: transforms.Compose([ transforms.RandomResizedCrop(224, scale(0.6, 1.0)), transforms.RandomHorizontalFlip(), transforms.RandomRotation(15), transforms.ColorJitter(brightness0.2, contrast0.2, saturation0.2), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]), val: transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]), }逻辑说明训练集这边先用随机裁剪模拟拍摄距离的变化scale(0.6, 1.0)表示裁剪面积在原图的60%到100%之间浮动让模型适应花朵在画面中的大小变化随机水平翻转增加样本多样性随机旋转15度模拟手持拍摄时的角度偏移颜色抖动则针对同一种花在不同光照下的色差。验证集只用等比缩放加中心裁剪不做随机增广保证评估结果的稳定性。参数说明Normalize的三个通道均值和标准差用的是ImageNet统计值因为我们会加载ImageNet预训练权重预处理必须和预训练时保持一致。如果你从头训练可以去掉Normalize或者改为自己数据集的统计值否则模型看到的输入分布与预训练时不一致微调效果会明显变差。RandomResizedCrop不要改成纯Resize后者会丢失尺度多样性。4. 把训练脚本跑通数据加载、训练循环与模型保存4.1 用ImageFolder配合DataLoader完成数据加载数据加载是整个训练脚本里最容易被忽略也最常报错的部分。理解了3.1里的目录结构这里就顺理成章了。用ImageFolder读取目录用DataLoader分批喂给模型PyTorch的标准写法如下。from torch.utils.data import DataLoader from torchvision import datasets, transforms import torch # 使用3.3节定义的data_transforms image_datasets { x: datasets.ImageFolder(fflower_data/{x}, data_transforms[x]) for x in [train, val] } dataloaders { x: DataLoader(image_datasets[x], batch_size32, shuffle(x train), num_workers0) for x in [train, val] } print(f训练集类别数: {len(image_datasets[train].classes)}) print(f训练集图片数: {len(image_datasets[train].samples)}) print(f验证集图片数: {len(image_datasets[val].samples)})逻辑说明ImageFolder按目录名自动生成类别到数字的映射DataLoader按照batch_size分批训练集shuffleTrue打乱顺序验证集不打乱。num_workers0在Windows上最保险设置大于0在Windows上可能因多进程启动问题报错Linux上可以提高到2或4加速数据读取。参数说明batch_size32在CPU上偏大时可降到16这会直接影响显存或内存占用。类别数通过len(image_datasets[train].classes)输出核对如果这里输出的数字和你预期不符不用继续跑先回头检查目录结构。4.2 训练主循环损失函数、优化器与学习率衰减训练循环的骨架大同小异但有几个参数直接决定最终能不能收敛。花卉识别属于多分类问题损失函数用交叉熵优化器常用SGD加动量或AdamW学习率需要配合epoch数做衰减。import torch.nn as nn import torch.optim as optim from torch.optim.lr_scheduler import StepLR device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device) criterion nn.CrossEntropyLoss() optimizer optim.AdamW(model.parameters(), lr0.001, weight_decay1e-4) scheduler StepLR(optimizer, step_size10, gamma0.5) num_epochs 25 best_acc 0.0 for epoch in range(num_epochs): print(fEpoch {epoch 1}/{num_epochs}) for phase in [train, val]: if phase train: model.train() else: model.eval() running_loss 0.0 running_corrects 0 for inputs, labels in dataloaders[phase]: inputs, labels inputs.to(device), labels.to(device) optimizer.zero_grad() with torch.set_grad_enabled(phase train): outputs model(inputs) loss criterion(outputs, labels) _, preds torch.max(outputs, 1) if phase train: loss.backward() optimizer.step() running_loss loss.item() * inputs.size(0) running_corrects torch.sum(preds labels.data) epoch_loss running_loss / len(image_datasets[phase]) epoch_acc running_corrects.double() / len(image_datasets[phase]) if phase val and epoch_acc best_acc: best_acc epoch_acc torch.save(model.state_dict(), best_model.pth) print(f{phase} Loss: {epoch_loss:.4f} Acc: {epoch_acc:.4f}) scheduler.step()逻辑说明外层循环控制epoch内层循环分train和val两个阶段。训练阶段开启梯度计算并反向传播验证阶段用torch.set_grad_enabled(False)关闭梯度计算只做前向推理这样省显存且不会影响模型参数的确定性。每个epoch结束后计算平均损失和准确率验证集准确率创新高时就把当前权重保存下来防止后续过拟合导致最佳模型被覆盖。参数说明AdamW的lr0.001是微调场景下的常用起点weight_decay1e-4是L2正则化系数抑制过拟合。StepLR每10个epoch把学习率乘以0.5让训练后期用更小步长精细收敛。best_model.pth保存的是完整的参数状态加载时必须先用相同的模型结构实例化一个模型。4.3 用训练好的模型做单张图片预测训练完成后大部分课程设计要求做一个推理演示输入一张花图输出预测类别和置信度。这里有一个新手常犯的错误直接加载模型后忘了设置eval()模式导致同一张图多次预测结果不一致因为BatchNorm和Dropout在训练模式下行为不同。import torch from PIL import Image from torchvision import transforms def predict_image(image_path, model, class_names, device): # 推理前必须切到eval模式 model.eval() # 验证集预处理resize center crop normalize transform transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) image Image.open(image_path).convert(RGB) input_tensor transform(image).unsqueeze(0).to(device) with torch.no_grad(): outputs model(input_tensor) probs torch.softmax(outputs, dim1) confidence, predicted torch.max(probs, 1) return class_names[predicted.item()], confidence.item() class_names image_datasets[train].classes pred_label, conf predict_image(test_flower.jpg, model, class_names, device) print(f预测结果: {pred_label}, 置信度: {conf:.2f})逻辑说明convert(RGB)把灰度图或RGBA图统一转成三通道RGB避免通道数不一致报错unsqueeze(0)给单张图加一个batch维度因为模型的输入要求是四维张量。torch.no_grad()在推理时关闭梯度记录省内存也更快。置信度通过softmax把输出转成概率分布后取最大值。参数说明预测阶段的预处理必须与训练时验证集保持一致这里Resize(256)配合CenterCrop(224)是ResNet系列的标准配置。如果你在3.3节的验证集transform里加了其他操作这里也要同步否则准确率会明显低于训练时的验证结果。5. 花卉识别训练避坑指南环境、数据与训练的5个常见问题5.1 中文路径报错ImageFolder读不到数据或编码异常现象运行训练脚本提示FileNotFoundError或Non-ASCII characters或者目录检查时类别数为0。Windows上尤其常见。原因数据集文件夹放在带中文的路径下比如D:\课程设计\花卉识别\dataPython在Windows下读取文件路径时默认编码可能无法处理中文字符另一种原因是源码里用字符串拼接路径写死了旧的目录名。解决把整个项目目录迁移到纯英文路径例如D:\flower_project\data。同时检查训练脚本里是否有硬编码的绝对路径统一改成相对路径或通过os.path.join构建。这个问来源码里出现频率极高拿到代码第一件事就是把路径全部改成英文。5.2 环境装不上torchvision和opencv-python版本冲突现象安装依赖时pip install torchvision报错或代码里import cv2直接ModuleNotFoundError更隐蔽的是装完后import torch报DLL load failed。原因PyTorch和torchvision有严格的版本对应关系pip在解析依赖时如果网络不稳定或者本机Python版本过老会装出互不兼容的组合opencv-python和opencv-contrib-python混装时也经常相互覆盖导致导入失败。解决建议按Python版本安装匹配的PyTorch版本。Python 3.10以上优先装PyTorch 2.x安装前到PyTorch官网用安装命令生成器核对命令。opencv相关的库只保留一个课程设计用途装opencv-python-headless就够避免GUI依赖在服务器上装不上的问题。另外把pip源换成清华镜像能显著减少网络造成的安装失败多数环境和配置问题都能在这一步解决。5.3 损失一直不掉准确率卡在20%附近现象训练了十几个epochloss稳定在1.6上下不下降验证准确率略高于随机猜测比如5分类准确率20%附近。原因最常见的有三个——学习率过大导致loss震荡不收敛预处理里没有做Normalize像素值范围不一致影响梯度以及修改全连接层后num_classes和实际类别数不一致。解决先把学习率降到0.0001试跑5个epoch看loss是否开始下降然后检查transform里是否有ToTensor()和Normalize两步只有ToTensor会直接把像素压缩到0到1但分布未经标准化最后验证model.fc.out_features是否等于数据集类别数。按这个顺序排查大多数卡准确率的问题都能解决。5.4 验证集准确率远低于训练集现象训练集准确率达到95%以上验证集却只有60%左右且随着训练进行差距越来越大。原因这是典型的过拟合信号。花卉数据集通常只有几千张模型容量大时很容易把训练集“背下来”尤其是最后一层全连接被替换后新增的参数量没有足够数据约束。解决优先增强正则化——调高weight_decay到1e-3在model.fc后加一个nn.Dropout(p0.5)训练集增广里增加RandomErasing随机遮挡部分区域强迫模型学更鲁棒的特征。如果过拟合依然严重直接换参数量更小的MobileNetV3往往比强行调ResNet效果来得更快。5.5 CPU训练太慢一个epoch要跑一小时现象CPU上训练ResNet50batch_size32图片224×224一个epoch耗时长到让人怀疑脚本死循环了。原因ResNet50在CPU上的计算量本来就大加上num_workers0时数据加载串行执行CPU既要跑前向反向又要做图片解码瓶颈叠加。解决把模型换成ResNet18或MobileNetV3batch_size降到8到16训练线程数显式设为4如果机器有独显但PyTorch没用上检查安装的是不是CPU版本的torch重新安装CUDA版本后device会自动变成cuda。可以额外确认一下图片有没有被意外resize到超大尺寸源码里如果出现Resize(512)这类配置改成224会快数倍。6. 让模型被看见把训练结果变成一个可展示的花卉识别Demo课程设计最终要现场演示控制台打印准确率图表比较单薄一个可视化上传界面往往能成为答辩的加分项。这里推荐用Gradio学习成本比Flask低很多。核心代码在30行以内写好之后在本地启动一个Web页面上传图片就能看到预测结果和各类别置信度。import gradio as gr import torch def predict_gradio(image): # image是PIL Image类型直接复用4.3节的pipeline model.eval() transform ... # 与验证集相同的预处理 tensor transform(image).unsqueeze(0).to(device) with torch.no_grad(): out torch.softmax(model(tensor), dim1)[0] return {class_names[i]: float(out[i]) for i in range(len(class_names))} gr.Interface( fnpredict_gradio, inputsgr.Image(typepil, label上传花卉图片), outputsgr.Label(num_top_classes3, label识别结果), title花卉识别系统, description基于ResNet18的花卉分类演示 ).launch()逻辑说明gr.Image(typepil)会把上传的图片自动转为PIL Image对象省去手动读取文件输出用gr.Label能直接把各类别的概率做成条形图展示一张图清楚看出模型的置信度分布。如果想让演示更完整可以把4.2节保存的best_model.pth加载进来先实例化模型结构再load_state_dict。细节上再补两点。第一启动前把launch()参数里的server_name127.0.0.1固定住避免在实验室网络环境下被其他设备扫描到第二演示前准备几张有代表性的测试图包括一张背景复杂的花卉图、一张花朵占比很小的图各测试一次既能展示模型效果也能顺便讲清数据增广的作用。我自己的习惯是每次训练完顺手把混淆矩阵和几类预测失败的图片保存下来。答辩时老师问“模型哪里还不好”直接翻出几张真实失败案例比空口说“某些类别容易混淆”可信得多。这是做深度学习课程设计性价比最高的一个习惯希望帮到你。本文还有配套的精品资源点击获取