- 本文为365天深度学习训练营 中的学习记录博客
- 原作者:K同学啊
导入数据集,这里使用k同学的数据集,共17个分类。
from tensorflow import keras
from tensorflow.keras import layers, models
import numpy as np
import matplotlib.pyplot as plt
import os, PIL, pathlib
import tensorflow as tf
data_dir = 'D:/DL_Camp/CNN/T7/49-data'
data_dir = pathlib.Path(data_dir)
这段代码将字符串类型的 data_dir 转换为了 pathlib.Path 类型的对象。pathlib 是 Python3.4 中新增的模块,用于处理文件路径。
通过 Path 对象,可以方便地操作文件和目录,如创建、删除、移动、复制等。
在这里,我们使用 pathlib.Path() 函数将 data_dir 转换为路径对象,这样可以更加方便地进行文件路径的操作和读写等操作。
image_count = len(list(data_dir.glob('*/*.png')))
print("图片总数为:", image_count)
获取指定目录下所有子文件夹中 jpg 格式的文件数量,并将其存储在变量 image_count 中。
data_dir 是一个路径变量,表示需要计算的目标文件夹的路径。
glob() 方法可以返回匹配指定模式(通配符)的文件列表,该方法的参数 “/.jpg” 表示匹配所有子文件夹下以 .jpg 结尾的文件。
list() 方法将 glob() 方法返回的生成器转换为列表,方便进行数量统计。最后,len() 方法计算列表中元素的数量,就得到了指定目录下 jpg 格式文件的总数。
所以,这行代码的作用就是计算指定目录下 jpg 格式文件的数量。
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
)
这行代码使用 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 的计算资源。
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
)
这段代码和上一段代码类似,使用 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 对象,可以使用这段代码来创建一个验证数据集对象,用于模型训练和评估,从而提高模型性能。
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 的元素个数设置输出层的神经元数量,并将每个类别与一个唯一的整数标签相关联。
plt.figure(figsize = (20, 10))
for images, labels in train_ds.take(1):
for i in range(20):
ax = plt.subplot(5, 10, i + 1)
plt.imshow(images[i].numpy().astype("uint8"))
plt.title(class_names[np.argmax(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() 函数之后调用,以便在显示图像时去掉多余的细节信息,仅仅显示图像本身。
for image_batch, labels_batch in train_ds:
print(image_batch.shape)
print(labels_batch.shape)
break
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.experimental.preprocessing.Rescaling(1./255)
train_ds = train_ds.map(lambda x, y: (normalization_layer(x), y))
val_ds = train_ds.map(lambda x, y: (normalization_layer(x), y))
image_batch, label_batch = next(iter(val_ds))
first_image = image_batch[0]
print(np.min(first_image), np.max(first_image))
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 参数定义的缓冲大小进行预加载。
具体来说,这个函数可以在当前训练或评估任务执行期间异步读取和处理数据集中的下一批样本,从而减少模型训练或评估过程中的等待时间和延迟。
因此,这段代码的作用是对训练集和验证集进行预处理,并将它们缓存到内存或磁盘中,以便更快地访问数据。
然后,它对训练集进行了一次随机打乱操作,并将缓存结果设为可以提前预加载的形式,以便在训练时能够快速地读取和处理数据。
最后,它将缓存结果也设置为可以提前预加载的形式,以加速评估过程。
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)
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)
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)
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)
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)
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()
layers.experimental.preprocessing.Rescaling(1./255, input_shape=(img_height, img_width, 3)) 是 TensorFlow 中的一个预处理层,
用于将图像数据缩放至 [0, 1] 范围内,在神经网络的训练过程中稳定模型训练过程。
在这个代码片段中,1./255 表示将输入数据除以 255,这样就把原来范围在 [0, 255] 的像素值缩放到 [0, 1] 的范围内。
这个操作可以使得神经网络的输入数据归一化,从而更好地适应不同的计算机视觉任务,例如图像分类、目标检测、语义分割等。
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'] )
epochs = 20
history = model.fit(
train_ds,
validation_data = val_ds,
epochs = epochs,
)
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 是自然对数。通过最小化损失函数,模型可以调整参数以提高预测准确率,并不断逼近真实结果
history = model.fit(
train_ds,
validation_data = val_ds,
epochs = epochs,
)
这段代码是基于给定数据集 train_ds 进行模型训练,并在验证集 val_ds 上评估模型表现。同时,使用了之前定义的 checkpointer 和 earlystopper 两个回调函数来保存模型和控制早期停止。
其中,epochs = epochs 表示训练的轮数
该方法会返回一个 history 对象,包含了模型在训练过程中的各种指标,例如训练损失、验证损失、训练准确率、验证准确率等等。
通过对这些指标的分析,我们可以更加深入地了解模型的表现和训练效果,从而进一步调整模型的参数和结构,提高模型的性能并得到更好的预测结果。
acc = history.history["accuracy"]
val_acc = history.history["val_accuracy"]
loss = history.history["loss"]
val_loss = history.history["val_loss"]
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()
初步了解了VGG16架构。