news 2026/8/20 1:40:01

第T8周:猫狗识别

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
第T8周:猫狗识别
  • 🍨 本文为🔗365天深度学习训练营 中的学习记录博客
  • 🍖 原作者:K同学啊

前言

  • 实验环境
python 3.9.2 tensorflow 2.10.0 Jupyter Notebook: 7.4.5

代码实现

设置gpu

importtensorflowastf gpus=tf.config.list_physical_devices("GPU")#设置GPU显存用量按需使用ifgpus:tf.config.experimental.set_memory_growth(gpus[0],True)tf.config.set_visible_devices([gpus[0]],"GPU")# 打印出检测到的 GPU 列表print(gpus)

导入数据

importmatplotlib.pyplotasplt plt.rcParams['font.sans-serif']=['SimHei']# 用来正常显示中文标签plt.rcParams['axes.unicode_minus']=False# 用来正常显示负号
importwarnings,pathlib# 隐藏警告warnings.filterwarnings('ignore')data_dir="./data"data_dir=pathlib.Path(data_dir)image_count=len(list(data_dir.glob('*/*')))print("图片总数为:{}".format(image_count))

数据加载

# 基本参数设置batch_size=8img_height=224img_width=224# 数据加载# 加载数据集,自动完成:调整尺寸、打乱数据、划分验证集train_ds=tf.keras.preprocessing.image_dataset_from_directory(data_dir,validation_split=0.2,subset="training",seed=12,image_size=(img_height,img_width),batch_size=batch_size)

val_ds=tf.keras.preprocessing.image_dataset_from_directory(data_dir,validation_split=0.2,subset="validation",seed=12,image_size=(img_height,img_width),batch_size=batch_size)

输出标签

class_names=train_ds.class_namesprint(class_names)

再次检查数据

forimage_batch,labels_batchintrain_ds:print(image_batch.shape)print(labels_batch.shape)break

处理数据集以及优化数据加载效率

AUTOTUNE=tf.data.AUTOTUNE# 将图像像素值从 [0, 255](通常是 uint8 类型)缩放到 [0, 1] 的浮点范围。defpreprocess_image(image,label):return(image/255.0,label)# 归一化处理,.map(func)表示对数据集中的每个元素应用 func。num_parallel_calls=AUTOTUNE表示并行执行预处理操作。train_ds=train_ds.map(preprocess_image,num_parallel_calls=AUTOTUNE)val_ds=val_ds.map(preprocess_image,num_parallel_calls=AUTOTUNE)train_ds=train_ds.cache().shuffle(1000).prefetch(buffer_size=AUTOTUNE)val_ds=val_ds.cache().prefetch(buffer_size=AUTOTUNE)

数据可视化

plt.figure(figsize=(15,10))# 图形的宽为15高为10forimages,labelsintrain_ds.take(1):foriinrange(8):ax=plt.subplot(5,8,i+1)plt.imshow(images[i])plt.title(class_names[labels[i]])plt.axis("off")

构建模型

  • 各层的作用:

    • 前五层卷积块(Block 1 - Block 5)

      • Block 1 & 2: 通过双卷积层堆叠提取基础纹理与中级形状。
      • Block 3, 4 & 5: 采用三卷积层堆叠,通道数由 256 提升至 512。随着层数加深,感受野扩大,模型开始理解复杂的语义信息(如猫的耳朵、狗的鼻子等)。
      • 最大池化层 (MaxPooling2D):在每个 Block 结束处进行下采样,在保留关键特征的同时,将空间维度从224 × 224 224 \times 224224×224逐步压缩至7 × 7 7 \times 77×7
    • 全连接分类层(针对二分类优化)

      • Flatten 层: 将末端卷积特征展平为 25,088 维向量。
      • 全连接层 1 & 2 (Dense 4096):进行全局特征的非线性组合与变换。
      • Dropout 层 (Dropout(0.5)) :
        • 作用:在训练过程中随机断开 50% 的神经元连接。
        • 原理:防止模型过度依赖某些特定特征。
      • 输出层 (Dense(2, activation=‘softmax’)) :
        • 作用:输出一个二维概率向量,分别表示输入图像属于“猫”和“狗”两个类别的预测概率。Softmax 激活函数确保这两个概率之和为 1,模型通过选择概率较高的类别作为最终预测结果。
    • 为什么引入 Dropout 层?

      • VGG16 是一个拥有超过 1.3 亿参数的巨大模型。而使用的数据集(几千张图片)相对模型容量来说太小了,就会导致模型找捷径记住图片,就好比一个老师给了学生四五百道题,而这个学生是个记忆天才可以记忆上千甚至上万道题,所以他就会直接去记住这些题而不是总结解题规律,这就导致遇到新题还是不会做。
fromtensorflowimportkerasfromtensorflow.kerasimportlayers,models,Inputfromtensorflow.keras.modelsimportModelfromtensorflow.keras.layersimportConv2D,MaxPooling2D,Dense,Flatten,DropoutdefVGG16(num_classes,input_shape):input_tensor=Input(shape=input_shape)# Block 1x=Conv2D(64,(3,3),activation='relu',padding='same',name='block1_conv1')(input_tensor)x=Conv2D(64,(3,3),activation='relu',padding='same',name='block1_conv2')(x)x=MaxPooling2D((2,2),strides=2,name='block1_pool')(x)# Block 2x=Conv2D(128,(3,3),activation='relu',padding='same',name='block2_conv1')(x)x=Conv2D(128,(3,3),activation='relu',padding='same',name='block2_conv2')(x)x=MaxPooling2D((2,2),strides=2,name='block2_pool')(x)# Block 3x=Conv2D(256,(3,3),activation='relu',padding='same',name='block3_conv1')(x)x=Conv2D(256,(3,3),activation='relu',padding='same',name='block3_conv2')(x)x=Conv2D(256,(3,3),activation='relu',padding='same',name='block3_conv3')(x)x=MaxPooling2D((2,2),strides=2,name='block3_pool')(x)# Block 4x=Conv2D(512,(3,3),activation='relu',padding='same',name='block4_conv1')(x)x=Conv2D(512,(3,3),activation='relu',padding='same',name='block4_conv2')(x)x=Conv2D(512,(3,3),activation='relu',padding='same',name='block4_conv3')(x)x=MaxPooling2D((2,2),strides=2,name='block4_pool')(x)# Block 5x=Conv2D(512,(3,3),activation='relu',padding='same',name='block5_conv1')(x)x=Conv2D(512,(3,3),activation='relu',padding='same',name='block5_conv2')(x)x=Conv2D(512,(3,3),activation='relu',padding='same',name='block5_conv3')(x)x=MaxPooling2D((2,2),strides=2,name='block5_pool')(x)# 全连接分类x=Flatten()(x)x=Dense(4096,activation='relu',name='fc1')(x)x=Dropout(0.5)(x)# 训练时,随机将 50% 的神经元输出置零,提高泛化能力x=Dense(4096,activation='relu',name='fc2')(x)x=Dropout(0.5)(x)output_tensor=Dense(2,activation='softmax',name='predictions')(x)model=Model(input_tensor,output_tensor)returnmodel num_classes=len(class_names)model=VGG16(num_classes,(img_height,img_width,3))model.summary()


编译模型

model.compile(optimizer="adam",loss='sparse_categorical_crossentropy',metrics=['accuracy'])

训练模型

  • model.train_on_batch(x, y)是 对单个 batch 的数据执行一次前向传播、损失计算、反向传播和参数更新。相比model.fit()(自动处理整个数据集、epoch、batch 等),train_on_batch可以自定义训练循环。

  • tqdm是一个 Python 进度条库,能为可迭代对象添加进度条。

    • 参数说明:
      • total: 总步数(用于计算百分比)
      • desc: 前缀描述(如 “Epoch 1/10”)
      • mininterval: 最小更新间隔
      • ncols: 进度条总宽度(字符数)
      • set_postfix(): 在进度条末尾动态显示额外信息(如 loss、acc)
      • update(n): 手动推进 n 步
  • 可能的BUG:

    • 由于model.train_on_batch(x, y)是 对单个 batch 的数据执行一次前向传播、损失计算、反向传播和参数更新,而loss/accuracy是在循环结束后才记录的,这就导致每个epoch中的各个batch的记录会逐次被后一个batch的记录所覆盖,最终只记录了每个 epoch 的最后一个 batch 的值,而不是整个 epoch 的平均值。
fromtqdmimporttqdmimporttensorflow.keras.backendasK epochs=10lr=1e-4# 记录训练数据history_train_loss=[]history_train_accuracy=[]history_val_loss=[]history_val_accuracy=[]forepochinrange(epochs):train_total=len(train_ds)val_total=len(val_ds)""" total:预期的迭代数目 ncols:控制进度条宽度 mininterval:进度更新最小间隔,以秒为单位(默认值:0.1) """withtqdm(total=train_total,desc=f'Epoch{epoch+1}/{epochs}',mininterval=1,ncols=100)aspbar:lr=lr*0.92K.set_value(model.optimizer.lr,lr)forimage,labelintrain_ds:history=model.train_on_batch(image,label)train_loss=history[0]train_accuracy=history[1]pbar.set_postfix({"loss":"%.4f"%train_loss,"accuracy":"%.4f"%train_accuracy,"lr":K.get_value(model.optimizer.lr)})pbar.update(1)history_train_loss.append(train_loss)history_train_accuracy.append(train_accuracy)print('开始验证!')withtqdm(total=val_total,desc=f'Epoch{epoch+1}/{epochs}',mininterval=0.3,ncols=100)aspbar:forimage,labelinval_ds:history=model.test_on_batch(image,label)val_loss=history[0]val_accuracy=history[1]pbar.set_postfix({"loss":"%.4f"%val_loss,"accuracy":"%.4f"%val_accuracy})pbar.update(1)history_val_loss.append(val_loss)history_val_accuracy.append(val_accuracy)print('结束验证!')print("验证loss为:%.4f"%val_loss)print("验证准确率为:%.4f"%val_accuracy)


模型评估

fromdatetimeimportdatetime current_time=datetime.now()# 获取当前时间epochs_range=range(epochs)plt.figure(figsize=(12,4))plt.subplot(1,2,1)plt.plot(epochs_range,history_train_accuracy,label='Training Accuracy')plt.plot(epochs_range,history_val_accuracy,label='Validation Accuracy')plt.legend(loc='lower right')plt.title('Training and Validation Accuracy')plt.xlabel(current_time)plt.subplot(1,2,2)plt.plot(epochs_range,history_train_loss,label='Training Loss')plt.plot(epochs_range,history_val_loss,label='Validation Loss')plt.legend(loc='upper right')plt.title('Training and Validation Loss')plt.show()

预测

importnumpyasnp# 采用加载的模型(new_model)来看预测结果plt.figure(figsize=(18,3))# 图形的宽为18高为5plt.suptitle("预测结果展示")forimages,labelsinval_ds.take(1):foriinrange(8):ax=plt.subplot(1,8,i+1)# 显示图片plt.imshow(images[i].numpy())# 需要给图片增加一个维度img_array=tf.expand_dims(images[i],0)# 使用模型预测图片中的人物predictions=model.predict(img_array)plt.title(class_names[np.argmax(predictions)])plt.axis("off")

学习总结

  • 知道了可以通过model.train_on_batch()tqdm的配合使用来自定义训练循环。不同于fit()函数,train_on_batch()允许我在每一个 Batch 迭代中精准控制梯度更新,并实时获取当前的 Loss 和准确率。配合tqdm进度条,能直观的看到通过set_postfix实时监控学习率衰减对收敛的影响,从而能即时分析模型训练情况。

  • 为了缓解 VGG-16 庞大参数量带来的过拟合问题,我在全连接层中加入了Dropout(0.5),通过随机失活神经元迫使模型学习更具泛化性的特征,而非死记硬背像素。

  • 可能的BUG:train_on_batch返回的是当前这个 batch的 loss 和 accuracy。一个 epoch 包含 N 个 batches,每个 batch 的指标都不同。正确的 epoch 指标应该是所有 batches 的平均值,而不是最后一个 batch 的值。而原代码在 epoch 结束后直接执行history_train_loss.append(train_loss),由于train_loss在 batch 循环中不断被新值覆盖,最终存入历史记录的仅仅是最后一个 batch 的数值,而非整个 epoch 的平均表现。这导致生成的 loss 曲线波动剧烈且无法代表真实的收敛趋势。

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

Flutter 三方库 iso_duration 的鸿蒙化适配指南 - 掌控 ISO 8601 持续时间标准、精准解析时间增量、构建鸿蒙跨端时间交互的基石

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net Flutter 三方库 iso_duration 的鸿蒙化适配指南 - 掌控 ISO 8601 持续时间标准、精准解析时间增量、构建鸿蒙跨端时间交互的基石 在日常的鸿蒙(OpenHarmony)应用开发…

作者头像 李华
网站建设 2026/7/14 16:26:18

类加载子系统

类加载子系统 类加载子系统是用于加载编译后class文件的,但它只负责将符合格式要求的class字节码信息加载进内存,而只要符合格式规范的class文件都能被加载,至于加载进入的class文件到底是否能执行就并不是它负责的了,这是执行引擎…

作者头像 李华
网站建设 2026/7/14 16:26:19

探索单向整流器搭配无功功率补偿器/STATCOM的奇妙世界

单向整流器带无功功率补偿器/STATCOM 图一整体电路拓扑图 图二电网电压,电网电流,逆变器侧电流,无功负载电流曲线对比 图三电网电流跟踪电压电压 图四直流母线电压 图五电网功率与负载功率之和等于输入在电力电子领域,单向整流器与…

作者头像 李华
网站建设 2026/7/14 16:26:17

青蛙跳台阶解密:C语言实现(26.3.13)

#define _CRT_SECURE_NO_WARNINGS #include<stdio.h>int main() {int n 0;int i 0;int a 1;int b 2;int c 0;printf("请输入台阶数\n");scanf("%d", &n);if (n 1)printf("有一种跳法\n");else if (n 2)printf("有2种跳…

作者头像 李华
网站建设 2026/7/14 16:26:20

研发与市场准入必备,最新基本药物目录查询及历史版本动态分析

为了查找某一药品是否进入基药目录而翻遍多个网站&#xff1f;或是因无法无法快速比对不同年份的目录差异而头疼不已&#xff1f;在医药行业政策频出、市场竞争激烈的环境下&#xff0c;快速准确地获取基本药物目录信息已成为药品研发、市场准入、销售策略制定的关键一环。然而…

作者头像 李华