1. 简介 & 数据集介绍

利用 TensorFlow,通过构建 VGG-16 网络实现猫狗识别。数据集中有 dog 和 cat 2 类图片,每类图片数量各有 1700 张图片。
与上一章文章不同的是,之前批次大小用的是 8 ,这次用的是 64。

2. 环境

  • 语言环境:Python 3.12.7
  • 编译器:Jupyter Notebook
  • 深度学习环境:TensorFlow 2.21.0

3. 代码实现

3.1 前期准备

3.1.1 设置GPU & 导入库

导入必要的库并配置 GPU 显存增长,以解决在 Windows 环境下可能出现的显存占用或驱动兼容性问题。

import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
from tensorflow.keras import layers, models, Input
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Conv2D, MaxPooling2D, Dense, Flatten, Dropout
from datetime import datetime
from tqdm import tqdm
import tensorflow.keras.backend as K

plt.rcParams['font.sans-serif'] = ['SimHei'] 
plt.rcParams['axes.unicode_minus'] = False

import os,PIL,pathlib

#隐藏警告
import warnings
warnings.filterwarnings('ignore')

gpus = tf.config.list_physical_devices("GPU")

if gpus:
    tf.config.experimental.set_memory_growth(gpus[0], True)
    tf.config.set_visible_devices([gpus[0]],"GPU")

# 打印显卡信息,确认GPU可用
print(gpus)

3.1.2 数据集统计与预览

通过 pathlib 扫描本地目录统计 3400 张图像。

data_dir = "./Data/365-7-data"
data_dir = pathlib.Path(data_dir)
image_count = len(list(data_dir.glob('*/*')))
print("图片总数为:",image_count)

在这里插入图片描述

3.2 数据预处理

3.2.1 数据集划分与预处理

使用 Keras 提供的便捷接口构建了训练数据集,将图像统一缩放至 VGG16 标准的 224x224 尺寸,设置批次大小为 64,并按 8:2 的比例划出了 80%(2720 张图片)的数据用于模型训练。

batch_size = 64
img_height = 224
img_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)

在这里插入图片描述
采用与构建训练集完全相同的参数和随机种子(seed=123),从同一个目录中划分出剩余的20%(680 张图片)作为验证数据集,以确保训练集和验证集互不重叠,用于评估模型性能。

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)

在这里插入图片描述

3.2.2 类别识别

从创建好的训练数据集中提取并打印了分类的类别名称,输出的列表为[‘cat’, ‘dog’],明确了当前实验是一个简单的图像二分类任务。

class_names = train_ds.class_names
print(class_names)

在这里插入图片描述

3.2.3 可视化

从训练集中抽取一个批次的数据(包含图像和标签),并打印它们的维度。输出结果 (8, 224, 224, 3) 验证了每批次包含 8 张长宽为 224 的 RGB 三通道彩色图片。

AUTOTUNE = tf.data.AUTOTUNE

def preprocess_image(image,label):
    return (image/255.0,label)

# 归一化处理
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))

for images, labels in train_ds.take(1):
    for i in range(8):
        
        ax = plt.subplot(5, 8, i + 1) 
        plt.imshow(images[i])
        plt.title(class_names[labels[i]])
        
        plt.axis("off")

在这里插入图片描述

3.2.4 整体数据检查

通过打印第一个批次中图像和标签的维度(shape)来验证数据结构的正确性,输出显示图像张量维度为 (64, 224, 224, 3),标签张量维度为(8,)。

for image_batch, labels_batch in train_ds:
    print(image_batch.shape)
    print(labels_batch.shape)
    break

在这里插入图片描述

3.3 模型建立与训练

3.3.1 构建 VGG16 模型

利用 Keras 函数式 API 从零开始手动搭建了一个经典的 VGG16 卷积神经网络架构,包含5个特征提取卷积块和末端的高维全连接层,并打印了包含约 1.38 亿个参数的模型结构摘要。

def VGG16(nb_classes, input_shape):
    input_tensor = Input(shape=input_shape)
    # 1st block
    x = 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,2), name = 'block1_pool')(x)
    # 2nd block
    x = 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,2), name = 'block2_pool')(x)
    # 3rd block
    x = 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,2), name = 'block3_pool')(x)
    # 4th block
    x = 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,2), name = 'block4_pool')(x)
    # 5th block
    x = 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,2), name = 'block5_pool')(x)
    # full connection
    x = Flatten()(x)
    x = Dense(4096, activation='relu',  name='fc1')(x)
    x = Dense(4096, activation='relu', name='fc2')(x)
    output_tensor = Dense(nb_classes, activation='softmax', name='predictions')(x)

    model = Model(input_tensor, output_tensor)
    return model

model=VGG16(1000, (img_width, img_height, 3))
model.summary()

在这里插入图片描述

3.3.2 模型编译与训练

配置初始值为 0.0001 且呈指数衰减的学习率策略,使用 Adam 优化器对模型进行编译,随后在训练集上进行了 10 个周期的训练。

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

epochs = 10
lr = 1e-4

history_train_loss = []
history_train_accuracy = []
history_val_loss = []
history_val_accuracy = []

for epoch in range(epochs):
    train_total = len(train_ds)
    val_total   = len(val_ds)
    # 训练集
    with tqdm(total=train_total, desc=f'Epoch {epoch + 1}/{epochs}',mininterval=1,ncols=100) as pbar:
        
        lr = lr*0.92
        model.optimizer.learning_rate.assign(lr)
        
        train_loss     = []
        train_accuracy = []
        for image,label in train_ds:   

            history = model.train_on_batch(image,label)
            
            train_loss.append(history[0])
            train_accuracy.append(history[1])
            
            pbar.set_postfix({"train_loss": "%.4f"%history[0], "train_acc":"%.4f"%history[1], "lr": model.optimizer.learning_rate.assign(lr)})
            pbar.update(1)
            
        history_train_loss.append(np.mean(train_loss))
        history_train_accuracy.append(np.mean(train_accuracy))
            
    print('开始验证!')
    # 测试集
    with tqdm(total=val_total, desc=f'Epoch {epoch + 1}/{epochs}',mininterval=0.3,ncols=100) as pbar:

        val_loss = []
        val_accuracy = []
        for image,label in val_ds:      

            history = model.test_on_batch(image,label)
            
            val_loss.append(history[0])
            val_accuracy.append(history[1])
            
            pbar.set_postfix({"val_loss": "%.4f"%history[0], "val_acc":"%.4f"%history[1]})
            pbar.update(1)
        history_val_loss.append(np.mean(val_loss))
        history_val_accuracy.append(np.mean(val_accuracy))
            
    print('结束验证!')
    print("验证loss为:%.4f"%np.mean(val_loss))
    print("验证准确率为:%.4f"%np.mean(val_accuracy))
Epoch 1/10: 100%|| 43/43 [13:44<00:00, 19.16s/it, train_loss=1.2895, train_acc=0.5507, lr=tf.Tensor
开始验证!
Epoch 1/10: 100%|██████████████████| 11/11 [00:47<00:00,  4.33s/it, val_loss=1.1596, val_acc=0.5715]
结束验证!
验证loss为:1.2126
验证准确率为:0.5614
Epoch 2/10: 100%|| 43/43 [14:29<00:00, 20.22s/it, train_loss=0.9417, train_acc=0.5739, lr=tf.Tensor
开始验证!
Epoch 2/10: 100%|██████████████████| 11/11 [00:55<00:00,  5.06s/it, val_loss=0.9110, val_acc=0.5847]
结束验证!
验证loss为:0.9239
验证准确率为:0.5793
Epoch 3/10: 100%|| 43/43 [15:20<00:00, 21.40s/it, train_loss=0.8171, train_acc=0.6116, lr=tf.Tensor
开始验证!
Epoch 3/10: 100%|██████████████████| 11/11 [00:39<00:00,  3.59s/it, val_loss=0.7936, val_acc=0.6226]
结束验证!
验证loss为:0.8043
验证准确率为:0.6174
Epoch 4/10: 100%|| 43/43 [13:04<00:00, 18.25s/it, train_loss=0.7095, train_acc=0.6629, lr=tf.Tensor
开始验证!
Epoch 4/10: 100%|██████████████████| 11/11 [00:39<00:00,  3.63s/it, val_loss=0.6902, val_acc=0.6723]
结束验证!
验证loss为:0.6987
验证准确率为:0.6682
Epoch 5/10: 100%|| 43/43 [12:44<00:00, 17.78s/it, train_loss=0.6078, train_acc=0.7140, lr=tf.Tensor
开始验证!
Epoch 5/10: 100%|██████████████████| 11/11 [00:38<00:00,  3.53s/it, val_loss=0.5874, val_acc=0.7239]
结束验证!
验证loss为:0.5961
验证准确率为:0.7197
Epoch 6/10: 100%|| 43/43 [12:57<00:00, 18.08s/it, train_loss=0.5218, train_acc=0.7563, lr=tf.Tensor
开始验证!
Epoch 6/10: 100%|██████████████████| 11/11 [00:39<00:00,  3.55s/it, val_loss=0.5073, val_acc=0.7634]
结束验证!
验证loss为:0.5135
验证准确率为:0.7604
Epoch 7/10: 100%|| 43/43 [11:57<00:00, 16.69s/it, train_loss=0.4567, train_acc=0.7880, lr=tf.Tensor
开始验证!
Epoch 7/10: 100%|██████████████████| 11/11 [00:35<00:00,  3.25s/it, val_loss=0.4465, val_acc=0.7930]
结束验证!
验证loss为:0.4508
验证准确率为:0.7909
Epoch 8/10: 100%|| 43/43 [11:32<00:00, 16.09s/it, train_loss=0.4071, train_acc=0.8126, lr=tf.Tensor
开始验证!
Epoch 8/10: 100%|██████████████████| 11/11 [00:37<00:00,  3.39s/it, val_loss=0.3986, val_acc=0.8167]
结束验证!
验证loss为:0.4022
验证准确率为:0.8149
Epoch 9/10: 100%|| 43/43 [11:41<00:00, 16.31s/it, train_loss=0.3664, train_acc=0.8319, lr=tf.Tensor
开始验证!
Epoch 9/10: 100%|██████████████████| 11/11 [00:37<00:00,  3.40s/it, val_loss=0.3596, val_acc=0.8352]
结束验证!
验证loss为:0.3625
验证准确率为:0.8338
Epoch 10/10: 100%|| 43/43 [11:20<00:00, 15.83s/it, train_loss=0.3329, train_acc=0.8477, lr=tf.Tenso
开始验证!
Epoch 10/10: 100%|█████████████████| 11/11 [00:40<00:00,  3.67s/it, val_loss=0.3275, val_acc=0.8504]
结束验证!
验证loss为:0.3298
验证准确率为:0.8493

4. 模型评估

4.1 绘制训练集与验证集的 Accuracy 和 Loss 趋势图

提取了 model.fit() 返回的历史训练数据,并使用 Matplotlib 将训练集与验证集的准确率(Accuracy)和损失值(Loss)随时间变化的趋势绘制成了两幅直观的折线图。

current_time = datetime.now()

epochs_range = range(epochs)

plt.figure(figsize=(14, 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()

请添加图片描述

4.2 模型预测

从验证集中抽取第一个批次(8张图片),利用已经存在于上下文中的模型(model)对其逐一进行张量维度扩充和推理预测。最后使用 Matplotlib 绘制一行 8 列的画板,将图像打印出来,并将模型识别出的分类名称(猫或狗)写在各子图的标题上。

plt.figure(figsize=(18, 3))
plt.suptitle("预测结果展示")

for images, labels in val_ds.take(1):
    for i in range(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")

在这里插入图片描述
在这里插入图片描述

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐