深度学习-第T5周——运动鞋品牌识别
- 深度学习-第T5周——运动鞋品牌识别
- 一、前言
- 二、我的环境
- 三、前期工作
- 1、导入数据集
- 2、查看图片数目
- 3、查看数据
- 四、数据预处理
- 1、 加载数据
- 1、设置图片格式
- 2、划分训练集
- 3、划分验证集
- 4、查看标签
- 2、数据可视化
- 3、检查数据
- 4、配置数据集
- 五、搭建CNN网络
- 六、编译
- 七、训练模型
- 八、模型评估
- 1、Loss和Acc图
- 2、指定图片进行预测
- 九、总结
深度学习-第T5周——运动鞋品牌识别
一、前言
- 🍨 本文为🔗365天深度学习训练营 中的学习记录博客
- 🍖 原作者:K同学啊
二、我的环境
- 电脑系统:Windows 10
- 语言环境:Python 3.8.5
- 编译器:colab在线编译
- 深度学习环境:Tensorflow
三、前期工作
1、导入数据集
导入数据集,数据集(K同学啊)
from tensorflow import keras
from tensorflow.keras import layers, models
import os, PIL, pathlib
import matplotlib.pyplot as plt
import tensorflow as tf
data_dir = "/content/drive/MyDrive/Training_Camp_DL/TensorFlow/CNN/46-data"
data_dir = pathlib.Path(data_dir)
这段代码将字符串类型的 data_dir 转换为了 pathlib.Path 类型的对象。pathlib 是 Python3.4 中新增的模块,用于处理文件路径。
通过 Path 对象,可以方便地操作文件和目录,如创建、删除、移动、复制等。
在这里,我们使用 pathlib.Path() 函数将 data_dir 转换为路径对象,这样可以更加方便地进行文件路径的操作和读写等操作。
2、查看图片数目
image_count = len(list(data_dir.glob('*/*/*.jpg')))
print("图片总数为:" ,image_count)
获取指定目录下所有子文件夹中 jpg 格式的文件数量,并将其存储在变量 image_count 中。
具体来说,data_dir 是一个路径变量,表示需要计算的目标文件夹的路径。glob() 方法可以返回匹配指定模式(通配符)的文件列表,该方法的参数 “/.jpg” 表示匹配所有子文件夹下以 .jpg 结尾的文件。
list() 方法将 glob() 方法返回的生成器转换为列表,方便进行数量统计。最后,len() 方法计算列表中元素的数量,就得到了指定目录下 jpg 格式文件的总数。
所以,这行代码的作用就是计算指定目录下 jpg 格式文件的数量。
3、查看数据
roses = list(data_dir.glob('train/nike/*.jpg'))
PIL.Image.open(str(roses[0]))
在第一行代码中,glob() 方法匹配了 data_dir 目录下位于 train/nike/ 子目录中所有后缀名为 .jpg 的文件,并将其转换为一个 pathlib.Path 对象列表。
因此 roses 列表中包含了符合条件的所有文件路径。需要注意的是,这里只是选取了 nike 子目录下的所有 .jpg 文件。
接下来,我们使用 PIL.Image.open() 函数打开了列表中的第一张图片,即 roses[0]。str() 函数将 pathlib.Path 对象转换为字符串类型的路径,使其能够被 PIL.Image.open() 正确解析。
最终,PIL.Image.open(str(roses[0])) 返回的是一个 PIL.Image.Image 类型的对象,代表了打开的图片。可以使用该对象进行后续处理,如调整大小、裁剪、旋转等操作。
四、数据预处理
1、 加载数据
1、设置图片格式
batch_size = 32
img_height = 224
img_width = 224
2、划分训练集
train_ds = tf.keras.preprocessing.image_dataset_from_directory(
"D:/DL_Camp/CNN/T5/46-data",
seed = 123,
image_size = (img_height, img_width),
batch_size = batch_size)
这行代码使用 TensorFlow 读取指定路径下的图片文件,并生成一个 tf.data.Dataset 对象,用于模型的训练和评估。
具体来说,tf.keras.preprocessing.image_dataset_from_directory() 函数从指定目录中读取图像数据,并自动对其进行标准化和预处理。该函数有以下参数:
directory:指定图像文件所在目录的路径;
seed:用于随机划分数据集的种子;
image_size:指定图像缩放后的尺寸;
batch_size:指定批次中图像的数量。
通过这些参数,函数将指定目录中的图像按照指定大小预处理后,随机划分为训练集和验证集。最终,生成的 tf.data.Dataset 对象包含了划分好的数据集,可以用于后续的模型训练和验证。
需要注意的是,这里的 img_height 和 img_width 变量应该提前定义,并且应该与实际图像的尺寸相对应。同时,batch_size 也应该根据硬件设备的性能合理调整,以充分利用 GPU/CPU 的计算资源。
3、划分验证集
val_ds = tf.keras.preprocessing.image_dataset_from_directory(
"D:/DL_Camp/CNN/T5/46-data",
seed = 123,
image_size = (img_height, img_width),
batch_size = batch_size
)
这段代码和上一段代码类似,使用 TensorFlow 的 keras.preprocessing.image_dataset_from_directory() 函数从指定的目录中读取图像数据集,并将其划分为训练集和验证集。
其中,data_dir 指定数据集目录的路径,validation_split 表示从数据集中划分出多少比例的数据作为验证集,subset 参数指定为 “validation” 则表示从数据集的 20% 中选择作为验证集,其余 80% 作为训练集。seed 是一个随机种子,用于生成可重复的随机数。image_size 参数指定输出图像的大小,batch_size 表示每批次加载的图像数量。
该函数返回一个 tf.data.Dataset 对象,代表了整个数据集(包含训练集和验证集)。可以使用 train_ds 和 val_ds 两个对象分别表示训练集和验证集。
不过两段代码的 subset 参数值不同,一个是 “training”,一个是 “validation”。
因此,在含有交叉验证或者验证集的深度学习训练过程中,需要定义两个数据集对象 train_ds 和 val_ds。我们已经定义了包含训练集和验证集的数据集对象 train_ds,可以省略这段代码,无需重复定义 val_ds 对象。只要确保最终的训练过程中,两个数据集对象都能够被正确地使用即可。
如果你没有定义 val_ds 对象,可以使用这段代码来创建一个验证数据集对象,用于模型训练和评估,从而提高模型性能。
4、查看标签
class_names = train_ds.class_names
class_names
train_ds.class_names 是一个属性,它是通过数据集对象 train_ds 中的类别信息自动生成的一个包含类别名称的列表。
在创建数据集对象 train_ds 时,你可以通过 class_names 参数手动指定类别名称,也可以根据图像文件夹的目录结构自动推断出来。
例如,假设你有一个包含猫和狗两种类别的图像数据集,其中猫类别的图像存储在 “cat” 文件夹中,狗类别的图像存储在 “dog” 文件夹中,
那么当你使用 keras.preprocessing.image_dataset_from_directory() 函数加载数据集时,会自动将 “cat” 和 “dog” 文件夹作为两个不同的类别,
并将它们的名称存储在 train_ds.class_names 属性中。
执行该代码后,你就可以在控制台或者输出窗口中看到包含数据集中所有类别名称的列表。这些名称通常是按照字母顺序排列的
因此,train_ds.class_names 属性可以让你方便地查看数据集中所有的类别名称,以便后续的模型训练、预测和评估等任务。
如果你要对数据集进行多类别分类,则需要根据 train_ds.class_names 的元素个数设置输出层的神经元数量,并将每个类别与一个唯一的整数标签相关联。
2、数据可视化
plt.figure(figsize = (20, 10))
for images, labels in train_ds.take(1):
for i in range(20):
plt.subplot(5, 10, i + 1)
plt.imshow(images[i].numpy().astype("uint8"))
plt.title(class_names[labels[i]])
plt.axis("off")
train_ds.take(1) 是一个方法调用,它返回一个数据集对象 train_ds 中的子集,其中包含了 take() 方法参数指定的数量的样本。
在这个例子中,take(1) 意味着我们从 train_ds 数据集中获取一批包含一个样本的数据块。
因此,for images, labels in train_ds.take(1): 的作用是遍历这个包含一个样本的数据块,并将其中的图像张量和标签张量依次赋值给变量 images 和 labels。具体来说,
它的执行过程如下:
从 train_ds 数据集中获取一批大小为 1 的数据块。
遍历这个数据块,每次获取一个图像张量和一个标签张量。
将当前图像张量赋值给变量 images,将当前标签张量赋值给变量 labels。
执行 for 循环中的代码块,即对当前图像张量和标签张量进行处理。
plt.imshow() 函数是 Matplotlib 库中用于显示图像的函数,它接受一个数组或张量作为输入,并在窗口中显示对应的图像。
在这个代码中,images[i] 表示从训练集中获取的第 i 个图像张量。由于 images 是一个包含多个图像的张量列表,因此使用 images[i] 可以获取其中的一个图像。
由于 imshow() 函数需要输入的数组或张量的类型是整型或浮点型,而从数据集中获取的图像张量通常是浮点型张量,因此需要将其转换为整型张量,以便进行显示。
这里使用了 .numpy().astype(“uint8”) 操作来将图像张量转换为整型张量(uint8 表示无符号8位整数),然后将结果传递给 plt.imshow() 函数进行显示。
因此,plt.imshow(images[i].numpy().astype(“uint8”)) 的作用是在 Matplotlib 窗口中显示训练集中的第 i 个图像。
你可以通过改变 i 的值来显示不同的图像,例如 i = 0 表示显示训练集中的第一张图像。
plt.axis(“off”) 是 Matplotlib 库中的一个函数调用,它用于控制图像显示时的坐标轴是否可见。
具体来说,当参数为 “off” 时,图像的坐标轴会被关闭,不会显示在图像周围。这个函数通常在 plt.imshow() 函数之后调用,以便在显示图像时去掉多余的细节信息,仅仅显示图像本身。
3、检查数据
for image_batch, labels_batch in train_ds:
print(image_batch.shape)
print(labels_batch.shape)
break
4、配置数据集
AUTOTUNE = tf.data.AUTOTUNE
train_ds = train_ds.cache().shuffle(1000).prefetch(buffer_size = AUTOTUNE)
val_ds = val_ds.cache().prefetch(buffer_size = AUTOTUNE)
AUTOTUNE 是 TensorFlow 的一个常量,它的值取决于当前硬件配置和运行环境。
它表示 TensorFlow 数据处理流程中可以自动选择最优化参数(例如 GPU 处理数量等)的范围,在不同的硬件配置下可能会有不同的取值。
在这个代码片段中,AUTOTUNE 的作用是为数据集的预处理过程提供了一个启发式的缓冲大小,以便更好地平衡内存使用和计算速度。
train_ds.cache() 和 val_ds.cache() 函数是 Tensorflow 的数据转换函数,它们的作用是将数据集中的元素缓存到内存或者磁盘中,以便后续访问时能够更快地读取数据。
使用缓存可以避免由于磁盘 I/O 等因素导致数据读取速度变慢的问题,从而加速训练或评估过程。
train_ds.shuffle(1000) 函数是 Tensorflow 的数据转换函数,它的作用是将输入数据集中的元素随机打乱顺序。
这样做的目的是防止模型过拟合,并促进模型对不同数据的学习能力。其中,1000 表示用于对数据集进行重排的元素数量,其具体取值可以根据数据集大小进行调整。
train_ds.prefetch(buffer_size = AUTOTUNE) 和 val_ds.prefetch(buffer_size = AUTOTUNE) 函数是 Tensorflow 的数据转换函数,
它们的作用是将输入数据集中的元素通过 AUTOTUNE 参数定义的缓冲大小进行预加载。
具体来说,这个函数可以在当前训练或评估任务执行期间异步读取和处理数据集中的下一批样本,从而减少模型训练或评估过程中的等待时间和延迟。
因此,这段代码的作用是对训练集和验证集进行预处理,并将它们缓存到内存或磁盘中,以便更快地访问数据。
然后,它对训练集进行了一次随机打乱操作,并将缓存结果设为可以提前预加载的形式,以便在训练时能够快速地读取和处理数据。
最后,它将缓存结果也设置为可以提前预加载的形式,以加速评估过程。
五、搭建CNN网络
num_classes = 2
model = models.Sequential([
layers.experimental.preprocessing.Rescaling(1. / 255, input_shape = (img_height, img_width, 3)),
layers.Conv2D(16, (3, 3), activation = 'relu', input_shape = (img_height, img_width, 3)),
layers.AveragePooling2D((2, 2)),
layers.Conv2D(32, (3, 3), activation = 'relu'),
layers.AveragePooling2D((2, 2)),
layers.Dropout(0.1),
layers.Conv2D(64, (3, 3), activation = 'relu'),
layers.Dropout(0.1),
layers.Flatten(),
layers.Dense(128, activation = 'relu'),
layers.Dense(num_classes)
])
model.summary()
layers.experimental.preprocessing.Rescaling(1./255, input_shape=(img_height, img_width, 3)) 是 TensorFlow 中的一个预处理层,
用于将图像数据缩放至 [0, 1] 范围内,在神经网络的训练过程中稳定模型训练过程。
在这个代码片段中,1./255 表示将输入数据除以 255,这样就把原来范围在 [0, 255] 的像素值缩放到 [0, 1] 的范围内。
这个操作可以使得神经网络的输入数据归一化,从而更好地适应不同的计算机视觉任务,例如图像分类、目标检测、语义分割等。
input_shape=(img_height, img_width, 3) 参数表示输入图像的形状,其中 img_height 和 img_width 分别表示图像的高度和宽度,而 3 表示通道数。
由于这个预处理层是作为神经网络模型的第一层使用的,因此需要指定输入数据的形状。
因此,这行代码的作用是将输入图像的像素值除以 255,从而将像素范围缩放到 [0, 1],并将其作为神经网络模型的第一层,
以便在训练过程中更好地归一化输入数据,并提高模型的鲁棒性和泛化性能。
六、编译
model.summary()
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 = True),
metrics = ['accuracy'] )
lr_schedule 是一个学习率的调度器,它是基于指数衰减方法来自动调整学习率的。在深度学习中,学习率是一个非常重要的超参数,它会影响模型的性能和训练速度。
这个调度器包含四个参数。initial_learning_rate 为初始学习率,一开始通常需要较大的学习率以便更快地进行收敛,然后逐步减小以提高训练效果。
decay_steps 是衰减步数,指定了多少次更新后进行学习率衰减。 这里设置成30,防止学习率衰减过快。decay_rate 是衰减速率,决定每一次衰减后学习率的大小。
通过以上两个参数,可以控制学习率的下降速度和幅度。staircase 参数则表示是否对步数取整,如果设置为 True,则表示将步数向下取整。
这个调度器的计算公式为:
decayed_learning_rate = initial_learning_rate * decay_rate ^ (step / decay_steps)
其中 step 表示当前训练步数。通过指数函数的方式,学习率会随着训练步数的增加而不断降低。
这种调度器的优点在于,它可以在训练初期使用较大的学习率以便快速接近最优解,而在后期则可以逐渐降低学习率以更精细的调整模型参数。
在上述代码段中,我们使用了初始学习率为 initial_learning_rate,衰减步数为 30,衰减速率为 0.92 的指数衰减策略来更新模型的学习率。
这种学习率调度器是一种常见的方法,在实践中通常可以通过尝试不同的参数设置来确定最佳的学习率调度策略。
SparseCategoricalCrossentropy 是一种常用的损失函数,通常用于多分类问题。
它的输入是模型输出结果经过 softmax 处理后得到的概率分布和真实的分类标签,输出是一个标量值,表示模型在当前数据上的损失值。
其中 from_logits=True 表示输入的模型输出结果是没有进行 softmax 处理的 logits,这种设置可以提高计算效率,并且对于梯度计算也更加稳定。
具体地,SparseCategoricalCrossentropy 的计算公式为:
loss = -Σ[y * log(y_hat)]
其中 y 是真实的分类标签,y_hat 是模型在该分类下的预测概率分布,log 是自然对数。通过最小化损失函数,模型可以调整参数以提高预测准确率,并不断逼近真实结果
七、训练模型
from tensorflow.keras.callbacks import ModelCheckpoint
epochs = 50
checkpointer = ModelCheckpoint(
'best_model.h5',
monitor = 'val_accuracy',
verbose = 1,
save_best_only = True,
save_weights_only = True
)
ModelCheckpoint 是 Keras 中的一个回调函数(Callback),用于在训练过程中保存模型的权重。
具体来说,对于每一轮训练后,该回调函数都可以按照一定的条件将这一轮训练得到的最佳模型权重保存下来。
使用 ModelCheckpoint 可以避免在训练过程中出现意外情况导致模型信息的丢失,同时也可以方便地对比不同阶段训练得到的模型效果,进行模型的选择和调整。
具体来说,这段代码中的参数设置如下:
best_model.h5:保存模型权重的文件路径和名称。
monitor=‘val_accuracy’:表示在验证集上监测模型的准确率并作为指标,以便在每个 epoch 结束时评估模型效果。当发现模型在验证集上的准确率有提高时,就会保存当前的模型权重。
verbose=1:表示每个 epoch 结束时,在控制台输出一条信息说明模型权重是否被更新了。
save_best_only=True:表示只有在模型效果提高时才保存模型权重。如果设为 False,则每个 epoch 结束后都会保存模型权重,不管模型效果是否提高。
save_weights_only=True:表示只保存模型的权重,而不保存模型的结构和配置信息。这样可以方便地加载已经训练好的模型权重,并重新构建模型。
综合起来,这段代码的作用是在训练过程中不断保存最佳的模型权重,以便后续的模型加载和使用。
earlystopper = EarlyStopping(
monitor = 'val_accuracy',
min_delta = 0.001,
patience = 20,
verbose = 1
)
earlystopper 是一个早期停止器,用于在训练过程中监控模型的表现,并在满足终止条件时提前结束训练。它是 tensorflow.keras.callbacks.EarlyStopping 类的一个实例对象。
在深度学习中,由于模型参数非常多,训练时间可能会很长。而对于一些问题,模型可能已经学习到了足够好的特征,此时继续训练只会导致过拟合或者浪费计算资源。
为了避免这种情况的发生,我们可以通过早期停止器来控制模型的训练过程。
通过 EarlyStopping,我们可以设置一些参数来指定如何判断模型是否应该停止训练,并在满足条件时终止训练。其中,monitor = ‘val_accuracy’ 表示监控模型在验证集上的准确率,
min_delta = 0.001 表示设置检测指标的最小变化量,如果变化量小于该值,则认为模型已经不再进步,可以终止训练。patience = 20 表示设置容忍指标在多少个 epoch 中没有进步,
如果超过该次数,则终止训练。verbose = 1 表示在控制台打印提示信息。
当早期停止器被激活时,它会自动停止训练,同时保留模型在最后一个 epoch 的参数。这个参数可以用于进一步的预测和测试任务。在具体实践中,我们可以根据问题和数据集的特点,
合理地设置早期停止器的参数,从而获得更好的训练效果。
history = model.fit(
train_ds,
validation_data = val_ds,
epochs = epochs,
callbacks = [checkpointer, earlystopper]
)
这段代码是基于给定数据集 train_ds 进行模型训练,并在验证集 val_ds 上评估模型表现。同时,使用了之前定义的 checkpointer 和 earlystopper 两个回调函数来保存模型和控制早期停止。
其中,epochs = epochs 表示训练的轮数,callbacks = [checkpointer, earlystopper] 表示在训练过程中使用了之前定义的回调函数来保存模型和控制早期停止。
该方法会返回一个 history 对象,包含了模型在训练过程中的各种指标,例如训练损失、验证损失、训练准确率、验证准确率等等。
通过对这些指标的分析,我们可以更加深入地了解模型的表现和训练效果,从而进一步调整模型的参数和结构,提高模型的性能并得到更好的预测结果。
八、模型评估
1、Loss和Acc图
loss = history.history['loss']
val_loss = history.history['val_loss']
acc = history.history['accuracy']
val_acc = history.history['val_accuracy']
epochs_range = range(len(loss))
plt.figure(figsize = (12, 4))
plt.subplot(1, 2, 1)
plt.plot(epochs_range, acc, label = "Training Acc")
plt.plot(epochs_range, val_acc, label = "Validation Acc")
plt.legend(loc = 'lower right')
plt.title("Training And Validation Acc")
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()
decay_steps 设置成 10:
decay_steps 修改成30后:
2、指定图片进行预测
model.load_weights('best_model.h5')
#这段代码用于加载之前训练中保存的最佳模型权重。'best_model.h5' 指的是之前保存的模型权重文件路径和名称。
#这样可以避免从头开始训练模型,直接使用已经训练好的最佳模型进行预测的工作。
from PIL import Image
import numpy as np
img = Image.open("D:/DL_Camp/CNN/T5/46-data/train/nike/1 (12).jpg") #使用 PIL 库中的 Image.open() 方法打开一张待预测的图片。
image = tf.image.resize(img, [img_height, img_width])
#这个函数调整输入图像的大小以符合模型的要求。
#在这个例子中,使用 TensorFlow 的 tf.image.resize() 函数将图像缩放为指定大小,其中 img_height 和 img_width 是指定的图像高度和宽度。
img_array = tf.expand_dims(image, 0)
'''
这个函数将输入图像转换为形状为 (1, height, width, channels) 的四维数组,
其中 height 和 width 是图像的高度和宽度,channels 是图像的通道数(例如 RGB 图像有 3 个通道)。
这里使用 TensorFlow 的 tf.expand_dims() 函数来扩展图像数组的维度,以匹配模型的输入格式。
具体来说:
image 是一个二维图片张量,它的形状是 (height, width, channels)。其中 height 和 width 分别为图片的高度和宽度,channels 为图片的颜色通道数。
0 是一个整数值,它指定在哪个维度上扩展此张量,这里表示在最前面(第一个)的维度上扩展。
因此,函数的作用是将输入张量 image 在最前面添加一个额外的维度(batch_size),生成一个四维张量。
tf.expand_dims(input, axis)
其中 input 表示要扩展的输入张量,axis 表示要在哪个维度上进行扩展。在这个例子中,input 是变量 image,axis 是 0。
'''
predictions = model.predict(img_array)
#这个函数用于对输入图像进行分类预测。它使用已经训练好的模型来对输入数据进行推断,并输出每个类别的概率分布。
print("预测结果为:", class_names[np.argmax(predictions)])
'''
将模型输出的概率分布转换为最终预测结果。
具体来说,使用 np.argmax() 函数找到概率最大的类别索引,然后使用该索引在 class_names 列表中查找相应的类别名称,并输出预测结果。
'''
九、总结
通过多次实验,我已经更加熟悉了识别任务的流程。然而,在本次实验中,要求测试集准确率达到84%。尽管我对模型参数进行了调整,例如将Dropout层的参数都改成0.1,但是准确率仍然远远没有达到目标。
后来我查阅了资料和博客,发现动态学习率里的参数也会影响模型的效果。起初,我只把注意力放在模型参数上,这才导致模型效果并没有太大的提升。原因是先前的 decay_steps 设置成了 10,导致学习率衰减过快,模型很快就停止训练了。这样模型的准确率提升并不明显。为了解决这个问题,我将 decay_steps 设置成了 30,学习率变化缓慢,可以充分训练模型,因此准确率也随之提高。最后,我成功将测试集准确率提高到了 0.9394。
综上所述,调整动态学习率的参数对模型的训练结果也十分重要,这个经验也对我的后续研究和实验有了很大的指导意义。