TensorFlow学习笔记:猫狗识别
- 🍨 本文为🔗365天深度学习训练营 中的学习记录博客
- 🍖 原作者:K同学啊
一、基础设置与导入数据
import matplotlib.pyplot as plt
import numpy as np
import os
import PIL
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers, models
import pathlib
from tqdm import tqdm # 进度条神器
# 设置 GPU
gpus = tf.config.list_physical_devices('GPU')
print("Found GPUs:", gpus)
# 准备数据路径
data_dir = pathlib.Path('./T8_data')
# 加载数据
img_height = 224
img_width = 224
batch_size = 32
# 训练集
train_ds = tf.keras.utils.image_dataset_from_directory(
data_dir,
validation_split=0.2,
subset="training",
seed=123,
image_size=(img_height, img_width),
batch_size=batch_size)
# 验证集
val_ds = tf.keras.utils.image_dataset_from_directory(
data_dir,
validation_split=0.2,
subset="validation",
seed=123,
image_size=(img_height, img_width),
batch_size=batch_size)
class_names = train_ds.class_names
print(f"识别目标: {class_names}")
# 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)
二、搭建 CNN 模型
model = models.Sequential([
# 预处理:归一化
layers.Rescaling(1./255, input_shape=(img_height, img_width, 3)),
# 卷积池化层 1
layers.Conv2D(16, 3, padding='same', activation='relu'),
layers.MaxPooling2D(),
# 卷积池化层 2
layers.Conv2D(32, 3, padding='same', activation='relu'),
layers.MaxPooling2D(),
# 卷积池化层 3
layers.Conv2D(64, 3, padding='same', activation='relu'),
layers.MaxPooling2D(),
# 防过拟合 Dropout (20% 神经元随机断开)
layers.Dropout(0.2),
# 全连接层
layers.Flatten(),
layers.Dense(128, activation='relu'),
layers.Dense(len(class_names)) # 输出两类 (cat, dog)
])
model.summary()
三、 编译模型
model.compile(optimizer='adam',
loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
metrics=['accuracy'])
四、手动挡训练循环
epochs = 20
history = {'accuracy': [], 'val_accuracy': [], 'loss': [], 'val_loss': []}
for epoch in range(epochs):
print(f"\nEpoch {epoch+1}/{epochs}")
# 1.训练阶段
train_loss, train_acc, train_steps = 0, 0, 0
# tqdm 给循环加上进度条
for images, labels in tqdm(train_ds, desc='Training'):
loss, acc = model.train_on_batch(images, labels)
train_loss += loss # 把每一小批的 loss 攒起来
train_acc += acc # 把每一小批的 acc 攒起来
train_steps += 1
# 计算整个 Epoch 的平均成绩
epoch_train_loss = train_loss / train_steps
epoch_train_acc = train_acc / train_steps
# 2.验证阶段
val_loss, val_acc, val_steps = 0, 0, 0
for images, labels in tqdm(val_ds, desc='Validation'):
loss, acc = model.test_on_batch(images, labels) # 验证集用 test_on_batch
val_loss += loss
val_acc += acc
val_steps += 1
epoch_val_loss = val_loss / val_steps
epoch_val_acc = val_acc / val_steps
# 把平均成绩记录到 history 字典里,准备画图
history['loss'].append(epoch_train_loss)
history['accuracy'].append(epoch_train_acc)
history['val_loss'].append(epoch_val_loss)
history['val_accuracy'].append(epoch_val_acc)
print(f"loss: {epoch_train_loss:.4f} - acc: {epoch_train_acc:.4f} - val_loss: {epoch_val_loss:.4f} - val_acc: {epoch_val_acc:.4f}")
修复课程中的bug:
在本次实验中,我们放弃了傻瓜式的 model.fit(),改用 model.train_on_batch() 来手动控制训练过程。但在原始逻辑下,绘制出的 Training 和 Validation Accuracy/Loss 曲线出现了极其剧烈的震荡,就像心电图一样忽上忽下,模型似乎完全没有稳定收敛。
经过代码排查,发现问题出在数据记录的逻辑上。
在使用 for 循环遍历一整个 Epoch 的数据时,每一次 train_on_batch 都会返回当前这一小批的 loss 和 accuracy。
错误的做法(原因):直接将循环结束时最后一次的 acc 和 loss 添加到 history 列表中作为这一个 Epoch 的最终成绩。
影响:这相当于用最后一批数据的成绩,代表了整个训练集的成绩。如果最后一批图片刚好都很简单,准确率就会飙升到 100%;如果刚好都很模糊、很难认,准确率就会暴跌。
如何解决:为了真实反映模型在一个 Epoch 内的表现,我们需要计算整个 Epoch 的平均成绩。
在每个 Epoch 开始前,初始化三个累加器:train_loss = 0, train_acc = 0, step = 0。
在遍历每一个 Batch 时,将当批的成绩累加起来:train_loss += loss,并且步骤数加一:step += 1。
当一整个 Epoch 结束(遍历完所有 Batch)后,除以总步骤数求平均值:epoch_loss = train_loss / step。
最后,将这个平均值追加到 history 字典中。
五、 画图评估以及预测
acc = history['accuracy']
val_acc = history['val_accuracy']
loss = history['loss']
val_loss = 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.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()
# 预测一张图片
plt.figure(figsize=(18, 5))
plt.suptitle('Prediction Results')
for images, labels in val_ds.take(1):
for i in range(8):
ax = plt.subplot(1, 8, i + 1)
plt.imshow(images[i].numpy().astype("uint8"))
# 增加一个维度,模型预测需要
img_array = tf.expand_dims(images[i], 0)
# 让模型进行预测
predictions = model.predict(img_array)
predicted_class = class_names[np.argmax(predictions)]
plt.title(predicted_class)
plt.axis("off")
plt.show()



六、总结
本次课程放弃了之前使用的“自动挡” model.fit() 训练方式,全面转向“手动挡”的 train_on_batch(),实现对模型训练过程的底层控制。
自定义训练循环 (Custom Training Loop):
使用 train_on_batch() 和 test_on_batch() 逐批次(Batch)地将数据喂给模型。
优势:打破了封装的黑盒,能精确掌控每一个 Epoch 和 Batch 的前向传播与反向传播过程。
可视化进度追踪 (tqdm):
引入了 tqdm 库,在命令行中实时渲染训练和验证的进度条。
优势:极大地提升了代码的可读性和交互体验,模型训练状态一目了然。
BUG 排查与修复:
问题:原版代码逻辑中,直接将每个 Epoch 最后一次 Batch 的准确率(Accuracy)和损失(Loss)作为整个 Epoch 的成绩,训练曲线出现剧烈震荡。
修复方案:在代码逻辑中引入了累加器。将当前 Epoch 内每一个 Batch 的 Loss 和 Accuracy 进行累加,并在 Epoch 结束时除以总步数(Steps)求得平均值。
修复效果:采用平均值记录后,数据客观反映了模型的全局表现,彻底消除了由个别极端 Batch 引起的曲线抖动。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)