♂️ 个人主页艾派森的个人主页✍作者简介Python学习者 希望大家多多支持我们一起进步如果文章对你有帮助的话欢迎评论 点赞 收藏 加关注目录1.项目背景2.数据集介绍3.技术工具4.实验过程4.1导入数据4.2数据可视化4.3特征工程4.4构建模型4.5模型评估4.6模型评估5.总结源代码1.项目背景蝴蝶分类研究在生态监测、生物多样性保护等领域具有重要价值传统分类方法依赖昆虫学家的专业知识和经验通过肉眼观察蝴蝶翅膀的纹理、斑纹、色泽等形态特征进行物种鉴别。这种方法不仅效率较低而且对专业人员的依赖性强难以应对大规模图像数据的处理需求。随着数字成像技术的普及和图像采集设备的进步积累了大量蝴蝶物种的影像资料如何利用这些图像资源实现自动化、智能化的物种识别成为值得探索的方向。当前公开的蝴蝶图像数据集涵盖了多个常见物种不同种类间存在形态相似性高、类内差异大的特点特别是在同属物种或亚种之间区分特征往往十分细微。这些特点对自动识别算法提出了较高要求需要模型能够捕捉翅膀鳞片的微观结构和复杂的图案纹理。卷积神经网络在图像特征提取方面展现出显著优势能够从像素级数据中学习到具有判别性的视觉特征。本项目基于公开的蝴蝶图像数据集研究如何构建有效的深度学习模型来解决蝴蝶物种自动分类问题。通过设计合适的网络架构和训练策略探索模型对蝴蝶复杂纹理特征的学习能力并分析在实际应用场景中可能遇到的挑战为昆虫图像识别和生物多样性信息化管理提供技术参考。2.数据集介绍本实验数据集来源于Kaggle该数据集包含75种不同的蝴蝶类别。数据集包含1000多张已标注的图像其中包括验证图像。每张图像仅属于一个蝴蝶类别。3.技术工具Python版本:3.9代码编辑器jupyter notebook4.实验过程4.1导入数据这里主要完成准备工作包括导入必要的Python库、设置环境参数以及加载数据集。蝴蝶图像分类是一个典型的计算机视觉任务我们需要建立一个能够识别不同蝴蝶种类的深度学习模型。下面先配置好所需的各种工具包括数据处理、可视化、机器学习和深度学习框架。# 导入基础数据处理和科学计算库 import numpy as np # 数值计算库用于矩阵和数组操作 import pandas as pd # 数据分析库用于处理表格数据 import os # 操作系统接口用于文件和目录操作 # 导入数据可视化库 import matplotlib.pyplot as plt # 基础绘图库 import seaborn as sns # 统计图形库基于matplotlib import random # 随机数生成 import matplotlib # matplotlib主库 import matplotlib.cm as cm # 色彩映射 # 导入机器学习相关工具 from sklearn.model_selection import train_test_split # 数据集划分工具 from sklearn.metrics import classification_report, confusion_matrix # 模型评估指标 # 导入TensorFlow深度学习框架 import tensorflow as tf # TensorFlow主库 import keras_tuner as kt # 超参数调优库 from tensorflow import keras # Keras高级API from tensorflow.keras import layers, models # Keras层和模型 from tensorflow.keras.models import Sequential # 顺序模型 # 导入具体的神经网络层类型 from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout # 导入图像数据预处理工具 from tensorflow.keras.preprocessing.image import ImageDataGenerator, load_img, img_to_array, array_to_img from tensorflow.keras import regularizers # 正则化工具 from tensorflow.keras.callbacks import LearningRateScheduler # 学习率调度回调 from kerastuner.tuners import RandomSearch # 随机搜索超参数调优 # 忽略警告信息使输出更清晰 import warnings warnings.filterwarnings(ignore) # 读取训练集的标注文件 # CSV文件中应该包含图像路径和对应的蝴蝶种类标签 df pd.read_csv(./butterfly-image-classification/Training_set.csv)4.2数据可视化主要对蝴蝶数据集进行初步的可视化分析帮助我们了解数据的基本情况。通过可视化各类别的分布我们可以知道数据集是否平衡这对于后续的模型训练策略选择非常重要。不平衡的数据集可能需要采用加权损失函数或数据增强等策略来改善模型性能。# 统计每个蝴蝶类别的图像数量 # df[label] 是包含蝴蝶种类标签的列value_counts() 统计每个唯一值的出现次数 class_counts df[label].value_counts() # 创建一个新的图形窗口设置尺寸为15x9英寸确保有足够空间显示所有类别 plt.figure(figsize(15, 9)) # 使用seaborn绘制柱状图显示每个蝴蝶类别的图像数量 # x轴蝴蝶类别名称class_counts.index # y轴对应类别的图像数量class_counts.values # colorpurple设置柱状图颜色为紫色 sns.barplot(xclass_counts.index, yclass_counts.values, colorpurple) # 设置图表标题使用较大的字体和加粗效果以提高可读性 plt.title(Distribution of All Butterfly Species, fontsize18, fontweightbold) # 设置x轴标签说明该轴代表蝴蝶种类 plt.xlabel(Butterfly Species, fontsize12) # 设置y轴标签说明该轴代表图像数量 plt.ylabel(Number of Images, fontsize12) # 将x轴上的标签旋转90度因为蝴蝶种类名称可能较长旋转后可以避免重叠提高可读性 plt.xticks(rotation90) # 自动调整子图参数确保标签和标题不会被截断 plt.tight_layout() # 显示绘制好的图表 plt.show()接着展示数据集中的实际蝴蝶图像样本让我们能够直观了解不同种类蝴蝶的外观特征和图像质量。通过随机抽样展示多个图像我们可以检查数据质量观察图像是否清晰、蝴蝶姿态是否多样、背景是否复杂等这些因素都会影响后续模型的训练效果和识别精度。# 设置训练图像所在的目录路径 image_dir ./butterfly-image-classification/train # 为数据框添加完整的图像文件路径列 # 使用lambda函数将每个文件名与图像目录路径拼接得到完整的文件路径 df[Filepath] df[filename].apply(lambda x: os.path.join(image_dir, x)) # 创建新的标签列或重命名现有列确保标签列名称一致 df[Label] df[label] # 从数据集中随机选择9个不同的索引 # np.random.randint生成随机整数范围从0到数据集长度不包含重复索引 random_index np.random.randint(0, len(df), 9) # 创建3行3列的子图网格用于展示9张随机选择的蝴蝶图像 # figsize(10, 10)设置整个图形的大小为10x10英寸 # subplot_kw{xticks: [], yticks: []}移除所有子图的x轴和y轴刻度使图像显示更干净 fig, axes plt.subplots(nrows3, ncols3, figsize(10, 10), subplot_kw{xticks: [], yticks: []}) # 遍历所有子图和对应的随机索引 for i, ax in enumerate(axes.flat): # 读取并显示图像 # df.Filepath.iloc[random_index[i]]获取第i个随机索引对应的图像文件路径 # plt.imread()读取图像文件为numpy数组 # ax.imshow()在当前的子图中显示图像 ax.imshow(plt.imread(df.Filepath.iloc[random_index[i]])) # 设置子图标题为对应的蝴蝶种类标签 ax.set_title(df.Label.iloc[random_index[i]]) # 自动调整子图之间的间距避免标题和图像重叠 plt.tight_layout() # 显示完整的图形 plt.show()4.3特征工程我们需要将完整的数据集划分为训练集、验证集和测试集三个部分。合理的划分比例和策略能够确保模型训练的有效性并且能够准确评估模型的泛化能力。对于蝴蝶图像分类这种多分类任务我们特别需要注意保持每个数据集中各类别比例的一致性。# 第一次划分将完整数据集分为训练验证集80%和测试集20% # train_test_split是scikit-learn提供的标准数据集划分函数 # df: 完整的数据集 # test_size0.2: 测试集占总数据的20% # shuffleTrue: 在划分前打乱数据顺序避免数据排序带来的偏差 # random_state42: 设置随机种子确保每次运行结果一致便于实验复现 # stratifydf[label]: 按标签进行分层采样确保训练集、验证集和测试集中各类别比例与原始数据集相同 train_val_df, test_df train_test_split(df, test_size0.2, shuffleTrue, random_state42, stratifydf[label]) # 第二次划分将训练验证集进一步划分为训练集64%和验证集16% # 这里从剩余的80%数据中再划分出20%作为验证集即总数据的16% # stratifytrain_val_df[label]: 同样保持训练集和验证集中的类别比例一致 train_df, val_df train_test_split(train_val_df, test_size0.2, shuffleTrue, random_state42, stratifytrain_val_df[label]) # 打印各个数据集的大小行数×列数 print(fTraining set: {train_df.shape}) print(fValidation set: {val_df.shape}) print(fTest set: {test_df.shape})由于蝴蝶图像可能存在大小不一、方向各异的情况我们需要将图像标准化到统一尺寸并应用数据增强技术来提升模型的泛化能力。特别是对于训练集我们采用了多种增强手段来模拟蝴蝶在自然环境中的各种变化这样训练出来的模型在实际应用中会更鲁棒。# 定义图像的目标尺寸和批量大小 IMG_SIZE (224, 224) # 将图像统一调整为224x224像素这是很多CNN模型的常用输入尺寸 BATCH_SIZE 32 # 每个训练批次包含32张图像 # 创建训练数据的数据增强生成器 # ImageDataGenerator是Keras提供的强大工具可以实时进行数据增强和预处理 train_datagen ImageDataGenerator( rescale1./255, # 像素值归一化到0-1范围将0-255的像素值除以255 rotation_range30, # 随机旋转角度范围±30度模拟蝴蝶不同飞行角度 width_shift_range0.2, # 水平平移范围±20%宽度模拟蝴蝶在图像中的位置变化 height_shift_range0.2, # 垂直平移范围±20%高度 shear_range0.2, # 剪切变换强度20%模拟透视变化 zoom_range0.2, # 随机缩放范围80%-120%模拟相机远近变化 horizontal_flipTrue, # 允许水平翻转蝴蝶左右对称水平翻转合理 fill_modenearest # 填充新像素的方式使用最近邻像素填充变换后产生的空白区域 ) # 创建验证集和测试集的数据生成器不进行数据增强只做归一化 # 验证集和测试集只需要简单的归一化处理保持数据原始分布以准确评估模型 val_datagen ImageDataGenerator(rescale1./255) # 仅进行像素归一化 test_datagen ImageDataGenerator(rescale1./255) # 仅进行像素归一化 # 创建训练数据生成器 train_generator train_datagen.flow_from_dataframe( dataframetrain_df, # 训练集数据框 directoryimage_dir, # 图像所在目录 x_colfilename, # 数据框中包含图像文件名的列 y_collabel, # 数据框中包含标签的列 target_sizeIMG_SIZE, # 目标图像尺寸 batch_sizeBATCH_SIZE, # 每批数据的大小 class_modecategorical, # 分类模式多分类会生成one-hot编码的标签 shuffleTrue, # 打乱数据顺序避免模型学习到数据顺序 seed42 # 随机种子确保可重复性 ) # 创建验证数据生成器 val_generator val_datagen.flow_from_dataframe( dataframeval_df, # 验证集数据框 directoryimage_dir, x_colfilename, y_collabel, target_sizeIMG_SIZE, batch_sizeBATCH_SIZE, class_modecategorical, shuffleFalse # 验证集不需要打乱便于跟踪每个样本的表现 ) # 创建测试数据生成器 test_generator test_datagen.flow_from_dataframe( dataframetest_df, # 测试集数据框 directoryimage_dir, x_colfilename, y_collabel, target_sizeIMG_SIZE, batch_sizeBATCH_SIZE, class_modecategorical, shuffleFalse # 测试集也不需要打乱 )4.4构建模型我们采用经典的CNN设计模式通过多个卷积块逐步提取图像特征最后通过全连接层进行分类。这种层次化设计能够从低级特征边缘、纹理到高级特征翅膀图案、身体结构逐步学习蝴蝶的视觉特征。# 导入必要的Keras模块 from tensorflow.keras import layers, models, Input # 定义模型的输入层 # shape(224, 224, 3) 表示输入图像尺寸为224x224像素3个颜色通道RGB inputs Input(shape(224, 224, 3)) # ------------------- 第一个卷积块 ------------------- # 第一层卷积32个3x3卷积核ReLU激活函数same padding保持特征图尺寸不变 x layers.Conv2D(32, (3, 3), activationrelu, paddingsame)(inputs) # 批归一化标准化激活值加速训练并提高稳定性 x layers.BatchNormalization()(x) # 第二层卷积继续提取特征 x layers.Conv2D(32, (3, 3), activationrelu, paddingsame)(x) # 最大池化2x2窗口步长为2将特征图尺寸减半提取更显著的特征 x layers.MaxPooling2D((2, 2))(x) # Dropout随机丢弃20%的神经元防止过拟合 x layers.Dropout(0.2)(x) # ------------------- 第二个卷积块 ------------------- # 增加卷积核数量到64个提取更复杂的特征 x layers.Conv2D(64, (3, 3), activationrelu, paddingsame)(x) x layers.BatchNormalization()(x) x layers.Conv2D(64, (3, 3), activationrelu, paddingsame)(x) x layers.MaxPooling2D((2, 2))(x) # 增加Dropout比率到30%随网络加深逐渐增加正则化强度 x layers.Dropout(0.3)(x) # ------------------- 第三个卷积块 ------------------- # 进一步增加卷积核数量到128个提取更抽象的特征 # 给这一层命名便于后续可视化或特征提取 x layers.Conv2D(128, (3, 3), activationrelu, paddingsame, namelast_conv)(x) x layers.BatchNormalization()(x) x layers.Conv2D(128, (3, 3), activationrelu, paddingsame)(x) x layers.MaxPooling2D((2, 2))(x) # 继续增加Dropout比率到40% x layers.Dropout(0.4)(x) # ------------------- 展平层和全连接层 ------------------- # 将三维特征图展平为一维向量供全连接层处理 x layers.Flatten()(x) # 全连接层256个神经元进一步组合特征 x layers.Dense(256, activationrelu)(x) x layers.BatchNormalization()(x) # 最终Dropout50%的高丢弃率强正则化防止过拟合 x layers.Dropout(0.5)(x) # ------------------- 输出层 ------------------- # 输出层75个神经元对应75种蝴蝶类别softmax激活函数输出概率分布 outputs layers.Dense(75, activationsoftmax)(x) # ------------------- 模型编译 ------------------- # 创建模型实例指定输入和输出 model models.Model(inputsinputs, outputsoutputs) # 编译模型配置训练过程 # optimizeradam: 使用Adam优化器自适应学习率 # losscategorical_crossentropy: 使用分类交叉熵损失函数适合多分类问题 # metrics[accuracy]: 评估指标为准确率 model.compile(optimizeradam, losscategorical_crossentropy, metrics[accuracy]) # 打印模型结构摘要 # 显示每层的输出形状、参数数量和总参数量 model.summary()接着配置模型训练过程中的各种回调函数这些是提升训练效果和模型质量的关键工具。回调函数能够在训练的不同阶段执行特定操作比如在验证损失不再改善时提前停止训练、保存最佳模型权重、或动态调整学习率。合理配置这些回调可以显著提高训练效率防止过拟合并确保我们得到最优的模型。# ------------------- 早停回调Early Stopping ------------------- # 当验证集性能不再提升时提前停止训练防止过拟合 early_stopping keras.callbacks.EarlyStopping( monitorval_loss, # 监控验证集损失值 patience10, # 容忍验证损失连续10个epoch没有改善 restore_best_weightsTrue, # 训练结束后恢复最佳模型权重而不是最后一轮的权重 verbose1 # 显示回调的详细信息 ) # ------------------- 模型检查点回调Model Checkpoint ------------------- # 在训练过程中定期保存模型特别是保存性能最好的模型 checkpoint keras.callbacks.ModelCheckpoint( filepathbest_model.keras, # 模型保存的文件路径和名称 monitorval_loss, # 根据验证损失选择最佳模型 save_best_onlyTrue, # 只保存验证损失最小的模型而不是每个epoch都保存 save_weights_onlyFalse, # 保存完整的模型包括结构和权重而不仅仅是权重 verbose1 # 显示保存模型的提示信息 ) # ------------------- 学习率衰减回调ReduceLROnPlateau ------------------- # 当模型性能停滞时自动降低学习率帮助模型跳出局部最优 reduce_lr tf.keras.callbacks.ReduceLROnPlateau( monitorval_loss, # 监控验证集损失 factor0.5, # 学习率衰减因子当触发条件时学习率乘以0.5降低50% patience4, # 等待4个epoch如果验证损失没有改善则降低学习率 min_lr0.00001, # 学习率的最小值避免学习率过小导致训练停滞 verbose1 # 显示学习率调整的详细信息 )4.5模型评估通过调用model.fit()方法我们将准备好的训练数据、验证数据和配置好的回调函数整合在一起开始模型的迭代学习。训练过程会持续多个epoch每个epoch都会在训练集上更新模型参数并在验证集上评估模型性能直到满足停止条件为止。# 开始模型训练 # model.fit()是Keras中用于训练模型的主要方法 history model.fit( train_generator, # 训练数据生成器提供批量训练数据 validation_dataval_generator, # 验证数据生成器用于监控训练过程中的泛化能力 epochs50, # 最大训练轮数实际可能因早停而提前结束 batch_size32, # 每个训练批次的样本数量与生成器中设置的一致 callbacks[early_stopping, checkpoint, reduce_lr], # 训练回调函数列表 verbose1 # 训练过程详细程度1显示进度条和每个epoch的信息 )4.6模型评估开始对训练好的模型进行全面的性能评估。我们首先需要确定在哪个epoch模型表现最好然后详细分析这个最佳epoch的各项指标。这是因为在训练过程中模型性能可能会有波动我们关心的是模型在整个训练过程中的最佳状态而不是最后一个epoch的状态特别是如果触发了早停机制的话。def get_best_epoch_details(history): 从训练历史中提取最佳epoch的详细信息 参数: history: model.fit()返回的历史记录对象包含每个epoch的指标 返回: epoch_details: 包含最佳epoch所有指标值的字典 # 获取所有epoch的验证损失值列表 val_losses history.history[val_loss] # 找到最小验证损失对应的索引 # min(val_losses): 找到验证损失的最小值 # val_losses.index(): 找到该最小值在列表中的位置索引 min_val_loss_index val_losses.index(min(val_losses)) # 最佳epoch编号列表索引从0开始epoch编号从1开始所以要1 best_epoch min_val_loss_index 1 # 创建一个字典来存储最佳epoch的所有指标 epoch_details {} # 遍历历史记录中的所有指标 for key in history.history.keys(): # 获取该指标在最佳epoch的值 epoch_details[key] history.history[key][min_val_loss_index] # 将最佳epoch编号也添加到字典中 epoch_details[best_epoch] best_epoch return epoch_details # 调用函数获取最佳epoch的详细信息 best_epoch_details get_best_epoch_details(history) # 打印最佳epoch的各项指标 print(fBest epoch details: {best_epoch_details})接着对模型的训练过程进行可视化分析通过绘制准确率和损失曲线来直观展示模型的学习动态。这些曲线能够帮助我们诊断模型训练的健康状况识别潜在问题如过拟合或欠拟合并评估模型是否已经充分收敛。对于深度学习项目来说这种可视化分析是不可或缺的调试和评估手段。# 创建一个新的图形窗口设置尺寸为12x4英寸 # 宽12英寸、高4英寸的尺寸能够很好地并排显示两个子图 plt.figure(figsize(12, 4)) # ------------------- 第一个子图准确率曲线 ------------------- # 创建1行2列的子图网格这是第1个子图 plt.subplot(1, 2, 1) # 绘制训练准确率曲线 # history.history[accuracy]: 训练过程中每个epoch的训练准确率记录 # labelTraining Accuracy: 设置曲线标签用于图例显示 plt.plot(history.history[accuracy], labelTraining Accuracy) # 绘制验证准确率曲线 # history.history[val_accuracy]: 训练过程中每个epoch的验证准确率记录 plt.plot(history.history[val_accuracy], labelValidation Accuracy) # 设置子图标题和坐标轴标签 plt.title(Model Accuracy) # 图表标题 plt.xlabel(Epoch) # x轴标签训练轮数 plt.ylabel(Accuracy) # y轴标签准确率 # 显示图例区分训练曲线和验证曲线 plt.legend() # 添加网格线便于观察曲线变化趋势 plt.grid(True) # ------------------- 第二个子图损失曲线 ------------------- # 创建1行2列的子图网格这是第2个子图 plt.subplot(1, 2, 2) # 绘制训练损失曲线 # history.history[loss]: 训练过程中每个epoch的训练损失记录 plt.plot(history.history[loss], labelTraining Loss) # 绘制验证损失曲线 # history.history[val_loss]: 训练过程中每个epoch的验证损失记录 plt.plot(history.history[val_loss], labelValidation Loss) # 设置子图标题和坐标轴标签 plt.title(Model Loss) # 图表标题 plt.xlabel(Epoch) # x轴标签训练轮数 plt.ylabel(Loss) # y轴标签损失值 # 显示图例 plt.legend() # 添加网格线 plt.grid(True) # 注意这里缺少了plt.show()实际运行时应加上以显示图形 # plt.show()接着对模型在验证集上的具体预测结果进行可视化展示。通过随机选取一批验证图像我们不仅可以看到模型预测的类别还能将其与真实标签进行对比直观了解模型在哪些图像上预测正确在哪些图像上预测错误。这种可视化方式对于理解模型的实际表现、识别模型容易混淆的蝴蝶种类非常有帮助。# 从验证数据生成器中获取一个批次的图像和标签 # next()函数获取生成器的下一个批次数据 val_images, val_labels next(val_generator) # 使用训练好的模型对验证图像进行预测 # model.predict()对输入图像进行前向传播得到每个类别的预测概率 pred_labels model.predict(val_images) # 将预测的概率分布转换为具体的类别标签 # np.argmax(axis1): 在类别维度上找到概率最大的索引即预测的类别 pred_labels np.argmax(pred_labels, axis1) # 将验证集的one-hot编码标签转换为具体的类别标签 # val_labels是one-hot编码格式如[0,0,1,0,...,0]表示第3类 # np.argmax(axis1): 找到每个样本标签中值为1的位置索引 true_labels np.argmax(val_labels, axis1) # 获取类别索引映射关系 # val_generator.class_indices: 返回类别名称到数字索引的字典如{Adonis: 0, Brimstone: 1, ...} class_indices val_generator.class_indices # 创建反向映射从数字索引到类别名称 # 通过字典推导式创建新的字典将键值对反转 class_names {v: k for k, v in class_indices.items()} def display_images(images, true_labels, pred_labels, class_names, num_images9): 可视化展示模型的预测结果 参数: images: 图像数据数组 true_labels: 真实标签数组数字索引格式 pred_labels: 预测标签数组数字索引格式 class_names: 类别索引到名称的映射字典 num_images: 要显示的图像数量默认为9 # 创建一个大的图形窗口尺寸为15x15英寸 plt.figure(figsize(15, 15)) # 循环显示指定数量的图像 for i in range(num_images): # 创建子图网格5行3列共15个子图位置 # i1: 子图位置索引从1开始 plt.subplot(5, 3, i 1) # 显示当前图像 # images[i]: 第i张图像注意图像值已经在0-1范围内经过了归一化 plt.imshow(images[i]) # 获取当前图像的真实标签名称和预测标签名称 true_label class_names[int(true_labels[i])] # 将数字索引转换为类别名称 pred_label class_names[int(pred_labels[i])] # 将数字索引转换为类别名称 # 设置子图标题显示真实标签和预测标签 # 如果预测正确可以使用不同颜色标注这里可以添加颜色判断 color green if true_label pred_label else red plt.title(fTrue: {true_label}\nPred: {pred_label}, colorcolor) # 关闭坐标轴让图像显示更清晰 plt.axis(off) # 自动调整子图布局避免重叠 plt.tight_layout() # 显示完整的图形 plt.show() # 调用函数显示15个验证样本的预测结果 display_images(val_images, true_labels, pred_labels, class_names, num_images15)5.总结本文基于自定义的CNN卷积神经网络构建了一个蝴蝶图像分类识别模型在Kaggle提供的包含75种蝴蝶类别的数据集上进行了系统的实验研究。通过构建包含三个卷积块的特征提取网络结合批归一化、Dropout正则化和数据增强策略模型在验证集上取得了79.9%的准确率训练过程在第44个epoch达到最佳状态。实验结果表明所设计的CNN架构能够有效学习蝴蝶翅膀的纹理、颜色和形态特征但对部分相似物种仍存在一定的混淆现象。可视化分析显示模型在多数常见蝴蝶种类上表现良好但在少数样本量较少的类别上识别精度有待提升。通过早停机制、学习率衰减和模型检查点等训练策略的配合使用有效防止了过拟合并确保了模型的最佳性能。该研究为基于深度学习的生物多样性图像识别提供了实践参考后续可通过引入迁移学习、注意力机制等技术进一步优化模型性能。源代码import numpy as np import pandas as pd import os import matplotlib.pyplot as plt import seaborn as sns import random import matplotlib import matplotlib.cm as cm from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report, confusion_matrix import tensorflow as tf import keras_tuner as kt from tensorflow import keras from tensorflow.keras import layers, models from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout from tensorflow.keras.preprocessing.image import ImageDataGenerator, load_img, img_to_array, array_to_img from tensorflow.keras import regularizers from tensorflow.keras.callbacks import LearningRateScheduler from kerastuner.tuners import RandomSearch import warnings warnings.filterwarnings(ignore) df pd.read_csv(./butterfly-image-classification/Training_set.csv) class_counts df[label].value_counts() # her türün sayısını alır plt.figure(figsize(15, 9)) # grafik boyutu ayarlanır sns.barplot(xclass_counts.index, yclass_counts.values, colorpurple) plt.title(Distribution of All Butterfly Species, fontsize18, fontweightbold) plt.xlabel(Butterfly Species, fontsize12) plt.ylabel(Number of Images, fontsize12) plt.xticks(rotation90) # x ekseninde tüm isimleri 90 derece döndürürerek okunabilir hale getirir. plt.tight_layout() plt.show() image_dir ./butterfly-image-classification/train df[Filepath] df[filename].apply(lambda x: os.path.join(image_dir, x)) df[Label] df[label] random_index np.random.randint(0, len(df), 9) fig, axes plt.subplots(nrows3, ncols3, figsize(10, 10), subplot_kw{xticks: [], yticks: []}) for i, ax in enumerate(axes.flat): ax.imshow(plt.imread(df.Filepath.iloc[random_index[i]])) ax.set_title(df.Label.iloc[random_index[i]]) plt.tight_layout() plt.show() train_val_df, test_df train_test_split(df, test_size0.2, shuffleTrue, random_state42, stratifydf[label]) train_df, val_df train_test_split(train_val_df, test_size0.2, shuffleTrue, random_state42, stratifytrain_val_df[label]) print(fTraining set: {train_df.shape}) print(fValidation set: {val_df.shape}) print(fTest set: {test_df.shape}) IMG_SIZE (224, 224) BATCH_SIZE 32 train_datagen ImageDataGenerator( rescale1./255, # normalize et rotation_range30, # rastgele döndür width_shift_range0.2, # yatay kaydır height_shift_range0.2, # dikey kaydır shear_range0.2, # kesme zoom_range0.2, # yakınlaştır horizontal_flipTrue, # yatay çevir fill_modenearest # boşlukları doldur ) val_datagen ImageDataGenerator(rescale1./255) test_datagen ImageDataGenerator(rescale1./255) train_generator train_datagen.flow_from_dataframe( dataframetrain_df, directoryimage_dir, x_colfilename, y_collabel, target_sizeIMG_SIZE, batch_sizeBATCH_SIZE, class_modecategorical, shuffleTrue, seed42 ) val_generator val_datagen.flow_from_dataframe( dataframeval_df, directoryimage_dir, x_colfilename, y_collabel, target_sizeIMG_SIZE, batch_sizeBATCH_SIZE, class_modecategorical, shuffleFalse ) test_generator test_datagen.flow_from_dataframe( dataframetest_df, directoryimage_dir, x_colfilename, y_collabel, target_sizeIMG_SIZE, batch_sizeBATCH_SIZE, class_modecategorical, shuffleFalse ) from tensorflow.keras import layers, models, Input inputs Input(shape(224, 224, 3)) # 1. Evrişim bloğu x layers.Conv2D(32, (3, 3), activationrelu, paddingsame)(inputs) x layers.BatchNormalization()(x) x layers.Conv2D(32, (3, 3), activationrelu, paddingsame)(x) x layers.MaxPooling2D((2,2))(x) x layers.Dropout(0.2)(x) # 2. Evrişim bloğu x layers.Conv2D(64, (3, 3), activationrelu, paddingsame)(x) x layers.BatchNormalization()(x) x layers.Conv2D(64, (3, 3), activationrelu, paddingsame)(x) x layers.MaxPooling2D((2,2))(x) x layers.Dropout(0.3)(x) # 3. Evrişim bloğu x layers.Conv2D(128, (3, 3), activationrelu, paddingsame, namelast_conv)(x) x layers.BatchNormalization()(x) x layers.Conv2D(128, (3, 3), activationrelu, paddingsame)(x) x layers.MaxPooling2D((2,2))(x) x layers.Dropout(0.4)(x) # Flatten ve Fully Connected x layers.Flatten()(x) x layers.Dense(256, activationrelu)(x) x layers.BatchNormalization()(x) x layers.Dropout(0.5)(x) # Output Layer outputs layers.Dense(75, activationsoftmax)(x) # Model tanımı model models.Model(inputsinputs, outputsoutputs) model.compile(optimizeradam, losscategorical_crossentropy, metrics[accuracy]) model.summary() early_stopping keras.callbacks.EarlyStopping( monitorval_loss, patience10, restore_best_weightsTrue, verbose1 # Durumu yazdırır ) checkpoint keras.callbacks.ModelCheckpoint( filepathbest_model.keras, monitorval_loss, save_best_onlyTrue, save_weights_onlyFalse, # Modelin tamamını kaydeder verbose1 ) reduce_lr tf.keras.callbacks.ReduceLROnPlateau( monitorval_loss, factor0.5, # %50 azaltma - daha yumuşak patience4, min_lr0.00001, # Daha küçük min LR verbose1 ) history model.fit( train_generator, validation_dataval_generator, epochs50, batch_size32, callbacks[early_stopping, checkpoint, reduce_lr], verbose1 ) def get_best_epoch_details(history): val_losses history.history[val_loss] min_val_loss_index val_losses.index(min(val_losses)) best_epoch min_val_loss_index 1 epoch_details {} for key in history.history.keys(): epoch_details[key] history.history[key][min_val_loss_index] epoch_details[best_epoch] best_epoch return epoch_details best_epoch_details get_best_epoch_details(history) print(fBest epoch details: {best_epoch_details}) plt.figure(figsize(12, 4)) plt.subplot(1, 2, 1) plt.plot(history.history[accuracy], labelTraining Accuracy) plt.plot(history.history[val_accuracy], labelValidation Accuracy) plt.title(Model Accuracy) plt.xlabel(Epoch) plt.ylabel(Accuracy) plt.legend() plt.grid(True) plt.subplot(1, 2, 2) plt.plot(history.history[loss], labelTraining Loss) plt.plot(history.history[val_loss], labelValidation Loss) plt.title(Model Loss) plt.xlabel(Epoch) plt.ylabel(Loss) plt.legend() plt.grid(True) val_images, val_labels next(val_generator) pred_labels model.predict(val_images) pred_labels np.argmax(pred_labels, axis1) true_labels np.argmax(val_labels, axis1) class_indices val_generator.class_indices class_names {v: k for k, v in class_indices.items()} def display_images(images, true_labels, pred_labels, class_names, num_images9): plt.figure(figsize(15, 15)) for i in range(num_images): plt.subplot(5, 3, i 1) plt.imshow(images[i]) true_label class_names[int(true_labels[i])] pred_label class_names[int(pred_labels[i])] plt.title(fTrue: {true_label}\nPred: {pred_label}) plt.axis(off) plt.tight_layout() plt.show() display_images(val_images, true_labels, pred_labels, class_names, num_images15)资料获取更多粉丝福利关注下方公众号获取
深度学习实战-基于CNN卷积神经网络的蝴蝶图像分类识别模型
♂️ 个人主页艾派森的个人主页✍作者简介Python学习者 希望大家多多支持我们一起进步如果文章对你有帮助的话欢迎评论 点赞 收藏 加关注目录1.项目背景2.数据集介绍3.技术工具4.实验过程4.1导入数据4.2数据可视化4.3特征工程4.4构建模型4.5模型评估4.6模型评估5.总结源代码1.项目背景蝴蝶分类研究在生态监测、生物多样性保护等领域具有重要价值传统分类方法依赖昆虫学家的专业知识和经验通过肉眼观察蝴蝶翅膀的纹理、斑纹、色泽等形态特征进行物种鉴别。这种方法不仅效率较低而且对专业人员的依赖性强难以应对大规模图像数据的处理需求。随着数字成像技术的普及和图像采集设备的进步积累了大量蝴蝶物种的影像资料如何利用这些图像资源实现自动化、智能化的物种识别成为值得探索的方向。当前公开的蝴蝶图像数据集涵盖了多个常见物种不同种类间存在形态相似性高、类内差异大的特点特别是在同属物种或亚种之间区分特征往往十分细微。这些特点对自动识别算法提出了较高要求需要模型能够捕捉翅膀鳞片的微观结构和复杂的图案纹理。卷积神经网络在图像特征提取方面展现出显著优势能够从像素级数据中学习到具有判别性的视觉特征。本项目基于公开的蝴蝶图像数据集研究如何构建有效的深度学习模型来解决蝴蝶物种自动分类问题。通过设计合适的网络架构和训练策略探索模型对蝴蝶复杂纹理特征的学习能力并分析在实际应用场景中可能遇到的挑战为昆虫图像识别和生物多样性信息化管理提供技术参考。2.数据集介绍本实验数据集来源于Kaggle该数据集包含75种不同的蝴蝶类别。数据集包含1000多张已标注的图像其中包括验证图像。每张图像仅属于一个蝴蝶类别。3.技术工具Python版本:3.9代码编辑器jupyter notebook4.实验过程4.1导入数据这里主要完成准备工作包括导入必要的Python库、设置环境参数以及加载数据集。蝴蝶图像分类是一个典型的计算机视觉任务我们需要建立一个能够识别不同蝴蝶种类的深度学习模型。下面先配置好所需的各种工具包括数据处理、可视化、机器学习和深度学习框架。# 导入基础数据处理和科学计算库 import numpy as np # 数值计算库用于矩阵和数组操作 import pandas as pd # 数据分析库用于处理表格数据 import os # 操作系统接口用于文件和目录操作 # 导入数据可视化库 import matplotlib.pyplot as plt # 基础绘图库 import seaborn as sns # 统计图形库基于matplotlib import random # 随机数生成 import matplotlib # matplotlib主库 import matplotlib.cm as cm # 色彩映射 # 导入机器学习相关工具 from sklearn.model_selection import train_test_split # 数据集划分工具 from sklearn.metrics import classification_report, confusion_matrix # 模型评估指标 # 导入TensorFlow深度学习框架 import tensorflow as tf # TensorFlow主库 import keras_tuner as kt # 超参数调优库 from tensorflow import keras # Keras高级API from tensorflow.keras import layers, models # Keras层和模型 from tensorflow.keras.models import Sequential # 顺序模型 # 导入具体的神经网络层类型 from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout # 导入图像数据预处理工具 from tensorflow.keras.preprocessing.image import ImageDataGenerator, load_img, img_to_array, array_to_img from tensorflow.keras import regularizers # 正则化工具 from tensorflow.keras.callbacks import LearningRateScheduler # 学习率调度回调 from kerastuner.tuners import RandomSearch # 随机搜索超参数调优 # 忽略警告信息使输出更清晰 import warnings warnings.filterwarnings(ignore) # 读取训练集的标注文件 # CSV文件中应该包含图像路径和对应的蝴蝶种类标签 df pd.read_csv(./butterfly-image-classification/Training_set.csv)4.2数据可视化主要对蝴蝶数据集进行初步的可视化分析帮助我们了解数据的基本情况。通过可视化各类别的分布我们可以知道数据集是否平衡这对于后续的模型训练策略选择非常重要。不平衡的数据集可能需要采用加权损失函数或数据增强等策略来改善模型性能。# 统计每个蝴蝶类别的图像数量 # df[label] 是包含蝴蝶种类标签的列value_counts() 统计每个唯一值的出现次数 class_counts df[label].value_counts() # 创建一个新的图形窗口设置尺寸为15x9英寸确保有足够空间显示所有类别 plt.figure(figsize(15, 9)) # 使用seaborn绘制柱状图显示每个蝴蝶类别的图像数量 # x轴蝴蝶类别名称class_counts.index # y轴对应类别的图像数量class_counts.values # colorpurple设置柱状图颜色为紫色 sns.barplot(xclass_counts.index, yclass_counts.values, colorpurple) # 设置图表标题使用较大的字体和加粗效果以提高可读性 plt.title(Distribution of All Butterfly Species, fontsize18, fontweightbold) # 设置x轴标签说明该轴代表蝴蝶种类 plt.xlabel(Butterfly Species, fontsize12) # 设置y轴标签说明该轴代表图像数量 plt.ylabel(Number of Images, fontsize12) # 将x轴上的标签旋转90度因为蝴蝶种类名称可能较长旋转后可以避免重叠提高可读性 plt.xticks(rotation90) # 自动调整子图参数确保标签和标题不会被截断 plt.tight_layout() # 显示绘制好的图表 plt.show()接着展示数据集中的实际蝴蝶图像样本让我们能够直观了解不同种类蝴蝶的外观特征和图像质量。通过随机抽样展示多个图像我们可以检查数据质量观察图像是否清晰、蝴蝶姿态是否多样、背景是否复杂等这些因素都会影响后续模型的训练效果和识别精度。# 设置训练图像所在的目录路径 image_dir ./butterfly-image-classification/train # 为数据框添加完整的图像文件路径列 # 使用lambda函数将每个文件名与图像目录路径拼接得到完整的文件路径 df[Filepath] df[filename].apply(lambda x: os.path.join(image_dir, x)) # 创建新的标签列或重命名现有列确保标签列名称一致 df[Label] df[label] # 从数据集中随机选择9个不同的索引 # np.random.randint生成随机整数范围从0到数据集长度不包含重复索引 random_index np.random.randint(0, len(df), 9) # 创建3行3列的子图网格用于展示9张随机选择的蝴蝶图像 # figsize(10, 10)设置整个图形的大小为10x10英寸 # subplot_kw{xticks: [], yticks: []}移除所有子图的x轴和y轴刻度使图像显示更干净 fig, axes plt.subplots(nrows3, ncols3, figsize(10, 10), subplot_kw{xticks: [], yticks: []}) # 遍历所有子图和对应的随机索引 for i, ax in enumerate(axes.flat): # 读取并显示图像 # df.Filepath.iloc[random_index[i]]获取第i个随机索引对应的图像文件路径 # plt.imread()读取图像文件为numpy数组 # ax.imshow()在当前的子图中显示图像 ax.imshow(plt.imread(df.Filepath.iloc[random_index[i]])) # 设置子图标题为对应的蝴蝶种类标签 ax.set_title(df.Label.iloc[random_index[i]]) # 自动调整子图之间的间距避免标题和图像重叠 plt.tight_layout() # 显示完整的图形 plt.show()4.3特征工程我们需要将完整的数据集划分为训练集、验证集和测试集三个部分。合理的划分比例和策略能够确保模型训练的有效性并且能够准确评估模型的泛化能力。对于蝴蝶图像分类这种多分类任务我们特别需要注意保持每个数据集中各类别比例的一致性。# 第一次划分将完整数据集分为训练验证集80%和测试集20% # train_test_split是scikit-learn提供的标准数据集划分函数 # df: 完整的数据集 # test_size0.2: 测试集占总数据的20% # shuffleTrue: 在划分前打乱数据顺序避免数据排序带来的偏差 # random_state42: 设置随机种子确保每次运行结果一致便于实验复现 # stratifydf[label]: 按标签进行分层采样确保训练集、验证集和测试集中各类别比例与原始数据集相同 train_val_df, test_df train_test_split(df, test_size0.2, shuffleTrue, random_state42, stratifydf[label]) # 第二次划分将训练验证集进一步划分为训练集64%和验证集16% # 这里从剩余的80%数据中再划分出20%作为验证集即总数据的16% # stratifytrain_val_df[label]: 同样保持训练集和验证集中的类别比例一致 train_df, val_df train_test_split(train_val_df, test_size0.2, shuffleTrue, random_state42, stratifytrain_val_df[label]) # 打印各个数据集的大小行数×列数 print(fTraining set: {train_df.shape}) print(fValidation set: {val_df.shape}) print(fTest set: {test_df.shape})由于蝴蝶图像可能存在大小不一、方向各异的情况我们需要将图像标准化到统一尺寸并应用数据增强技术来提升模型的泛化能力。特别是对于训练集我们采用了多种增强手段来模拟蝴蝶在自然环境中的各种变化这样训练出来的模型在实际应用中会更鲁棒。# 定义图像的目标尺寸和批量大小 IMG_SIZE (224, 224) # 将图像统一调整为224x224像素这是很多CNN模型的常用输入尺寸 BATCH_SIZE 32 # 每个训练批次包含32张图像 # 创建训练数据的数据增强生成器 # ImageDataGenerator是Keras提供的强大工具可以实时进行数据增强和预处理 train_datagen ImageDataGenerator( rescale1./255, # 像素值归一化到0-1范围将0-255的像素值除以255 rotation_range30, # 随机旋转角度范围±30度模拟蝴蝶不同飞行角度 width_shift_range0.2, # 水平平移范围±20%宽度模拟蝴蝶在图像中的位置变化 height_shift_range0.2, # 垂直平移范围±20%高度 shear_range0.2, # 剪切变换强度20%模拟透视变化 zoom_range0.2, # 随机缩放范围80%-120%模拟相机远近变化 horizontal_flipTrue, # 允许水平翻转蝴蝶左右对称水平翻转合理 fill_modenearest # 填充新像素的方式使用最近邻像素填充变换后产生的空白区域 ) # 创建验证集和测试集的数据生成器不进行数据增强只做归一化 # 验证集和测试集只需要简单的归一化处理保持数据原始分布以准确评估模型 val_datagen ImageDataGenerator(rescale1./255) # 仅进行像素归一化 test_datagen ImageDataGenerator(rescale1./255) # 仅进行像素归一化 # 创建训练数据生成器 train_generator train_datagen.flow_from_dataframe( dataframetrain_df, # 训练集数据框 directoryimage_dir, # 图像所在目录 x_colfilename, # 数据框中包含图像文件名的列 y_collabel, # 数据框中包含标签的列 target_sizeIMG_SIZE, # 目标图像尺寸 batch_sizeBATCH_SIZE, # 每批数据的大小 class_modecategorical, # 分类模式多分类会生成one-hot编码的标签 shuffleTrue, # 打乱数据顺序避免模型学习到数据顺序 seed42 # 随机种子确保可重复性 ) # 创建验证数据生成器 val_generator val_datagen.flow_from_dataframe( dataframeval_df, # 验证集数据框 directoryimage_dir, x_colfilename, y_collabel, target_sizeIMG_SIZE, batch_sizeBATCH_SIZE, class_modecategorical, shuffleFalse # 验证集不需要打乱便于跟踪每个样本的表现 ) # 创建测试数据生成器 test_generator test_datagen.flow_from_dataframe( dataframetest_df, # 测试集数据框 directoryimage_dir, x_colfilename, y_collabel, target_sizeIMG_SIZE, batch_sizeBATCH_SIZE, class_modecategorical, shuffleFalse # 测试集也不需要打乱 )4.4构建模型我们采用经典的CNN设计模式通过多个卷积块逐步提取图像特征最后通过全连接层进行分类。这种层次化设计能够从低级特征边缘、纹理到高级特征翅膀图案、身体结构逐步学习蝴蝶的视觉特征。# 导入必要的Keras模块 from tensorflow.keras import layers, models, Input # 定义模型的输入层 # shape(224, 224, 3) 表示输入图像尺寸为224x224像素3个颜色通道RGB inputs Input(shape(224, 224, 3)) # ------------------- 第一个卷积块 ------------------- # 第一层卷积32个3x3卷积核ReLU激活函数same padding保持特征图尺寸不变 x layers.Conv2D(32, (3, 3), activationrelu, paddingsame)(inputs) # 批归一化标准化激活值加速训练并提高稳定性 x layers.BatchNormalization()(x) # 第二层卷积继续提取特征 x layers.Conv2D(32, (3, 3), activationrelu, paddingsame)(x) # 最大池化2x2窗口步长为2将特征图尺寸减半提取更显著的特征 x layers.MaxPooling2D((2, 2))(x) # Dropout随机丢弃20%的神经元防止过拟合 x layers.Dropout(0.2)(x) # ------------------- 第二个卷积块 ------------------- # 增加卷积核数量到64个提取更复杂的特征 x layers.Conv2D(64, (3, 3), activationrelu, paddingsame)(x) x layers.BatchNormalization()(x) x layers.Conv2D(64, (3, 3), activationrelu, paddingsame)(x) x layers.MaxPooling2D((2, 2))(x) # 增加Dropout比率到30%随网络加深逐渐增加正则化强度 x layers.Dropout(0.3)(x) # ------------------- 第三个卷积块 ------------------- # 进一步增加卷积核数量到128个提取更抽象的特征 # 给这一层命名便于后续可视化或特征提取 x layers.Conv2D(128, (3, 3), activationrelu, paddingsame, namelast_conv)(x) x layers.BatchNormalization()(x) x layers.Conv2D(128, (3, 3), activationrelu, paddingsame)(x) x layers.MaxPooling2D((2, 2))(x) # 继续增加Dropout比率到40% x layers.Dropout(0.4)(x) # ------------------- 展平层和全连接层 ------------------- # 将三维特征图展平为一维向量供全连接层处理 x layers.Flatten()(x) # 全连接层256个神经元进一步组合特征 x layers.Dense(256, activationrelu)(x) x layers.BatchNormalization()(x) # 最终Dropout50%的高丢弃率强正则化防止过拟合 x layers.Dropout(0.5)(x) # ------------------- 输出层 ------------------- # 输出层75个神经元对应75种蝴蝶类别softmax激活函数输出概率分布 outputs layers.Dense(75, activationsoftmax)(x) # ------------------- 模型编译 ------------------- # 创建模型实例指定输入和输出 model models.Model(inputsinputs, outputsoutputs) # 编译模型配置训练过程 # optimizeradam: 使用Adam优化器自适应学习率 # losscategorical_crossentropy: 使用分类交叉熵损失函数适合多分类问题 # metrics[accuracy]: 评估指标为准确率 model.compile(optimizeradam, losscategorical_crossentropy, metrics[accuracy]) # 打印模型结构摘要 # 显示每层的输出形状、参数数量和总参数量 model.summary()接着配置模型训练过程中的各种回调函数这些是提升训练效果和模型质量的关键工具。回调函数能够在训练的不同阶段执行特定操作比如在验证损失不再改善时提前停止训练、保存最佳模型权重、或动态调整学习率。合理配置这些回调可以显著提高训练效率防止过拟合并确保我们得到最优的模型。# ------------------- 早停回调Early Stopping ------------------- # 当验证集性能不再提升时提前停止训练防止过拟合 early_stopping keras.callbacks.EarlyStopping( monitorval_loss, # 监控验证集损失值 patience10, # 容忍验证损失连续10个epoch没有改善 restore_best_weightsTrue, # 训练结束后恢复最佳模型权重而不是最后一轮的权重 verbose1 # 显示回调的详细信息 ) # ------------------- 模型检查点回调Model Checkpoint ------------------- # 在训练过程中定期保存模型特别是保存性能最好的模型 checkpoint keras.callbacks.ModelCheckpoint( filepathbest_model.keras, # 模型保存的文件路径和名称 monitorval_loss, # 根据验证损失选择最佳模型 save_best_onlyTrue, # 只保存验证损失最小的模型而不是每个epoch都保存 save_weights_onlyFalse, # 保存完整的模型包括结构和权重而不仅仅是权重 verbose1 # 显示保存模型的提示信息 ) # ------------------- 学习率衰减回调ReduceLROnPlateau ------------------- # 当模型性能停滞时自动降低学习率帮助模型跳出局部最优 reduce_lr tf.keras.callbacks.ReduceLROnPlateau( monitorval_loss, # 监控验证集损失 factor0.5, # 学习率衰减因子当触发条件时学习率乘以0.5降低50% patience4, # 等待4个epoch如果验证损失没有改善则降低学习率 min_lr0.00001, # 学习率的最小值避免学习率过小导致训练停滞 verbose1 # 显示学习率调整的详细信息 )4.5模型评估通过调用model.fit()方法我们将准备好的训练数据、验证数据和配置好的回调函数整合在一起开始模型的迭代学习。训练过程会持续多个epoch每个epoch都会在训练集上更新模型参数并在验证集上评估模型性能直到满足停止条件为止。# 开始模型训练 # model.fit()是Keras中用于训练模型的主要方法 history model.fit( train_generator, # 训练数据生成器提供批量训练数据 validation_dataval_generator, # 验证数据生成器用于监控训练过程中的泛化能力 epochs50, # 最大训练轮数实际可能因早停而提前结束 batch_size32, # 每个训练批次的样本数量与生成器中设置的一致 callbacks[early_stopping, checkpoint, reduce_lr], # 训练回调函数列表 verbose1 # 训练过程详细程度1显示进度条和每个epoch的信息 )4.6模型评估开始对训练好的模型进行全面的性能评估。我们首先需要确定在哪个epoch模型表现最好然后详细分析这个最佳epoch的各项指标。这是因为在训练过程中模型性能可能会有波动我们关心的是模型在整个训练过程中的最佳状态而不是最后一个epoch的状态特别是如果触发了早停机制的话。def get_best_epoch_details(history): 从训练历史中提取最佳epoch的详细信息 参数: history: model.fit()返回的历史记录对象包含每个epoch的指标 返回: epoch_details: 包含最佳epoch所有指标值的字典 # 获取所有epoch的验证损失值列表 val_losses history.history[val_loss] # 找到最小验证损失对应的索引 # min(val_losses): 找到验证损失的最小值 # val_losses.index(): 找到该最小值在列表中的位置索引 min_val_loss_index val_losses.index(min(val_losses)) # 最佳epoch编号列表索引从0开始epoch编号从1开始所以要1 best_epoch min_val_loss_index 1 # 创建一个字典来存储最佳epoch的所有指标 epoch_details {} # 遍历历史记录中的所有指标 for key in history.history.keys(): # 获取该指标在最佳epoch的值 epoch_details[key] history.history[key][min_val_loss_index] # 将最佳epoch编号也添加到字典中 epoch_details[best_epoch] best_epoch return epoch_details # 调用函数获取最佳epoch的详细信息 best_epoch_details get_best_epoch_details(history) # 打印最佳epoch的各项指标 print(fBest epoch details: {best_epoch_details})接着对模型的训练过程进行可视化分析通过绘制准确率和损失曲线来直观展示模型的学习动态。这些曲线能够帮助我们诊断模型训练的健康状况识别潜在问题如过拟合或欠拟合并评估模型是否已经充分收敛。对于深度学习项目来说这种可视化分析是不可或缺的调试和评估手段。# 创建一个新的图形窗口设置尺寸为12x4英寸 # 宽12英寸、高4英寸的尺寸能够很好地并排显示两个子图 plt.figure(figsize(12, 4)) # ------------------- 第一个子图准确率曲线 ------------------- # 创建1行2列的子图网格这是第1个子图 plt.subplot(1, 2, 1) # 绘制训练准确率曲线 # history.history[accuracy]: 训练过程中每个epoch的训练准确率记录 # labelTraining Accuracy: 设置曲线标签用于图例显示 plt.plot(history.history[accuracy], labelTraining Accuracy) # 绘制验证准确率曲线 # history.history[val_accuracy]: 训练过程中每个epoch的验证准确率记录 plt.plot(history.history[val_accuracy], labelValidation Accuracy) # 设置子图标题和坐标轴标签 plt.title(Model Accuracy) # 图表标题 plt.xlabel(Epoch) # x轴标签训练轮数 plt.ylabel(Accuracy) # y轴标签准确率 # 显示图例区分训练曲线和验证曲线 plt.legend() # 添加网格线便于观察曲线变化趋势 plt.grid(True) # ------------------- 第二个子图损失曲线 ------------------- # 创建1行2列的子图网格这是第2个子图 plt.subplot(1, 2, 2) # 绘制训练损失曲线 # history.history[loss]: 训练过程中每个epoch的训练损失记录 plt.plot(history.history[loss], labelTraining Loss) # 绘制验证损失曲线 # history.history[val_loss]: 训练过程中每个epoch的验证损失记录 plt.plot(history.history[val_loss], labelValidation Loss) # 设置子图标题和坐标轴标签 plt.title(Model Loss) # 图表标题 plt.xlabel(Epoch) # x轴标签训练轮数 plt.ylabel(Loss) # y轴标签损失值 # 显示图例 plt.legend() # 添加网格线 plt.grid(True) # 注意这里缺少了plt.show()实际运行时应加上以显示图形 # plt.show()接着对模型在验证集上的具体预测结果进行可视化展示。通过随机选取一批验证图像我们不仅可以看到模型预测的类别还能将其与真实标签进行对比直观了解模型在哪些图像上预测正确在哪些图像上预测错误。这种可视化方式对于理解模型的实际表现、识别模型容易混淆的蝴蝶种类非常有帮助。# 从验证数据生成器中获取一个批次的图像和标签 # next()函数获取生成器的下一个批次数据 val_images, val_labels next(val_generator) # 使用训练好的模型对验证图像进行预测 # model.predict()对输入图像进行前向传播得到每个类别的预测概率 pred_labels model.predict(val_images) # 将预测的概率分布转换为具体的类别标签 # np.argmax(axis1): 在类别维度上找到概率最大的索引即预测的类别 pred_labels np.argmax(pred_labels, axis1) # 将验证集的one-hot编码标签转换为具体的类别标签 # val_labels是one-hot编码格式如[0,0,1,0,...,0]表示第3类 # np.argmax(axis1): 找到每个样本标签中值为1的位置索引 true_labels np.argmax(val_labels, axis1) # 获取类别索引映射关系 # val_generator.class_indices: 返回类别名称到数字索引的字典如{Adonis: 0, Brimstone: 1, ...} class_indices val_generator.class_indices # 创建反向映射从数字索引到类别名称 # 通过字典推导式创建新的字典将键值对反转 class_names {v: k for k, v in class_indices.items()} def display_images(images, true_labels, pred_labels, class_names, num_images9): 可视化展示模型的预测结果 参数: images: 图像数据数组 true_labels: 真实标签数组数字索引格式 pred_labels: 预测标签数组数字索引格式 class_names: 类别索引到名称的映射字典 num_images: 要显示的图像数量默认为9 # 创建一个大的图形窗口尺寸为15x15英寸 plt.figure(figsize(15, 15)) # 循环显示指定数量的图像 for i in range(num_images): # 创建子图网格5行3列共15个子图位置 # i1: 子图位置索引从1开始 plt.subplot(5, 3, i 1) # 显示当前图像 # images[i]: 第i张图像注意图像值已经在0-1范围内经过了归一化 plt.imshow(images[i]) # 获取当前图像的真实标签名称和预测标签名称 true_label class_names[int(true_labels[i])] # 将数字索引转换为类别名称 pred_label class_names[int(pred_labels[i])] # 将数字索引转换为类别名称 # 设置子图标题显示真实标签和预测标签 # 如果预测正确可以使用不同颜色标注这里可以添加颜色判断 color green if true_label pred_label else red plt.title(fTrue: {true_label}\nPred: {pred_label}, colorcolor) # 关闭坐标轴让图像显示更清晰 plt.axis(off) # 自动调整子图布局避免重叠 plt.tight_layout() # 显示完整的图形 plt.show() # 调用函数显示15个验证样本的预测结果 display_images(val_images, true_labels, pred_labels, class_names, num_images15)5.总结本文基于自定义的CNN卷积神经网络构建了一个蝴蝶图像分类识别模型在Kaggle提供的包含75种蝴蝶类别的数据集上进行了系统的实验研究。通过构建包含三个卷积块的特征提取网络结合批归一化、Dropout正则化和数据增强策略模型在验证集上取得了79.9%的准确率训练过程在第44个epoch达到最佳状态。实验结果表明所设计的CNN架构能够有效学习蝴蝶翅膀的纹理、颜色和形态特征但对部分相似物种仍存在一定的混淆现象。可视化分析显示模型在多数常见蝴蝶种类上表现良好但在少数样本量较少的类别上识别精度有待提升。通过早停机制、学习率衰减和模型检查点等训练策略的配合使用有效防止了过拟合并确保了模型的最佳性能。该研究为基于深度学习的生物多样性图像识别提供了实践参考后续可通过引入迁移学习、注意力机制等技术进一步优化模型性能。源代码import numpy as np import pandas as pd import os import matplotlib.pyplot as plt import seaborn as sns import random import matplotlib import matplotlib.cm as cm from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report, confusion_matrix import tensorflow as tf import keras_tuner as kt from tensorflow import keras from tensorflow.keras import layers, models from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout from tensorflow.keras.preprocessing.image import ImageDataGenerator, load_img, img_to_array, array_to_img from tensorflow.keras import regularizers from tensorflow.keras.callbacks import LearningRateScheduler from kerastuner.tuners import RandomSearch import warnings warnings.filterwarnings(ignore) df pd.read_csv(./butterfly-image-classification/Training_set.csv) class_counts df[label].value_counts() # her türün sayısını alır plt.figure(figsize(15, 9)) # grafik boyutu ayarlanır sns.barplot(xclass_counts.index, yclass_counts.values, colorpurple) plt.title(Distribution of All Butterfly Species, fontsize18, fontweightbold) plt.xlabel(Butterfly Species, fontsize12) plt.ylabel(Number of Images, fontsize12) plt.xticks(rotation90) # x ekseninde tüm isimleri 90 derece döndürürerek okunabilir hale getirir. plt.tight_layout() plt.show() image_dir ./butterfly-image-classification/train df[Filepath] df[filename].apply(lambda x: os.path.join(image_dir, x)) df[Label] df[label] random_index np.random.randint(0, len(df), 9) fig, axes plt.subplots(nrows3, ncols3, figsize(10, 10), subplot_kw{xticks: [], yticks: []}) for i, ax in enumerate(axes.flat): ax.imshow(plt.imread(df.Filepath.iloc[random_index[i]])) ax.set_title(df.Label.iloc[random_index[i]]) plt.tight_layout() plt.show() train_val_df, test_df train_test_split(df, test_size0.2, shuffleTrue, random_state42, stratifydf[label]) train_df, val_df train_test_split(train_val_df, test_size0.2, shuffleTrue, random_state42, stratifytrain_val_df[label]) print(fTraining set: {train_df.shape}) print(fValidation set: {val_df.shape}) print(fTest set: {test_df.shape}) IMG_SIZE (224, 224) BATCH_SIZE 32 train_datagen ImageDataGenerator( rescale1./255, # normalize et rotation_range30, # rastgele döndür width_shift_range0.2, # yatay kaydır height_shift_range0.2, # dikey kaydır shear_range0.2, # kesme zoom_range0.2, # yakınlaştır horizontal_flipTrue, # yatay çevir fill_modenearest # boşlukları doldur ) val_datagen ImageDataGenerator(rescale1./255) test_datagen ImageDataGenerator(rescale1./255) train_generator train_datagen.flow_from_dataframe( dataframetrain_df, directoryimage_dir, x_colfilename, y_collabel, target_sizeIMG_SIZE, batch_sizeBATCH_SIZE, class_modecategorical, shuffleTrue, seed42 ) val_generator val_datagen.flow_from_dataframe( dataframeval_df, directoryimage_dir, x_colfilename, y_collabel, target_sizeIMG_SIZE, batch_sizeBATCH_SIZE, class_modecategorical, shuffleFalse ) test_generator test_datagen.flow_from_dataframe( dataframetest_df, directoryimage_dir, x_colfilename, y_collabel, target_sizeIMG_SIZE, batch_sizeBATCH_SIZE, class_modecategorical, shuffleFalse ) from tensorflow.keras import layers, models, Input inputs Input(shape(224, 224, 3)) # 1. Evrişim bloğu x layers.Conv2D(32, (3, 3), activationrelu, paddingsame)(inputs) x layers.BatchNormalization()(x) x layers.Conv2D(32, (3, 3), activationrelu, paddingsame)(x) x layers.MaxPooling2D((2,2))(x) x layers.Dropout(0.2)(x) # 2. Evrişim bloğu x layers.Conv2D(64, (3, 3), activationrelu, paddingsame)(x) x layers.BatchNormalization()(x) x layers.Conv2D(64, (3, 3), activationrelu, paddingsame)(x) x layers.MaxPooling2D((2,2))(x) x layers.Dropout(0.3)(x) # 3. Evrişim bloğu x layers.Conv2D(128, (3, 3), activationrelu, paddingsame, namelast_conv)(x) x layers.BatchNormalization()(x) x layers.Conv2D(128, (3, 3), activationrelu, paddingsame)(x) x layers.MaxPooling2D((2,2))(x) x layers.Dropout(0.4)(x) # Flatten ve Fully Connected x layers.Flatten()(x) x layers.Dense(256, activationrelu)(x) x layers.BatchNormalization()(x) x layers.Dropout(0.5)(x) # Output Layer outputs layers.Dense(75, activationsoftmax)(x) # Model tanımı model models.Model(inputsinputs, outputsoutputs) model.compile(optimizeradam, losscategorical_crossentropy, metrics[accuracy]) model.summary() early_stopping keras.callbacks.EarlyStopping( monitorval_loss, patience10, restore_best_weightsTrue, verbose1 # Durumu yazdırır ) checkpoint keras.callbacks.ModelCheckpoint( filepathbest_model.keras, monitorval_loss, save_best_onlyTrue, save_weights_onlyFalse, # Modelin tamamını kaydeder verbose1 ) reduce_lr tf.keras.callbacks.ReduceLROnPlateau( monitorval_loss, factor0.5, # %50 azaltma - daha yumuşak patience4, min_lr0.00001, # Daha küçük min LR verbose1 ) history model.fit( train_generator, validation_dataval_generator, epochs50, batch_size32, callbacks[early_stopping, checkpoint, reduce_lr], verbose1 ) def get_best_epoch_details(history): val_losses history.history[val_loss] min_val_loss_index val_losses.index(min(val_losses)) best_epoch min_val_loss_index 1 epoch_details {} for key in history.history.keys(): epoch_details[key] history.history[key][min_val_loss_index] epoch_details[best_epoch] best_epoch return epoch_details best_epoch_details get_best_epoch_details(history) print(fBest epoch details: {best_epoch_details}) plt.figure(figsize(12, 4)) plt.subplot(1, 2, 1) plt.plot(history.history[accuracy], labelTraining Accuracy) plt.plot(history.history[val_accuracy], labelValidation Accuracy) plt.title(Model Accuracy) plt.xlabel(Epoch) plt.ylabel(Accuracy) plt.legend() plt.grid(True) plt.subplot(1, 2, 2) plt.plot(history.history[loss], labelTraining Loss) plt.plot(history.history[val_loss], labelValidation Loss) plt.title(Model Loss) plt.xlabel(Epoch) plt.ylabel(Loss) plt.legend() plt.grid(True) val_images, val_labels next(val_generator) pred_labels model.predict(val_images) pred_labels np.argmax(pred_labels, axis1) true_labels np.argmax(val_labels, axis1) class_indices val_generator.class_indices class_names {v: k for k, v in class_indices.items()} def display_images(images, true_labels, pred_labels, class_names, num_images9): plt.figure(figsize(15, 15)) for i in range(num_images): plt.subplot(5, 3, i 1) plt.imshow(images[i]) true_label class_names[int(true_labels[i])] pred_label class_names[int(pred_labels[i])] plt.title(fTrue: {true_label}\nPred: {pred_label}) plt.axis(off) plt.tight_layout() plt.show() display_images(val_images, true_labels, pred_labels, class_names, num_images15)资料获取更多粉丝福利关注下方公众号获取