深度学习实战-基于ResNet50的葡萄叶图像分类识别模型

发布时间:2026/7/14 18:27:44
深度学习实战-基于ResNet50的葡萄叶图像分类识别模型 ‍♂️ 个人主页艾派森的个人主页✍作者简介Python学习者 希望大家多多支持我们一起进步如果文章对你有帮助的话欢迎评论 点赞 收藏 加关注目录1.项目背景2.数据集介绍3.技术工具4.实验过程4.1导入数据4.2数据可视化4.3特征工程4.4构建模型4.5训练模型4.6模型评估5.总结源代码1.项目背景在现代葡萄产业的精准化管理中品种的快速且准确识别是确保果园分级采收、提升酿酒品质以及保护种质资源的核心环节。传统的品种鉴定高度依赖农业专家的经验不仅效率受限且在面对 Ak、Dimnit 或 Nazli 等形态极为接近的葡萄品种时叶缘锯齿的微小差异、叶脉的分布走向以及裂片的深浅极易造成视觉误判。随着智慧农业的深入发展如何利用计算机视觉技术替代繁琐的人工鉴定并在不同光照及生长环境下实现稳健的品种识别已成为数字化果园建设中亟待解决的实战课题。本项目针对葡萄叶片形态的多样性与复杂性构建了一套基于ResNet50残差网络的深度学习识别方案。通过引入“迁移学习”策略模型能够借用预训练架构在通用视觉领域的感知本能并结合针对性的局部微调Fine-tuning精准捕捉葡萄叶片特有的生物学性状。实验过程不仅涵盖了从自照明系统采集的高质量影像预处理还深入探讨了通过自定义监控回调与动态学习率调节来优化模型收敛轨迹的技巧。本实战不仅展示了深度学习在 5 类典型葡萄品种识别中的优异性能更通过多维度的分类报告解析了模型在相似特征间的判别边界为开发便携式葡萄品种智能鉴定设备提供了可落地的算法参考。2.数据集介绍本实验数据集来源于Kaggle葡萄叶图像数据集包括Ak、Ala Idris、Büzgülü、Dimnit 和 Nazli 葡萄藤叶子图片。使用特殊的自照明系统拍摄了5个品种共500张葡萄叶图像。3.技术工具Python版本:3.9代码编辑器jupyter notebook4.实验过程4.1导入数据在开启深度学习流水线之前首要任务是构建一个稳健的数据索引环境。我们首先集成了数值计算核心库 NumPy 与深度学习框架 TensorFlow并引入了用于数据探索性分析的 Pandas 和 Seaborn。针对葡萄叶片数据集的存储结构我们通过 Python 的文件系统接口对目标路径进行扫描。在初步的目录检索中我们排除了非图像的说明文档从而精准锁定五个核心品种的存储文件夹。通过这种自动化的路径解析我们不仅确认了数据集的类别完整性也为后续将原始图像转化为张量流奠定了逻辑基础确保每一类品种都能在预处理阶段被准确地打上语义标签。import numpy as np import os import tensorflow as tf import matplotlib.pyplot as plt import seaborn as sns import pandas as pd from sklearn.metrics import classification_report # 定义数据集根路径 DATA_DIR ../input/grapevine-leaves-image-dataset/Grapevine_Leaves_Image_Dataset # 获取目录下所有子文件夹即品种分类 folder_elements os.listdir(DATA_DIR) # 移除与图像无关的引用说明文件 folder_elements.remove(Grapevine_Leaves_Image_Dataset_Citation_Request.txt) # 输出并核验最终参与训练的类别名称 print(f - Classes name : {folder_elements})4.2数据可视化为了全面覆盖五个葡萄品种的视觉特性我们构建了一套随机抽样与矩阵排布的展示系统。通过choice_random_images_for_each_classe函数我们从每个品种的文件夹中随机提取 4 张具有代表性的原始影像并利用 Matplotlib 搭建起一个 5 x 4 的对比画布。这种排布方式允许我们横向观察同类品种内部的形态一致性纵向对比不同品种间的显著差异。通过tf.keras.utils.load_img加载的图像保留了叶片的细微纹理这对于评估模型在处理诸如 Ak阿克、Bg博兹加等品种时是否能有效识别其叶缘锯齿密度及叶面粗糙度至关重要。# --- 1. 定义随机路径选择逻辑 --- def choice_random_images_for_each_classe(list_dir_data, nbr_random_images4): random_dir_class_images [] # 遍历每个品种的目录 for c in list_dir_data: class_images os.listdir(c) # 从该品种中随机挑选指定数量的图片文件名 random_class_images np.random.choice(class_images, nbr_random_images) # 拼接成完整的物理路径 random_dir_class_images.append([os.path.join(c, img) for img in random_class_images]) # 重新整理数组维度适配后续的循环显示 list_dir_images np.array(random_dir_class_images).reshape(len(list_dir_data), nbr_random_images) return list_dir_images # --- 2. 批量展示随机样本 --- def display_random_images(list_dir_data, list_classes_name, n_classes2, nbr_random_images4): # 初始化子图画布 fig, ax plt.subplots(ncolsnbr_random_images, nrowsn_classes) fig.set_size_inches(16, 12) # 调整画布高度以容纳 5 个类别 fig.suptitle(\nRandom images for each classe\n, cgray, fontsize20) # 获取随机抽取的路径矩阵 random_dir_class_images choice_random_images_for_each_classe(list_dir_data) # 嵌套循环外层控制品种内层控制样本数量 for i, cn in enumerate(list_classes_name): for j, images_dir in enumerate(random_dir_class_images[i]): # 实时加载并渲染图像 img tf.keras.utils.load_img(images_dir) ax[i, j].imshow(img, aspectequal) ax[i, j].set_title(f{cn} image {j1}) ax[i, j].axis(off) # 移除坐标轴突出叶片主体 plt.tight_layout() plt.show() # 执行可视化展示针对 5 个品种每类显示 4 张 display_random_images(dir_classes_list, folder_elements, n_classes5, nbr_random_images4)4.3特征工程本环节我们通过封装split_data函数将葡萄叶片数据集严谨地切分为训练集、验证集与测试集。我们采用了tf.keras.utils.image_dataset_from_directory接口将图像统一调整为 224 x 224 的标准分辨率并以Categorical模式处理 5 类品种标签。为了榨干硬件性能我们引入了cache()内存缓存技术与prefetch(tf.data.AUTOTUNE)预取机制这使得 GPU 在进行当前批次计算时CPU 能同步准备下一组数据极大地减少了 I/O 阻塞。通过这种流式处理我们确保了模型在 70% 数据的驱动下进行学习同时在剩余的 15% 验证集上进行参数微调最终由独立的 15% 测试集给出客观评价。# --- 1. 定义数据三分法与性能优化函数 --- def split_data(dir_data, image_size, label_modelbinary, batch_size32, percentage_val_data0.15, percentage_test_data0.15): 将原始目录数据划分为训练、验证与测试集并开启流水线优化 total_val_test_split percentage_val_data percentage_test_data if total_val_test_split 1.0: raise ValueError(验证集与测试集占比之和必须小于 1.0) # 提取训练集主体 train_data tf.keras.utils.image_dataset_from_directory( directorydir_data, label_modelabel_model, color_modergb, batch_sizebatch_size, image_sizeimage_size, shuffleTrue, seed101, validation_splittotal_val_test_split, subsettraining, ) # 提取临时集合后续再切分为验证与测试 temp_data tf.keras.utils.image_dataset_from_directory( directorydir_data, label_modelabel_model, color_modergb, batch_sizebatch_size, image_sizeimage_size, shuffleTrue, seed101, validation_splittotal_val_test_split, subsetvalidation, ) # 计算批次数量并执行逻辑切分 total_temp_batches tf.data.experimental.cardinality(temp_data).numpy() validation_batches int(total_temp_batches * (percentage_val_data / total_val_test_split)) validation_data temp_data.take(validation_batches) test_data temp_data.skip(validation_batches) # --- 数据流水线性能炼金术 --- # cache: 缓存数据至内存shuffle: 打乱顺序防止偏差prefetch: 异步预取加速计算 train_data train_data.cache().shuffle(1000).prefetch(buffer_sizetf.data.AUTOTUNE) validation_data validation_data.cache().prefetch(buffer_sizetf.data.AUTOTUNE) test_data test_data.cache().prefetch(buffer_sizetf.data.AUTOTUNE) return train_data, validation_data, test_data # 执行 224x224 分辨率下的五分类数据划分设定 20% 为非训练数据测试与验证各占 10% 左右 training_data, validation_data, testing_data split_data(DATA_DIR, (224, 224), categorical, 32, 0.2)针对葡萄叶片在实际拍摄中可能出现的倾斜、远近不一或光照过强等问题我们构建了一个基于tf.keras.Sequential的增强层。这套系统集成了水平/垂直翻转、20% 比例的随机缩放、旋转以及对比度调整。通过可视化增强前后的样本对比可以看出同一张叶片被赋予了多样的“生命力”它可能在画面中发生偏移也可能呈现出不同的色彩对比。这种动态增强技术本质上是在不增加物理采集成本的前提下人为制造了数倍的特征扰动强迫模型去学习叶片最本质的脉络结构而非死记硬背像素位置从而显著提升了分类器在复杂多变果园环境下的稳健性。# --- 2. 构建数据增强序列 --- def data_augmentation(): # 将增强算子封装为可调用的模型层 data_aug tf.keras.Sequential([ tf.keras.layers.RandomFlip(horizontal_and_vertical), # 模拟多角度叶片分布 tf.keras.layers.RandomZoom(0.2, fill_modenearest), # 模拟拍摄距离变化 tf.keras.layers.RandomRotation(0.2), # 模拟自然摆放角度 tf.keras.layers.RandomContrast(0.2) # 模拟不同天气下的光照强度 ]) return data_aug data_augmentor data_augmentation() # --- 3. 增强效果可视化验证 --- # 选取样本批次中的一张图连续施加 10 次随机增强变换 sample_image tf.keras.utils.array_to_img(example_batch_images[2]) images [sample_image] for i in range(5): # 对单张图像执行动态增量变换 image_aug data_augmentor(tf.expand_dims(sample_image, axis0)) image_aug tf.keras.utils.array_to_img(tf.squeeze(image_aug)) images.append(image_aug) # 绘制原始图与增强效果的对比阵列 fig, axs plt.subplots(1, 6, figsize(16, 7)) for ax, image, title in zip(axs, images, [Original, Aug 1, Aug 2, Aug 3, Aug 4, Aug 5]): ax.imshow(image) ax.set_title(title) ax.axis(off) plt.show()4.4构建模型本阶段我们构建了一个高度模块化的深度学习架构。首先通过features_extractor_resNet50函数加载预训练的 ResNet50 权重并采取了“阶梯式冻结”方案我们冻结了前 125 层的基础特征提取算子确保模型底层对边缘和色块的敏感度不被破坏同时释放后续的高级卷积层直至conv4_block3_out参与训练以适配葡萄叶片特有的品种特征。在分类头Classifier Head的设计上我们采用了由 1024、512 到 256 个神经元组成的递减式全连接网络并高频嵌入了BatchNormalization层以稳定由于微调带来的参数波动。最后的Dropout(0.3)层则作为正则化屏障确保 5 类品种的预测概率分布既精准又具备良好的泛化性能。# --- 1. 定义 ResNet50 特征提取器含微调逻辑 --- def features_extractor_resNet50(input_images_size, name_last_layer, fine_tune_at150): # 载入 ImageNet 预训练权重不包含原始千分类顶层 pre_trained_resNet50 tf.keras.applications.resnet.ResNet50( input_shapeinput_images_size, include_topFalse, weightsimagenet) # 默认开启可训练模式后续通过循环局部锁定 pre_trained_resNet50.trainable True # 冻结前 fine_tune_at 层保护基础通用特征 for layer in pre_trained_resNet50.layers[:fine_tune_at]: layer.trainable False # 截取指定卷积层的输出作为特征图 last_layer pre_trained_resNet50.get_layer(name_last_layer) last_output last_layer.output return pre_trained_resNet50, last_output # --- 2. 构建自定义深度分类头 --- def classifier(input): # 全局平均池化配合平坦化大幅降低参数冗余 x tf.keras.layers.GlobalAveragePooling2D()(input) x tf.keras.layers.Flatten()(x) # 逐层递减的全连接层整合高阶语义特征 x tf.keras.layers.Dense(1024, activationrelu)(x) x tf.keras.layers.BatchNormalization()(x) x tf.keras.layers.Dense(512, activationrelu)(x) x tf.keras.layers.BatchNormalization()(x) x tf.keras.layers.Dense(256, activationrelu)(x) x tf.keras.layers.BatchNormalization()(x) # 引入 30% 比例的失活层防止模型产生训练依赖 x tf.keras.layers.Dropout(0.3)(x) # 输出层对应 5 个葡萄品种的分类概率 x tf.keras.layers.Dense(5, activationsoftmax)(x) return x # --- 3. 封装最终端到端模型 --- def final_model(input_images_size, name_last_layer, fine_tune_at150): inputs tf.keras.Input(shapeinput_images_size) # 集成之前定义的数据增强模块实现实时训练增强 augmented_inputs data_augmentation()(inputs) pre_trained_model, output features_extractor_resNet50(input_images_size, name_last_layer, fine_tune_at) # 提取特征流注意在微调时 training 设置为 False保持 BN 层的均值方差稳定 features pre_trained_model(augmented_inputs, trainingFalse) # 挂载分类头 x classifier(features) model tf.keras.Model(inputs, x) # 配置优化算法使用较小的学习率0.0001以配合微调过程 model.compile( optimizertf.keras.optimizers.Adam( learning_rate0.0001, beta_10.9, beta_20.999, epsilon1e-08 ), losstf.keras.losses.CategoricalCrossentropy(), metrics[accuracy] ) return model # 实例化模型输入尺寸 224x224截取 conv4 块输出微调后 75 层左右 INPUT_IMAGE_SIZE (224,224,3) NAME_LAST_LAYER conv4_block3_out model final_model(INPUT_IMAGE_SIZE, NAME_LAST_LAYER, 125) # 输出模型拓扑结构 model.summary()通过model.summary()的反馈可以观察到模型在final_model中巧妙地将数据增强层直接嵌套在输入端这意味着增强操作完全在计算图中完成具有极高的并行效率。我们选择截断在conv4_block3_out层而非网络最末端是因为 ResNet 后期的特征图尺度过小对于葡萄叶片这种依靠细微边缘锯齿来判别的任务保留稍大尺寸的特征图有助于捕捉更丰富的空间细节。此时的模型已经整装待发准备在接下来的拟合过程中将预训练的通用视觉经验转化为对葡萄品种的精准洞察。4.5训练模型本环节的训练策略核心在于“稳健增益”。我们首先构建了一个名为EarlyStoppingCallback_90的自定义监听器它在每轮迭代结束时自动复核模型的双向精度——一旦训练集与验证集的准确率双双突破90%系统将果断触发保护性停机锁定当前最优权重。与此同时为了应对训练后期的梯度平原问题我们部署了ReduceLROnPlateau机制当发现验证集损失值在连续 3 轮内未能进一步下降时系统会自动将学习率压缩至原先的10%。这种降速策略能让优化器在损失函数的波谷处进行更细微的探索从而精准定位 5 类葡萄品种之间的最窄分类边界。# --- 1. 定义自定义“双达标”早期停止回调 --- class EarlyStoppingCallback_90(tf.keras.callbacks.Callback): 当训练准确率与验证准确率同时达到 90% 时自动终止训练 def on_epoch_end(self, epoch, logs{}): # 获取当前轮次的监控指标 if logs[accuracy] 0.90 and logs[val_accuracy] 0.90: self.model.stop_training True print(f\n[预判终止] 第 {epoch1} 轮训练与验证精度均已达标90%停止迭代以防止过拟合) # --- 2. 配置动态学习率衰减策略 --- # 监控验证集损失若连续 3 轮无改善则降低学习率 reduce_lr tf.keras.callbacks.ReduceLROnPlateau( monitorval_loss, factor0.1, # 学习率缩减倍数 patience3, # 容忍轮次 min_lr1e-10 # 学习率下限 ) # --- 3. 启动模型拟合程序 --- EPOCHS 50 hist model.fit( training_data, epochsEPOCHS, # 集成自定义逻辑与标准优化回调 callbacks[EarlyStoppingCallback_90(), reduce_lr], validation_datavalidation_data )在 50 轮的最大预期迭代中这种“双保险”机制显著提升了训练质量。由于葡萄叶片的品种识别极易受到实验光照背景的干扰单纯观察训练精度Accuracy是具有欺骗性的唯有当验证精度同步跟进才意味着ResNet50的残差模块真正消化了叶缘轮廓与叶脉布局的视觉逻辑。随着学习率的适时下调我们可以观察到val_loss呈现出更加平滑的收敛轨迹。4.6模型评估为了客观呈现ResNet50在微调过程中的演进状态我们构建了performance_avaluation函数将训练集与验证集的准确率Accuracy与损失值Loss进行同步对比。理想的评估图表应当显示出两条曲线稳步上升或下降且间隔较小这标志着模型在学习过程中成功保持了泛化性。通过红色标记的训练曲线与蓝色的验证曲线我们可以清晰地观察到在触发ReduceLROnPlateau后模型在损失平面上的进一步下潜。这种数字化的轨迹复盘能够帮助我们确认模型是在“理解特征”还是在“背诵数据”。# --- 1. 定义性能轨迹可视化函数 --- def performance_avaluation(train_accuracy, train_loss, validation_accuracy, validation_loss, size_fig(16, 4)): # 绘制准确率随轮次演进的对比图 plt.figure(figsizesize_fig) plt.plot(train_accuracy, label训练准确率, cred, markero) plt.plot(validation_accuracy, label验证准确率, markero) plt.xlabel(迭代轮次 (Epoch)) plt.ylabel(准确率) plt.title(\n模型准确率变化轨迹\n, cred, fontsize14) plt.legend() plt.grid(True, alpha0.3) # 绘制损失函数随轮次演进的对比图 plt.figure(figsize(16, 4)) plt.plot(train_loss, label训练损失, cred, markero) plt.plot(validation_loss, label验证损失, markero) plt.xlabel(迭代轮次 (Epoch)) plt.ylabel(损失值) plt.title(\n损失函数收敛轨迹\n, cred, fontsize14) plt.legend() plt.grid(True, alpha0.3) # 提取训练历史数据 train_accuracy hist.history[accuracy] train_loss hist.history[loss] validation_accuracy hist.history[val_accuracy] validation_loss hist.history[val_loss] # 执行可视化绘图 performance_avaluation(train_accuracy, train_loss, validation_accuracy, validation_loss)当模型的训练轨迹趋于平稳后我们使用从未参与训练的独立测试批次来检验其真正的实战水平。通过model.predict获取预测概率分布并利用np.argmax锁定置信度最高的类别我们得以构建出一份详尽的分类报告Classification Report。这份报告涵盖了针对 5 个葡萄品种的精确率Precision、召回率Recall以及F1-Score。对于阿克Ak或博兹加Bg等特定品种观察其召回率能反映模型是否存在“漏诊”现象。这种基于真实样本的统计学复盘为模型从实验室环境走向自动化果园管理提供了最核心的技术背书。# --- 2. 独立批次预测与多指标性能复盘 --- # 使用测试集样本进行预测此处 images 与 labels 为预先提取的测试批次 predict model.predict(images) # 将 One-hot 编码的预测值与真实标签转换为类别索引 predict_idx np.argmax(predict, axis1) true_labels_idx np.argmax(labels, axis1) # 生成并打印按品种分类的详细报告 report classification_report( true_labels_idx, predict_idx, target_names[i for i in folder_elements] ) print(report)5.总结本实验围绕葡萄品种的自动化识别利用采集自专业自照明系统的葡萄叶影像数据集成功构建并验证了基于ResNet50的深度学习分类模型。该数据集涵盖了 Ak、Ala Idris、Büzgülü、Dimnit 及 Nazli 五个核心品种通过迁移学习与局部微调策略模型在训练集上表现出极强的特征捕获能力准确率达到95%且损失值有效收敛至0.1559。在独立测试集的评估中模型展现了科学的判别逻辑尤其是对 Dimnit 品种实现了100%的精准识别而加权平均 F1 分数达到0.82证明了残差网络在处理具有细微形态差异的农学图像时具有显著优势。尽管受限于 500 张原始样本的规模部分品种在召回率上仍有波动但整体81%的测试准确率已充分论证了计算机视觉技术在智慧果园管理中进行品种鉴定的实战价值。本研究不仅为葡萄种质资源的数字化保护提供了技术路径也为未来在复杂自然光照下实现端侧设备的实时监测奠定了坚实的算法基础。源代码import numpy as np import os import tensorflow as tf import matplotlib.pyplot as plt import seaborn as sns import pandas as pd from sklearn.metrics import classification_report DATA_DIR ../input/grapevine-leaves-image-dataset/Grapevine_Leaves_Image_Dataset folder_elements os.listdir(DATA_DIR) folder_elements.remove(Grapevine_Leaves_Image_Dataset_Citation_Request.txt) print(f - Classes name : {folder_elements}) # display random images def choice_random_images_for_each_classe(list_dir_data,nbr_random_images4) : random_dir_class_images [] for c in list_dir_data : class_images os.listdir(c) random_class_images np.random.choice(class_images,nbr_random_images) random_dir_class_images.append([os.path.join(c, img) for img in random_class_images]) list_dir_images np.array(random_dir_class_images).reshape(len(list_dir_data),nbr_random_images) return list_dir_images def display_random_images(list_dir_data,list_classes_name,n_classes2,nbr_random_images4) : fig , ax plt.subplots(ncolsnbr_random_images,nrowsn_classes) fig.set_size_inches(16,5) fig.suptitle(\nRandom images for each classe\n,cgray,fontsize20) random_dir_class_images choice_random_images_for_each_classe(list_dir_data) for i,cn in enumerate(list_classes_name) : for j,images_dir in enumerate(random_dir_class_images[i]) : img tf.keras.utils.load_img(images_dir) ax[i,j].imshow(img,aspectequal) ax[i,j].set_title(f{cn} image {j1}) ax[i,j].axis(off) plt.tight_layout() plt.show() display_random_images(dir_classes_list,folder_elements,n_classes5,nbr_random_images4) def split_data(dir_data, image_size, label_modelbinary, batch_size32, percentage_val_data0.15, percentage_test_data0.15): Chia dữ liệu thành 3 tập: training, validation, testing Args: dir_data (str): đường dẫn của thư mục dữ liệu. image_size (tuple): Kích thước của ảnh (height, width). label_model (str): Label mode (binary, categorical, or None). batch_size (int): số lượng mẫu trên một lô. percentage_val_data (float): Phần dữ liệu để xác định đánh giá. percentage_test_data (float): Phần dữ liệu để kiểm thử. Returns: train_data, validation_data, test_data: Tập dữ liệu cho traininh, testing, validation. total_val_test_split percentage_val_data percentage_test_data if total_val_test_split 1.0: raise ValueError(The sum of percentage_val_data and percentage_test_data must be less than 1.0.) # Tạo tập dữ liệu đào tạo và tạm thời (xác nhận kiểm tra) train_data tf.keras.utils.image_dataset_from_directory( directorydir_data, label_modelabel_model, color_modergb, batch_sizebatch_size, image_sizeimage_size, shuffleTrue, seed101, validation_splittotal_val_test_split, subsettraining, ) temp_data tf.keras.utils.image_dataset_from_directory( directorydir_data, label_modelabel_model, color_modergb, batch_sizebatch_size, image_sizeimage_size, shuffleTrue, seed101, validation_splittotal_val_test_split, subsetvalidation, ) # Tính số lô để xác nhận và phân chia thử nghiệm total_temp_batches tf.data.experimental.cardinality(temp_data).numpy() validation_batches int(total_temp_batches * (percentage_val_data / total_val_test_split)) test_batches total_temp_batches - validation_batches # Chia temp_data thành tập dữ liệu xác thực và kiểm tra validation_data temp_data.take(validation_batches) test_data temp_data.skip(validation_batches) # Tối ưu hóa tập dữ liệu với bộ nhớ đệm và tìm nạp trước train_data train_data.cache().shuffle(1000).prefetch(buffer_sizetf.data.AUTOTUNE) validation_data validation_data.cache().prefetch(buffer_sizetf.data.AUTOTUNE) test_data test_data.cache().prefetch(buffer_sizetf.data.AUTOTUNE) return train_data, validation_data, test_data training_data , validation_data, testing_data split_data(DATA_DIR,(224,224),categorical,32,0.2) def data_augmentation() : data_aug tf.keras.Sequential([ tf.keras.layers.RandomFlip(horizontal), tf.keras.layers.RandomFlip(vertical), tf.keras.layers.RandomZoom(0.2,fill_modenearest) , tf.keras.layers.RandomRotation(0.2), tf.keras.layers.RandomContrast(0.2) ]) return data_aug data_augmentor data_augmentation() # Chụp ảnh mẫu sample_image tf.keras.utils.array_to_img(example_batch_images[2]) images [sample_image] # Áp dụng tăng ngẫu nhiên 10 lần for i in range(10) : image_aug data_augmentor(tf.expand_dims(sample_image, axis0)) image_aug tf.keras.utils.array_to_img(tf.squeeze(image_aug)) images.append(image_aug) fig, axs plt.subplots(1, 6, figsize(16, 7)) for ax, image, title in zip(axs, images, [Original Image, augmented Image 1 , augmented image 2,augmented Image 3, augmented image 4,augmented Image 5 ]) : ax.imshow(image, cmapgray) ax.set_title(title) ax.axis(off) plt.show() def features_extractor_resNet50(input_images_size, name_last_layer, fine_tune_at150): pre_trained_resNet50 tf.keras.applications.resnet.ResNet50( input_shapeinput_images_size, include_topFalse, weightsimagenet) # Unfreeze all layers pre_trained_resNet50.trainable True # Freeze the first few layers, up to fine_tune_at for layer in pre_trained_resNet50.layers[:fine_tune_at]: layer.trainable False last_layer pre_trained_resNet50.get_layer(name_last_layer) last_output last_layer.output return pre_trained_resNet50, last_output def classifier(input): x tf.keras.layers.GlobalAveragePooling2D()(input) x tf.keras.layers.Flatten()(x) x tf.keras.layers.Dense(1024, activationrelu)(x) x tf.keras.layers.BatchNormalization()(x) x tf.keras.layers.Dense(512, activationrelu)(x) x tf.keras.layers.BatchNormalization()(x) x tf.keras.layers.Dense(256, activationrelu)(x) x tf.keras.layers.BatchNormalization()(x) x tf.keras.layers.Dropout(0.3)(x) x tf.keras.layers.Dense(5, activationsoftmax)(x) return x def final_model(input_images_size, name_last_layer , fine_tune_at150 ): inputs tf.keras.Input(shapeinput_images_size) # Apply data augmentation augmented_inputs data_augmentation()(inputs) pre_trained_model, output features_extractor_resNet50(input_images_size, name_last_layer , fine_tune_at) # Apply the pre-trained model to the augmented input features pre_trained_model(augmented_inputs, trainingFalse) # Classifier head x classifier(features) # Define the final model model tf.keras.Model(inputs, x) # Compile the model model.compile( optimizertf.keras.optimizers.Adam( learning_rate0.0001, beta_10.9, beta_20.999, epsilon1e-08 ), # loss mse, losstf.keras.losses.CategoricalCrossentropy(), metrics[accuracy] ) return model INPUT_IMAGE_SIZE (224,224,3) NAME_LAST_LAYER conv4_block3_out model final_model(INPUT_IMAGE_SIZE,NAME_LAST_LAYER,125) model.summary() class EarlyStoppingCallback_90(tf.keras.callbacks.Callback): def on_epoch_end(self, epoch, logs{}) : if logs[accuracy] 0.90 and logs[val_accuracy] 0.90 : self.model.stop_training True print(\nReached 0.90% train accuracy and 0.90% validation accuracy, so cancelling training!) reduce_lr tf.keras.callbacks.ReduceLROnPlateau( monitorval_loss, factor0.1, patience3, min_lr1e-10 ) EPOCHS 50 hist model.fit( training_data, epochsEPOCHS, callbacks [EarlyStoppingCallback_90(),reduce_lr], validation_datavalidation_data ) def performance_avaluation(train_accuracy , train_loss , validation_accuracy , validation_loss,size_fig(16,4) ) : plt.figure(figsizesize_fig) plt.plot(train_accuracy,labeltrain accuracy,cred,markero) plt.plot(validation_accuracy,labelvalidation accuracy,markero) plt.xlabel(epoch) plt.ylabel(Accuracy) plt.title(\nAccuracy\n,cred,fontsize14) plt.legend() plt.figure(figsize(16,4)) plt.plot(train_loss,labeltrain loss,cred,markero) plt.plot(validation_loss,labelvalidation loss,markero) plt.xlabel(epoch) plt.ylabel(Loss) plt.title(\nLoss\n,cred,fontsize14) plt.legend() train_accuracy hist.history[accuracy] train_loss hist.history[loss] validation_accuracy hist.history[val_accuracy] validation_loss hist.history[val_loss] performance_avaluation(train_accuracy , train_loss , validation_accuracy , validation_loss,size_fig(16,4) ) predict model.predict(images) predict np.argmax(predict, axis1) labels np.argmax(labels, axis1) report classification_report(labels, predict, target_names[i for i in folder_elements]) print(report)资料获取更多粉丝福利关注下方公众号获取