尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

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

花卉识别项目实战:从数据集处理到PyTorch迁移学习

花卉识别项目实战:从数据集处理到PyTorch迁移学习 简介本资源面向计算机视觉初学者、深度学习实践者及花卉识别相关科研人员提供一套开箱即用的花卉分类数据集与完整训练方案。资源包含16类常见花卉如曼陀罗、桔梗、蒲苇等共32000张224×224彩色图像并配套基于TensorFlow编写的训练源码支持Inception、ResNet、MobileNet等23种主流CNN模型灵活切换训练。压缩包共110个文件以33个Python训练/预处理脚本含reshape_layers.py、inception.py等模块化实现、46个文本配置与说明文件为主辅以bat启动脚本、exe工具及少量示例图片整体684.45MB结构清晰便于二次开发与模型对比实验。目前已有1498人学习下载读者可直接运行run.bat快速启动训练流程复现分类效果并基于dat数据索引与分层目录设计高效扩展新类别或迁移学习任务。 最近在整理花卉识别项目代码时发现不少初学者卡在同一个位置上数据集明明下载好了源码也能跑通但换到自己构造的数据集上就各种报错。尤其是以“花卉识别-花卉数据集-花卉识别训练源码-花卉图片集02”这类标题命名的资源包网上一抓一大把解压之后往往是一个分类好的图片文件夹加一个训练脚本。如果你手头正好有类似的资源或者正打算从零做一个花卉分类项目这篇文章就是写给你看的。接下来我会从项目拆解、数据集处理、模型训练到踩坑实录把整个流程完整过一遍。内容以自己的实操经验为主代码部分基于PyTorch实现环境是Python 3.8 PyTorch 1.10 CUDA 11.3你换成更新版本基本不影响理解。1. 项目整体拆解与思路分析1.1 花卉识别到底是个什么任务从技术角度看花卉识别属于图像分类中的细粒度识别Fine-Grained Image Classification。和普通分类任务比如区分猫和狗不同花卉种类之间的视觉差异往往非常细微有些花外形极度相似比如菊科下面一堆品种外行基本分不清。这导致花卉识别比通用分类任务更难对模型的特征提取能力要求更高。当年我做这个项目时数据集有几十个类别每个类别图片数量参差不齐有的几百张有的只有三十几张。直接用深度学习模型从头训练效果惨不忍睹训练集准确率倒是很快到90%以上但验证集一直卡在50%左右。这就是典型的过拟合数据量太小模型参数太多学到的都是训练集上的噪声特征。1.2 数据集的常见结构与组织方式典型的花卉数据集资源包解压后通常长这样flower_dataset/ ├── train/ │ ├── daisy/ │ │ ├── image_001.jpg │ │ ├── image_002.jpg │ │ └── ... │ ├── dandelion/ │ ├── rose/ │ ├── sunflower/ │ └── tulip/ └── test/ ├── daisy/ ├── dandelion/ ├── rose/ ├── sunflower/ └── tulip/这种按类别分文件夹的组织方式是最常见的也是PyTorch的ImageFolder接口默认支持的数据格式。如果你拿到的资源包不是这种结构比如所有图片都在一个文件夹里、配一个CSV标注文件那就需要自己写数据加载逻辑后面我会详细说。1.3 训练源码的核心组成所谓“训练源码”核心一般包含以下模块数据加载与预处理逻辑模型定义或迁移学习的预训练模型加载训练循环前向传播、损失计算、反向传播、参数更新验证与评估逻辑模型保存与加载一份合格的花卉识别训练源码至少要把以上五件事讲清楚。很多网上下载的源码问题就出在数据加载部分写得太死只适配特定的数据集目录结构换一个数据集就没法跑。我写这篇博客的目的之一就是帮你练出一双火眼金睛拿到源码能快速定位问题、改造成适合自己的项目。2. 核心细节解析与实操要点2.1 数据预处理为什么不能直接喂原图图像数据进入模型之前必须经过预处理。很多新手容易忽略这一步把任意尺寸的图片直接送到模型里结果就是报错或者训练效果极差。标准做法包含三项缩放Resize、归一化Normalize、数据增强Data Augmentation。缩放的目的很直白深度学习模型通常要求固定尺寸的输入比如224x224。无论原图是800x600还是1024x768最终都要统一到224x224。这里需要注意的是直接暴力拉伸会改变图片比例导致花形畸变。常见做法是先按比例缩放到短边等于224再中心裁剪224x224区域。PyTorch里的transforms.Resize(256)加上transforms.CenterCrop(224)就是干这个事的。归一化也很关键。PyTorch预训练模型默认期望输入是三通道RGB图像像素值归一化到[0,1]然后再用均值[0.485, 0.456, 0.406]和标准差[0.229, 0.224, 0.225]做标准化。这三组数不是拍脑袋定的而是ImageNet数据集的统计值。如果你用了PyTorch官方预训练模型但没做标准化输入数据分布和预训练时不一致迁移学习的效果会大打折扣。下面这段代码是标准预处理流程from torchvision import transforms train_transforms transforms.Compose([ transforms.Resize(256), transforms.RandomResizedCrop(224), transforms.RandomHorizontalFlip(), transforms.RandomRotation(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_transforms transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ])训练集和验证集的预处理是有差异的。训练集加入了随机裁剪、翻转、旋转和颜色扰动相当于免费扩充了数据量这是缓解过拟合最重要的手段之一。验证集则只是缩放加裁剪因为验证时我们要的是稳定的预测结果不需要随机性。这个细节很多人会忽略直接把训练预处理用到验证集上导致验证准确率忽高忽低。2.2 数据增强的花样玩法上面提到的RandomRotation和ColorJitter还只是基础操作。做花卉识别有个增强策略特别好用RandomResizedCrop。这个操作会让模型每次随机裁剪图片的不同区域并缩放到固定尺寸。直观理解就是让模型学会看花的不同局部也能判断出花的种类类似人类识别技术的训练方式。还有一个更高级的思路CutMix或MixUp。这两种是基于样本混合的增强策略。CutMix是把一张图的某块区域用另一张图的区域替换MixUp则是把两张图按比例线性混合。它们的共同点是强迫模型不依赖某一个过于显眼的局部特征从而提升泛化能力。我在实验中用过CutMix在花卉数据集上大约能提升1到2个百分点但同时训练时间会变长Loss曲线也更震荡需要一定耐心调参。需要注意的是数据增强不是万能的它只是手段。如果数据集本身就小每个类别只有几十张增强能缓解过拟合但不能根治。根本解法还是尽量收集更多数据或者利用公开的大规模预训练模型。2.3 模型选型为什么用迁移学习而不是从零训练花卉识别这种任务绝大多数情况下应该使用迁移学习而不是从零训练一个卷积神经网络。原因很简单数据量不够。ImageNet数据集有128万张图片、1000个类别在这种规模上训练出来的模型底层的边缘、纹理、形状特征提取器已经非常强大了。花卉识别的类别虽然和ImageNet里的重分类目不同但底层特征完全通用。你只需要把预训练模型后端的全连接分类层换掉改成输出自己数据集的类别数然后微调即可。常见的选择是ResNet50、EfficientNet、MobileNet这三类。ResNet50结构经典、稳定性好适合作为默认选项EfficientNet在参数量和准确率之间平衡好但是训练更慢MobileNet轻量级适合部署到移动端或者边缘设备。我自己的经验是如果只是学习练手ResNet50就够了如果要做实际产品需要考虑推理速度优先MobileNetV3或EfficientNet-Lite。更极端一点的做法是用EfficientNetV2或ConvNeXt这类新模型效果确实更好但训练成本高而且对新手来说不太友好。除非你设备配置好、时间充裕否则不建议一上来就用大模型。2.4 损失函数和优化器的细节图像分类最常用的损失函数是交叉熵损失CrossEntropyLoss。PyTorch里直接用nn.CrossEntropyLoss()即可它内部会把模型的原始输出logits做softmax再计算损失不需要你自己手动加softmax层。优化器方面默认推荐AdamW学习率初始值一般设置在1e-4到3e-4之间。如果你使用SGD需要加Momentum一般0.9学习率要调大一些比如1e-2到3e-2。用SGD需要更多的调参经验AdamW则对新手友好很多。一个非常重要但容易被忽视的细节迁移学习中预训练模型的主体部分和新增的分类头学习率应该不同。分类头是随机初始化的需要更大学习率快速收敛预训练主干参数已经比较成熟适合用更小的学习率细微调整。PyTorch里可以用param_group分别控制backbone_params [] head_params [] for name, param in model.named_parameters(): if classifier in name or fc in name: head_params.append(param) else: backbone_params.append(param) optimizer torch.optim.AdamW([ {params: backbone_params, lr: 1e-4}, {params: head_params, lr: 1e-3} ], weight_decay1e-4)这样设置的好处是分类头可以在前期快速学习新类别的特征映射而主干网络不会因为学习率太大而出现灾难性遗忘。2.5 评估指标不只是准确率评估模型时准确率Accuracy是最直观的指标但它不是唯一的。花卉识别中如果类别不平衡非常严重比如玫瑰有500张图、小雏菊只有30张那么模型只需要全猜玫瑰就能拿到很高的准确率这显然是自欺欺人。所以需要引入更多指标精准率Precision、召回率Recall、F1分数以及混淆矩阵Confusion Matrix。混淆矩阵能直观展示哪些类别之间容易混淆比如向日葵和雏菊都是黄色花瓣黑色花心模型可能经常搞混。用混淆矩阵发现此类问题后可以针对性地增加容易混淆类别的样本或者在前处理时加强颜色归一化。另外Top-5准确率在花卉识别中也有参考价值。对于102类花卉数据集Top-5准确率往往比Top-1高出不少说明模型虽然没完全正确判断但至少把正确类放在了前五个候选中。这在某些实际应用场景如植物百科App中是完全可以接受的。3. 实操过程与核心环节实现3.1 环境准备做深度学习训练有个好用的GPU设备当然是好事没有的话用CPU也能跑只是慢很多。我建议至少要有GTX 1060 6GB以上的显卡否则训练ResNet50会非常煎熬一个epoch可能要几十分钟甚至几小时。软件环境建议用conda管理避免把系统Python环境搞乱conda create -n flower python3.8 conda activate flower pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 pip install tensorboard matplotlib scikit-learn tqdm3.2 数据集划分与加载拿到原始数据集后第一步不是训练而是划分数据。如果原始数据没有分成train和test建议按7:2:1的比例划分为训练集、验证集、测试集。注意先打乱再划分保证每个类别的样本均匀分布在各个集合中。下面这段代码能把flower_all/目录下按类分好的图片随机划分到train/和val/中import os import shutil import random random.seed(42) source_dir flower_all train_dir flower_dataset/train val_dir flower_dataset/val for class_name in os.listdir(source_dir): class_path os.path.join(source_dir, class_name) if not os.path.isdir(class_path): continue images os.listdir(class_path) random.shuffle(images) split_idx int(len(images) * 0.8) train_imgs images[:split_idx] val_imgs images[split_idx:] os.makedirs(os.path.join(train_dir, class_name), exist_okTrue) os.makedirs(os.path.join(val_dir, class_name), exist_okTrue) for img in train_imgs: shutil.copy(os.path.join(class_path, img), os.path.join(train_dir, class_name, img)) for img in val_imgs: shutil.copy(os.path.join(class_path, img), os.path.join(val_dir, class_name, img))划分完成后用torchvision.datasets.ImageFolder加载数据from torchvision.datasets import ImageFolder from torch.utils.data import DataLoader train_dataset ImageFolder(flower_dataset/train, transformtrain_transforms) val_dataset ImageFolder(flower_dataset/val, transformval_transforms) train_loader DataLoader(train_dataset, batch_size32, shuffleTrue, num_workers4) val_loader DataLoader(val_dataset, batch_size32, shuffleFalse, num_workers4)ImageFolder会自动按照文件夹名称解析类别把类别名称映射成从0开始的整数索引。class_to_idx属性里保存了这个映射关系推理阶段要用到千万别丢了。3.3 迁移学习模型构建与训练循环模型部分以ResNet50为例import torch import torch.nn as nn from torchvision import models def create_model(num_classes): model models.resnet50(weightsmodels.ResNet50_Weights.IMAGENET1K_V2) # 将最后全连接层替换为适配自己数据集的分类器 in_features model.fc.in_features model.fc nn.Sequential( nn.Dropout(0.5), nn.Linear(in_features, num_classes) ) return model model create_model(num_classeslen(train_dataset.classes))weightsmodels.ResNet50_Weights.IMAGENET1K_V2表示加载ImageNet上预训练好的权重。新版torchvision里旧写法pretrainedTrue已经弃用会报DeprecationWarning所以直接用新版写法就好。训练循环是源码中最重要的部分下面给出一个完整实现import torch import torch.nn as nn import torch.optim as optim from tqdm import tqdm device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device) criterion nn.CrossEntropyLoss() optimizer optim.AdamW(model.parameters(), lr1e-4, weight_decay1e-4) # 学习率余弦退火 scheduler optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max20) num_epochs 20 best_val_acc 0.0 for epoch in range(num_epochs): # 训练阶段 model.train() running_loss 0.0 correct 0 total 0 for images, labels in tqdm(train_loader, descfEpoch {epoch1} Train): 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() * images.size(0) _, predicted torch.max(outputs, 1) total labels.size(0) correct (predicted labels).sum().item() train_loss running_loss / total train_acc correct / total # 验证阶段 model.eval() val_loss 0.0 correct 0 total 0 with torch.no_grad(): for images, labels in tqdm(val_loader, descfEpoch {epoch1} Val): images, labels images.to(device), labels.to(device) outputs model(images) loss criterion(outputs, labels) val_loss loss.item() * images.size(0) _, predicted torch.max(outputs, 1) total labels.size(0) correct (predicted labels).sum().item() val_acc correct / total print(fEpoch {epoch1} | Train Loss: {train_loss:.4f} | Train Acc: {train_acc:.4f} | Val Acc: {val_acc:.4f}) scheduler.step() # 保存最佳模型 if val_acc best_val_acc: best_val_acc val_acc torch.save(model.state_dict(), best_model.pth)这段代码里有几个关键点第一model.train()和model.eval()必须正确调用。前者会启用Dropout和BatchNorm的训练模式后者则切换到推理模式。很多新手忘了切换导致验证效果异常。第二optimizer.zero_grad()必须在每次前向传播之前调用否则梯度会累加。这是PyTorch的机制和TensorFlow有区别。第三验证阶段用torch.no_grad()包裹避免计算图被保存大幅节省内存。第四torch.save(model.state_dict(), best_model.pth)保存的是模型参数而不是整个模型。加载时还需要重新构建模型结构再使用load_state_dict导入参数。3.4 训练过程中的可视化监控训练时干等不看指标变化效率太低。我习惯用TensorBoard实时监控。启用方法很简单from torch.utils.tensorboard import SummaryWriter writer SummaryWriter(runs/flower_experiment) # 每个epoch结束后记录 writer.add_scalar(Loss/train, train_loss, epoch) writer.add_scalar(Accuracy/train, train_acc, epoch) writer.add_scalar(Accuracy/val, val_acc, epoch)训练完成后在终端运行tensorboard --logdirruns浏览器打开http://localhost:6006就能看到曲线。如果发现训练集准确率和验证集准确率差距越拉越大说明过拟合在加剧需要考虑增加数据增强强度、加大Dropout概率或者提前停止训练。3.5 单张图片推理从训练到实战训练完模型后还要写一个推理代码把模型用起来。这里最容易踩的坑是训练时的预处理和数据增强必须和推理时保持一致但推理时不要用随机数据增强。直接复用验证集的预处理就行def predict(image_path, model_path, class_names): from PIL import Image device torch.device(cuda if torch.cuda.is_available() else cpu) # 构建模型 num_classes len(class_names) model create_model(num_classes) model.load_state_dict(torch.load(model_path, map_locationdevice)) model model.to(device) model.eval() # 读取并预处理图片 image Image.open(image_path).convert(RGB) image val_transforms(image).unsqueeze(0).to(device) # 加上batch维度 with torch.no_grad(): outputs model(image) probabilities torch.softmax(outputs, dim1) top_prob, top_class torch.topk(probabilities, 3) for i in range(3): prob top_prob[0][i].item() idx top_class[0][i].item() print(fTop {i1}: {class_names[idx]} ({prob:.4f})) return class_names[top_class[0][0].item()]代码中val_transforms就是之前定义好的验证集预处理。注意unsqueeze(0)这步模型输入要求四维张量(batch_size, channels, height, width)单张图片是三维的必须手动增加一个batch维度。4. 常见问题与排查技巧实录4.1 训练Loss不下降或没有收敛迹象这是最让人头大的问题之一。如果你发现几个epoch后损失函数基本不变准确率还在随机猜的水平假设10类准确率约等于10%按以下顺序排查首选核对学习率。学习率太大loss会震荡甚至变成NaN学习率太小模型收敛极慢几个epoch看不出变化。AdamW初始学习率1e-4到3e-4如果用的SGD则要调到1e-2左右。其次检查数据预处理。图片归一化的均值和标准差填错了没有ToTensor()之前做了除法了吗如果ToTensor()之后图片值域不对模型输入分布异常很难收敛。再检查模型结构。如果自定义模型有误比如全连接层输入维度不对模型可以跑起来但学习效果差。用print(model)打印模型结构对照一下确认分类头输出维度等于类别数。4.2 过拟合严重典型症状是训练集准确率越来越高验证集准确率反而下降。这在花卉识别项目中很常见因为很多类别图片数量太少。解决办法按效果排序第一增加数据增强强度例如加大随机旋转角度、增加随机遮挡、加入RandomResizedCrop第二使用更强的Dropout比如把分类头的Dropout从0.3提到0.5第三增加Weight Decay也就是L2正则化一般从1e-4调大到1e-3第四最直接的办法是模型换成参数量更小的结构例如从ResNet50换到ResNet18如果数据量小小模型的泛化能力往往更好。4.3 某些类别准确率爆炸低用混淆矩阵看如果某些类别的准确率特别低而且经常被预测成固定的另一类说明这两类图片视觉相似度过高模型确实没学会区分它们。结合我过去的经验菊花和向日葵这类同科植物属于重灾区。应对策略有两个方向。一个是收集更多这两个类别的数据把相似形态的样本尽量补齐。另一个是针对性增强比如对容易被混淆的类别做更细致的颜色校正和背景去干扰处理。有些场景下增加输入分辨率也能有明显改善从224x224提高到320x320甚至384x384让模型看到更多细节花卉的纹理、花蕊结构都能更清晰地被模型捕捉。4.4 类别数不匹配的错误很多网上下载的源码模型最后一个全连接层的输出类别数是写死的。比如原作者用了5类花卉数据集你换成102类的Oxford Flowers数据集不修改源码直接跑必定报错。这个问题的解决方式是让类别数从数据集中动态获取而不是硬编码num_classes len(train_dataset.classes)train_dataset.classes返回的是所有类别名的列表长度就是类别数。这样无论数据集是5类还是102类代码都不用改。我建议拿到任何训练源码第一件事就是检查模型输出类别数是不是写死的。如果是果断改成动态获取。4.5 加载预训练模型时网络报错torchvision在新版本中修改了预训练权重的下载逻辑。如果遇到SSL证书问题通常是本地网络环境导致的可以在下载前设置豆瓣或清华镜像源或者直接下载权重文件放到用户目录下的.cache/torch/hub/checkpoints/里文件名要按照torchvision的命名规则。这个问题不算难解决但很消磨人心。遇到下载失败不要反复重试直接手动下载权重文件再放入缓存目录最省事。4.6 训练和验证的数据泄漏问题这个坑比较隐蔽。如果你在划分数据集之前做了全局数据增强或者某些图片同时出现在训练集和验证集验证集准确率会虚高。比如有些资源包原始目录结构里自带train/和test/但test目录里又有和train重复的图片此时直接用原始划分训练验证集参考价值就失效了。确保划分的图片完全互斥可以用文件名hash做去重。划分完成后抽查几个类别确认训练和验证图片没有重叠。4.7 GPU显存不足Batch size设置过大ResNet50加上224x224输入显存消耗大约在4-6GB。如果你的显卡只有4GB显存Batch size设为32几乎必然OOM。解决办法是把Batch size降到8或16同时调低num_workers减轻内存压力。真的不够就需要换个更轻量的模型比如MobileNetV3。另外提醒一点训练结束后要及时释放显存。PyTorch进程退出后显存会自动释放但如果你在Jupyter Notebook中反复训练多个模型显存可能一直被占用。用torch.cuda.empty_cache()可以释放缓存显存不过关闭内核才是最彻底的方式。5. 项目进一步的优化方向训练完基线模型后还可以在几个方向上继续提升。提升模型精度使用集成学习思路训练多个不同架构的模型取平均作为最终输出。比如ResNet50和EfficientNet-B3分别训练最终对每个类别的概率求平均往往能再涨1到2个百分点。代价是推理时间成倍增加。细粒度识别增强花卉识别进阶方向有部件检测、注意力机制等。比如引入SE模块或CBAM注意力模块让模型自动关注花蕊、花瓣边缘等判别性区域。这类改进在细粒度识别任务上通常效果明显。模型部署模型训练完不能只在训练环境里玩落地到实际应用才是目标。如果是移动端用ONNX导出模型通过ONNX Runtime或CoreML做推理比直接在手机上跑PyTorch高效得多。如果是Web应用用FastAPI包装一个推理接口配合Docker部署简单可靠。主动学习收集数据在真实落地场景中靠人工标注花卉数据成本很高。可以考虑用半监督方案或主动学习策略模型对一批未标注图片预测对置信度较低的样本要求人工标注再把标注后的数据加入训练集。这样能用最小的标注成本持续提升模型效果。最后说说我自己的体会花卉识别虽然在深度学习入门教程里经常出现但它一点都不“小儿科”。细粒度、类别不平衡、背景干扰这些问题在真实商业项目中比比皆是。把花卉识别这个项目搞透彻了再去做皮肤病分类、农作物病害识别、卫星遥感地物分类思路基本是相通的——都是图分类任务难点都在数据、细节和模型的适配性上。如果你手头拿到的是别人整理好的源码资源包记住一定要理解每个模块在干什么再动手训练千万别直接一键跑完就以为万事大吉。改一改数据路径、调一调类别数、看一看Loss曲线这些基本功迟早会通过真实项目把你训练出来。本文还有配套的精品资源点击获取
返回列表