前言

本篇是我训练营 TensorFlow 篇的第 2 次学习,主要目标是使用 TensorFlow 完成 CIFAR10 彩色图片识别——和 PyTorch 篇 P2 周完全相同的数据集、相同的 10 分类任务。

感谢 K同学啊 老师的教学,以及 ChatGPT 和 Kimi。

P2 与 T2 的主要对比

对比项目 P2:PyTorch 实现 CIFAR10 T2:TensorFlow 实现 CIFAR10
深度学习框架 PyTorch 2.11(CPU 版) TensorFlow 2.10 + DirectML(AMD GPU)
数据集 CIFAR10(torchvision 下载约 11 分钟) 同一个 CIFAR10(tf.keras.datasets 内置)
标签形状 [32] 一维索引 (50000, 1) 二维列向量
彩色图可视化 必须 transpose((1, 2, 0)) 调整通道顺序 直接 imshow(NHWC 天生适配 matplotlib)
网络结构 3 卷积(64/64/128) + 3 池化 3 卷积(32/64/64) + 2 池化
Flatten 后维度 128 × 2 × 2 = 512 4 × 4 × 64 = 1024
全连接层 512 → 256 → 10 1024 → 64 → 10
模型参数量 246,474 122,570(约为 P2 的一半)
优化器 SGD(lr=0.01) adam
每 epoch 步数 1563 个 batch(50000/32) 1563 steps(完全一致)
10 轮训练准确率 59.0% 78.1%
10 轮测试准确率 58.5% 70.5%(最高 70.85%)

一、前期工作

1. 设置 GPU

和 T1 周完全相同的流程,AMD DirectML 环境(py310)直接使用:

import tensorflow as tf
gpus = tf.config.list_physical_devices("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")

2. 导入数据

import tensorflow as tf
from tensorflow.keras import datasets, layers, models
import matplotlib.pyplot as plt

(train_images, train_labels), (test_images, test_labels) = datasets.cifar10.load_data()

3. 归一化

# 将像素的值标准化至0到1的区间内。
train_images, test_images = train_images / 255.0, test_images / 255.0

train_images.shape,test_images.shape,train_labels.shape,test_labels.shape
((50000, 32, 32, 3), (10000, 32, 32, 3), (50000, 1), (10000, 1))

这一段的意思是:训练集 50,000 张 + 测试集 10,000 张,图片形状 (32, 32, 3)——高 32、宽 32、RGB 3 通道,通道在最后是 TensorFlow 的 NHWC 约定。

标签的形状是 (50000, 1) 而不是 (50000,)。T1 周 MNIST 的标签是一维数组,CIFAR10 的标签是二维列向量(每个标签外面还包了一层)。这个差异会在可视化取标签时体现出来(train_labels[i][0] 要多取一层)。


4. 可视化

class_names = ['airplane', 'automobile', 'bird', 'cat', 'deer','dog', 'frog', 'horse', 'ship', 'truck']

plt.figure(figsize=(20,10))
for i in range(20):
    plt.subplot(5,10,i+1)
    plt.xticks([])
    plt.yticks([])
    plt.grid(False)
    plt.imshow(train_images[i], cmap=plt.cm.binary)
    plt.xlabel(class_names[train_labels[i][0]])
plt.show()

![在这里插入数据示例图片]

为什么 T2 不需要 transpose((1, 2, 0)) P2 周彩色图可视化时必须用 transpose((1, 2, 0)) 把 PyTorch 的 [C, H, W] 转成 matplotlib 要的 [H, W, C];而 TensorFlow 的数据天生就是 (H, W, C) 的 NHWC 格式,恰好是 matplotlib 需要的格式,直接 imshow 即可。

为什么设置了 cmap=plt.cm.binary 显示的还是彩色图? cmap 只对单通道灰度图生效,传入的是 RGB 三通道彩色图时,cmap 参数会被直接忽略。所以 T1 周 MNIST 灰度图受 cmap 控制显示黑白,本周彩色图不受任何影响。


二、构建 CNN 网络

1. 网络结构

model = models.Sequential([
    layers.Conv2D(32, (3, 3), activation='relu', input_shape=(32, 32, 3)), #卷积层1,卷积核3*3
    layers.MaxPooling2D((2, 2)),                   #池化层1,2*2采样
    layers.Conv2D(64, (3, 3), activation='relu'),  #卷积层2,卷积核3*3
    layers.MaxPooling2D((2, 2)),                   #池化层2,2*2采样
    layers.Conv2D(64, (3, 3), activation='relu'),  #卷积层3,卷积核3*3
    
    layers.Flatten(),                      #Flatten层,连接卷积层与全连接层
    layers.Dense(64, activation='relu'),   #全连接层,特征进一步提取
    layers.Dense(10)                       #输出层,输出预期结果
])

model.summary()  # 打印网络结构

这一段的意思是:输入形状从 T1 周的 (28, 28, 1) 变成 (32, 32, 3)(彩色图 3 通道),网络也从"2 卷积 + 2 池化"加深到"3 卷积 + 2 池化"。和 P2 周 PyTorch 版的结构对比:

P2(PyTorch) T2(TensorFlow/Keras) 差异
卷积 1 nn.Conv2d(3, 64, 3) Conv2D(32, (3,3)) 输出通道 64 → 32
卷积 2 nn.Conv2d(64, 64, 3) Conv2D(64, (3,3)) 一致
卷积 3 nn.Conv2d(64, 128, 3) Conv2D(64, (3,3)) 输出通道 128 → 64
池化次数 3 次(conv3 后还有 pool3) 2 次(conv3 后直接展平) T2 少一次池化
展平维度 128×2×2 = 512 64×4×4 = 1024 殊途不同归
全连接 512→256→10 1024→64→10 T2 的隐藏层更小

两个网络都是"3 卷积 + 2 全连接"的骨架,但 P2 靠第三次池化把特征图压到 2×2、用 128 通道堆出 512 维;T2 少一次池化,保留 4×4 的空间尺寸、用 64 通道得到 1024 维。这也导致 T2 的参数量(12.2 万)只有 P2(24.6 万)的一半,P2 的大头在全连接层 512×256,T2 把特征压到 64 维就输出,省了近 7 万参数。

2. 模型结构与 shape 推导

Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d (Conv2D)              (None, 30, 30, 32)        896       
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 15, 15, 32)        0         
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 13, 13, 64)        18496     
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 6, 6, 64)          0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 4, 4, 64)          36928     
_________________________________________________________________
flatten (Flatten)            (None, 1024)              0         
_________________________________________________________________
dense (Dense)                (None, 64)                65600     
_________________________________________________________________
dense_1 (Dense)              (None, 10)                650       
=================================================================
Total params: 122,570
Trainable params: 122,570
Non-trainable params: 0
_________________________________________________________________

shape 推导规则不变(3×3 卷积无 padding 尺寸减 2,2×2 池化尺寸减半),和 P2 周的推导过程逐项对照:

P2(3 次池化):  32 → conv 30 → pool 15 → conv 13 → pool 6 → conv 4 → pool 2 → 128×2×2 = 512
T2(2 次池化):  32 → conv 30 → pool 15 → conv 13 → pool 6 → conv 4 → Flatten 4×4×64 = 1024

参数量验算(方法见 P2、P4 周笔记):

计算式 参数量
conv2d 3 × 32 × 3 × 3 + 32 896
conv2d_1 32 × 64 × 3 × 3 + 64 18,496
conv2d_2 64 × 64 × 3 × 3 + 64 36,928
dense 1024 × 64 + 64 65,600
dense_1 64 × 10 + 10 650
合计 122,570

model.summary() 输出完全一致。CIFAR10 的参数量比 MNIST(T1 周 121,930)只多了 640,因为网络骨架几乎一样,只是第一层卷积的输入通道从 1 变成 3(多了 32×2×3×3=576 个参数)加上输出通道配置略有不同。


三、编译

model.compile(optimizer='adam',
              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
              metrics=['accuracy'])

和 T1 周完全一致:Adam 优化器 + 稀疏交叉熵(标签是索引不是 one-hot)+ from_logits=True(输出层没接 softmax)。


四、训练模型

history = model.fit(train_images, train_labels, epochs=10, 
                    validation_data=(test_images, test_labels))

fit 默认 batch_size=32,每个 epoch 是 ⌈50000 / 32⌉ = 1563 个 step——和 P2 周 DataLoader 的 1563 个 batch 完全一致,框架不同、数学相同。

Epoch 1/10
1563/1563 [==============================] - 7s 4ms/step - loss: 1.5787 - accuracy: 0.4231 - val_loss: 1.3765 - val_accuracy: 0.5084
Epoch 2/10
1563/1563 [==============================] - 6s 4ms/step - loss: 1.1883 - accuracy: 0.5767 - val_loss: 1.1512 - val_accuracy: 0.5957
Epoch 3/10
1563/1563 [==============================] - 7s 4ms/step - loss: 1.0238 - accuracy: 0.6399 - val_loss: 1.0110 - val_accuracy: 0.6442
Epoch 4/10
1563/1563 [==============================] - 6s 4ms/step - loss: 0.9214 - accuracy: 0.6760 - val_loss: 0.9369 - val_accuracy: 0.6651
Epoch 5/10
1563/1563 [==============================] - 6s 4ms/step - loss: 0.8500 - accuracy: 0.7019 - val_loss: 0.9450 - val_accuracy: 0.6725
Epoch 6/10
1563/1563 [==============================] - 6s 4ms/step - loss: 0.7923 - accuracy: 0.7220 - val_loss: 0.8756 - val_accuracy: 0.7023
Epoch 7/10
1563/1563 [==============================] - 6s 4ms/step - loss: 0.7426 - accuracy: 0.7392 - val_loss: 0.9143 - val_accuracy: 0.6812
Epoch 8/10
1563/1563 [==============================] - 6s 4ms/step - loss: 0.6967 - accuracy: 0.7540 - val_loss: 0.8602 - val_accuracy: 0.7011
Epoch 9/10
1563/1563 [==============================] - 6s 4ms/step - loss: 0.6542 - accuracy: 0.7705 - val_loss: 0.8588 - val_accuracy: 0.7085
Epoch 10/10
1563/1563 [==============================] - 6s 4ms/step - loss: 0.6187 - accuracy: 0.7809 - val_loss: 0.8609 - val_accuracy: 0.7050

训练结果分析

本次共训练 10 个 epoch。训练侧收敛迅速:训练 loss 从 1.5787 降至 0.6187,训练准确率从 42.31% 一路升到 78.09%,10 轮内没有看到平台期。验证侧同样稳步爬升:验证准确率从 50.84% 升至 70.50%,第 9 轮达到最高的 70.85%

教程的达标要求是测试准确率 72%,本次最高 70.85%,略低于目标。但对比 P2 周 PyTorch 版同样 10 轮的 58.5%,提升了约 12 个百分点。

过拟合信号在尾声出现:第 9 → 10 轮,训练准确率继续上升(77.05% → 78.09%),但验证准确率回落(70.85% → 70.50%)、val_loss 微升(0.8588 → 0.8609),训练与验证的准确率差距拉大到 7.6 个百分点——和 T1 周 MNIST 尾段的轻度过拟合现象一致,只是 CIFAR10 上来得更早、更明显。这说明继续加 epoch 收益有限,想把准确率推过 72%,应该换思路:数据增强(P5 周学过)、加入 BatchNorm(P3 周)、换更深的网络(P7、P8 周的 VGG-16、ResNet-34)。

与 P2 周的对比

  • P2(PyTorch CPU + SGD):训练 59.0% / 测试 58.5%,两者几乎贴合,是典型的欠拟合——SGD 固定学习率收敛慢,10 轮还没学够;
  • T2(TensorFlow GPU + Adam):训练 78.1% / 测试 70.5%,训练明显超过验证,是轻度过拟合——Adam 收敛快,10 轮已经开始记忆训练集细节。

同一数据集上呈现出欠拟合与过拟合两种截然不同的状态,主要变量是优化器(SGD lr=0.01 vs Adam 自适应学习率,两者对比见 P9 周笔记);当然两边网络细节也不完全相同(P2 三次池化 128 通道、T2 两次池化 64 通道),不是严格的单变量实验,但 Adam 带来的收敛速度差异是主导因素。


五、预测

通过模型进行预测得到的是每一个类别的分数,数字越大该图片为该类别的可能性越大。

plt.imshow(test_images[1])

在这里插入图片描述

import numpy as np

pre = model.predict(test_images)
print(class_names[np.argmax(pre[1])])
313/313 [==============================] - 0s 1ms/step
ship

pre 的 shape 是 (10000, 10),取 pre[1] 的 argmax 得到类别索引,再查 class_names 还原成类别名——test_images[1] 是一张**船(ship)**的图片,预测正确。


六、模型评估

1. 训练曲线

import matplotlib.pyplot as plt

plt.plot(history.history['accuracy'], label='accuracy')
plt.plot(history.history['val_accuracy'], label = 'val_accuracy')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.ylim([0.5, 1])
plt.legend(loc='lower right')
plt.show()

![在这里插入训练曲线图片]

从曲线可以清楚看到四、训练结果分析中的判断:训练准确率持续爬升、训练 loss 持续下降;验证准确率在 70% 附近开始走平,val_loss 在最后两轮停止下降并微微抬头——过拟合的拐点就出现在第 9 轮附近。

2. 测试集最终评估

test_loss, test_acc = model.evaluate(test_images,  test_labels, verbose=2)
print(test_acc)
0.7049999833106995

model.evaluate() 在全部 10,000 张测试图片上评估,结果与最后一轮的 val_accuracy 一致(评估的就是同一个测试集):测试准确率 70.50%313 是评估步数(⌈10000 / 32⌉)。


总结

本周学习了以下几个方面:

  1. 完成了 TensorFlow 版 CIFAR10 全流程:从 load_data() → 归一化 → Sequential 建模 → compile/fit → 预测 → evaluate,T1 周建立的 Keras 流程感在更难的彩色数据集上得到巩固,测试准确率达到 70.50%(最高 70.85%)。
  2. 理清了彩色图可视化的框架差异:PyTorch 的 NCHW 必须 transpose((1, 2, 0)) 才能用 matplotlib 显示,TensorFlow 的 NHWC 天生适配、imshow 直接显示;并理解了 cmap 只对单通道灰度图生效、对彩色图无效的原因。
  3. 完成了第三轮 shape 推导与参数验算32 → 30 → 15 → 13 → 6 → 4,展平 4×4×64 = 1024,总参数量 122,570 与 summary 完全一致;并通过与 P2 网络(3 池化、512 展平、24.6 万参数)的结构对照,理解了"池化次数与通道配置如何共同决定展平维度和参数规模"。
Logo

AtomGit AI 社区提供模型库、数据集、Agent、Token等资源

更多推荐