news 2026/9/11 22:59:00

DenseNet201迁移学习花朵识别:数据预处理到模型微调实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
DenseNet201迁移学习花朵识别:数据预处理到模型微调实战

简介:这是一份基于深度学习TensorFlow框架的花朵识别项目源码包,面向需要完成毕业设计、课程设计或期末大作业的在校学生,也适合入门图像分类的开发者。项目采用DenseNet201预训练模型,实现从数据预处理、模型训练到花朵分类识别的完整流程。压缩包共238个文件,包括196张花朵样本图片(jpeg/jpg)用于训练与测试、19个Python源码文件负责模型构建与预测、DenseNet201.h5预训练权重文件、flower_info.db花名数据库,以及XML配置、PNG图像等辅助文件,整体大小约68.89MB,目录结构清晰,便于按模块查看与二次开发。代码经过完整测试,下载后无需修改即可直接运行,读者可快速跑通“数据加载—模型训练—花朵分类”全流程,也可在此基础上调整网络参数或替换数据集进行功能扩展,能有效帮助理解深度学习图像分类项目的工程化组织方式。目前已有1811人学习下载,适合作为高完成度的课程项目参考。

1. 使用 DenseNet201 做花朵识别,为什么迁移学习起步就能跑出效果

拿到这个项目压缩包时,第一反应不是去读代码,而是先看有没有模型文件。看到DenseNet201.h5就知道,作者已经把最耗时间的事情做完了。花朵识别属于细粒度图像分类,不同种类的花瓣颜色、纹理、花蕊形态差异非常小,靠几张图从头训练一个深度卷积网络,几乎必然过拟合。而 DenseNet201 在 ImageNet 上预训练过,前几层已经学会了边缘、纹理、形状等通用视觉特征,只需要把最后分类头换掉,再在小规模花卉数据上微调,就能取得很稳的效果。这也是 TensorFlow 生态里做毕业设计、课程作业最省力的路线。后面几章我会从项目文件拆起,讲清楚数据怎么从数据库读出、DenseNet201 怎么微调、训练参数怎么定,以及模型训练完以后怎么验证、怎么复用。

2. 项目结构与数据准备:flower_info.db 的标签映射与图像解码

2.1 压缩包里每个文件是干什么的

解开python实现基于深度学习TensorFlow框架的花朵识别项目源码.zip之后,能看到一个混合了.h5.db.iml和若干.jpeg的目录。很多人看到.iml会疑惑,这里直接说清楚:.iml是 PyCharm 的模块描述文件,保留它只是方便在 IDE 里直接打开项目,对运行没有任何影响。

文件作用
DenseNet201.h5训练好的模型权重,可能包含完整网络结构,也可能是纯权重
flower_info.dbSQLite 数据库,存放花朵类别 ID 与类别名称的映射关系
xxx.jpeg(多个)样例图片,用于快速验证预测效果
flower_info.imlPyCharm 项目模块信息,IDE 使用
.gitignoreGit 忽略规则,与本机运行无关

flower_info.db是容易被忽略但很关键的文件。模型输出的只是类别序号,比如012,没有名称,想要在屏幕上显示“玫瑰”“向日葵”或者项目报告里要求的自定义类别名,必须从数据库里反查。我一般拿到项目会先用 Python 打开它,确认表结构和字段名,避免后面写死字段导致查询报错。

2.2 读取 flower_info.db 构建类别映射

先写一个最小脚本,看看库里到底有什么:

import sqlite3 conn = sqlite3.connect("flower_info.db") cur = conn.cursor() # 列出所有表 cur.execute("SELECT name FROM sqlite_master WHERE type='table';") tables = cur.fetchall() print("tables:", tables) # 查看每张表的字段 for t in tables: cur.execute(f"PRAGMA table_info({t[0]});") print(t[0], cur.fetchall()) conn.close()

这段代码里我用sqlite_master查询表名,再用PRAGMA table_info拿字段定义。常见情况下,花朵分类表结构是flower_classes(id, name),如果项目改动过也可能叫class_names。字段可能不同,所以先探一下再往下写。

拿到结构后,构建一个 Python 字典,后面预测时把模型输出的整数索引转成可读名称:

import sqlite3 def load_class_map(db_path="flower_info.db"): """读取类别表,返回 {id: name} 字典,id 必须与模型训练时的类别序号一致。""" conn = sqlite3.connect(db_path) cur = conn.cursor() # 以最常规的 flower_classes(id, name) 为例 cur.execute("SELECT id, name FROM flower_classes ORDER BY id") class_map = {int(row[0]): row[1] for row in cur.fetchall()} conn.close() return class_map if __name__ == "__main__": print(load_class_map())

id转成int是因为 SQLite 返回的行数据里字段类型可能是字符串,而 Keras 预测结果的下标是整数,不转换会匹配不上。项目里如果字段名不同,比如是class_id, class_name,把SELECT部分换掉即可。这一步是数据与模型之间的桥,漏了它,后面打印出来的就是一串数字,报告没法写。

2.3 图像解码:load_img 与数据流水线

花朵图片来自真实拍摄,尺寸不一,DenseNet201 的输入要求是(224, 224, 3)。常见做法是直接用tf.keras.utils.load_img做加载和缩放,因为它内部用 PIL,返回后还能方便地转成 numpy 数组:

from tensorflow.keras.utils import load_img, img_to_array img = load_img("a668e078bd0892334c0a3767cb8f5cdc.jpeg", target_size=(224, 224)) x = img_to_array(img) # 缩放至 [0,1] x = x / 255.0 print(x.shape)

target_size=(224, 224)会强制把图片等比拉伸到目标尺寸,虽然花朵有时会变形,但单目标分类任务里影响很小。img_to_array得到的是(224, 224, 3)的 float 数组,除以 255 是为了匹配预训练模型的输入分布。DenseNet201 预训练时数据本来就是[0,1]范围,不做这一步,输出结果基本是错的。

如果训练数据量较大,我会改用tf.keras.preprocessing.image.ImageDataGenerator或者tf.data.Dataset做批量加载。前者写起来简单,适合几百张图的课程设计:

from tensorflow.keras.preprocessing.image import ImageDataGenerator datagen = ImageDataGenerator(rescale=1./255, validation_split=0.2) train_gen = datagen.flow_from_directory( "flower_dataset", target_size=(224, 224), batch_size=32, subset="training", class_mode="categorical" )

rescale必须放在生成器里,不能只对单张测试图做然后训练图忘了做,这样训练与推理的预处理不一致,模型表现会谜之变差。validation_split=0.2会在目录里按文件名随机取 20% 做验证,文件夹名就是类别名,这也是项目里没有单独类别文件时最常用的组织方式。

3. DenseNet201 迁移学习参数详解:冻结层、微调策略与训练配置

3.1 加载预训练模型:include_top 与 weights

DenseNet201 在 TensorFlow 中可以直接从tensorflow.keras.applications加载。加载时两个参数最关键:weightsinclude_topweights='imagenet'表示使用 ImageNet 预训练权重,include_top=False表示不要最后的 1000 类全连接层,只保留卷积特征提取部分:

from tensorflow.keras.applications import DenseNet201 from tensorflow.keras.models import Sequential from tensorflow.keras.layers import GlobalAveragePooling2D, Dense, Dropout base_model = DenseNet201(weights="imagenet", include_top=False, input_shape=(224, 224, 3)) base_model.trainable = False

include_top=False后,base_model 输出的是卷积特征图,形状通常是(7, 7, 1920),1920 维通道数是 DenseNet201 最后一个 dense block 的结构决定的。把这个特征图直接拉平再接 Dense 层会有 7×7×1920 ≈ 94080 个输入维度,参数爆炸,所以我一般会在中间加GlobalAveragePooling2D,把每个通道的 7×7 平均成一个值,变成 1920 维向量,既降维又保留全局信息。

base_model.trainable = False是迁移学习第一阶段的通用做法。冻结的层只做前向传播,不参与梯度更新,这样训练压力小,而且预训练特征不会被小数据集破坏。如果你的数据集只有几千张甚至几百张,这个设置能显著抑制过拟合。

3.2 自定义分类头与微调结构

分类头加几层、每层多大,需要根据类别数决定。假设项目里识别 5 类花,那么最后 Dense 层输出 5 个神经元,用 softmax 激活:

model = Sequential([ base_model, GlobalAveragePooling2D(), Dropout(0.5), Dense(256, activation="relu"), Dropout(0.3), Dense(5, activation="softmax") ]) model.summary()

两个 Dropout 是防止全连接层过拟合的关键。Dropout(0.5)表示在训练时随机丢弃一半神经元,我一般放在 GAP 之后和最后的 softmax 之前。中间这个Dense(256)不是必须的,如果类别数很少,比如 5 类以内,也可以直接 GAP 后接输出层。加这个层是为了让模型有更强的非线性拟合能力,但代价是参数量增加,训练需要更多数据。数据集如果只有每类几十张图,建议去掉中间层,只保留一个 Dropout 加 Dense 输出。

3.3 编译、回调与训练参数配置

编译时选优化器和学习率非常重要。常见选择是 Adam,初始学习率用1e-4而不是默认的1e-3。预训练模型的特征已经很好,学习率太大一步就会把调整好的权重冲乱:

from tensorflow.keras.optimizers import Adam from tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping, ReduceLROnPlateau model.compile( optimizer=Adam(learning_rate=1e-4), loss="categorical_crossentropy", metrics=["accuracy"] ) checkpoint = ModelCheckpoint( "best_flower_model.h5", monitor="val_accuracy", save_best_only=True, mode="max" ) early_stop = EarlyStopping( monitor="val_loss", patience=10, restore_best_weights=True ) reduce_lr = ReduceLROnPlateau( monitor="val_loss", factor=0.5, patience=4, min_lr=1e-6 ) history = model.fit( train_gen, validation_data=val_gen, epochs=50, callbacks=[checkpoint, early_stop, reduce_lr] )

配置里各参数的含义如下:

参数作用
learning_rate=1e-41e-4微调阶段的安全学习率,避免破坏预训练特征
monitor="val_accuracy"val_accuracy以验证集准确率作为模型保存的评判指标
patience=1010验证损失连续 10 轮不降则停止训练
factor=0.50.5验证集指标停滞时学习率减半
min_lr=1e-61e-6学习率下限,避免降到零

save_best_only=True保证磁盘上只保留验证集最优的那个模型,而不是最后一轮。课程设计交报告时你一般希望交“最优状态”,不是“训练结束状态”,这个回调就是干这个的。EarlyStoppingrestore_best_weights能让你在回调触发后,模型权重自动回滚到验证集最好的那轮,省得手动去 reload 权重。

3.4 二阶段微调:解冻部分 Dense Block

第一阶段训练到验证准确率平台期后,可以进入微调阶段。微调不是直接base_model.trainable = True然后全量跑,那样前面层梯度变化大,不仅慢而且容易灾难性遗忘。更稳的做法是从后往前冻结前面若干层:

base_model.trainable = True # 从第 200 层开始让后面的层参与训练 for layer in base_model.layers[:200]: layer.trainable = False model.compile( optimizer=Adam(learning_rate=1e-5), loss="categorical_crossentropy", metrics=["accuracy"] ) model.fit(...) # 继续训练

这里我取了 200 层作为例子,具体层数要看base_model.layers的长度。DenseNet201 共有 200 多层,把前面 200 层冻结、只让最后几个 dense block 和 transition 层更新,能针对性调整高层语义特征,同时避免底层通用特征被扰动。注意:重新compile是必须的,trainable改动后要重新编译,否则不生效。二阶段学习率要更小,一般取1e-5 ~ 5e-6,因为我这里已经做了一轮训练,权重已经接近局部最优,学习率大了会在最优值附近震荡。

4. 训练与验证中的踩坑记录:过拟合、显存不足与模型重载

4.1 过拟合:训练集准确率 98%,验证集 70%

这是迁移学习项目最常遇到的症状。在 TensorFlow 中,如果发现训练曲线上accuracy持续上升而val_accuracy停滞不动,就说明模型开始死记硬背训练集了。常见应对方案有三个。

第一个是加强数据增强。在ImageDataGenerator里加随机翻转、旋转、缩放:

datagen = ImageDataGenerator( rescale=1./255, rotation_range=30, width_shift_range=0.2, height_shift_range=0.2, shear_range=0.2, zoom_range=0.2, horizontal_flip=True, validation_split=0.2 )

rotation_range=30表示随机旋转 0~30 度,zoom_range=0.2表示随机缩放 20%,horizontal_flip=True做水平翻转。这些变换能让模型看到更多“变体”,变相扩大数据集。但要注意,验证集不能做增强,只能用rescale,否则验证指标会失真。

第二个是提高 Dropout 比率。把分类头的Dropout(0.5)提到Dropout(0.6),让模型不那么依赖某一个特征。第三个是直接降低全连接层维数,比如把 256 降到 128。很多时候,项目里给的源码在“裸运行”时过拟合,先调这三处,验证准确率会上升 5~10 个百分点。

4.2 显存不足:batch_size 和图像尺寸的调整

训练时报ResourceExhaustedError是 TensorFlow 常见问题。尤其是在 Windows 上跑,显存只有 4GB 时,batch_size=32直接爆掉。我一般先看nvidia-smi确认显存占用,然后把batch_size改成 16 或 8。还有一个技巧是减少验证集批大小,因为验证时不更新梯度,但每个 batch 依然会占用相同显存:

train_gen = datagen.flow_from_directory( "flower_dataset", target_size=(224, 224), batch_size=16, # 从 32 调下来 subset="training", class_mode="categorical" )

如果改成 8 仍然爆显存,再考虑把输入尺寸降到(192, 192)(160, 160),但这时必须同时修改训练和验证的target_size,并且之后预测新图片也要用同样尺寸。DenseNet201 对 192×192 输入依然可用,只是精度会略微下降,不过影响不大。不要试图通过allow_growth=True解决一切,它只是让显存按需增长,实际峰值没变。

4.3 模型重载:自定义层与 custom_objects

训练好的DenseNet201.h5如果是用model.save()保存的,直接load_model就能恢复。如果保存时只存了权重,那就要先重建结构再加载。还有一个小坑:如果在模型里加了自定义层,或者用了 Lambda 层,重载时 TensorFlow 会因为找不到自定义类而报错。我遇到过项目里模型用了自定义F1Score指标,加载时就必须这样写:

from tensorflow.keras.models import load_model model = load_model( "DenseNet201.h5", custom_objects={"F1Score": F1Score} )

custom_objects里填的是自定义类名和类对象的映射。如果你是从网上下载的项目,重载时报错,第一件事就是把模型文件里涉及的所有自定义函数、类都收集起来,按这个格式填进去。项目本身提供的这个DenseNet201.h5如果是最新版直接load_model能过,说明没有额外自定义层。

5. 复用已有模型:单图预测、批量评估与类别日志核对

5.1 单张图片预测并输出类别名称

模型训练好之后,最常被问到的就是“我拿一张新图片,怎么知道模型认为它是什么花”。这里给出一个完整可跑的推理脚本,把前面章节的数据库映射和预处理整合起来:

import sqlite3 import numpy as np from tensorflow.keras.models import load_model from tensorflow.keras.utils import load_img, img_to_array def predict_single(model_path, image_path, db_path="flower_info.db"): # 读取类别映射 conn = sqlite3.connect(db_path) cur = conn.cursor() cur.execute("SELECT id, name FROM flower_classes ORDER BY id") class_map = {int(i): name for i, name in cur.fetchall()} conn.close() # 加载模型 model = load_model(model_path) # 预处理:尺寸、归一化、添加 batch 维度 img = load_img(image_path, target_size=(224, 224)) x = img_to_array(img) / 255.0 x = np.expand_dims(x, axis=0) # (1, 224, 224, 3) probs = model.predict(x)[0] idx = int(np.argmax(probs)) confidence = float(probs[idx]) return class_map.get(idx, f"unknown_{idx}"), confidence name, conf = predict_single("DenseNet201.h5", "48c812f2d14b43d500767fb5881e7a28.jpeg") print(f"预测结果: {name}, 置信度: {conf:.2%}")

numpy.expand_dims非常关键,因为 Keras 模型默认按 batch 处理数据,单张图片必须扩出一个维度变成(1, 224, 224, 3)class_map.get(idx, f"unknown_{idx}")防止类别 ID 对不上时程序崩溃,而是返回一个带下划线的未知标签,这样你可以回查数据库。置信度打印成百分比,方便直接判断这个预测可不可信。

5.2 批量验证:从验证集算 Top-1 准确率

如果想快速检验项目自带的模型是不是真能用,不要一张张测,把目录下的样例图片全部跑一遍:

import os from tensorflow.keras.models import load_model model = load_model("DenseNet201.h5") correct = 0 total = 0 for img_name in os.listdir("test_imgs"): if not img_name.lower().endswith((".jpeg", ".jpg", ".png")): continue # 假设文件名格式是 类名_编号.jpg,按下划线取真值 true_label = img_name.split("_")[0] pred_label, _ = predict_single("DenseNet201.h5", os.path.join("test_imgs", img_name)) total += 1 if true_label == pred_label: correct += 1 print(f"{img_name}: 预测={pred_label}, 真实={true_label}") print(f"Top-1 准确率: {correct/total:.2%}")

我在这里用文件名的前缀作为真实标签,这是一种常见的临时验证手段。项目里如果没有带标签的测试集,你也可以从flower_info.db中读取类别 ID 与图片文件名的对应关系来做。批量脚本跑完,你就能知道这个DenseNet201.h5在当前图片上准确率大致是什么水平,而不是盲目相信 README 里的描述。

5.3 回查数据库确认类别 ID 对齐

最后强调一个容易被坑的细节:训练出的模型类别序号和flower_info.db里的 ID 不一定一致。TensorFlow 的flow_from_directory会自动按文件夹名称的字母序生成类别索引,比如文件夹叫daisy, dandelion, rose,索引就是0,1,2。而数据库里 ID 可能按1,2,3编排,或者按中文名排序,顺序完全不同。所以推理之前,一定要用一张你已知类别的图片先测一下,把模型输出的 idx 与数据库映射结果打印出来对比:

print("模型输出 idx:", idx, "数据库名称:", class_map.get(idx))

如果发现对不上,要么修改数据库查询语句按字母序处理,要么在训练时手动把类别索引写入数据库。做完这一步,整个项目的闭环才算打通:训练有模型,验证有指标,推理有名称,写课程设计文档时每一张测试图都能给出“图片路径、预测类别、置信度”三要素。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/11 22:56:57

擦亮眼睛!不是每款 AI 都能用来写学术论文,2026 导师力荐工具汇总

每年毕业季,无数同学深陷论文难题:开题毫无思路、搭建框架耗费数日、初稿逻辑松散、查重标红泛滥、AI检测超标、格式反复被导师驳回。现如今市面上通用型AI工具遍地开花,但绝大多数通用大模型存在编造虚假参考文献、学术语句口语化、AI生成痕…

作者头像 李华
网站建设 2026/9/11 22:56:00

3分钟把SystemInformer界面改成中文:系统监控工具汉化全解

3分钟把SystemInformer界面改成中文:系统监控工具汉化全解 【免费下载链接】systeminformer A free, powerful, multi-purpose tool that helps you monitor system resources, debug software and detect malware. Brought to you by Winsider Seminars & Solu…

作者头像 李华
网站建设 2026/9/11 22:53:44

基于YOLOv8的游泳动作识别系统:从数据准备到部署的完整实践

简介:一套基于YOLOv8的游泳动作识别系统完整工程包,面向计算机视觉、人工智能等专业的毕业设计与课程设计场景,解决动作识别从模型训练、指标评估到可视化界面展示的全流程需求。压缩包共97个文件,以70个Python脚本(覆…

作者头像 李华
网站建设 2026/9/11 22:52:31

手语动作实时识别:YOLOv5-tiny定制化部署与优化

简介:本资源是一个基于YOLOv5实现的手语识别系统完整工程包,面向人工智能初学者、计算机视觉方向学习者及无障碍交互技术研究者,旨在解决手语图像中手部目标定位与手势类别识别的核心问题,适用于特殊教育辅助、智能手语翻译设备开…

作者头像 李华
网站建设 2026/9/11 22:50:51

800+免费API大全:public-api-lists 开源项目实战入门

800免费API大全:public-api-lists 开源项目实战入门 【免费下载链接】public-api-lists A curated list of free public APIs — searchable, community-maintained, with a free JSON API. 项目地址: https://gitcode.com/GitHub_Trending/pu/public-api-lists …

作者头像 李华