ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

基于Python深度学习的花卉识别课程设计实战

基于Python深度学习的花卉识别课程设计实战 简介基于Python深度学习的花卉识别课程设计源码专注于利用卷积神经网络实现花卉图像的自动分类与识别可作为计算机视觉方向课程设计、毕业设计参考也适合初学者入门图像识别实战。压缩包内共四十九个文件整体容量约三点三六兆字节内部以花卉图像数据、程序源码、训练测试日志及说明文档为主其中包含二十八张不同品种的花卉图片涵盖菊花等多个类别。代码部分提供两套深度学习模型实现一套采用预训练权重进行迁移学习一套为自定义卷积神经网络并配有数据读取、数据集划分、模型测试等模块帮助学习者快速掌握从图像预处理到模型训练与评估的完整流程。项目还保留模型关注区域热图以及训练测试日志可直观观察模型关注特征并追踪损失与准确率变化同时附带忽略规则文件、使用说明文本等目录结构清晰便于复现与二次开发。已有三百六十三人学习下载适合想要系统掌握卷积神经网络与迁移学习图像分类、快速搭建完整花卉识别项目的开发者参考。1. 课程设计选花卉识别为什么它是深度学习的“最佳练手题”如果你正在为Python深度学习课程设计发愁花卉识别几乎是所有选题里性价比最高的一个数据集好找、任务直观、模型效果容易可视化而且它同时覆盖了图像分类、数据增强、迁移学习和模型部署这几块硬骨头。很多人的第一个深度学习项目就是从“把一张花照片分成菊花、玫瑰、蒲公英”开始的。做这样一个基于Python深度学习的花卉识别课程设计源码项目你真正要交付的不只是一段能跑通的代码而是一套“数据怎么处理、模型怎么选、参数怎么调、结果怎么解释”的完整思路——这也是答辩时老师真正关心的东西。这篇笔记我按自己做课程设计带组员时的习惯从torchvision自带的花卉数据集讲起把从数据预处理到模型训练的完整链路拆开最后把最容易翻车的几个点和验证技巧也一并交代清楚。2. 数据集与预处理先解决“喂什么”的问题再谈模型2.1 用torchvision自带数据集跑通最小流程花卉识别的公开数据集很多但做课程设计我强烈建议先用torchvision.datasets里自带的花卉数据集跑通全流程。原因很现实它不需要你额外去下载、解压、手工划分目录API直接返回已经分好类的图像和标签能帮你把“数据集的问题”和“模型的问题”分开排查起来省一半的力气。import torchvision from torchvision import transforms from torch.utils.data import DataLoader 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]) ]) train_data torchvision.datasets.Flowers102( root./data, splittrain, downloadTrue, transformtransform ) train_loader DataLoader(train_data, batch_size32, shuffleTrue, num_workers4)这段代码会从torchvision自动下载Flowers102数据集——102个类别的花卉图片每个类别在训练集里有10张、验证集里有10张测试集里数量不等。这里的预处理管线Resize((224, 224))是配合ImageNet预训练模型的固定输入尺寸Normalize用ImageNet的均值和标准差这两项是迁移学习场景下的标准做法不是随便选的。参数说明batch_size在课程设计里设32或64都合适显存不够就降到16num_workers取决于你的CPU核心数Windows下设成2~4就足够了设太高反而容易在数据加载时报错这个坑后面细说。splittrain和splittest会分别拿到训练集和验证集不需要自己再划分。2.2 自制数据集目录结构与标签编码如果你不想用现成的Flowers102想自己拍照片或者从网上收集花卉图片那目录结构建议直接按ImageFolder的规范来组织这样torchvision.datasets.ImageFolder能直接读取少写一堆自定义Dataset的代码# 目录结构data/train/rose/*.jpg, data/train/sunflower/*.jpg # 目录结构data/val/rose/*.jpg, data/val/sunflower/*.jpg from torchvision.datasets import ImageFolder train_dataset ImageFolder(root./data/train, transformtransform) val_dataset ImageFolder(root./data/val, transformtransform) print(train_dataset.class_to_idx) # 打印类别名到索引的映射这里有个细节值得关注每个类别的训练图片数量最好控制在几十张到两三百张之间太少模型学不到类内差异太多对课程设计来说训练时间不划算。另外采集图片时尽量让同一类花有不同背景、不同角度、不同光照的样本否则模型很容易学会“识别背景”而不是“识别花”——这是个玄学问题但根源在数据不在模型。预处理这一步的关键结论是不管用什么数据源预处理都不该只做一个Resize加ToTensor至少要加上RandomHorizontalFlip或RandomRotation做数据增强。训练集和验证集的transform要分开写验证集不能做随机增强否则评估指标会失真。3. 模型选型与训练ResNet是底线微调参数决定上限3.1 为什么课程设计首选ResNet而不是自己搭CNN很多人一开始会想自己搭一个几层的卷积神经网络觉得这样“更像自己做的”。我的建议是如果你不想在答辩时被问到“为什么你的模型过拟合这么严重”而答不上来那就直接用torchvision里预训练的ResNet18做迁移学习。理由有三个ResNet18只有约1100万参数训练速度和显存占用都友好CPU也能跑推理——这很关键因为很多课程设计最终是在没有GPU的机器上演示的。预训练权重是在ImageNet上学到的已经内置了大量通用视觉特征边缘、纹理、形状你的花卉数据集只需要微调最后几层就能取得不错的效果。ResNet的残差结构在答辩时是一个很好的“技术亮点”你可以清楚地解释这个结构如何解决网络退化问题比手写CNN更容易讲出深度。import torchvision.models as models import torch.nn as nn model models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1) num_ftrs model.fc.in_features model.fc nn.Linear(num_ftrs, 102) # 102对应Flowers102的类别数 # 冻结前几层只微调最后一层 for param in model.parameters(): param.requires_grad False for param in model.fc.parameters(): param.requires_grad True device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device)这段代码的关键操作有两处一是model.fc nn.Linear(num_ftrs, 102)把全连接层的输出维度改成你的类别数二是通过requires_grad控制只训练最后一层。第一个操作是通用的分类头替换模式第二个操作则是控制在多大程度上“借用”预训练知识。如果只训练最后一层且数据量少就几乎不会过拟合但模型上限也低如果解冻更多层并加大学习率模型上限高但风险也高。3.2 训练脚本的完整结构与两个必调的损失函数参数训练循环看起来简单但课程设计翻车率高往往是因为缺少简单的检查点、没有合理使用损失函数的参数或者监控了错误的指标。下面这段代码是我平时带项目时给组员的模板包含了“保存最佳模型”和“打印每个epoch的分类准确率”这两个关键能力import torch import torch.nn as nn import torch.optim as optim criterion nn.CrossEntropyLoss() optimizer optim.Adam(model.fc.parameters(), lr1e-3) best_acc 0.0 epochs 15 for epoch in range(epochs): model.train() running_loss 0.0 for images, labels in train_loader: images, labels images.to(device), labels.to(device) optimizer.zero_grad() outputs model(images) loss criterion(outputs, labels) loss.backward() optimizer.step() running_loss loss.item() # 验证阶段 model.eval() correct, total 0, 0 with torch.no_grad(): for images, labels in val_loader: images, labels images.to(device), labels.to(device) outputs model(images) _, predicted torch.max(outputs.data, dim1) total labels.size(0) correct (predicted labels).sum().item() acc 100 * correct / total print(fEpoch {epoch1}/{epochs}, Loss: {running_loss/len(train_loader):.4f}, Val Acc: {acc:.2f}%) if acc best_acc: best_acc acc torch.save(model.state_dict(), best_flower_model.pth)这里有一个新手常犯的错在验证阶段忘了加model.eval()和with torch.no_grad()。前者会关掉Dropout和BatchNorm的随机行为后者会停止构建计算图并大幅减少显存占用。不加这两行验证指标是不准确的。另一个隐蔽的问题是total labels.size(0)它统计的是所有batch的样本总和而不是只取最后那个不完整batch的数量——不过DataLoader默认drop_lastFalse所以写成labels.size(0)就对了。CrossEntropyLoss在PyTorch里默认已经把LogSoftmax和NLLLoss合并了所以模型最后一层直接输出原始logits就行不要再手动加Softmax。如果要处理类别不均衡的数据集可以在CrossEntropyLoss里传weight参数——以各个类别的样本数倒数计算这是处理数据不均衡时最直接有效的做法。3.3 学习率和优化器课程设计场景下的调整思路训练时你会经常遇到一个现象loss在降低但准确率上不去或者验证集准确率忽高忽低。这时候调整学习率是第一步。常见的做法是先用1e-3的Adam跑几个epoch如果loss下降太慢就调大到1e-2如果loss震荡就调小到1e-4。# 学习率衰减每5个epoch衰减到原来的0.1倍 scheduler optim.lr_scheduler.StepLR(optimizer, step_size5, gamma0.1) # 在训练循环每轮结束后调用 scheduler.step()Adam已经内置了一阶动量和二阶动量所以它对学习率不像SGD那么敏感。但如果用了ImageNet预训练模型做完整微调SGD配momentum的收敛效果往往比Adam更稳——这是一个“热身”阶段学习率从1e-4开始动量0.9训练曲线会平滑很多。课程设计时间如果紧张Adam配上StepLR是最省心的组合。4. 避坑花卉识别课程设计最常见的6个翻车现场4.1 Flowers102数据集标签与类名不匹配现象模型训练准确率很高但用测试图片推理时的输出结果和真实花名对不上。原因torchvision.datasets.Flowers102返回的是整数标签从0到101这些索引和数据集自带的labels.txt文件是严格对应的但和你从网上看到的类别名称列表不一定一致。花卉名在Flowers102的官方标注里是拉丁文比如“n04356074”这样的WordNet ID如果你自行映射到中文花名很容易错位。解决直接使用dataset._labels或者下载官方label文件做一次映射表。比较保险的做法是训练完就保存一份idx_to_name.json推理时从该文件读取类别名不要每次手工指定列表。4.2 Windows下num_workers设置过高导致程序卡死或反复重启现象代码在for images, labels in train_loader:处停住不动或每个epoch刚开始就报BrokenPipeError。原因Windows下多进程数据加载和Linux行为不同当num_workers大于0时如果代码不是写在if __name__ __main__保护块里或者num_workers超过了CPU核心数就可能出现子进程崩溃。解决把训练代码放进if __name__ __main__:下或者将num_workers直接设为0。课程设计阶段的数据量并不大num_workers0的加载速度完全可以接受这是最省心的方案。4.3 验证集准确率很高但单张图片推理效果很差现象训练时在验证集上达到90%以上准确率但拿一张手机拍的花去预测结果是错的。原因验证集图像经过了和你训练集一致的Resize(224, 224)和Normalize但你在推理时会忘记做同样的预处理或用了不同的尺寸。这是最典型的“黑匣子”场景——模型本身没问题问题是输入管线的格式不一致。解决推理代码和数据加载代码复用同一个预处理函数。单独定义一个get_transform()函数训练、验证、推理各环节都调用它不要在推理时重新写一遍transform。4.4 训练loss不下降准确率一直在1%左右现象第一个epoch后loss基本不变准确率远低于类别数的倒数例如102类时约1%。原因最常见的是标签错位。比如用了ImageFolder但类别目录和实际图像不对应或者数据集里部分图片损坏导致自动跳过。另一个原因是学习率过大导致loss直接nan。解决先用极小的学习率1e-5跑一个epoch确认loss确实在下降再调回正常学习率。然后逐个检查train_dataset.class_to_idx的映射挑几张图打印标签和图像路径确认一致性。4.5 显存不足CUDA out of memory现象训练第二个epoch时报CUDA out of memory。原因batch_size过大是主因但很多人忽略的是验证阶段也占显存——如果代码加了torch.no_grad()但没加model.eval()BatchNorm层仍然可能累积额外状态。解决先把batch_size减半或者减少num_workers验证阶段务必同时配合model.eval()和torch.no_grad()。如果还在爆就把输入尺寸从224降到160——但注意这会改变预训练模型的输入分布准确性会有轻微下降。4.6 保存的模型加载后结果不一样现象训练时指标正常但重新加载.pth后推理结果明显不对。原因加载模型时用了不同的类数量或者使用了不同的模型结构。例如训练时是resnet18(num_classes102)加载时却默认了resnet18(num_classes1000)——因为torchvision默认输出维度是1000加载权重时不会报错但最后一层对不上。解决保存模型时同时保存模型结构和超参数至少保存类别数torch.save({ model_state_dict: model.state_dict(), num_classes: 102, transform: transform }, flower_model_checkpoint.pth)加载时从checkpoint里读取num_classes来重建模型结构这样就不会出现维度错位。5. 从课程设计到真正可用推理脚本、界面与指标验证5.1 写一个能接受单张图片的推理脚本课程设计交到这一步很多同学认为训练出模型就算结束了但答辩时老师最常问的一句话是“你这个模型实际用起来什么样”所以一个能接收单张图片并输出“预测类别置信度”的脚本比多训练10个epoch都重要。推理脚本我习惯的做法如下import torch from PIL import Image import torchvision.transforms as transforms from torchvision import models 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]) ]) def load_model(checkpoint_path, num_classes102): model models.resnet18(weightsNone) model.fc torch.nn.Linear(model.fc.in_features, num_classes) checkpoint torch.load(checkpoint_path, map_locationcpu) model.load_state_dict(checkpoint[model_state_dict]) model.eval() return model def predict(image_path, model, idx_to_name): img Image.open(image_path).convert(RGB) img_tensor transform(img).unsqueeze(0) with torch.no_grad(): outputs model(img_tensor) probabilities torch.nn.functional.softmax(outputs[0], dim0) top_prob, top_idx torch.max(probabilities, dim0) return idx_to_name[top_idx.item()], top_prob.item() if __name__ __main__: model load_model(best_flower_model.pth) name, prob predict(test_rose.jpg, model, idx_to_name) print(f预测结果: {name}, 置信度: {prob:.2%})加上map_locationcpu意味着这个脚本即使在演示机器上只有CPU也能正常工作同时Image.open后接.convert(RGB)能避免某些灰度图或RGBA图在预处理时报通道数错误。置信度输出是所有课程设计演示中展现专业度的最好手段展示的可信度明显高于只输出类别名。5.2 用混淆矩阵验证模型的真实盲区准确率是一个过于笼统的指标。对花卉识别这个小数据集任务来说准确率达到92%并不能证明模型“会认花”可能只是绕开了某些容易混淆的类别。我习惯的做法是输出一张混淆矩阵图把它贴到课程设计报告里作为结果分析部分的核心证据。import matplotlib.pyplot as plt import numpy as np from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay all_labels, all_preds [], [] with torch.no_grad(): for images, labels in val_loader: images images.to(device) outputs model(images) _, predicted torch.max(outputs.cpu(), dim1) all_labels.extend(labels.numpy()) all_preds.extend(predicted.numpy()) cm confusion_matrix(all_labels, all_preds) disp ConfusionMatrixDisplay(confusion_matrixcm) disp.plot(cmapBlues) plt.savefig(confusion_matrix.png, dpi150)混淆矩阵能一眼看出来哪些类别互相打架比如向日葵和蒲公英如果经常混淆说明两者的颜色和纹理特征在模型看来没有足够的区分度——这时可以增加这类别的训练样本量或者查一下是不是数据增强把花色、背景过度扭曲了。这里的一个关键技巧confusion_matrix的输入必须是CPU上的NumPy数组很多人在all_labels.extend(labels)时忘了labels还在GPU上结果直接报错。5.3 后续值得做的两个方向Gradio部署和细粒度分类如果要让这个课程设计更出彩我强烈建议用Gradio把模型包装成一个网页应用拖一张图进去就能看到识别结果和置信度。这是目前最讨巧的展示手段——代码量不到二十行且效果直观。课程设计答辩时能把模型跑在网页上的人基本上不会再被追问任何实现细节。另一个方向是细粒度花卉识别。常见的Flowers102已经是一个难度不低的细粒度识别任务但如果你的数据是自己收集的、类别之间外观差异更小时可以尝试引入注意力机制模块比如SE Block或者CBAM加到ResNet的每个残差块后面。这个改动会显著提升相似花朵的区分度还能给你的答辩增加一个“为了提升性能做了什么设计”的技术亮点。不过要注意加了注意力之后显存占用上升约10%到15%训练时间也会变长课程设计阶段把精力放在可视化验证上更值得。最后说个我自己带过的真实教训有一个组在答辩前一天还在大量调参想把准确率从91%再推到95%以上结果答辩当天模型过拟合在训练集上——这其实也是很多课程设计的通病。课程设计不是论文你的目标是在有限时间内展示“完整链路合理指标可解释的结果”而不是刷极限数字。把推理脚本、混淆矩阵和几组对比实验的截图准备好比多跑50个epoch有价值得多。希望帮到你。本文还有配套的精品资源点击获取
返回列表