- 🍨 本文为🔗365天深度学习训练营 中的学习记录博客
- 🍖 原作者:K同学啊
前言
-
实验环境
python 3.9.2
tensorflow 2.10.0
Jupyter Notebook: 7.4.5
代码实现
设置gpu
tf.config.experimental.set_memory_growth(gpu0, True)- 作用:启用显存按需分配。
- 参数说明:
- 第一个参数:要设置的
PhysicalDevice对象(这里是gpu0)。 - 第二个参数:
True表示启用显存增长;False表示禁用(默认行为是一次性分配全部显存)。
- 第一个参数:要设置的
- 为什么要这样设置
- 默认情况下,TensorFlow 会在程序启动时锁定 GPU 的全部显存,即使你只用了一小部分。
- 启用
memory_growth=True后,显存会随着模型训练/推理的需求逐步增长,更节省资源,也允许多个进程共享同一块 GPU(只要总显存够用)。
tf.config.set_visible_devices([gpu0], "GPU")- 作用:限制 TensorFlow 只能"看到"指定的设备。
- 参数说明:
- 第一个参数:一个设备列表(这里只包含
gpu0)。 - 第二个参数:设备类型(如
"GPU"、"CPU")。
- 第一个参数:一个设备列表(这里只包含
- 效果:
- 即使系统有多个 GPU,TensorFlow 也只会使用
[gpu0]这一块。 - 其他 GPU 对 TensorFlow 完全不可见,不会被分配任务,也不会被占用显存。
- 即使系统有多个 GPU,TensorFlow 也只会使用
python
# 设置GPU
import tensorflow as tf
gpus = tf.config.list_physical_devices("GPU") # 列出tensorflow能识别到的所有的gpu设备,返回的是列表
if gpus:
gpu0 = gpus[0] # 设置为如果有多个gpu,仅使用第0个gpu
tf.config.experimental.set_memory_growth(gpu0, True) # 设置gpu显存用量按需使用
tf.config.set_visible_devices([gpu0], "GPU") # 将其它gpu对tensorflow隐藏,确保只使用指定的gpu
gpus

导入数据
python
import pathlib
# 定义数据目录路径
data_dir = "./data/"
# 将字符串路径转换为 pathlib.Path 对象,便于后续使用路径时可以直接使用/来拼接
data_dir = pathlib.Path(data_dir)
查看数据
python
# 查看数据集图片数量
image_count = len(list(data_dir.glob('*/*.jpg')))
print("数据集中图片总数量为:", image_count)

python
import PIL
# 打开某种图片查看图片情况
Monkeypox = list(data_dir.glob('Monkeypox/*.jpg'))
# 由于PIL.Image.open() 只接受字符串路径(或类文件对象),不接受 pathlib.Path 对象,因此还需要使用str来进行强制类型转换
PIL.Image.open(str(Monkeypox[0]))

数据加载
-
tf.keras.preprocessing.image_dataset_from_directory(...)-
作用:从按类别组织的图像文件夹结构中自动加载数据,生成一个可直接用于训练的
tf.data.Dataset对象,每个批次包含(images, labels)。 -
参数说明与设计目的:
-
directory-
作用:指定图像数据集的根目录路径。
-
为什么要这样设置
函数需要知道从哪里读取图像。要求目录下每个子文件夹代表一个类别(如
cloudy/,rainy/),这是监督学习中常见的"文件夹即标签"约定,无需额外标注文件。
-
-
validation_split与subset-
作用:
validation_split:指定保留多少比例的数据作为验证集(如0.2表示 20%)。subset:指明当前加载的是"training"还是"validation"子集。
-
为什么要这样设置
为了在不手动划分数据的前提下,自动将数据分为训练集和验证集,便于模型评估和防止过拟合。配合固定
seed可确保划分结果可复现。
-
-
seed-
作用:随机种子,控制数据打乱和训练/验证划分的随机性。
-
为什么要这样设置
确保每次运行代码时,训练集和验证集的划分完全一致,提升实验的可重复性。若不设
seed,每次划分可能不同,导致结果不可比。
-
-
image_size=(height, width)-
作用:将所有输入图像统一调整为指定高度和宽度。
-
为什么要这样设置
深度学习模型(尤其是 CNN)要求输入尺寸固定。原始图像大小不一,必须通过 resize 统一维度,才能组成 batch(张量需形状一致)。
-
-
batch_size-
作用:每个批次包含的图像数量。
-
为什么要这样设置
批量处理能提升 GPU 利用率和训练稳定性。太小则训练慢、梯度噪声大;太大则显存可能溢出。
32是常用默认值,可根据 GPU 显存调整。
-
-
label_mode-
作用:控制标签的编码格式,可选
int(整数)、'categorical'(one-hot)、'binary'(二分类)或None。默认值为int。 -
为什么要这样设置
为了匹配损失函数的要求:
- 用
SparseCategoricalCrossentropy→ 需'int'标签; - 用
CategoricalCrossentropy→ 需'categorical'(one-hot)标签; - 自动适配避免手动转换标签格式。
- 用
-
-
color_mode-
作用:指定图像加载的颜色通道数,如
'rgb'(3通道)、'grayscale'(1通道)。默认值为rgb。 -
为什么要这样设置
根据任务需求决定输入维度。例如医学图像常用灰度图(节省计算),而自然图像通常用 RGB。确保输入张量通道数与模型第一层匹配。
-
-
shuffle(默认True)-
作用:是否在每轮训练前打乱数据顺序。
-
为什么要这样设置
打乱数据可避免模型学习到"顺序依赖",提升泛化能力。但在验证/测试时通常设为
False以保持结果稳定。
-
-
class_names(可选)-
作用:显式指定类别的顺序和名称列表。
-
为什么要这样设置
默认按子目录字母顺序排序类别,但有时希望固定类别索引(如保证
cloudy=0始终不变)。
-
-
python
# 基本参数设置
batch_size = 32
img_height = 224
img_width = 224
# 数据加载
train_ds = tf.keras.preprocessing.image_dataset_from_directory(
data_dir, # 数据所在的根目录路径,每个子文件夹代表一个类别
validation_split=0.2, # 20%的数据将被保留作为验证集
subset="training", # 指定当前加载的是训练集部分
seed=123, # 随机种子,确保训练集和验证集划分的一致性
image_size=(img_height, img_width), # 所有图像都将调整到这个尺寸
batch_size=batch_size # 每个批次包含的样本数量
)

python
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)

输出标签
python
class_names = train_ds.class_names
print(class_names)

数据可视化
python
import matplotlib.pyplot as plt
# 创建一个宽 20 英寸、高 10 英寸的绘图窗口(画布)
# 较大的尺寸便于清晰展示多张图像及其标签
plt.figure(figsize=(20, 10))
# 从训练数据集 train_ds 中取出 1 个 batch 的数据(包含 images 和 labels)
# .take(1) 返回一个只包含第一个 batch 的 Dataset 对象,用于可视化
for images, labels in train_ds.take(1):
# 遍历该 batch 中的前 20 张图像(假设 batch_size ≥ 20)
for i in range(20):
# 在 5 行 10 列的网格中创建第 (i+1) 个子图(共最多 50 个位置,仅使用前 20 个)
ax = plt.subplot(5, 10, i + 1)
# 将第 i 张图像从 TensorFlow 张量转换为 NumPy 数组,并转为 uint8 类型(像素值范围 [0, 255])
# 这是 matplotlib 显示图像所要求的数据格式
plt.imshow(images[i].numpy().astype("uint8"))
# 设置子图标题为对应的类别名称
# labels[i] 是一个整数标签,通过 class_names[labels[i]] 映射为可读的字符串
plt.title(class_names[labels[i]])
# 关闭坐标轴(不显示刻度和边框)
plt.axis("off")

再次检查数据
python
for image_batch, labels_batch in train_ds:
print(image_batch.shape)
print(labels_batch.shape)
break

配置数据集
-
AUTOTUNE = tf.data.AUTOTUNE-
作用:
-
tf.data.AUTOTUNE是一个特殊常量,告诉 TensorFlow 自动根据系统资源和运行时情况,动态选择最优的并行度或缓冲区大小。 -
它可以作为
prefetch(),map(),interleave()等函数的num_parallel_calls或buffer_size参数。
-
-
-
train_ds.cache()-
作用:
- 将数据集缓存到内存或本地磁盘,避免每次 epoch 都重复从磁盘读取和解码图像。
-
缓存位置
-
默认(无参数):缓存到内存(适合中小型数据集)。
-
指定路径:
cache("/path/to/cache")→ 缓存到磁盘(适合大型数据集,避免 OOM)。
-
-
-
train_ds.shuffle(1000)-
作用:
-
在每个 epoch 开始前,打乱数据顺序,确保模型不会学习到"顺序依赖"。
-
关键参数:
buffer_size=1000-
TensorFlow 采用蓄水池抽样实现 shuffle。
-
它维护一个大小为 1000 的缓冲区:
- 先从数据集中读取前 1000 个样本放入缓冲区;
- 每次随机从缓冲区中取出一个样本输出,并从数据集补充一个新样本;
- 重复直到数据集耗尽。
-
-
-
-
.prefetch(buffer_size=AUTOTUNE)- 作用:
- 预加载下一个 batch 的数据到内存,实现 "数据加载"与"模型训练"的并行化。
- 作用:
python
AUTOTUNE = tf.data.AUTOTUNE
train_ds = train_ds.cache().shuffle(1000).prefetch(buffer_size=AUTOTUNE)
val_ds = val_ds.cache().prefetch(buffer_size=AUTOTUNE)
构建模型
- 各层的作用:
- 输入预处理层 (
Rescaling):- 作用:对输入图像像素值进行归一化预处理。
- 原理说明:原始图像像素值通常在
[0, 255]范围内,而神经网络在[0, 1]或[-1, 1]范围内训练更稳定。Rescaling(1./255)将每个像素除以 255,将其缩放到[0, 1]区间。
- 卷积层 1 (
Conv2D(16, (3, 3), activation='relu')):- 作用:提取输入图像的初级局部特征(如边缘、角点、简单纹理)。
- 参数解析:
16:使用 16 个 3×3 卷积核,输出 16 通道的特征图;(3, 3):卷积核尺寸为 3×3;activation='relu':引入非线性激活函数,缓解梯度消失问题。
- 输入形状:由
input_shape=(img_height, img_width, 3)指定,表示接收 224×224 的 RGB 彩色图像。
- 池化层 1 (
AveragePooling2D((2, 2))):- 作用:对特征图进行空间下采样,降低分辨率。
- 工作方式:使用 2×2 的平均池化窗口,将每 2×2 区域的像素取平均值作为输出,使特征图的高和宽各缩小一半。
- 卷积层 2 (
Conv2D(32, (3, 3), activation='relu')):- 作用:在第一层提取的低级特征基础上,组合出更复杂的中级语义特征(如局部部件、重复纹理或光照变化模式)。
- 参数解析:
32:卷积核数量翻倍,提升特征表达能力;- 继续使用 3×3 卷积核和 ReLU 激活,保持结构一致性,同时逐步增加模型容量。
- 池化层 2 (
AveragePooling2D((2, 2))):- 作用:再次进行 2 倍下采样,进一步压缩空间维度,聚焦更高层次的抽象特征。
- 效果:经过两次池化后,原始 180×180 图像的空间尺寸已显著缩小(默认无填充时约为 43×43),减少计算量并增强平移不变性。
- Dropout 层 (
Dropout(0.3)):- 作用:一种正则化技术,在训练过程中随机将 30% 的神经元输出置零。
- 防止过拟合:强制网络不依赖于特定神经元,提升泛化能力。尤其在小数据集(如天气图像)上,可有效缓解训练准确率高但验证准确率低的问题。
- 注意:仅在训练阶段生效,推理(预测)时自动关闭。
- 卷积层 3 (
Conv2D(64, (3, 3), activation='relu')):- 作用:提取高级语义特征,如整体场景结构、判别性强的模式(例如"是否有云层"、"是否出现强光"等二分类关键线索)。
- 参数解析:
64:继续增加通道数,使模型能学习更丰富的特征表示;- 此层输出的特征图具有较强判别性,为后续分类提供关键依据。
- Dropout 层 (
Dropout(0.3))(第二次):- 作用:在深层特征之后再次施加正则化,进一步抑制模型对训练数据的过度拟合。
- Flatten 层 (
Flatten()):- 作用:将多维卷积特征张量(如
[batch, H, W, C])展平为一维向量([batch, H×W×C]),作为全连接层的输入。
- 作用:将多维卷积特征张量(如
- 全连接层 (
Dense(128, activation='relu')):- 作用:整合来自卷积主干的所有空间和通道信息,进行全局特征融合与非线性变换。
- 参数解析:
128:使用 128 个神经元构建一个紧凑的高维嵌入空间;ReLU激活继续引入非线性,增强模型判别能力。
- 输出层 (
Dense(num_classes)):- 作用:生成最终的分类 logits(未归一化的原始分数)。
- 结构说明:
num_classes = 2,因此输出 2 个数值,分别对应两个类别;- 无激活函数:配合损失函数
SparseCategoricalCrossentropy(from_logits=True)使用,由损失函数内部完成数值稳定的 softmax 计算; - 最终预测类别通过
argmax(output)获得。
- 输入预处理层 (
python
from tensorflow.keras import layers, models
# 定义类别数量(此处为二分类任务)
num_classes = 2
# 使用 Sequential 模型按顺序堆叠神经网络层
model = models.Sequential([
# 输入预处理层:像素值归一化
layers.experimental.preprocessing.Rescaling(
1./255,
input_shape=(img_height, img_width, 3)
),
# 第一组卷积块:提取低级视觉特征(边缘、角点、纹理)
layers.Conv2D(16, (3, 3), activation='relu'), # 卷积层1,使用16个3×3卷积核
layers.AveragePooling2D((2, 2)), # 池化层1,2×2平均池化,空间尺寸减半
# 第二组卷积块:组合低级特征,形成中级语义模式
layers.Conv2D(32, (3, 3), activation='relu'), # 卷积层2,通道数翻倍至32,增强表达能力
layers.AveragePooling2D((2, 2)), # 池化层2,再次下采样,聚焦更抽象区域
# 正则化层:防止过拟合
layers.Dropout(0.3),
# 第三组卷积块:提取高级语义特征(如整体结构、判别性模式)
layers.Conv2D(64, (3, 3), activation='relu'), # 卷积层3,64个卷积核,捕捉复杂模式
layers.Dropout(0.3), # 再次加入 Dropout,进一步抑制过拟合
# 全连接分类头:从特征图到类别预测
layers.Flatten(), # 将多维特征图展平为一维向量
layers.Dense(128, activation='relu'), # 全连接隐藏层
layers.Dense(num_classes) # 输出层,输出num_classes 个 logits(未归一化分数)
])
# 打印模型各层的名称、输出形状和参数量,用于验证结构是否符合预期
model.summary()

编译模型
python
# 设置优化器
opt = tf.keras.optimizers.Adam(learning_rate=1e-4)
model.compile(optimizer=opt,
loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
metrics=['accuracy'])
训练模型
python
from tensorflow.keras.callbacks import ModelCheckpoint
# 设置训练总轮数(epochs)为 50
epochs = 50
# 创建 ModelCheckpoint 回调函数,用于在训练过程中自动保存最优模型
checkpointer = ModelCheckpoint(
'best_model.h5', # 保存路径:将最优模型权重保存为 best_model.h5 文件
monitor='val_accuracy', # 监控指标:以验证集准确率(validation accuracy)作为评判标准
verbose=1, # 日志级别:1 表示在保存时打印提示信息
save_best_only=True, # 仅保存最佳模型:只有当 monitored 指标比之前更好时才保存
save_weights_only=True # 仅保存权重:不保存完整模型结构(只存 .h5 权重文件,体积更小)
)
# 启动模型训练
history = model.fit(
train_ds, # 训练数据集(tf.data.Dataset 对象)
validation_data=val_ds, # 验证数据集,用于每轮结束后评估泛化性能
epochs=epochs, # 总训练轮数
callbacks=[checkpointer] # 注册回调函数列表,此处包含模型自动保存机制
)


模型评估
python
from datetime import datetime
current_time = datetime.now() # 获取当前时间
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.xlabel(current_time)
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()

指定图片预测
python
# 加载效果最好的模型权重
model.load_weights('best_model.h5')
python
import tensorflow as tf
from PIL import Image
import numpy as np
# 加载图像
img = Image.open("./data/Monkeypox/M06_01_04.jpg")
# 将 PIL 图像转换为 NumPy 数组
img_np = np.array(img)
# 调整图像尺寸
image = tf.image.resize(img_np, [img_height, img_width])
# 添加 Batch 维度
img_array = tf.expand_dims(image, 0)
# 使用模型预测
predictions = model.predict(img_array)
print("预测结果为:", class_names[np.argmax(predictions)])

学习总结
- 更熟练掌握了
tf.keras.preprocessing.image_dataset_from_directory的用法。该函数能自动从"文件夹即标签"的目录结构中加载图像数据,生成高效的tf.data.Dataset对象。通过设置validation_split和subset参数,不用手动划分数据集就可以得到训练集与验证集;固定seed保证了数据集划分的可复现。 - 对cnn网络中各层的作用有了更清晰的认识:
Rescaling层完成像素归一化;三个Conv2D层逐级提取从边缘到语义的多层次特征;AveragePooling2D进行平滑下采样;Dropout(0.3)有效缓解小数据集上的过拟合问题;Flatten层连接卷积与全连接部分;最后的Dense层输出 logits。 - 学会了 通过
ModelCheckpoint回调机制 "只保存验证集上表现最好的模型权重"。 避免训练结束时因过拟合导致的次优模型被误用,还保证了后续预测基于泛化能力最强的版本。 - 知道了对数据预处理一致性的重要:必须将 PIL 图像先转换为 NumPy 数组,再经过与训练时相同的尺寸调整和归一化流程,才能进行预测。