第T7周:咖啡豆识别

前言

  • 实验环境

    python 3.9.2
    tensorflow 2.10.0
    Jupyter Notebook: 7.4.5

代码实现

设置gpu

python 复制代码
import tensorflow as tf

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

if gpus:
    tf.config.experimental.set_memory_growth(gpus[0], True)  #设置GPU显存用量按需使用
    tf.config.set_visible_devices([gpus[0]],"GPU")

导入数据

python 复制代码
import pathlib
# 导入数据
data_dir = "./data/"
data_dir = pathlib.Path(data_dir)

查看数据

python 复制代码
# 查看数据集图片数量
image_count = len(list(data_dir.glob('*/*.png')))
print("图片总数为:", image_count)

数据加载

python 复制代码
from tensorflow import keras
# 基本参数设置
batch_size = 16 # 由于显存不足,这里使用16
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)
python 复制代码
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)

输出标签

python 复制代码
class_names = train_ds.class_names
print(class_names)

数据可视化

python 复制代码
import matplotlib.pyplot as plt

plt.figure(figsize=(10, 4))  # 图形的宽为10高为5

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

再次检查数据

python 复制代码
for image_batch, labels_batch in train_ds:
    print(image_batch.shape)
    print(labels_batch.shape)
    break

优化数据加载效率

python 复制代码
AUTOTUNE = tf.data.AUTOTUNE

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

数据归一化

python 复制代码
from tensorflow.keras import layers

normalization_layer = layers.experimental.preprocessing.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))
  • train_dsval_dstf.data.Dataset 对象,每个元素是 (image, label)
  • .map(lambda x, y: (normalization_layer(x), y)) 表示:对数据集中的每一张图像 x 应用归一化,标签 y 保持不变。
python 复制代码
import numpy as np

# 验证归一化
image_batch, labels_batch = next(iter(val_ds))
first_image = image_batch[0]

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

构建模型

  • 各层的作用:
    • 输入层 (Input(shape=(img_width, img_height, 3))):
      • 作用:定义模型的输入张量形状。
      • 原理说明:接收 RGB 彩色图像,尺寸为 (img_height, img_width, 3)( 224×224×3),作为整个卷积网络的起点。
    • 第1卷积块(block1):
      • 卷积层 1 (Conv2D(64, (3,3), activation='relu', padding='same', name='block1_conv1')):
        • 作用:提取图像中最基础的局部视觉特征,如边缘、角点和简单纹理。
        • 参数解析:
          • 64:使用 64 个 3×3 卷积核,输出 64 通道的特征图;
          • padding='same':保持特征图空间尺寸不变;
          • ReLU 激活引入非线性,缓解梯度消失。
      • 卷积层 2 (Conv2D(64, (3,3), activation='relu', padding='same', name='block1_conv2')):
        • 作用:在第一层特征基础上进一步组合,增强对局部结构的感知能力。
        • 特点:通道数保持 64,形成"双卷积"结构,是 VGG 系列标志性设计。
      • 池化层 (MaxPooling2D((2,2), strides=(2,2), name='block1_pool')):
        • 作用:对特征图进行 2 倍下采样,降低空间分辨率( 224→112),减少后续计算量。
        • 工作方式:取每个 2×2 区域的最大值,保留最显著响应。
    • 第2卷积块(block2):
      • 卷积层 1 & 2 (Conv2D(128, (3,3), ...) ):
        • 作用:学习更复杂的中级语义特征,如纹理组合、局部形状或重复图案。
        • 参数变化:通道数翻倍至 128,提升特征表达维度。
      • 池化层 (MaxPooling2D, name='block2_pool'):
        • 作用:再次 2 倍下采样( 112→56),聚焦更高层次结构信息。
    • 第3卷积块(block3):
      • 三层卷积 (Conv2D(256, (3,3), ...) ):
        • 作用:构建更丰富的特征表示,开始捕捉具有类别判别性的局部对象部件(如眼睛、轮子、叶片等)。
      • 池化层 (MaxPooling2D, name='block3_pool'):
        • 作用:空间维度继续减半( 56→28),压缩冗余信息。
    • 第4卷积块(block4):
      • 三层卷积 (Conv2D(512, (3,3), ...) ):
        • 作用:提取高级语义特征,如整体部件组合、场景上下文或类别专属模式。
        • 通道数增至 512,特征图高度抽象,具备强判别性。
      • 池化层 (MaxPooling2D, name='block4_pool'):
        • 作用:下采样至约 14×14,为最后的语义聚合做准备。
    • 第5卷积块(block5):
      • 三层卷积 (Conv2D(512, (3,3), ...) ):
        • 作用:进一步精炼高层语义,捕捉细微但关键的类别差异(如不同犬种的耳朵形状、车型细节等)。
        • 与 block4 相同通道数,强调深度而非宽度。
      • 池化层 (MaxPooling2D, name='block5_pool'):
        • 作用:最终空间下采样( 14→7),输出尺寸极小但语义密集的特征图(7×7×512)。
    • 全连接分类层:
      • Flatten 层 (Flatten()):
        • 作用:将 7×7×512 的三维特征张量展平为一维向量(长度 = 7×7×512 = 25,088),供全连接层处理。
      • 全连接层 1 (Dense(4096, activation='relu', name='fc1')):
        • 作用:对全局特征进行高维非线性融合,实现从局部特征到整体语义的映射。
      • 全连接层 2 (Dense(4096, activation='relu', name='fc2')):
        • 作用:进一步抽象和压缩特征表示,增强模型判别边界。
      • 输出层 (Dense(nb_classes, activation='softmax', name='predictions')):
        • 作用:生成每个类别的预测概率分布。
        • 结构说明:
          • 输出维度等于类别数 nb_classes
          • softmax 激活确保输出为归一化概率(总和为 1);
          • 最终预测结果通过 argmax 获得最高概率类别。
python 复制代码
from tensorflow.keras import layers, models, Input
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Conv2D, MaxPooling2D, Dense, Flatten, Dropout

def VGG16(nb_classes, input_shape):
    input_tensor = Input(shape=input_shape)
    # 第1卷积块
    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)
    # 第2卷积块
    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)
    # 第3卷积块
    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)
    # 第4卷积块
    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)
    # 第5卷积块
    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)
    # 全连接分类层
    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()


编译模型

python 复制代码
# 设置初始学习率
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'])

训练模型

python 复制代码
epochs = 20

history = model.fit(
    train_ds,
    validation_data=val_ds,
    epochs=epochs
)


模型评估

python 复制代码
from datetime import datetime
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()

学习总结

  • 这次参照指导文档写了一个 VGG-16 模型,开始去思考它的设计目的,比如它全用 3×3 的小卷积核,看起来好像很普通,但其实是有道理的:两个 3×3 卷积叠在一起,感受野就跟一个 5×5 差不多,三个就相当于 7×7,但参数却少很多,而且每层后面都加了 ReLU 激活,非线性更强,模型就更容易学到复杂的特征。

  • 另外,VGG-16 的结构特别整齐:先是两层卷积 + 一次池化,再两层(后面变成三层)卷积 + 池化,一路下去,通道数从 64 翻倍到 128、256、512,而图片尺寸却越缩越小(224 → 112 → 56 ... → 7)。这种"越往后,图越小,但信息越浓"的方式,其实就是在模拟人看东西的过程------先看边缘、颜色这些基础东西,再慢慢组合成眼睛、轮子、叶子这种局部部件,最后认出整个物体是什么。最后那两个超大的全连接层(4096 维),就像大脑在做最终判断,把所有看到的细节综合起来投票分类。

  • 不过自己跑起来也发现了问题,这模型真的重,光是全连接层就占了绝大部分参数,我 batch size 只敢设 16,不然显存直接爆掉。而且训练时明显过拟合------训练准确率很快就冲的很高,但验证集上忽高忽低,有时候突然掉到 85%,说明它把训练图片"背下来了",而不是真正学会了泛化。

相关推荐
Katecat996631 小时前
输液泵设备检测与识别基于改进YOLO11模型的实现详解_ETB
python
yao12497364732 小时前
【无标题】
python·synergy·deskflow·键鼠共享·hyprland·niri
workflower2 小时前
原子需求的属性
python·测试用例·需求分析·软件需求
尘缘浮梦2 小时前
协程asyncio入门案例 2
开发语言·python
kronos.荒3 小时前
滑动窗口+哈希表:最小覆盖子串
数据结构·python·散列表
AC赳赳老秦3 小时前
文旅AI趋势:DeepSeek赋能客流数据,驱动2026智慧文旅规模化跃迁
人工智能·python·mysql·安全·架构·prometheus·deepseek
一个处女座的程序猿O(∩_∩)O3 小时前
Python面向对象的多态特性详解
开发语言·python
秋刀奈3 小时前
Python 现代工程实践
python
清水白石0083 小时前
Fixture 的力量:pytest fixture 如何重新定义测试数据管理
数据库·python·pytest