1. 简介 & 数据集介绍

利用 TensorFlow,通过构建VGG-16 网络实现咖啡豆识别。数据集中有 Dark 和 Green 2 类咖啡豆图片,每类图片数量各有 300、183 张图片。

2. 环境

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

3. 代码实现

3.1 前期准备

3.1.1 设置GPU & 导入库

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

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers,models
import numpy as np
import matplotlib.pyplot as plt
import os,PIL,pathlib
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

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")

3.1.2 数据集统计与预览

通过 pathlib 扫描本地目录统计 483 张图像,并使用 PIL 打开一张图片进行直观确认。

data_dir = "./Data/49-data/"
data_dir = pathlib.Path(data_dir)
image_count = len(list(data_dir.glob('*/*.png')))

print("图片总数为:",image_count)

在这里插入图片描述

3.2 数据预处理

3.2.1 数据集划分与预处理

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

batch_size = 32
img_height = 224
img_width = 224

train_ds = tf.keras.preprocessing.image_dataset_from_directory(
    data_dir,
    validation_split=0.2,
    subset="training",
    seed=123,
    image_size=(img_height, img_width),
    batch_size=batch_size)

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

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

在这里插入图片描述

3.2.2 类别识别

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

class_names = train_ds.class_names
print(class_names)

在这里插入图片描述

3.2.3 可视化

利用Matplotlib库对数据进行了可视化抽查,它从训练集中取出了第一个批次(batch),并将前10张图片及其对应的类别标签绘制成了2行5列的网格图展示出来。

plt.figure(figsize=(10, 4))

for images, labels in train_ds.take(1):
    for i in range(10):
        
        ax = plt.subplot(2, 5, i + 1)  

        plt.imshow(images[i].numpy().astype("uint8"))
        plt.title(class_names[labels[i]])
        
        plt.axis("off")

在这里插入图片描述

3.2.4 整体数据检查

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

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

在这里插入图片描述

3.3 模型建立与训练

3.3.1 数据集性能优化与归一化

通过cache()和prefetch()方法优化了数据集的加载性能以消除I/O瓶颈,同时利用Rescaling层将图像的像素值从0-255缩放归一化到了0.0到1.0的区间,并提取首张图片的最值进行了验证。

AUTOTUNE = tf.data.AUTOTUNE

train_ds = train_ds.cache().shuffle(1000).prefetch(buffer_size=AUTOTUNE)
val_ds   = val_ds.cache().prefetch(buffer_size=AUTOTUNE)

normalization_layer = layers.Rescaling(1./255)

train_ds = train_ds.map(lambda x, y: (normalization_layer(x), y))
val_ds   = val_ds.map(lambda x, y: (normalization_layer(x), y))

image_batch, labels_batch = next(iter(val_ds))
first_image = image_batch[0]

# 查看归一化后的数据
print(np.min(first_image), np.max(first_image))

在这里插入图片描述

3.3.2 构建 VGG16 模型

3.3.2.1 构建自定义 VGG16 模型

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

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(len(class_names), (img_width, img_height, 3))
model.summary()

在这里插入图片描述

3.3.2.2 构建官方 VGG16 模型

直接调用Keras官方封装好的、带有ImageNet预训练权重的VGG16模型。

# 调用官方模型
model = tf.keras.applications.VGG16(weights='imagenet')
model.summary()

在这里插入图片描述

3.3.3 模型编译与训练

配置初始值为0.0001且呈指数衰减的学习率策略,使用Adam优化器对模型进行编译,随后在训练集上进行了20个周期的训练,训练日志显示模型在早期就在验证集上达到了100%的准确率。

# 设置初始学习率
initial_learning_rate = 1e-4

lr_schedule = tf.keras.optimizers.schedules.ExponentialDecay(
        initial_learning_rate, 
        decay_steps=30,
        decay_rate=0.92,
        staircase=True)

# 设置优化器
opt = tf.keras.optimizers.Adam(learning_rate=initial_learning_rate)

model.compile(optimizer=opt,
              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False),
              metrics=['accuracy'])

epochs = 20

history = model.fit( train_ds, validation_data=val_ds, epochs=epochs)
Epoch 1/20
13/13 ━━━━━━━━━━━━━━━━━━━━ 132s 10s/step - accuracy: 0.6202 - loss: 0.6729 - val_accuracy: 0.5625 - val_loss: 0.7464
Epoch 2/20
13/13 ━━━━━━━━━━━━━━━━━━━━ 127s 10s/step - accuracy: 0.6357 - loss: 0.6785 - val_accuracy: 0.5625 - val_loss: 0.6834
Epoch 3/20
13/13 ━━━━━━━━━━━━━━━━━━━━ 128s 10s/step - accuracy: 0.6357 - loss: 0.6063 - val_accuracy: 0.5625 - val_loss: 0.7986
Epoch 4/20
13/13 ━━━━━━━━━━━━━━━━━━━━ 142s 11s/step - accuracy: 0.6873 - loss: 0.3923 - val_accuracy: 0.9792 - val_loss: 0.2740
Epoch 5/20
13/13 ━━━━━━━━━━━━━━━━━━━━ 136s 11s/step - accuracy: 0.9406 - loss: 0.5001 - val_accuracy: 0.9062 - val_loss: 0.5947
Epoch 6/20
13/13 ━━━━━━━━━━━━━━━━━━━━ 151s 12s/step - accuracy: 0.9406 - loss: 0.1811 - val_accuracy: 1.0000 - val_loss: 0.0822
Epoch 7/20
13/13 ━━━━━━━━━━━━━━━━━━━━ 153s 12s/step - accuracy: 0.9845 - loss: 0.1205 - val_accuracy: 1.0000 - val_loss: 9.7821e-06
Epoch 8/20
13/13 ━━━━━━━━━━━━━━━━━━━━ 146s 11s/step - accuracy: 0.9845 - loss: 0.0437 - val_accuracy: 1.0000 - val_loss: 0.0063
Epoch 9/20
13/13 ━━━━━━━━━━━━━━━━━━━━ 146s 11s/step - accuracy: 0.9974 - loss: 0.0121 - val_accuracy: 1.0000 - val_loss: 8.6433e-04
Epoch 10/20
13/13 ━━━━━━━━━━━━━━━━━━━━ 148s 11s/step - accuracy: 1.0000 - loss: 9.6025e-04 - val_accuracy: 1.0000 - val_loss: 3.4087e-04
Epoch 11/20
13/13 ━━━━━━━━━━━━━━━━━━━━ 151s 12s/step - accuracy: 0.9974 - loss: 0.0026 - val_accuracy: 1.0000 - val_loss: 1.5991e-05
Epoch 12/20
13/13 ━━━━━━━━━━━━━━━━━━━━ 147s 11s/step - accuracy: 0.9974 - loss: 0.0211 - val_accuracy: 1.0000 - val_loss: 9.4641e-06
Epoch 13/20
13/13 ━━━━━━━━━━━━━━━━━━━━ 146s 11s/step - accuracy: 1.0000 - loss: 0.0020 - val_accuracy: 1.0000 - val_loss: 0.0092
Epoch 14/20
13/13 ━━━━━━━━━━━━━━━━━━━━ 143s 11s/step - accuracy: 0.9948 - loss: 0.0170 - val_accuracy: 1.0000 - val_loss: 1.2297e-04
Epoch 15/20
13/13 ━━━━━━━━━━━━━━━━━━━━ 151s 12s/step - accuracy: 1.0000 - loss: 5.1911e-04 - val_accuracy: 1.0000 - val_loss: 1.9974e-05
Epoch 16/20
13/13 ━━━━━━━━━━━━━━━━━━━━ 145s 11s/step - accuracy: 1.0000 - loss: 3.1367e-04 - val_accuracy: 1.0000 - val_loss: 1.2522e-05
Epoch 17/20
13/13 ━━━━━━━━━━━━━━━━━━━━ 145s 11s/step - accuracy: 1.0000 - loss: 5.5470e-05 - val_accuracy: 1.0000 - val_loss: 1.3598e-05
Epoch 18/20
13/13 ━━━━━━━━━━━━━━━━━━━━ 151s 12s/step - accuracy: 1.0000 - loss: 2.6451e-05 - val_accuracy: 1.0000 - val_loss: 1.2783e-05
Epoch 19/20
13/13 ━━━━━━━━━━━━━━━━━━━━ 145s 11s/step - accuracy: 1.0000 - loss: 2.2487e-05 - val_accuracy: 1.0000 - val_loss: 1.1326e-05
Epoch 20/20
13/13 ━━━━━━━━━━━━━━━━━━━━ 146s 11s/step - accuracy: 1.0000 - loss: 1.8026e-05 - val_accuracy: 1.0000 - val_loss: 9.1130e-06

4. 模型评估

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

current_time = datetime.now() # 获取当前时间

acc = history.history['accuracy']
val_acc = history.history['val_accuracy']

loss = history.history['loss']
val_loss = history.history['val_loss']

epochs_range = range(epochs)

plt.figure(figsize=(12, 4))
plt.subplot(1, 2, 1)
plt.plot(epochs_range, acc, label='Training Accuracy')
plt.plot(epochs_range, val_acc, 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, loss, label='Training Loss')
plt.plot(epochs_range, val_loss, label='Validation Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.show()

在这里插入图片描述

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐