ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于卷积神经网络的花卉图像识别:从数据预处理到迁移学习实战

基于卷积神经网络的花卉图像识别:从数据预处理到迁移学习实战 简介基于卷积神经网络的花卉图像识别项目文件面向毕业设计、课程设计及期末大作业场景适合深度学习初学者完成图像分类任务可用于解决花卉种类繁多、形态差异大等识别难题。资源包共10个文件包含4个Python脚本主程序、模型定义、训练、评估、2个编译后的pyc文件、1个文本依赖说明、1个类别映射JSON、1个说明文档等压缩包大小仅14KB结构轻量。目前已有45人学习或下载。借助这套代码可以掌握卷积神经网络在花卉识别中的完整流程包括数据加载、模型搭建、训练与评估等环节代码模块划分清晰便于读者二次开发可在此基础上扩展数据增强、迁移学习等策略快速搭建一个可用的图像识别系统同时理解模型压缩与部署思路满足课程设计或毕业设计的实际需求。1. 基于卷积神经网络的花卉图像识别这份资源到底能做什么做毕业设计或者课程大作业时最怕的不是模型跑不通而是拿到一份代码却不知道它为什么这么写、参数为什么这么设、换了自己的数据集该怎么改。这份基于卷积神经网络的花卉图像识别压缩包解决的就是这个诉求它不是一份只讲原理的PPT也不是一个黑匣子式的现成模型而是一套从数据预处理、CNN模型搭建到训练、评估、预测的完整可运行代码覆盖了图像识别方向毕业设计最常见的全部环节。适合三类人正在做深度学习图像识别方向毕业设计的本科生、需要短期完成课程大作业的在读学生以及想快速跑通一个CNN baseline再往上加改进点的初学者。解压后你拿到的是一条能跑通的主流程而不是一堆散乱的脚本。2. 数据集与预处理先把图片变成模型能吃的张量2.1 花卉数据集的两种常见组织方式这份资源里的数据集遵循了PyTorch和TensorFlow通用的目录约定根目录下按类别建文件夹每个文件夹里放该类别的所有图片。以Flower102数据集为例典型的目录结构是flower_data/train/下放daisy、dandelion、rose、sunflower、tulip五个子文件夹每个子文件夹里是几十到几百张不等、尺寸各异的jpg图片。这种结构的好处是torchvision.datasets.ImageFolder可以直接读取它会自动把子文件夹名映射成类别索引省去手写标签映射表的麻烦。如果你拿到的是Oxford 102 Flower数据集原始下载包里是jpg/和labels.txt的平铺结构那就需要先做一个转换把平铺的图片按labels.txt里的类别编号挪进对应文件夹。常见做法是写个脚本读labels.txt提取每张图片的文件名和标签然后shutil.copy到对应类目目录。这个步骤很机械但值得认真做因为后续所有代码都依赖目录结构。2.2 数据划分别再随机打乱后直接丢给训练集很多初学者的翻车点在于把训练集和验证集混在一起划分或者划分后不固定随机种子。这里我一般会先用脚本把数据集划分成训练集和验证集比例控制在 8:2 左右而且要保证每个类别的样本都按同一比例划分避免某个类别在验证集里一个样本都没有。下面是一个可用的划分脚本import os import random import shutil random.seed(42) data_root flower_data target_root flower_data_split # 先按类别遍历原始数据 for class_name in os.listdir(data_root): class_path os.path.join(data_root, 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_images images[:split_idx] val_images images[split_idx:] # 在目标目录下创建 train/val 结构 for split, split_images in [(train, train_images), (val, val_images)]: dest_dir os.path.join(target_root, split, class_name) os.makedirs(dest_dir, exist_okTrue) for img_name in split_images: src os.path.join(class_path, img_name) dst os.path.join(dest_dir, img_name) shutil.copy(src, dst) print(划分完成)这段代码的关键参数是split_idx int(len(images) * 0.8)也就是每个类别单独按 80% 训练、20% 验证的比例切分。random.seed(42)的作用是让每次运行结果一致方便复现实验。shutil.copy保留原始图片文件不做压缩或重编码避免引入额外信息损失。运行后在flower_data_split下生成train/和val/两个根目录每个根目录下依然按类别分子文件夹。2.3 数据增强与归一化参数的选择逻辑图像识别任务的预处理不能只有Resize和ToTensor至少应该加上随机水平翻转和归一化。以PyTorch为例from torchvision import transforms train_transforms transforms.Compose([ transforms.RandomResizedCrop(224), transforms.RandomHorizontalFlip(), 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]) ])RandomResizedCrop的作用是从图片中随机裁剪一块区域并缩放到 224x224这项操作等价于随机的尺度扰动和目标位置扰动能明显提升模型对花卉大小、位置变化的鲁棒性训练效率比固定Resize(224)高不少。RandomHorizontalFlip做水平翻转增强。Normalize的 mean 和 std 用的是 ImageNet 统计值这是约定俗成的做法原因很简单如果后续要加载预训练权重比如 ResNet、VGG输入分布必须和预训练时保持一致否则首层输出分布直接偏掉。注意验证集只做Resize(256) CenterCrop(224)不做随机裁剪和翻转这是为了保证验证指标的稳定性和可复现性。很多新手会在验证集上也用RandomResizedCrop这会让每次评估结果抖动复现实验时说不清楚涨点到底是模型改进带来的还是随机性带来的。3. CNN模型搭建与训练从LeNet-5改到适合花卉任务的深度3.1 模型选型为什么这份资源里用卷积层堆叠而不是直接上ResNet花卉图像识别属于细粒度图像分类的入门场景类别之间有视觉差异但不像鸟类、车型那样需要极端精细的判别特征所以一个中等深度的CNN完全够用。这份资源里的基础模型走的是经典LeNet-5改良路线核心结构是两轮“卷积池化激活”后接三层全连接。它的优势有两点第一网络规模小CPU上也能在几十分钟内完成训练对期末大作业来说不需要抢GPU第二结构直观每一层在做什么能画出来、能算清楚写进毕业论文里好解释。import torch.nn as nn class FlowerCNN(nn.Module): def __init__(self, num_classes5): super().__init__() self.features nn.Sequential( nn.Conv2d(3, 32, kernel_size3, padding1), nn.ReLU(inplaceTrue), nn.MaxPool2d(2, 2), nn.Conv2d(32, 64, kernel_size3, padding1), nn.ReLU(inplaceTrue), nn.MaxPool2d(2, 2), nn.Conv2d(64, 128, kernel_size3, padding1), nn.ReLU(inplaceTrue), nn.MaxPool2d(2, 2), ) self.classifier nn.Sequential( nn.Flatten(), nn.Linear(128 * 28 * 28, 256), nn.ReLU(inplaceTrue), nn.Dropout(0.5), nn.Linear(256, num_classes) ) def forward(self, x): x self.features(x) x self.classifier(x) return x输入 224x224 的图片经过三次MaxPool2d后特征图边长变成 28x28所以全连接层输入维度是128 * 28 * 28。如果你把输入尺寸改成 128 或其他值这里的维度要同步手算。Dropout(0.5)只在训练阶段生效PyTorch 中model.eval()会自动关闭它。参数kernel_size3, padding1保证了卷积不改变特征图尺寸设计卷积层堆叠时这是最省心的设置。3.2 训练脚本的参数配置与执行流程训练环节的参数设置会直接影响模型最终效果。这份资源默认配置中批大小是 32初始学习率是 0.001优化器用的是 Adam损失函数用交叉熵。交叉熵对应多分类任务的标准损失配合最后一层不接 Softmax——nn.CrossEntropyLoss内部已经做了 Softmax 和 LogSoftmax 的合并计算直接吃模型的 raw logits 就行。import torch.optim as optim from torch.utils.data import DataLoader, random_split from torchvision.datasets import ImageFolder train_dataset ImageFolder(flower_data_split/train, transformtrain_transforms) val_dataset ImageFolder(flower_data_split/val, transformval_transforms) train_loader DataLoader(train_dataset, batch_size32, shuffleTrue, num_workers2) val_loader DataLoader(val_dataset, batch_size32, shuffleFalse, num_workers2) model FlowerCNN(num_classeslen(train_dataset.classes)) criterion nn.CrossEntropyLoss() optimizer optim.Adam(model.parameters(), lr0.001) for epoch in range(20): model.train() running_loss 0.0 for inputs, labels in train_loader: optimizer.zero_grad() outputs model(inputs) loss criterion(outputs, labels) loss.backward() optimizer.step() running_loss loss.item() model.eval() correct, total 0, 0 with torch.no_grad(): for inputs, labels in val_loader: outputs model(inputs) _, predicted torch.max(outputs, 1) total labels.size(0) correct (predicted labels).sum().item() acc correct / total print(fEpoch {epoch1}/20, Loss: {running_loss/len(train_loader):.4f}, Val Acc: {acc:.4f})ImageFolder会自动从子文件夹目录生成标签映射model.train()和model.eval()的切换不能省前者保证 Dropout 生效后者保证 BatchNorm 的统计量使用训练集累积值。验证阶段用torch.no_grad()包裹关闭梯度计算能显著减少内存占用。num_workers2在本机 CPU 环境下一般够了数值设置过大会在 Windows 下触发 DataLoader 的 spawn 报错。3.3 训练中的观察指标不能只盯着训练损失训练集损失下降但验证集精度不涨是过拟合的典型信号这时应该优先调 Dropout 比例、加数据增强而不是盲目增加网络深度。验证集精度逐步上升但训练集损失还在下降是正常现象。每轮打印Val Acc的意义就在这里它决定了你该在哪个 epoch 保存模型。这份资源里通常建议在验证集精度最高时保存模型而不是最后一轮否则后期轻微过拟合会污染最终结果。4. 模型评估与单张图片预测验证集精度不等于能交差4.1 加载保存的权重文件并复现评估结果训练完成后会得到一个.pth权重文件。这里有个新手常犯的错误加载权重时直接把torch.load的结果丢给model结果报出模型结构不匹配的错。正确做法是先用FlowerCNN(num_classes5)实例化模型再load_state_dict加载键值对权重。checkpoint torch.load(best_model.pth) model FlowerCNN(num_classes5) model.load_state_dict(checkpoint[state_dict] if state_dict in checkpoint else checkpoint) model.eval()这份资源的保存形式以可移植性为主通常只存模型权重不存优化器状态。如果你的实验需要断点续训光存state_dict不够还要把optimizer.state_dict()和当前 epoch 一起存进 checkpoint 字典恢复时分别load_state_dict回去。4.2 单张预测的完整实现评估精度只能证明模型在统计意义上是好的交作业或实际演示时你需要的是“拿一张新图跑出类别和置信度”的代码。预测前要特别注意单张图片的预处理必须走val_transforms路径不能走train_transforms否则随机裁剪会破坏图片内容。from PIL import Image import torchvision.transforms as transforms def predict_image(model, image_path, class_names): img Image.open(image_path).convert(RGB) img val_transforms(img).unsqueeze(0) # 增加 batch 维度 with torch.no_grad(): outputs model(img) probs torch.softmax(outputs, dim1) confidence, pred_idx torch.max(probs, 1) pred_idx pred_idx.item() return class_names[pred_idx], confidence.item() class_names train_dataset.classes print(predict_image(model, test_rose.jpg, class_names))unsqueeze(0)把 3x224x224 的张量变成 1x3x224x224匹配训练时的输入形状。torch.softmax(outputs, dim1)把网络最后一层的 logits 转成和为 1 的概率分布torch.max(probs, 1)同时拿回最大概率值和对应下标。这一步需要强调如果直接对 logits 取 max得到的只是数值最大的得分不是概率写报告时展示置信度会失真。4.3 混淆矩阵看清模型到底混淆了哪两类花准确率之外混淆矩阵是最值得补充的评估手段。比如模型把雏菊误判为蒲公英单看准确率看不出来混淆矩阵可以明确展示。计算方式不复杂遍历验证集记录每个真实标签对应的预测标签填充一个num_classes x num_classes的矩阵用seaborn画热力图保存下来作为论文或报告的附件。5. 避坑与常见问题从解压到训练完成最容易翻车的五个地方5.1 文件解压后路径带空格导致数据集读取失败现象解压后运行训练脚本报错FileNotFoundError或Dataset not found。原因压缩包解压到路径含中文或空格的目录如C:\Users\Desktop\New Folder\flower_data部分环境下的ImageFolder枚举路径时对空格敏感。解决把整个工程目录移动到纯英文路径下比如D:\flower_cnn\并确保flower_data_split和训练脚本在同一级目录。路径问题影响的是整个工程优先处理。5.2 num_workers 设置过大导致 DataLoader 崩溃现象Windows 下运行训练时报BrokenPipeError或程序直接闪退。原因num_workers值设置过大时比如 8 或 16Windows 的进程创建机制与 Linux 不同容易触发数据加载子进程冲突。解决Windows 环境把num_workers设为 0 或 2。如果一定要开多进程把训练脚本放进if __name__ __main__:块中再运行这是 PyTorch 在 Windows 下的硬性要求。5.3 输入尺寸与全连接层维度不匹配现象模型定义没问题但训练时在Flatten之后报mat1 and mat2 shapes cannot be multiplied。原因你自己改了输入尺寸比如从 224 改成 128但nn.Linear的第一维128 * 28 * 28是按 224 输入手算出来的实际特征图尺寸对不上。解决先算清楚。224 经过 3 次池化是 28128 经过 3 次池化是 16。把全连接层改成nn.Linear(128 * 16 * 16, 256)。更稳妥的做法是在__init__里保留输入尺寸参数前向时动态计算展平后维度。5.4 验证集准确率在训练过程中剧烈抖动现象每轮验证集准确率忽高忽低上下浮动超过 10 个百分点。原因验证集样本量偏小尤其是花卉数据集某些类别测试图片只有 5~10 张单张预测错误就会导致 1% 到 2% 的波动。解决第一确认划分时每类按比例切分而不是全局随机切分第二验证集不做随机增强第三样本确实少时用 K 折交叉验证评估而不是单次划分。5.5 加载预训练权重时报 size mismatch 错误现象尝试加载官方 ResNet 预训练权重时报size mismatch for fc.weight。原因预训练模型在 ImageNet 上训练最后一层全连接输出是 1000 类你改成了 5 类形状对不上。解决只加载特征提取部分的权重或者加载后再替换全连接层import torchvision.models as models model models.resnet18(weightsmodels.ResNet18_Weights.DEFAULT) model.fc nn.Linear(model.fc.in_features, 5)6. 把效果再往上推一档数据增强增强与迁移学习的实战组合如果你不满足于基础 CNN 在验证集上 85% 上下的准确率想把结果做到 90% 以上最常见的做法是两条路同时走强化数据增强和改用迁移学习。TensorFlow 和 PyTorch 生态都内置了成熟的预训练模型花卉数据集和 ImageNet 的分布差异不算大迁移学习的效果通常非常显著。这里给出一个经过验证的组合策略。先强化数据增强。之前的基础增强只用了随机裁剪和水平翻转对花卉图来说还可以加上随机旋转和颜色抖动。其中颜色抖动是很多入门者会忽略的一点不同光照条件下拍同一朵花色调差异很大模型如果对颜色分布过于敏感很容易出现“换个光线就认错”的问题。transforms.ColorJitter(brightness0.3, contrast0.3, saturation0.3)可以缓解这一点。随机旋转的角度建议限制在 30 度以内旋转过大会把花蕊和花瓣的相对位置关系扭曲反而引入噪声。然后是迁移学习。用 PyTorch 的torchvision.models加载在 ImageNet 上预训练好的 ResNet18把最后一层全连接替换成自己的类别数。训练策略上先冻结主干参数只训练新加的全连接层用学习率 0.001 训练 10 轮左右然后解冻主干的后几层学习率降到 0.0001再训练 10 轮。两个阶段分步走的原因是直接解冻全部参数用大学习率微调容易破坏预训练提取好的底层特征导致训练初期损失震荡。冻结阶段相当于在预训练特征上训练一个线性分类器解冻后微调阶段再用小学习率适配花卉数据的特有分布。实际效果上在 Flower102 上跑过同一份基础 CNN和改进组合的对比基础 CNN 验证集准确率大概 82%~86%迁移学习方案通常能到 92%~96%提升幅度非常直观。之所以用 ResNet18 而不是 ResNet50是因为花卉分类的类间差异没那么细微ResNet18 的容量已经足够训练速度快在 CPU 上也能在两小时内跑完全部微调流程。如果你的数据集只有几百张图ResNet18 比 ResNet50 更不容易过拟合。我自己每次做这类图像分类项目最后一定会强制走一遍评估流程用训练好的模型跑一遍全部验证集图片输出每个类别的精确率和召回率再人工抽查 20 张预测对的图和 20 张预测错的图。预测对的抽查是为了确认模型学到的特征有实际意义预测错的抽查则是为了发现数据标注误差——比如雏菊和蒲公英在数据集里被标反了几张图模型的混淆矩阵会在对应位置出现异常的集中错误这个时候问题不在模型而在数据质量。迭代实验前先排除这类数据问题否则调参调得再用心效果也上不去。这份资源把从数据到训练的完整链路已经铺好了剩下的就是你自己动手跑一遍在错误中建立对模型行为的直觉。希望帮到你。本文还有配套的精品资源点击获取
返回列表