T3-天气图片识别

T3周:天气图片识别

⛽ 我的环境

  • 语言环境: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之间的可选浮点数,可保留一部分数据用于验证。
  • subsettrainingvalidation之一。仅在设置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辅助编程和提问真的很爽…

  • 24
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值