☰
Python+TensorFlow花卉识别实战:从CNN模型到迁移学习完整方案
2026/10/2 2:55:52 网站建设 项目流程

简介:这份资源面向计算机视觉课程设计、期末大作业与入门深度学习的学习者,提供一套基于TensorFlow与CNN的花卉图像识别完整方案,帮助解决从数据读取、模型搭建到训练测试的全流程实现问题。压缩包共13个文件,约10.82MB,以6个Python脚本为核心,覆盖模型定义、训练、测试与图形界面等模块,另含docx实验报告、pptx汇报材料、yaml与txt环境配置、md说明文档及rar数据压缩包,便于快速复现实验环境。目前已有615人学习下载,具备一定参考热度。代码附带注释,新手也能理解网络结构与训练逻辑,实验报告与演示文稿可直接用于课程答辩,环境配置文件有助于减少依赖安装与版本排错成本,适合作为课程设计或大作业的参考模板。

1. 花卉识别大作业:从数据集到 CNN 模型,一套能跑通的 Python 方案

很多计算机视觉大作业的题目看起来简单,真动手才发现坑全在细节里。花卉图像识别就是一个典型:网上能找到的代码不少,但要么数据集路径写死、要么版本对不上、要么训练完准确率上不去。这篇笔记围绕「Python + TensorFlow + CNN 做花卉图像识别」这条主线,把数据准备、模型搭建、训练调参、结果分析整条链路拆开讲清楚。适合正在做计算机视觉大作业、想用 TensorFlow 跑通一个完整 CNN 项目的人,也适合刚学完 CNN 基础、需要一个能复现的练手项目的人。读完之后你应该能自己搭出一套可训练、可评估、可写进实验报告的花卉识别流程,而不是只会复制一段跑不起来的代码。

2. 数据集与运行环境:先把地基打牢再谈模型

2.1 花卉数据集怎么选、怎么划分

花卉识别常用的公开数据集是 Oxford Flowers 系列,常见的有 17 类版本和 102 类版本。17 类版本每类 80 张左右,总量约 1360 张,适合大作业这种算力有限、时间有限的场景;102 类版本总量八千多张,类别更细,训练难度明显上升。如果你只是要交一份完整的大作业,17 类版本足够体现 CNN 的完整流程,102 类版本可以作为进阶对比实验。

数据集到手后,目录结构一般是按类别分文件夹,每个文件夹里是对应类别的图片。这种结构对tf.keras.utils.image_dataset_from_directory非常友好,直接按文件夹名生成标签。但要注意两点:一是图片尺寸不统一,二是类别样本数可能不均衡。前者靠统一 resize 解决,后者需要在划分时做分层抽样,否则某类样本太少会导致模型对这类几乎学不到东西。

划分比例上,我一般用 70% 训练、15% 验证、15% 测试。验证集用来调超参和早停,测试集只在最后评估一次,不要拿测试集反复调参,否则实验报告里的准确率就是自欺欺人。

2.2 TensorFlow 环境安装与版本确认

环境这块翻车最多。TensorFlow 2.x 和 1.x 的 API 差异巨大,网上很多老代码用的是tf.Session,在 2.x 里根本跑不了。所以第一步是确认版本,并且统一用 Keras 高层 API。

# 建议用虚拟环境隔离,避免和系统里的包打架 python -m venv flower_env source flower_env/bin/activate # Windows 用 flower_env\Scripts\activate # 安装 TensorFlow,CPU 版本足够跑花卉识别 pip install tensorflow==2.15.0 pip install numpy matplotlib pillow scikit-learn

安装完成后验证一下:

import tensorflow as tf print(tf.__version__) # 应输出 2.15.0 print(tf.config.list_physical_devices('GPU')) # 没有 GPU 会返回空列表

逻辑说明:虚拟环境是为了让这个项目的依赖独立,避免污染其他项目。TensorFlow 2.15 对 Python 3.9 到 3.11 支持较好,如果你本地 Python 版本太新或太旧,pip 可能找不到对应 wheel。参数上,tensorflow==2.15.0是明确锁版本,大作业场景不建议追最新版,稳定比新特性重要。如果你有 NVIDIA 显卡并且装好了 CUDA,可以换tensorflow[and-cuda],但花卉识别数据量不大,CPU 训练也就几分钟一轮,不必强求 GPU。

提示:如果安装时报Could not find a version,先确认 Python 版本,再确认 pip 是否升级到最新。国内网络下载慢可以换镜像源,但不要用来源不明的第三方包。

2.3 用 image_dataset_from_directory 加载数据

不推荐自己写 ImageDataGenerator 的复杂配置,Keras 现在推荐用image_dataset_from_directory,代码短、出错少。

import tensorflow as tf IMG_SIZE = (224, 224) BATCH_SIZE = 32 DATA_DIR = "flowers" # 根目录下每个子文件夹是一个类别 train_ds = tf.keras.utils.image_dataset_from_directory( DATA_DIR, validation_split=0.3, # 先切出 30% 做验证+测试 subset="training", seed=123, image_size=IMG_SIZE, batch_size=BATCH_SIZE, label_mode="int" ) val_test_ds = tf.keras.utils.image_dataset_from_directory( DATA_DIR, validation_split=0.3, subset="validation", seed=123, image_size=IMG_SIZE, batch_size=BATCH_SIZE, label_mode="int" ) # 从 val_test 里再切一半做测试 val_batches = tf.data.experimental.cardinality(val_test_ds) test_ds = val_test_ds.take(val_batches // 2) val_ds = val_test_ds.skip(val_batches // 2)

逻辑说明:validation_split=0.3先把数据分成训练和验证+测试两部分,seed保证两次调用划分一致。label_mode="int"表示标签是整数,配合sparse_categorical_crossentropy损失函数使用。参数上,image_size设成 224×224 是因为后面用的 CNN 结构对输入尺寸不敏感,但统一尺寸是必须的;batch_size=32是常见起点,显存或内存不够就降到 16。cardinality拿到验证+测试的批次数,再对半切,这样测试集不会参与任何训练决策。

3. CNN 模型搭建:从卷积层到分类头怎么配

3.1 卷积、池化、全连接各自在干什么

CNN 的基本结构是卷积层、池化层、全连接层交替堆叠。卷积层用卷积核在图像上滑动,提取局部特征,浅层学到边缘和颜色,深层学到花瓣形状、花蕊纹理这类更抽象的模式。池化层做下采样,把特征图尺寸缩小,减少参数量同时保留主要信息。全连接层把前面提取的特征图拉平,映射到类别数上做分类。

这里有个容易混淆的点:卷积层输出的是多通道特征图,不是一维向量,所以进全连接之前必须 Flatten。另外,卷积核数量、卷积核大小、池化窗口大小都是超参,不是越大越好。卷积核太多会过拟合,太少学不到东西;池化窗口太大丢信息,太小起不到降维作用。花卉识别这种中等复杂度任务,三到四个卷积块通常够用。

3.2 搭一个可训练的基础 CNN

下面是一个结构清晰、参数量适中的 CNN,适合花卉识别大作业。

from tensorflow.keras import layers, models def build_cnn(num_classes): model = models.Sequential([ # 第一个卷积块 layers.Conv2D(32, (3, 3), activation='relu', input_shape=(224, 224, 3)), layers.MaxPooling2D((2, 2)), # 第二个卷积块 layers.Conv2D(64, (3, 3), activation='relu'), layers.MaxPooling2D((2, 2)), # 第三个卷积块 layers.Conv2D(128, (3, 3), activation='relu'), layers.MaxPooling2D((2, 2)), # 分类头 layers.Flatten(), layers.Dense(128, activation='relu'), layers.Dropout(0.5), layers.Dense(num_classes, activation='softmax') ]) return model model = build_cnn(num_classes=17) model.summary()

逻辑说明:三个卷积块,卷积核数量从 32 到 64 到 128 递增,这是常见做法,浅层特征少用少量核,深层特征复杂用更多核。每个卷积后接最大池化,特征图尺寸从 224 逐步降到 28。Flatten把 28×28×128 拉成向量,接一个 128 维全连接层,Dropout(0.5)随机丢弃一半神经元,抑制过拟合。最后一层神经元数等于类别数,softmax输出每类的概率。

参数说明:input_shape=(224, 224, 3)要和前面数据加载的尺寸一致,不一致会直接报错。Dropout(0.5)是经验值,过拟合严重可以加到 0.6,欠拟合就降到 0.3。Dense(128)这个中间层维度可以调,但不要设得比类别数还小。

3.3 编译、回调与训练配置

模型搭好只是开始,编译时的损失函数、优化器、学习率,以及训练时的回调,直接决定能不能收敛。

model.compile( optimizer=tf.keras.optimizers.Adam(learning_rate=1e-3), loss='sparse_categorical_crossentropy', metrics=['accuracy'] ) callbacks = [ tf.keras.callbacks.EarlyStopping( monitor='val_loss', patience=5, restore_best_weights=True), tf.keras.callbacks.ReduceLROnPlateau( monitor='val_loss', factor=0.5, patience=3, min_lr=1e-6) ] history = model.fit( train_ds, validation_data=val_ds, epochs=50, callbacks=callbacks )

逻辑说明:sparse_categorical_crossentropy对应整数标签,如果标签是 one-hot 就要换成categorical_crossentropy,这是高频错误。Adam学习率设 1e-3 是默认起点,训练不动就降到 1e-4。EarlyStopping监控验证损失,连续 5 轮不下降就停并恢复最优权重,避免白跑。ReduceLROnPlateau在验证损失停滞时把学习率减半,帮助跳出局部最优。

参数说明:patience=5和patience=3不是固定值,数据噪声大可以适当加大。epochs=50配合早停,实际可能二三十轮就停了。min_lr=1e-6是学习率下限,防止降到几乎为零。

4. 训练过程排查:损失不降、准确率上不去怎么办

4.1 先看数据管道有没有问题

模型不收敛,第一嫌疑是数据。常见现象是训练准确率一直卡在随机水平,比如 17 类任务卡在 6% 左右。原因通常是标签和图片没对上,或者图片根本没加载成功。解决办法是手动取一个 batch 出来看。

import matplotlib.pyplot as plt for images, labels in train_ds.take(1): print("batch shape:", images.shape) print("label sample:", labels[:8].numpy()) plt.figure(figsize=(10, 4)) for i in range(6): plt.subplot(2, 3, i+1) plt.imshow(images[i].numpy().astype("uint8")) plt.title(f"label={labels[i].numpy()}") plt.axis("off") plt.show()

逻辑说明:打印 batch 形状确认是(32, 224, 224, 3),打印标签确认不是全零或越界。可视化几张图,肉眼确认图片内容和标签对得上。如果图片显示全黑或全白,说明归一化或读取环节有问题。

4.2 过拟合与欠拟合的区分处理

训练准确率高、验证准确率低,是过拟合;两者都低,是欠拟合。花卉识别数据量小,过拟合很常见。处理手段按优先级:先加数据增强,再加 Dropout,再考虑减小模型。数据增强用 Keras 的预处理层,直接嵌进模型里。

data_augmentation = tf.keras.Sequential([ layers.RandomFlip("horizontal"), layers.RandomRotation(0.1), layers.RandomZoom(0.1), ]) # 在 build_cnn 的输入后插入 model = models.Sequential([ layers.Input(shape=(224, 224, 3)), data_augmentation, layers.Rescaling(1./255), # ... 后续卷积层 ])

逻辑说明:RandomFlip、RandomRotation、RandomZoom在训练时随机变换图片,让模型见到更多样的样本,验证和测试时不生效。Rescaling(1./255)把像素值从 0-255 缩到 0-1,加速收敛。注意增强层只放在训练流程里,Keras 会自动处理训练和推理的差异。

参数说明:RandomRotation(0.1)表示旋转范围是 ±10%,花卉图片旋转太多会失真。RandomZoom(0.1)同理。增强不是越多越好,过度增强会让模型学不到真实特征。

4.3 学习率和 batch size 的联动调整

学习率太大,损失震荡不下降;太小,收敛慢甚至卡住。batch size 影响梯度估计的稳定性,太小梯度噪声大,太大泛化可能变差。经验上,batch size 翻倍,学习率可以适当放大,但不要机械套公式。花卉识别这种任务,batch size 32 配 1e-3 学习率是稳妥起点,跑几轮看损失曲线再微调。

如果损失曲线前期下降后期震荡,多半是学习率偏大,用ReduceLROnPlateau或手动降。如果损失几乎不动,先检查数据归一化和标签,再考虑换优化器或调结构。

5. 避坑与常见问题:这些错我基本都犯过

5.1 图片尺寸不一致导致训练直接报错

现象:image_dataset_from_directory能加载,但模型fit时报形状不匹配。原因:数据加载时设了image_size,但模型input_shape写的是另一个尺寸。解决:把两处尺寸统一,建议都写 224×224,并且用变量而不是硬编码两遍。

5.2 标签编码和损失函数不匹配

现象:训练时损失是nan或准确率始终为 0。原因:标签是整数却用了categorical_crossentropy,或者标签是 one-hot 却用了sparse_categorical_crossentropy。解决:整数标签配 sparse,one-hot 配 categorical,二选一,不要混。

5.3 忘记归一化导致收敛极慢

现象:损失能降但非常慢,几十轮还在高位。原因:像素值 0-255 直接进网络,梯度尺度太大。解决:在模型最前面加Rescaling(1./255),或者加载数据时手动除以 255。这一步不做,后面调参都是白费。

5.4 验证集和测试集混用

现象:实验报告里准确率很高,但换一批图片就崩。原因:反复用测试集调参,模型间接见过测试数据。解决:测试集只在最后评估一次,调参只看验证集。这是写实验报告的基本诚信。

5.5 保存模型时只存权重不存结构

现象:换台机器加载模型报错,或者加载后结构对不上。原因:只用了model.save_weights,没有保存完整模型。解决:用model.save("flower_cnn.keras")保存完整模型,加载时tf.keras.models.load_model直接还原结构和权重。

6. 进阶技巧:用迁移学习把准确率再拉一截

基础 CNN 在 17 类花卉上通常能到 70% 到 85%,想再往上走,迁移学习是最划算的手段。拿一个在 ImageNet 上预训练好的网络,比如 MobileNetV2 或 EfficientNetB0,去掉顶层分类头,换成自己的类别数,先冻结主干训练分类头,再解冻部分层做微调。

base_model = tf.keras.applications.MobileNetV2( input_shape=(224, 224, 3), include_top=False, weights='imagenet' ) base_model.trainable = False # 先冻结 model = models.Sequential([ base_model, layers.GlobalAveragePooling2D(), layers.Dropout(0.3), layers.Dense(17, activation='softmax') ]) model.compile( optimizer=tf.keras.optimizers.Adam(1e-3), loss='sparse_categorical_crossentropy', metrics=['accuracy'] ) model.fit(train_ds, validation_data=val_ds, epochs=10, callbacks=callbacks) # 解冻最后若干层做微调 base_model.trainable = True for layer in base_model.layers[:-30]: layer.trainable = False model.compile( optimizer=tf.keras.optimizers.Adam(1e-5), # 微调学习率要小 loss='sparse_categorical_crossentropy', metrics=['accuracy'] ) model.fit(train_ds, validation_data=val_ds, epochs=10, callbacks=callbacks)

逻辑说明:include_top=False去掉原网络的分类层,weights='imagenet'加载预训练权重。先冻结主干只训练新加的分类头,让分类头适应花卉特征;再解冻最后 30 层做微调,学习率降到 1e-5,避免把预训练权重冲掉。GlobalAveragePooling2D替代 Flatten,参数量更少,过拟合风险更低。

参数说明:解冻层数不是固定的,数据量小就少解冻,数据量大可以多解冻。微调学习率一定要比初始训练小一到两个数量级。Dropout(0.3)比基础 CNN 里的 0.5 小,因为迁移学习本身泛化更好。

验证方法上,除了看测试集准确率,建议画混淆矩阵,看看哪些类别容易混。花卉里形状相近的类别,比如不同品种的玫瑰,混淆是正常的,可以在实验报告里分析原因,这比只报一个准确率数字更有说服力。

我自己做这类大作业的习惯是:先把基础 CNN 跑通并记录每一组超参的结果,再上迁移学习做对比,最后把混淆矩阵和错误样本截图放进报告。这样一份实验报告既有完整流程,又有分析和改进,比单纯贴代码强得多。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询