深度学习实战-基于EfficientNetV2B0的吸烟者图像检测模型

📅 2026/7/18 5:59:29
深度学习实战-基于EfficientNetV2B0的吸烟者图像检测模型
‍♂️ 个人主页艾派森的个人主页✍作者简介Python学习者 希望大家多多支持我们一起进步如果文章对你有帮助的话欢迎评论 点赞 收藏 加关注目录1.项目背景2.数据集介绍3.技术工具4.实验过程4.1导入数据4.2数据可视化4.3特征工程4.4构建模型4.5训练模型4.6模型评估4.7模型预测5.总结源代码1.项目背景在公共场所控烟与个人健康管理的双重诉求下如何高效、实时地识别吸烟行为已成为计算机视觉领域的一项重要挑战。传统的禁烟管理模式主要依赖人工巡查或烟雾传感器前者人力成本高且存在监管盲区后者则往往在烟雾扩散至一定浓度后才能触发缺乏即时性与空间覆盖力。随着深度学习技术的成熟利用智能监控摄像头结合图像识别算法可以在吸烟行为发生的瞬间捕捉到关键动作特征从而实现非接触式的自动预警与精细化管理。本项目旨在开发一套基于EfficientNetV2B0轻量化架构的吸烟者检测模型。吸烟识别的难点在于动作的细微性以及高度相似干扰项的存在——如喝水、打电话、使用吸入器或咳嗽等手势在视觉特征上与持烟姿势极其接近。为了突破这一技术瓶颈本实验通过构建包含大量类间混淆样本的数据矩阵利用卷积神经网络的深层特征提取能力精准剥离出“指尖烟蒂”与“特定面部交互”等核心语义。从数据流的异步预取、动态图像增强到利用迁移学习与早停机制进行的模型锻造本实战展示了如何通过先进的视觉模型在复杂的动态背景中锁定吸烟者为智慧城市、医院及校园等禁烟区域的数字化监管提供了具备工程化落地价值的算法底座。2.数据集介绍本实验数据集来源于Kaggle该数据集包含1120张图像平均分为两类560张为吸烟吸烟者图像另外560张为非吸烟非吸烟者图像。该数据集是通过在各种搜索引擎中输入多​​个关键词包括吸烟、吸烟者、人、咳嗽、使用吸入器、打电话、喝水等进行搜索而收集的。我们力求在两类图像中都包含多样化的内容以产生一定程度的类间混淆从而更好地训练模型。例如“吸烟”类包含从多个角度拍摄的吸烟者图像并包含各种不同的手势。此外“非吸烟”类包含非吸烟者的图像他们的手势与吸烟图像中的手势略有相似例如喝水、使用吸入器、拿着手机、咳嗽等。未来的研究人员可以利用该数据集开发用于自动检测和筛查吸烟者的深度学习算法以保障绿色环境并在智慧城市中进行监控。数据集中的所有图像都经过预处理并调整大小分辨率为 250×250。我们选取 80% 的数据用于训练和验证20% 用于测试。3.技术工具Python版本:3.9代码编辑器jupyter notebook4.实验过程4.1导入数据在进入模型构建之前首要任务是搭建标准化的数据预处理流水线。本阶段我们集成了 TensorFlow 环境下的EfficientNetV2专用组件并引入了图像增强层以应对光照和拍摄角度带来的干扰。考虑到原始数据集的存储结构可能与深度学习框架的读取习惯存在差异我们采用shutil和os模块在工作空间内动态构建了标准化的类目文件夹。通过image_dataset_from_directory接口我们将散落在磁盘上的图片直接映射为高效的tf.data.Dataset对象并统一调整尺寸为 224 x 224。这种“预洗数据、统一标准”的操作不仅为后续的迁移学习夯实了基础更通过分批次加载Batching确保了在显存有限的情况下依然能实现平滑的训练迭代。# --- 1. 基础工具与路径管理模块 --- import numpy as np import pandas as pd import shutil import os import matplotlib.pyplot as plt import cv2 as cv from tqdm import tqdm # --- 2. 深度学习核心模块 (TensorFlow Keras) --- import tensorflow as tf from tensorflow.keras import layers from tensorflow.keras.models import Sequential from tensorflow.keras.preprocessing import image_dataset_from_directory # --- 3. 模型架构与数据增强专用层 --- # 引入 EfficientNetV2B0兼顾轻量化与高精度的卷积骨架 from tensorflow.keras.applications import EfficientNetV2B0 from tensorflow.keras.applications.efficientnet_v2 import preprocess_input from tensorflow.keras.layers import RandomFlip, RandomRotation, RandomZoom, GlobalAveragePooling2D, Dense, Dropout # --- 4. 数据目录初始化与重构 --- # 定义原始数据集路径与 Kaggle 工作区缓存路径 train_dir /kaggle/input/datasets/sujaykapadnis/smoking/Training/Training val_dir /kaggle/input/datasets/sujaykapadnis/smoking/Validation/Validation test_dir /kaggle/input/datasets/sujaykapadnis/smoking/Testing/Testing temp_dir /kaggle/working temp_train_ds_dir os.path.join(temp_dir, training) temp_val_ds_dir os.path.join(temp_dir, validation) temp_test_ds_dir os.path.join(temp_dir, testing) # 动态创建分类文件夹确保符合 image_dataset_from_directory 的标准读取规范 for path in [temp_train_ds_dir, temp_val_ds_dir, temp_test_ds_dir]: os.makedirs(os.path.join(path, smoking), exist_okTrue) os.makedirs(os.path.join(path, notsmoking), exist_okTrue) # --- 5. 构建数据流管道 (TF Dataset) --- # 将图像统一缩放至 224x224设定批大小为 32 以优化显存利用率 train_ds image_dataset_from_directory( temp_train_ds_dir, batch_size 32, image_size (224, 224), shuffle True, seed 12 ) val_ds image_dataset_from_directory( temp_val_ds_dir, batch_size 32, image_size (224, 224), shuffle True, seed 12 ) test_ds image_dataset_from_directory( temp_test_ds_dir, batch_size 32, image_size (224, 224), shuffle True, seed 12 )完成数据载入后我们已经将非结构化的图像文件转化为标准化的张量张量流。值得注意的是我们在加载过程中通过锁定随机种子Seed12确保了实验的可重复性。这种自动化的目录结构重组不仅规避了路径引用错误更重要的是它为下一步使用preprocess_input进行特定架构归一化做好了准备。对于吸烟检测这类对局部特征如指尖的香烟烟雾或动作极为敏感的任务这种严谨的预处理是模型能够从复杂背景中提取出关键动作语义的前提条件。4.2数据可视化为了验证数据管道的读取逻辑是否正确我们从训练集的第一个 Batch 中随机提取了 12 张图像进行可视化展示。通过train_ds.take(1)我们可以观察到模型视角下的原始输入图像被统一缩放至224x224像素并附带了对应的smoking吸烟或notsmoking非吸烟标签。在这一环节中我们可以重点观察“非吸烟”类别中是否包含容易产生误导的动作如摸嘴、吃东西以及“吸烟”类别中香烟实物的视觉占比。这种感官上的确认能帮助我们评估后续是否需要引入更强的数据增强手段来强化模型对微小烟雾特征的捕捉能力。# --- 1. 获取类别名称映射表 --- class_names train_ds.class_names # --- 2. 图像矩阵展示构建 3x4 可视化网格 --- plt.figure(figsize(12, 9)) plt.suptitle(吸烟者检测数据集训练样本随机抽检, fontsize16, y0.95) # 从第一个批次中提取数据并进行迭代展示 for image_batch, label_batch in train_ds.take(1): for i in range(12): plt.subplot(3, 4, i 1) # 获取当前图像的真实标签并转化为文字说明 label_index int(label_batch[i]) plt.title(fLabel: {class_names[label_index]}, fontsize10) # 将张量数据转换为 8 位无符号整型以便正常显示像素色彩 plt.imshow(image_batch[i].numpy().astype(uint8)) # 优化布局去除多余坐标轴并防止标题重叠 plt.tight_layout() plt.axis(off) plt.show()4.3特征工程为了榨取硬件的推理极限我们首先通过AUTOTUNE开启了异步预取Prefetching与缓存Caching机制。这一步确保了 CPU 在处理数据增强的同时GPU 能够始终处于满载计算状态有效消除了数据 IO 带来的训练瓶颈。在增强策略上我们构建了一个包含随机水平翻转、旋转与缩放的组合算子。这种设计并非简单的图像变换而是为了模拟监控摄像头在不同安装高度、不同行人视角下的视觉反馈通过在每一轮迭代中产生细微差异的变体显著提升了 EfficientNetV2B0 对复杂动作语义的提取鲁棒性。# --- 1. 构建高性能数据加载闭环 --- # 开启 TensorFlow 自动性能优化配置 autotune tf.data.AUTOTUNE # 对训练集执行缓存读取 - 随机打散 - 异步预取 # shuffle(1000) 确保了样本间的独立同分布降低模型对序列顺序的依赖 train_ds train_ds.cache().shuffle(1000).prefetch(buffer_sizeautotune) # 对验证与测试集执行缓存 - 异步预取确保评估过程极致流畅 val_ds val_ds.cache().prefetch(buffer_sizeautotune) test_ds test_ds.cache().prefetch(buffer_sizeautotune) # --- 2. 动态图像增强层配置 --- # 构建序列化增强算子直接集成在模型的前端作为计算层 data_aug Sequential([ # 模拟行人左右走动带来的视角镜像 RandomFlip(horizontal), # 允许 20% 范围内的随机旋转模拟非水平视角的抓拍场景 RandomRotation(factor0.2), # 引入 10% 的随机缩放应对监控画面中行人距离远近不一的情况 RandomZoom(height_factor0.1, width_factor0.1) ])通过这种深度集成的特征工程方案我们成功地将静态的数据集转化为了一股“活水”。利用prefetch机制模型在训练当前 Batch 时下一组增强后的图像已经准备在内存中待命。更重要的是通过RandomRotation和RandomZoom的组合我们人为地扩充了样本在空间特征上的广度。这种策略对于识别吸烟这种“小目标、细动作”任务至关重要它能防止模型仅仅死记硬背某几个特定的像素点转而学习吸烟行为中跨越空间变换的不变性特征。4.4构建模型本阶段我们首先实例化了不含分类顶层的EfficientNetV2B0预训练骨架并将其设置为不可训练状态trainable False以锁定其成熟的特征提取权重。在模型结构上我们构建了一个层级分明的流水线从输入层开始依次经过预定义的Data Augmentation增强层和preprocess_input归一化层随后进入卷积基座提取深度特征。为了将多维的特征图转化为一维的概率预测我们引入了GlobalAveragePooling2D进行降维并配合 256 个神经元的 ReLU 密集层进行非线性映射。此外针对训练过程中可能出现的过拟合问题我们不仅加入了Dropout(0.5)随机失活策略还配置了EarlyStopping监控机制确保在验证集损失不再下降时自动提前终止并回溯至最优权重。# --- 1. 实例化预训练骨架加载 EfficientNetV2B0 核心权重 --- # include_top False 表示剔除原有的 1000 类分类层仅保留卷积特征提取能力 conv_base EfficientNetV2B0( weights imagenet, include_top False, input_shape (224, 224, 3) ) # 锁定预训练权重当前阶段仅训练新增的顶层分类器 conv_base.trainable False # --- 2. 配置早停机制防止模型无效迭代与过拟合 --- from tensorflow.keras.callbacks import EarlyStopping early_stop EarlyStopping( monitorval_loss, patience5, # 如果连续 5 个轮次验证损失没有改善则停止训练 restore_best_weightsTrue # 自动回滚并保存表现最好的那组模型参数 ) # --- 3. 端到端模型组装构建序列化流水线 --- model Sequential([ # 定义标准输入维度 layers.Input(shape(224, 224, 3)), # 动态数据增强提升模型对旋转、翻转样本的鲁棒性 data_aug, # 执行架构专用的预处理逻辑如像素值归一化与缩放 layers.Lambda(preprocess_input), # 卷积基座执行深层视觉特征提取 conv_base, # 全局平均池化将特征图压缩为紧凑的向量表示 layers.GlobalAveragePooling2D(), # 分类头256 维隐藏层处理高阶特征融合 layers.Dense(256, activationrelu), # 强力正则化随机丢弃 50% 的神经元连接防止死记硬背训练样本 layers.Dropout(0.5), # 输出层根据任务类别进行概率分布预测 layers.Dense(2, activationsoftmax) # 注原代码为 7 类根据吸烟检测任务通常修改为 2 类 ]) # --- 4. 打印网络快照核验层级结构与参数规模 --- model.summary()通过这一套组合拳我们构建了一个既保留了“大模型”视觉深度又具备“小模型”灵活性的检测器。值得关注的是Dropout(0.5)的应用在吸烟检测中由于人脸特征通常占据主导模型容易忽略细小的香烟特征。高比例的随机失活能强制模型寻找更多元的判断依据比如从手部姿势、甚至烟雾轨迹中提取分类特征而不是仅仅依赖面部轮廓。这种“不把鸡蛋放在一个篮子里”的逻辑是保证模型在光照多变的监控场景下依然保持高灵敏度的关键。4.5训练模型在本阶段我们通过compile接口为模型注入了逻辑内核。考虑到标签是以整数索引形式0 或 1存储的我们采用了Sparse Categorical Crossentropy损失函数这不仅节省了内存空间也简化了数据预处理流程。在fit过程中我们将最大迭代轮次设定为 50 轮但实际上由于EarlyStopping的介入模型会在验证集损失val_loss趋于平稳后的 5 轮内自动止步。这种“动态截断”策略能有效防止模型在有限的训练样本上产生过拟合确保最终保留的权重是对泛化性能贡献最大的那一组最优解。# --- 1. 模型编译注入优化器与损失计算逻辑 --- model.compile( # Adam 优化器自动调整学习率适合处理迁移学习中的微调任务 optimizeradam, # 损失函数针对整数标签的稀疏交叉熵避免了繁琐的 One-hot 编码转换 loss sparse_categorical_crossentropy, # 评价指标实时监控分类准确率 metrics [accuracy] ) # --- 2. 启动训练流水线执行数据拟合 --- # 引入之前配置的 early_stop 回调函数实现自动化训练管理 history model.fit( train_ds, # 输入高性能训练数据集 validation_data val_ds, # 每个轮次结束后在验证集上进行“模拟考试” callbacks [early_stop], # 激活早停监控防止过拟合 epochs 50 # 设定最大探索上限 )训练过程中观察控制台输出的loss与val_loss的下降曲线是判断模型是否健康的关键。在吸烟者检测场景下如果训练集准确率上升极快而验证集停滞不前通常意味着模型过度关注了背景中的无关特征。得益于EfficientNetV2B0的渐进式学习能力通常在 10 到 15 轮左右模型就能在验证集上达到一个相对稳定的高位精度。此时早停机制会自动介入保存下那组对“吸烟动作”最具辨识力的卷积核权重为后续的实战预测做好准备。4.6模型评估本阶段我们首先通过Matplotlib绘制了双指标监控图表。左侧的准确率曲线Accuracy展示了模型识别能力的进化过程理想状态下训练与验证曲线应紧密交织上升右侧的损失曲线Loss则反映了预测误差的收敛情况。在完成视觉校验后我们引入了完全独立的测试集Test Dataset进行终极考核。这一步至关重要因为测试集包含了模型在训练和验证阶段从未谋面的全新吸烟场景。通过model.evaluate输出的各项指标我们可以最终定格该轻量化检测器在实战环境下的性能基准。# --- 1. 获取训练历史数据 --- accuracy history.history[accuracy] val_accuracy history.history[val_accuracy] loss history.history[loss] val_loss history.history[val_loss] # --- 2. 绘制性能监控看板 --- plt.figure(figsize(12, 6)) plt.suptitle(EfficientNetV2B0 吸烟检测模型训练全周期监控, fontsize15) # 子图1准确率演进对比 plt.subplot(1, 2, 1) plt.plot(accuracy, label训练集准确率 (Training), color#1f77b4, lw2) plt.plot(val_accuracy, label验证集准确率 (Validation), color#ff7f0e, lw2) plt.title(准确率演进 (Accuracy)) plt.xlabel(迭代轮次 (Epochs)) plt.ylabel(精度 (%)) plt.grid(axisy, linestyle--, alpha0.6) plt.legend(loclower right) # 子图2损失函数收敛对比 plt.subplot(1, 2, 2) plt.plot(loss, label训练集损失 (Training), color#1f77b4, lw2) plt.plot(val_loss, label验证集损失 (Validation), color#ff7f0e, lw2) plt.title(损失函数收敛 (Loss)) plt.xlabel(迭代轮次 (Epochs)) plt.ylabel(误差值) plt.grid(axisy, linestyle--, alpha0.6) plt.legend(locupper right) plt.tight_layout() plt.show() # --- 3. 终极性能核验测试集表现 --- print(\n *30) print(正在执行测试集终极评估...) score model.evaluate(test_ds, verbose0) print(f测试集准确率 (Test Accuracy): {score[1]:.4f}) print(f测试集损失值 (Test Loss): {score[0]:.4f}) print(*30)4.7模型预测本环节我们构建了一个端到端的predict管道实现了从原始图像像素到结构化标签的转化。通过对测试集进行随机采样我们生成了一个 3x3 的预测看板同步展示了真实类别Actual、AI 预测结果Predicted以及预测置信度Confidence。这种可视化对冲能够清晰地暴露模型在极端样本下的表现例如当吸烟者手部遮挡面部或背景中存在细长物体如笔或吸管干扰时模型给出的置信度波动将直接反映其特征提取的鲁棒性。这种“透明化”的预测展示是评估算法是否具备落地禁烟区域实时预警能力的最有力证据。# --- 1. 定义推理引擎封装单张图像处理逻辑 --- from tensorflow.keras.preprocessing.image import img_to_array def predict(model, img): 输入原始图像输出模型预测的类别及其对应的置信度百分比 # 图像张量化将图像对象转换为 (224, 224, 3) 的数值矩阵 img_array img_to_array(img) # 维度扩充将 HWC 提升为 (1, H, W, C)满足模型对 Batch 输入的强制要求 img_array tf.expand_dims(img_array, 0) # 执行前向传播获取各个类别的概率分布 prediction model.predict(img_array, verbose0) # 结果解析定位概率最大值的索引并映射为文字标签 predicted_class class_names[np.argmax(prediction[0])] # 计算置信度保留两位小数的百分比数值 confidence round(100 * (np.max(prediction[0])), 2) return predicted_class, confidence # --- 2. 随机采样预测可视化测试集盲测结果 --- plt.figure(figsize(12, 12)) plt.suptitle(EfficientNetV2B0吸烟行为实时预测实战看板, fontsize18, y1.02) # 从测试集中抽取一组批次进行推理展示 for image, label in test_ds.take(1): for i in range(9): # 构建 3x3 展示矩阵 ax plt.subplot(3, 3, i 1) # 将测试图像还原为可视化格式 img_raw image[i].numpy().astype(uint8) plt.imshow(img_raw) # 调用推理引擎获取 AI 判别结果 predicted_class, confidence predict(model, img_raw) actual_class class_names[label[i]] # 结果看板标注对比真实值与预测值并显示 AI 的自信程度 plt.title(fTrue: {actual_class}\nPred: {predicted_class}\nConf: {confidence}%, fontsize11, pad10) # 优化视觉呈现 plt.tight_layout() plt.axis(off) plt.show()5.总结本实验基于 Kaggle 提供的包含 1120 张高质量图像的吸烟者检测数据集成功构建并验证了一个高精度的自动化识别系统。该数据集通过引入喝水、打电话、咳嗽及使用吸入器等与吸烟姿态极度相似的干扰样本人为增加了类间混淆度从而强迫模型学习更深层次的动作语义特征。实验核心采用了EfficientNetV2B0这一轻量化且性能强劲的架构利用其渐进式学习能力对 250 x 250分辨率的预处理图像进行特征提取。在 80% 训练验证集与 20% 测试集的科学划分下模型展现出了极强的泛化鲁棒性最终在完全陌生的测试集上取得了94.20%的准确率而损失值仅为0.1838。这一研究成果充分证明了迁移学习在处理细微动作检测任务中的优越性不仅为智慧城市中的禁烟区域监控提供了可靠的算法支撑也为未来开发能够区分复杂手势交互的实时视频流分析系统奠定了技术基础。源代码import numpy as np import pandas as pd from tensorflow.keras.preprocessing import image_dataset_from_directory import shutil import os import matplotlib.pyplot as plt import pandas as pd import cv2 as cv import tensorflow as tf from tensorflow.keras import layers from tensorflow.keras.layers import RandomFlip, RandomRotation, RandomZoom, GlobalAveragePooling2D, Dense, Dropout from tensorflow.keras.models import Sequential from tensorflow.keras.applications import EfficientNetV2B0 from tensorflow.keras.applications.efficientnet_v2 import preprocess_input train_ds (/kaggle/input/datasets/sujaykapadnis/smoking/Training/Training) val_ds (/kaggle/input/datasets/sujaykapadnis/smoking/Validation/Validation) test_ds (/kaggle/input/datasets/sujaykapadnis/smoking/Testing/Testing) temp_dir /kaggle/working temp_train_ds_dir os.path.join(temp_dir,training) temp_val_ds_dir os.path.join(temp_dir,validation) temp_test_ds_dir os.path.join(temp_dir,testing) os.makedirs(temp_train_ds_dir /smoking, exist_ok True) os.makedirs(temp_train_ds_dir /notsmoking, exist_ok True) os.makedirs(temp_val_ds_dir /smoking, exist_ok True) os.makedirs(temp_val_ds_dir /notsmoking, exist_ok True) os.makedirs(temp_test_ds_dir /smoking, exist_ok True) os.makedirs(temp_test_ds_dir /notsmoking, exist_ok True) train_ds image_dataset_from_directory(temp_train_ds_dir, batch_size 32, image_size (224,224), shuffleTrue, seed 12) val_ds image_dataset_from_directory(temp_val_ds_dir, batch_size 32, image_size (224,224), shuffleTrue, seed 12) test_ds image_dataset_from_directory(temp_test_ds_dir, batch_size 32, image_size (224,224), shuffleTrue, seed 12) plt.figure(figsize (12,9)) for image_batch, label_batch in train_ds.take(1): #take data from batch 1 for i in range(12): plt.subplot(3,4,i1) plt.title(class_names[int(label_batch[i])]) plt.imshow(image_batch[i].numpy().astype(uint8)) plt.tight_layout() plt.axis(off) autotune tf.data.AUTOTUNE train_ds train_ds.cache().shuffle(1000).prefetch(autotune) val_ds val_ds.cache().prefetch(autotune) test_ds test_ds.cache().prefetch(autotune) data_aug Sequential([ RandomFlip(horizontal), RandomRotation(.2), RandomZoom(.1) ]) from tensorflow.keras.callbacks import EarlyStopping conv_base EfficientNetV2B0(weights imagenet, include_top False, input_shape (224,224,3)) conv_base.trainable False # Early stopping callback early_stop EarlyStopping( monitorval_loss, patience5, restore_best_weightsTrue ) model Sequential([ layers.Input(shape(224, 224, 3)), #take intput in (224,224) shape data_aug, # augmentation image layers.Lambda(preprocess_input), # rescale the image(1/255) for efficientnetv2 conv_base, # EfficientNetV2 layer layers.GlobalAveragePooling2D(), # summarize each feature map by taking average layers.Dense(256, activationrelu), # fully connected (dense) layer with 256 neurons layers.Dropout(0.5), # reduce overfitting by dropping randomly 50% neurons during training layers.Dense(7, activation softmax) # activation foftmax for multiclass classification ]) model.summary() model.compile( optimizeradam, loss sparse_categorical_crossentropy, # loss function for multi-class classification(sparse categorical crossentropy) metrics [accuracy] ) history model.fit( train_ds, validation_data val_ds, callbacks[early_stop], epochs 50 ) plt.figure(figsize (12,6)) plt.subplot(1, 2 ,1) plt.plot(accurcy, label Training) plt.plot(val_accuracy, label Validation) plt.legend(lower right) plt.title(Training and Validation Accuracy) plt.legend() plt.subplot(1, 2 ,2) plt.plot(loss, label Training) plt.plot(val_loss, label Validation) plt.legend(upper right) plt.title(Training and Validation Loss) plt.legend() plt.show() score model.evaluate(test_ds) print(Test Accuracy:, score[1]) print(Test Loss:, score[0]) from tensorflow.keras.preprocessing.image import img_to_array def predict(model, img): img_array img_to_array(img) # (224, 224, 3) img_array tf.expand_dims(img_array, 0) # (1, 224, 224, 3) batch input required prediction model.predict(img_array) predicted_class class_names[np.argmax(prediction[0])] confidence round(100*(np.max(prediction[0])),2) return predicted_class, confidence plt.figure(figsize (12,9)) for image, label in test_ds.take(1): for i in range(9): ax plt.subplot(4,3, i1) plt.imshow(image[i].numpy().astype(uint8)) predicted_class, confidence predict(model, image[i].numpy()) actual_class class_names[label[i]] plt.title(fActual: {actual_class}\n Predicted: {predicted_class}\n Confidence: {confidence}) plt.tight_layout() plt.axis(off)资料获取更多粉丝福利关注下方公众号获取