文章目录
T3周:天气图片识别
- 🍨 本文为🔗365天深度学习训练营 中的学习记录博客
- 🍖 原作者:K同学啊
⛽ 我的环境
- 语言环境:Python3.10.12
- 编译器:Google Colab
- 深度学习环境:
- TensorFlow2.15.0
👉 本文将采用CNN实现多云、下雨、晴、日出四种天气状态的识别。为了增加模型的泛化能力,使用Dropout层并且将最大池化层调整成了平均池化层。
一、前期工作
1.设置GPU(用CPU可忽略该步骤)
import tensorflow as tf
# 获取所有可用的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")
2.导入数据
#os提供了一些与操作系统交互的功能,比如文件和目录操作
import os
#提供图像处理的功能,包括打开和显示、保存、裁剪等
import PIL
#pathlib提供了一个面向对象的接口来处理文件系统路径。路径被表示为Path对象,可以调用方法来进行各种文件和目录操作。
import pathlib
#用于绘制图形和可视化数据
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
#挂载google drive
from google.colab import drive
drive.mount("/content/drive/")
Mounted at /content/drive/
#更改工作目录
%cd "/content/drive/MyDrive/Colab Notebooks/jupyter notebook/data/"
/content/drive/Othercomputers/My laptop/jupyter notebook/data
!ls
015_licence_plate MNIST P6 test weather_photos
best_model.pth P4 P7 tox_data_clean.csv
cifar-10-batches-py P5 P9_best_model.pth train
#这里使用相对路径
data_dir = './weather_photos/'
#将路径转换成pathlib.Path对象
data_dir = pathlib.Path(data_dir)
3.查看数据
# 使用glob方法获取当前目录的子目录里所有以'.jpg'为结尾的文件
# '*/*.jpg' 是一個通配符模式
# 第一个星号表示当前目录
# 第二个星号表示子目录
image_count = len (list(data_dir.glob("*/*.jpg")))
print("图片总数:", image_count)
图片总数: 1125
sunrise = list(data_dir.glob("sunrise/*.jpg"))
PIL.Image.open(str(sunrise[0]))
二、数据预处理
1.加载数据
使用image_dataset_from_directory
方法将磁盘中的数据加载到tf.data.Dataset
中
#设置批量大小,即每次训练模型时输入图像数量
#每次训练迭代时,模型需处理32张图像
batch_size = 32
#图像的高度,加载图像数据时,将所有的图像调整为相同的高度
img_height = 180
#图像的宽度,加载图像数据时,将所有的图像调整为相同的宽度
img_width = 180
"""
关于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.2表示20%数据会被用来当验证集
subset="training",#指定是用于训练还是验证的数据子集,这里设定为training
#用于设置随机数种子,以确保数据集划分的可重复性和一致性。
seed=123,
image_size=(img_height, img_width),
batch_size=batch_size)
Found 1125 files belonging to 4 classes.
Using 900 files for training.
⚡函数原型:
- 函数
image_dataset_from_directory
tf.keras.preprocessing.image_dataset_from_directory(
directory,
labels="inferred",
label_mode="int",
class_names=None,
color_mode="rgb",
batch_size=32,
image_size=(256, 256),
shuffle=True,
seed=None,
validation_split=None,
subset=None,
interpolation="bilinear",
follow_links=False,
)
作用:
将文件夹数据加载到tf.data.Dataset
中,且加载同时打乱数据。
支持的图像格式:jpeg,png,bmp,gif动图被截断到第一帧。
参数:
- directory: 数据所在目录。如果标签是
inferred
(默认),则它应该包含子目录,每个目录包含一个类的图像。否则,将忽略目录结构。 - labels:
inferred
(标签从目录结构生成),或者是整数标签的列表/元组,其大小与目录中找到的图像文件的数量相同。标签应根据图像文件路径的字母顺序排序(通过Python中的os.walk(directory)获得)。 - label_mode:
int
: 标签将被编码成整数(使用的损失函数应为:sparse_categorical_crossentropyloss)categorical
:标签将被编码为分类向量(使用的损失函数应为:categorical_crossentropy loss)binary
:意味着标签(只能有2个)被编码为值为0或1的float32标量(例如:binary_crossentropy)None
:无标签- class_names:仅当labels为inferred时有效。这是类名称的明确列表(必须与子目录的名称匹配)。用于控制类的顺序(否则使用字母数字顺序)。
- color_mode:grayscale、rgb、rgba之一。默认值:rgb。图像将被转换为1、3或者4通道。
- batch_size:数据批次的大小。默认值:32
- image_size:从磁盘读取数据后将其重新调整大小。默认:(256,256)。由于管道处理的图像批次必须具有相同的大小,因此该参数必须提供。
- shuffle:是否打乱数据。默认值:True。如果设置为False,则按字母数字顺序对数据进行排序。
- seed:用于
shuffle
和转换的可选随机种子。 - validation_split: 0和1之间的可选浮点数,可保留一部分数据用于验证。
- subset:
training
或validation
之一。仅在设置validation_split
时使用 - interpolation:字符串,当调整图像大小时使用的插值方法。默认:
bilinear
。支持bilinear, nearest, bicubic, area, lanczos3, lanczos5, gaussian, mitchellcubic - follow_links:是否访问符号链接指向的子目录。默认:False
返回:一个tf.data.Dataset
对象
- 如果label_mode为None,它将生成float32张量,其shape为(batch_size, image_size[0], image_size(1), num_channels),并对图像进行编码。
- 否则,将生成一个元组(images, labels),其中图像的shape为(batch_size, image_size[0], image_size(1), num_channels),并且labels遵循下面描述的格式。
关于labels格式规则:
- 如果label_mode 是 int, labels是形状为(batch_size, )的int32张量
- 如果label_mode 是 binary, labels是形状为(batch_size, 1)的1和0的float32张量。
- 如果label_mode 是 categorial, labels是形状为(batch_size, num_classes)的float32张量,表示类索引的one-hot编码。
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 1125 files belonging to 4 classes.
Using 225 files for validation.
class_names = tr_ds.class_names
class_names
['cloudy', 'rain', 'shine', 'sunrise']
2.可视化数据
#创建一个图形对象,设置图形大小宽度20英寸,高度10英寸
plt.figure(figsize=(20, 10))
#tr_ds.take(1)从训练数据集tr_ds中获取一个批次的数据。take(1)返回一个包含一批数据的Dataset对象
#images是这一个批次的图片
#labels是这一个批次的标签
for images, labels in tr_ds.take(1):
#选择当前批次的前20张图
for i in range(20):
#将图形分成5行10列,子图索引为i+1
ax = plt.subplot(5, 10, i + 1)
#显示第i张图
#images[i]是一个张量,使用.numpy()将其转化为Numpy数组
#.astype("uint8")将图像数据类型转换为无符号8位整数(uint8),以便正确显示图像
plt.imshow(images[i].numpy().astype("uint8"))
#设置当前子图标题为当前图片的类别名称
#labels[i]是当前图片对应的标签,通过该标签检索图片类别
plt.title(class_names[labels[i]])
#关闭坐标轴显示
plt.axis("off")
#显示图片
plt.show()
for image_batch, labels_batch in tr_ds:
print(image_batch.shape)
print(labels_batch.shape)
break
(32, 180, 180, 3)
(32,)
3.配置数据集
- shuffle():打乱数据,关于此函数的详细介绍参考:https://zhuanlan.zhihu.com/p/42417456
- prefetch():预取数据,加速运行
- cache():将数据集缓存在内存中,加速运行
prefetch()
功能详细介绍:CPU 正在准备数据时,加速器处于空闲状态。相反,当加速器正在训练模型时,CPU 处于空闲状态。因此,训练所用的时间是 CPU 预处理时间和加速器训练时间的总和。prefetch()将训练步骤的预处理和模型执行过程重叠到一起。当加速器正在执行第 N 个训练步时,CPU 正在准备第 N+1 步的数据。这样做不仅可以最大限度地缩短训练的单步用时(而不是总用时),而且可以缩短提取和转换数据所需的时间。如果不使用prefetch(),CPU 和 GPU/TPU 在大部分时间都处于空闲状态
shuffle()
的机制和作用
机制:
- 缓冲区:
shuffle(buffer_size)
会创建一个大小为buffer_size
的缓冲区。 - 数据打乱:
- 开始时,缓冲区会填满前buffer_size个数据
- 每次从缓冲区中随机抽取一个数据,输出到下游处理
- 抽取一个数据后,立即从数据集中读取下一个数据,填补缓冲区
- 这种方式确保每次从缓冲区中抽取的数据是随机的
作用:
- 防止过拟合:通过打乱数据顺序,防止模型在训练过程中记住数据的顺序,从而提高模型的泛化能力。
- 提高模型鲁棒性:使模型在不同的数据顺序下都能表现良好。
- 数据均匀分布:确保每个小批次(batch)中的数据分布更加均匀,避免因数据顺序导致的训练不稳定。
#自动调整数据管道性能
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)
三、构建CNN网络模型
卷积神经网络(CNN)的输入是张量 (Tensor) 形式的 (image_height, image_width, color_channels)
,包含了图像高度、宽度及颜色信息。不需要输入batch size
。color_channels 为 (R,G,B) 分别对应 RGB 的三个颜色通道(color channel)。在此示例中,我们的 CNN 输入形状是 (180, 180, 3)
。我们需要在声明第一层时将形状赋值给参数input_shape。
CNN的输入张量表示图像的结构和颜色信息。每个像素点都被表示为具有color_channels个数值的向量,在训练时,通过一系列卷积层、池化层和全连接层等操作提取和处理图像特征。
num_classes = 4
"""
关于卷积核的计算不懂的可以参考文章:https://blog.csdn.net/qq_38251616/article/details/114278995
layers.Dropout(0.4) 作用是防止过拟合,提高模型的泛化能力。
关于Dropout层的更多介绍可以参考文章:https://mtyjkh.blog.csdn.net/article/details/115826689
"""
#创建序列模型,一种线性堆叠模型,各层按照他们被添加到模型中的顺序来堆叠
model = models.Sequential([
# 数据预处理层:将像素值从 [0, 255] 缩放到 [0, 1]即归一化,输入(180,180,3)
layers.experimental.preprocessing.Rescaling(1./255, input_shape=(img_height, img_width, 3)),
#层输入可以手动添加batch size信息,model.summary()中每一层输出shape中None将为指定批次大小:
#layers.experimental.preprocessing.Rescaling(1./255, input_shape=(img_height, img_width, 3),batch_size = 32),
# 卷积层1:16 个 3x3 的卷积核,使用 ReLU 激活函数,输出 (178, 178, 16)
layers.Conv2D(16, (3, 3), activation='relu', input_shape=(img_height, img_width, 3)),
# 池化层1:2x2 的平均池化,输出(89,89,16)
layers.AveragePooling2D((2, 2)),
# 卷积层2:32 个 3x3 的卷积核,使用 ReLU 激活函数,(87,87,32)
layers.Conv2D(32, (3, 3), activation='relu'),
# 池化层2:2x2 的平均池化,(43,43,32)
layers.AveragePooling2D((2, 2)),
# 卷积层3:64 个 3x3 的卷积核,使用 ReLU 激活函数 (41,41,64)
layers.Conv2D(64, (3, 3), activation='relu'),
# Dropout层:以 30% 的概率随机停止神经元工作,防止过拟合
layers.Dropout(0.3),
# Flatten层:将多维特征图展平为一维,连接卷积层与全连接层
layers.Flatten(),
# 全连接层:128 个神经元,使用 ReLU 激活函数
layers.Dense(128, activation='relu'),
# 输出层:输出预期结果,神经元数量为 num_classes
layers.Dense(num_classes)
])
model.summary() # 打印网络结构
Model: "sequential_4"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
rescaling_4 (Rescaling) (32, 180, 180, 3) 0
conv2d_12 (Conv2D) (32, 178, 178, 16) 448
average_pooling2d_8 (Avera (32, 89, 89, 16) 0
gePooling2D)
conv2d_13 (Conv2D) (32, 87, 87, 32) 4640
average_pooling2d_9 (Avera (32, 43, 43, 32) 0
gePooling2D)
conv2d_14 (Conv2D) (32, 41, 41, 64) 18496
dropout_4 (Dropout) (32, 41, 41, 64) 0
flatten_4 (Flatten) (32, 107584) 0
dense_8 (Dense) (32, 128) 13770880
dense_9 (Dense) (32, 4) 516
=================================================================
Total params: 13794980 (52.62 MB)
Trainable params: 13794980 (52.62 MB)
Non-trainable params: 0 (0.00 Byte)
_________________________________________________________________
四、编译模型
在准备对模型进行训练之前,还需要再对其进行一些设置。以下内容是在模型的编译步骤中添加的:
- 损失函数(loss):用于衡量模型在训练期间的准确率。
- 优化器(optimizer):决定模型如何根据其看到的数据和自身的损失函数进行更新。
- 指标(metrics):用于监控训练和测试步骤。以下示例使用了准确率,即被正确分类的图像的比率。
# 设置优化器
opt = tf.keras.optimizers.Adam(learning_rate=0.001)
model.compile(optimizer=opt,
loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
metrics=['accuracy'])
#Adam优化器是一种常用的梯度下降优化算法,用于更新模型的权重以最小化训练过程中的损失函数
#使用稀疏分类交叉熵损失函数
#from_logits=True说明模型输出是未经softmax转换的原始分数或概率值
五、训练模型
# 指定训练轮数,即遍历所有训练数据的次数
epochs = 10
history = model.fit(
tr_ds,
validation_data=val_ds,
epochs=epochs
)
Epoch 1/10
29/29 [==============================] - 105s 2s/step - loss: 1.2378 - accuracy: 0.6156 - val_loss: 0.6501 - val_accuracy: 0.6711
Epoch 2/10
29/29 [==============================] - 1s 33ms/step - loss: 0.4772 - accuracy: 0.8244 - val_loss: 0.4069 - val_accuracy: 0.8400
Epoch 3/10
29/29 [==============================] - 1s 30ms/step - loss: 0.2996 - accuracy: 0.8844 - val_loss: 0.4175 - val_accuracy: 0.8356
Epoch 4/10
29/29 [==============================] - 1s 30ms/step - loss: 0.2471 - accuracy: 0.8956 - val_loss: 0.5346 - val_accuracy: 0.8400
Epoch 5/10
29/29 [==============================] - 1s 30ms/step - loss: 0.2385 - accuracy: 0.9156 - val_loss: 0.4631 - val_accuracy: 0.8578
Epoch 6/10
29/29 [==============================] - 1s 33ms/step - loss: 0.2040 - accuracy: 0.9278 - val_loss: 0.3787 - val_accuracy: 0.8622
Epoch 7/10
29/29 [==============================] - 1s 33ms/step - loss: 0.1520 - accuracy: 0.9378 - val_loss: 0.4164 - val_accuracy: 0.8489
Epoch 8/10
29/29 [==============================] - 1s 30ms/step - loss: 0.1180 - accuracy: 0.9600 - val_loss: 0.3765 - val_accuracy: 0.8400
Epoch 9/10
29/29 [==============================] - 1s 30ms/step - loss: 0.0851 - accuracy: 0.9711 - val_loss: 0.3596 - val_accuracy: 0.8889
Epoch 10/10
29/29 [==============================] - 1s 30ms/step - loss: 0.0743 - accuracy: 0.9733 - val_loss: 0.4587 - val_accuracy: 0.8622
六、模型评估
#从history中获取准确率和损失
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))
# 创建子图,绘制1行2列的第一个
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()
七、预测
for images, labels in val_ds.take(1):
image = images[0].numpy().astype("uint8")
label = labels[0].numpy()
plt.figure(figsize = (3,2))
plt.axis("off")
print("实际标签:", class_names[label])
plt.imshow(image)
# Reshape the image to add the missing batch dimension
image = np.expand_dims(image, axis=0) # Add a batch dimension
pre = model.predict(image)
# 获取预测结果
pred_class = class_names[np.argmax(pre[0])]
print(f"预测类别: {pred_class}, 结果: {pre}")
实际标签: shine
1/1 [==============================] - 0s 20ms/step
预测类别: shine, 结果: [[ 0.12794775 -1.4010949 7.355306 -5.8990536 ]]
八、总结
- 了解
preprocessing.image_dataset_from_directory()
函数及其用法,学会如何配置数据集(特别是了解了shuffle操作) - 使用CNN模型进行天气图片识别,并使用了平均池化层和dropout层
- 添加了对验证集第一个batch的第一张图的预测并输出结果
ps: google colab用Gemini辅助编程和提问真的很爽…