T7:咖啡豆识别

  • 时间:9月4日-9月x日

🍺 要求:

  1. 自己搭建VGG-16网络框架 ✅
  2. 调用官方的VGG-16网络框架 ✅

🍻 拔高(可选):

  1. 验证集准确率达到100% ❌
  2. 使用PPT画出VGG-16算法框架图(发论文需要这项技能)❌

🔎 探索(难度有点大)

  1. 在不影响准确率的前提下轻量化模型❌
  • VGG16总参数量是134,276,942

由于本人没GPU算力了,所以代码跑贼慢,结果后补

⛽ 我的环境

  • 语言环境:Python3.10.12
  • 编译器:Google Colab
  • 深度学习环境:
    • TensorFlow2.17.0

⛽ 参考学习博客汇总(暂时):

一、前期工作

1.设置GPU,导入库
#os提供了一些与操作系统交互的功能,比如文件和目录操作
import os
#提供图像处理的功能,包括打开和显示、保存、裁剪等
import PIL
from PIL import Image
#pathlib提供了一个面向对象的接口来处理文件系统路径。路径被表示为Path对象,可以调用方法来进行各种文件和目录操作。
import pathlib

#用于绘制图形和可视化数据
import tensorflow as tf
import matplotlib.pyplot as plt
#用于数值计算的库,提供支持多维数组和矩阵运算
import numpy as np
#keras作为高层神经网络API,已被集成进tensorflow,使得训练更方便简单
from tensorflow import keras
#layers提供了神经网络的基本构建块,比如全连接层、卷积层、池化层等
#提供了构建和训练神经网络模型的功能,包括顺序模型(Sequential)和函数式模型(Functional API)
from tensorflow.keras import layers, models
#导入两个重要的回调函数:前者用于训练期间保存模型最佳版本;后者监测到模型性能不再提升时提前停止训练,避免过拟合
from tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping
tf.__version__
'2.17.0'

由于本人没有GPU了,该部分跳过↓

# 获取所有可用的GPU设备列表,储存在变量gpus中
gpus = tf.config.list_physical_devices("GPU")

# 如果有GPU,即列表不为空
if gpus:
  # 获取第一个 GPU 设备
  gpu0 = gpus[0]
  # 设置 GPU 内存增长策略。开启这个选项可以让tf按需分配gpu内存,而不是一次性分配所有可用内存。
  tf.config.experimental.set_memory_growth(gpu0, True)
  #设置tf只使用指定的gpu(gpu[0])
  tf.config.set_visible_devices([gpu0],"GPU")

gpus
2.导入数据
from google.colab import drive
drive.mount("/content/drive/")
%cd "/content/drive/MyDrive/Colab Notebooks/jupyter notebook/data"
Mounted at /content/drive/
/content/drive/MyDrive/Colab Notebooks/jupyter notebook/data
data_dir = "./7"
data_dir = pathlib.Path(data_dir)
3.查看数据
# 使用glob方法获取当前目录的子目录里所有以'.png'为结尾的文件
# '*/*.jpg' 是一個通配符模式
# 第一个星号表示当前目录
# 第二个星号表示子目录
image_count = len (list(data_dir.glob("*/*.png")))

print("图片总数:", image_count)
图片总数: 1200
ex = list(data_dir.glob("Green/*.png"))
image=PIL.Image.open(str(ex[8]))
#查看图像属性
print(image.format, image.size,image.mode)
plt.axis("off")
plt.imshow(image)
plt.show()
PNG (224, 224) RGB

在这里插入图片描述

二、数据预处理

1.加载数据
#设置批量大小,即每次训练模型时输入图像数量
#每次训练迭代时,模型需处理32张图像
batch_size = 32
#图像的高度,加载图像数据时,将所有的图像调整为相同的高度
img_height = 224
#图像的宽度,加载图像数据时,将所有的图像调整为相同的宽度
img_width = 224
"""
关于image_dataset_from_directory()的详细介绍可以参考文章:https://mtyjkh.blog.csdn.net/article/details/117018789
"""
tr_ds = tf.keras.preprocessing.image_dataset_from_directory(
    data_dir,
    validation_split=0.2,
    #指定数据集中分割出多少比例数据当作验证集,0.1表示10%数据会被用来当验证集
    subset="training",
    #指定是用于训练还是验证的数据子集,这里设定为training
    seed=123,
    #用于设置随机数种子,以确保数据集划分的可重复性和一致性
    image_size=(img_height, img_width),
    batch_size=batch_size)
Found 1200 files belonging to 4 classes.
Using 960 files for training.
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
)
Found 1200 files belonging to 4 classes.
Using 240 files for validation.
class_names = tr_ds.class_names
# 可以通过class_names输出数据集的标签。标签将按字母顺序对应于目录名称
class_names
['Dark', 'Green', 'Light', 'Medium']
# #数据增强---参考博客:https://blog.csdn.net/afive54/article/details/135004174

# def augment_images(image, label):
#     image = tf.image.random_flip_up_down(image)  # 随机水平翻转
#     image = tf.image.random_flip_left_right(image)
#     image = tf.image.random_contrast(image, lower=0.1, upper=1.2)  # 随机对比度
#     image = tf.image.random_brightness(image, max_delta=0.2)  # 随机亮度
#     image = tf.image.random_saturation(image, lower=0.1, upper=1.2)  # 随机饱和度
#     #noise = tf.random.normal(tf.shape(image), mean=0.0, stddev=0.1)
#     #image = tf.clip_by_value(image, 0.0, 0.5)  # 添加高斯噪声并将像素值限制在0到1之间
#     return image, label
# # 对训练集数据进行增强
# augmented_tr_ds = tr_ds.map(augment_images)

2.可视化数据
plt.figure(figsize=(10, 4))  # 图形的宽为10高为5

for images, labels in tr_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")

在这里插入图片描述

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

#`(32, 224, 224, 3)`--最后一维指的是彩色通道RGB
#`label_batch`是形状(32,)的张量
(32, 224, 224, 3)
(32,)
3.配置数据集
#自动调整数据管道性能
AUTOTUNE = tf.data.AUTOTUNE
# 使用 tf.data.AUTOTUNE 具体的好处包括:
#自动调整并行度:自动决定并行处理数据的最佳线程数,以最大化数据吞吐量。
#减少等待时间:通过优化数据加载和预处理,减少模型训练时等待数据的时间。
#提升性能:自动优化数据管道的各个环节,使整个训练过程更高效。
#简化代码:不需要手动调整参数,代码更简洁且易于维护。

#使用cache()方法将训练集缓存到内存中,这样加快数据加载速度
#当多次迭代训练数据时,可以重复使用已经加载到内存的数据而不必重新从磁盘加载
#使用shuffle()对训练数据集进行洗牌操作,打乱数据集中的样本顺序
#参数1000指缓冲区大小,即每次从数据集中随机选择的样本数量
#prefetch()预取数据,节约在训练过程中数据加载时间


tr_ds = tr_ds.cache().shuffle(1000).prefetch(buffer_size=AUTOTUNE)
val_ds  = val_ds.cache().prefetch(buffer_size=AUTOTUNE)
normalization_layer = layers.Rescaling(1./255)

tr_ds = tr_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]

# 查看归一化后的数据,将每张图片的像素归一至0-1间的数值
print(np.min(first_image), np.max(first_image))
0.0 1.0

三、构建CNN网络模型

[引1]- VGGNet (Visual Geometry Group Network):

创新:VGGNet的创新在于采用了相对简单的卷积层堆叠的结构,其中使用了多个小卷积核(3*3)来替代较大的卷积核。这种结构使网络更深,同时参数共享更多,有助于提取丰富的特征。

  • 优点:
  • 相对简单而易于理解的网络结构。
  • 良好的性能在图像分类任务中得到了验证。
  • 网络结构可提取更丰富的特征信息
  • 缺点:
  • 参数量较大,网络结构比较深,需要消耗大量计算资源和时间来训练。
  • 网络结构比较复杂,容易出现梯度消失或爆炸等问题
  • 相对于一些后续的模型,不够高效。

网络结构如下图:

在这里插入图片描述

1、手动搭建
from tensorflow.keras import layers, models, Input
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Conv2D, MaxPooling2D, Dense, Flatten, Dropout
#functional model的搭建模式,之前是sequential
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)
    predictions = Dense(nb_classes, activation='softmax', name='predictions')(x)

    model = Model(inputs=input_tensor, outputs=predictions)
    return model

model = VGG16(len(class_names), (img_width, img_height, 3))
model.summary()
Model: "functional"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓
┃ Layer (type)                         ┃ Output Shape                ┃         Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━┩
│ input_layer (InputLayer)             │ (None, 224, 224, 3)         │               0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ block1_conv1 (Conv2D)                │ (None, 224, 224, 64)        │           1,792 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ block1_conv2 (Conv2D)                │ (None, 224, 224, 64)        │          36,928 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ block1_pool (MaxPooling2D)           │ (None, 112, 112, 64)        │               0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ block2_conv1 (Conv2D)                │ (None, 112, 112, 128)       │          73,856 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ block2_conv2 (Conv2D)                │ (None, 112, 112, 128)       │         147,584 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ block2_pool (MaxPooling2D)           │ (None, 56, 56, 128)         │               0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ block3_conv1 (Conv2D)                │ (None, 56, 56, 256)         │         295,168 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ block3_conv2 (Conv2D)                │ (None, 56, 56, 256)         │         590,080 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ block3_conv3 (Conv2D)                │ (None, 56, 56, 256)         │         590,080 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ block3_pool (MaxPooling2D)           │ (None, 28, 28, 256)         │               0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ block4_conv1 (Conv2D)                │ (None, 28, 28, 512)         │       1,180,160 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ block4_conv2 (Conv2D)                │ (None, 28, 28, 512)         │       2,359,808 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ block4_conv3 (Conv2D)                │ (None, 28, 28, 512)         │       2,359,808 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ block4_pool (MaxPooling2D)           │ (None, 14, 14, 512)         │               0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ block5_conv1 (Conv2D)                │ (None, 14, 14, 512)         │       2,359,808 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ block5_conv2 (Conv2D)                │ (None, 14, 14, 512)         │       2,359,808 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ block5_conv3 (Conv2D)                │ (None, 14, 14, 512)         │       2,359,808 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ block5_pool (MaxPooling2D)           │ (None, 7, 7, 512)           │               0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ flatten (Flatten)                    │ (None, 25088)               │               0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ fc1 (Dense)                          │ (None, 4096)                │     102,764,544 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ fc2 (Dense)                          │ (None, 4096)                │      16,781,312 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ predictions (Dense)                  │ (None, 4)                   │          16,388 │
└──────────────────────────────────────┴─────────────────────────────┴─────────────────┘
 Total params: 134,276,932 (512.23 MB)
 Trainable params: 134,276,932 (512.23 MB)
 Non-trainable params: 0 (0.00 B)
2、直接调用官方模型
#调用-去除顶层自定义全连接层,加imagenet权重参数,冻结conv,加BN和dropout
from tensorflow.keras.applications import VGG16

# 加载VGG16模型,不包括全连接层,使用ImageNet的权重
base_model = VGG16(weights="imagenet",
          include_top=False,
          input_shape=(img_height, img_width, 3),
          pooling = "max")

# 冻结VGG16的卷积层,不进行训练
# base_model.trainable = False


#部分解冻?
# 冻结直到某一层的所有层
#仅微调卷积基的最后的两三层
base_model.trainable = True

set_trainable = False
for layer in base_model.layers[:-2]:
  if layer.name == 'block5_conv1':
    set_trainable = True
  if set_trainable:
    layer.trainable = True
    print(layer)
  else:
    set_trainable = False
    layer.trainable = False
print(base_model.summary(),end="\n")

# 在VGG16基础上添加自定义的全连接层
model = models.Sequential([
    base_model,
    #layers.GlobalAveragePooling2D(),
    #layers.GlobalMaxPooling2D(),
    layers.Flatten(),
    layers.Dense(1024, activation="relu"),
    layers.BatchNormalization(),
    layers.Dropout(0.4),
    layers.Dense(128, activation= "relu"),
    layers.BatchNormalization(),
    layers.Dropout(0.4),
    layers.Dense(len(class_names), activation="softmax")
])
 # 打印网络结构
model.summary()

# model.load_weights("/content/drive/Othercomputers/My laptop/jupyter notebook/xunlianying/vgg16_1_final.weights.h5")

四、编译模型

在准备对模型进行训练之前,还需要再对其进行一些设置。以下内容是在模型的编译步骤中添加的:

  • 损失函数(loss):用于衡量模型在训练期间的准确率。
  • 优化器(optimizer):决定模型如何根据其看到的数据和自身的损失函数进行更新。
  • 指标(metrics):用于监控训练和测试步骤。以下示例使用了准确率,即被正确分类的图像的比率。
#本次使用代码
# 设置初始学习率
initial_learning_rate = 1e-4

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

# 将指数衰减学习率送入优化器
optimizer = tf.keras.optimizers.Adam(learning_rate=lr_schedule)

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

#Adam优化器是一种常用的梯度下降优化算法,用于更新模型的权重以最小化训练过程中的损失函数
#由于是多分类问题这里使用categorical crossentropy损失函数

五、训练模型

epochs = 20

# 保存最佳模型参数
checkpointer = ModelCheckpoint(
    "/content/drive/My Drive/Colab Notebooks/jupyter notebook/xunlianying/T7_shou1.weights.h5",
    monitor='val_accuracy',
    verbose=1,
    mode = "max",
    save_best_only=True,
    save_weights_only=True)

# 设置早停
earlystopper = EarlyStopping(
    monitor='val_accuracy',
    min_delta=0.0001,
    patience=5,
    mode = "max",
    verbose=1)
history = model.fit(
      tr_ds,
      validation_data=val_ds,
      epochs=epochs,
      callbacks=[checkpointer, earlystopper])

不做任何优化–手动搭建NO.1:在这里插入图片描述

六、模型评估

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.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()

在这里插入图片描述

七、预测

'''指定图片进行预测'''
# 加载效果最好的模型权重
model.load_weights("/content/drive/My Drive/Colab Notebooks/jupyter notebook/xunlianying/T7_shou1.weights.h5")
from PIL import Image

import numpy as np

img = Image.open("/content/drive/MyDrive/Colab Notebooks/jupyter notebook/data/7/Dark/dark (133).png")  #这里选择你需要预测的图片
image = tf.image.resize(img, [img_height, img_width])

img_array = tf.expand_dims(image, 0)

predictions = model.predict(img_array) # 这里选用你已经训练好的模型
print("预测结果为:",class_names[np.argmax(predictions)])

在这里插入图片描述

八、优化
  • 9
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值