【机器学习】任务九:卷积神经网络(基于 Cifar-10 数据集的彩色图像识别分类、基于 CNN 的手写数字识别的实验)
1.卷积神经网络
卷积神经网络(Convolutional Neural Network, CNN)是一种专门用于处理数据网格结构(如图像、视频等)的深度学习模型,在计算机视觉任务中被广泛应用,如图像分类、目标检测、图像分割等。以下是卷积神经网络的详细介绍:
1.1 卷积神经网络 (CNN) 结构及原理
核心组件:
- 卷积层: 使用卷积核对输入数据(如图像)进行滑动,生成特征图(feature map)。
- 池化层: 通过下采样减少特征图的尺寸,降低计算复杂度和内存消耗。常见方法包括最大池化 (max pooling)和平均池化 (average pooling)。
- 全连接层: 将卷积和池化提取到的特征映射为输出类别,实现分类或回归任务。
- Dropout层: 防止过拟合,通过随机丢弃部分神经元。
激活函数: 常用ReLU函数引入非线性,帮助模型学习复杂模式。
特点:
- 局部连接: 每一层只与相邻节点连接,减少参数数量。
- 权重共享: 同一卷积核在不同位置重复使用,进一步降低计算复杂度。
- 典型的卷积神经网络由卷积层、池化层和全连接层 3 部分组成。在图像分类中表现良好的深度卷积神经网络,往往由多个“卷积层+池化层”的组合堆叠而成,通常多达十几层甚至上百层
-
卷积层用于提取图像中的局部特征;池化层用于对提取出的局部特征进行降维处理,防止过拟合;全连接层用于接收池化层的输出,为后续分类任务做准备。
1.2 经典卷积神经网络结构
LeNet-5: 主要用于手写数字识别,包括多个卷积层和池化层组合。
- 网络结构:输入层 → 卷积层 → 池化层 → 卷积层 → 池化层 → 全连接层 → 输出层
VGGNet: 探索卷积网络深度与性能的关系,VGG16和VGG19为其常见变体。
- 特点:多组3×3卷积核,每组卷积后接2×2最大池化层。
ResNet: 引入残差模块,允许数据直接跳过某些层,提高网络性能。
2.基于 Cifar-10 数据集的彩色图像识别分类
2.1 导入所需的模块和包并进行数据预处理
2.1.1 实现目的:
导入TensorFlow等所需模块,用于加载 CIFAR-10 数据集,并完成数据类型转换和标准化处理,确保模型可以正常使用这些数据进行训练和测试。
2.1. 代码片段与结果
'''步骤一,导入本项目所需要的模块和包,从 Keras 中导入 Cifar-10 数据集,将训练集的特征值和标签值分别存储在
x_train 和 y_train 中,将测试集的特征值和标签值分别存储在 x_test 和 y_test 中。将特征值 x_train 和 x_test
的数据类型转换为 tf.float32,并进行标准化处理,使其取值范围为 0~1;将标签值 y_train 和 y_test的数据类型转换为 tf.int32。'''
# 导入所需要的模块与包
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
import os
# 导入Cifar-10数据集,将训练集和测试集存储在相应变量中
cifar10 = tf.keras.datasets.cifar10
(x_train, y_train), (x_test, y_test) = cifar10.load_data()
# 转换特征值的数据类型并进行标准化处理
x_train, x_test = tf.cast(x_train, dtype=tf.float32) / 255.0, tf.cast(x_test, dtype=tf.float32) / 255.0
# 转换标签值的数据类型
y_train, y_test = tf.cast(y_train, dtype=tf.int32), tf.cast(y_test, dtype=tf.int32)
# 显示数据集的特征值和标签值的 shape 属性值
print("x_train.shape =", x_train.shape)
print("y_train.shape =", y_train.shape)
print("x_test.shape =", x_test.shape)
print("y_test.shape =", y_test.shape)
2.1.3 代码解释:
1.导入 TensorFlow、NumPy、Matplotlib 和 OS 模块:
- TensorFlow:用于深度学习模型构建和数据处理。
- NumPy:用于数值计算和数据操作。
- Matplotlib:用于数据的可视化。
- OS:用于文件和目录操作。
2.加载 CIFAR-10 数据集:
tf.keras.datasets.cifar10
提供了 CIFAR-10 数据集。该数据集包含 10 类图片,适用于图像分类任务。load_data()
将数据集拆分为训练集和测试集,并存储在(x_train, y_train)
和(x_test, y_test)
中。
3.数据类型转换与标准化:
- 使用
tf.cast()
将特征值转换为tf.float32
数据类型。 - 将像素值标准化到 [0, 1] 范围内,方便模型训练。
- 将标签值转换为
tf.int32
,用于模型的分类任务。
4.显示数据形状:
- 通过
print()
输出训练集和测试集的特征值及标签值的形状,以验证数据加载是否正确。
2.1.4 结果与结果分析
x_train.shape = (50000, 32, 32, 3)
x_train.shape = (50000, 32, 32, 3)
y_train.shape = (50000, 1)
y_test.shape = (10000, 1)
1. 训练集数据 (x_train, y_train)
x_train.shape = (50000, 32, 32, 3)
- 50000:训练集包含 50,000 张图像。
- 32 × 32:每张图像的大小是 32 × 32 像素。
- 3:代表图像的 3 个颜色通道(RGB),即每个像素点包含红、绿、蓝三种颜色值。
y_train.shape = (50000, 1)
- 每张图像有 1 个标签,表示其所属类别。CIFAR-10 有 10 个类别,用标签 0~9 表示。
2. 测试集数据 (x_test, y_test)
x_test.shape = (10000, 32, 32, 3)
- 10000:测试集包含 10,000 张图像。
- 32 × 32:每张图像的尺寸为 32 × 32 像素。
- 3:表示每张图像有 3 个颜色通道(RGB)。
y_test.shape = (10000, 1)
- 每张测试图像也有 1 个标签,对应其类别。
2.2 构建 CNN 模型
2.2.1 实现目的:
构建一个用于 CIFAR-10 图像分类的卷积神经网络(CNN)模型。通过多个卷积层、池化层和全连接层的组合来提取图像特征,并最终完成分类任务。
2.2.2 代码片段:
'''步骤二,构建 CNN 模型,图像识别分类网络模型采用卷积神经网络,包括两
个卷积层、两个池化层、4 个 Dropout 层、一个拉伸层、两个全连接层和一个输
出层'''
# 构建 CNN 模型
model = tf.keras.models.Sequential([
# 使用 Input 层明确指定输入形状
tf.keras.Input(shape=x_train.shape[1:]),
# 创建卷积层
tf.keras.layers.Conv2D(32, kernel_size=(3, 3), padding='same', activation=tf.nn.relu),
# 创建最大池化层
tf.keras.layers.MaxPool2D(pool_size=(2, 2), strides=(1, 1), padding='same'),
# 创建 Dropout 层
tf.keras.layers.Dropout(0.2),
# 创建第二个卷积层
tf.keras.layers.Conv2D(64, kernel_size=(3, 3), padding='same', activation=tf.nn.relu),
# 创建最大池化层
tf.keras.layers.MaxPool2D(pool_size=(2, 2), strides=(1, 1), padding='same'),
# 创建 Dropout 层
tf.keras.layers.Dropout(0.2),
# 创建拉伸层(Flatten)
tf.keras.layers.Flatten(),
# 创建全连接层
tf.keras.layers.Dense(512, activation='relu'),
# 创建 Dropout 层
tf.keras.layers.Dropout(0.2),
# 创建另一个全连接层
tf.keras.layers.Dense(256, activation='relu'),
# 创建 Dropout 层
tf.keras.layers.Dropout(0.5),
# 创建全连接层作为输出层
tf.keras.layers.Dense(10, activation='softmax')
])
# 打印模型结构
model.summary()
2.2.3 代码解释:
1.Sequential 模型:
- 使用
tf.keras.models.Sequential()
按顺序定义模型结构。
2.输入层:
tf.keras.Input()
定义输入层,输入数据的形状与训练数据集的形状一致。
3.卷积层:
- 第一层卷积:
Conv2D(32, (3, 3))
使用 32 个大小为 3x3 的卷积核,提取低层次特征。 - 第二层卷积:
Conv2D(64, (3, 3))
使用 64 个卷积核,提取更复杂的特征。
4.池化层:
- 使用
MaxPool2D(pool_size=(2, 2), strides=(1, 1))
进行最大池化,减少特征图的尺寸,保留主要信息。
5.Dropout 层:
- 在多处使用
Dropout()
随机丢弃神经元,防止过拟合。丢弃概率分别为 0.2 和 0.5。
6.拉伸层(Flatten):
- 使用
Flatten()
将二维特征图展开为一维向量,方便传入全连接层。
7.全连接层:
- 第一层全连接层:512 个神经元,激活函数为 ReLU。
- 第二层全连接层:256 个神经元,激活函数为 ReLU。
8.输出层:
- 使用
Dense(10, activation='softmax')
作为输出层,适用于 CIFAR-10 的 10 分类任务。
9.打印模型结构:
- 使用
model.summary()
打印模型的结构、参数数量和层次关系。
2.2.4 结果与结果分析
(1)模型层结构:
1.Conv2D (卷积层)
- 输出形状:
(None, 32, 32, 32)
说明:32 个 3×3 的卷积核,输出 32 个特征图。 - 参数数量:896
计算:$(3 \times 3 \times 3 + 1) \times 32 = 896$(包含偏置项)。
2.MaxPooling2D (最大池化层)
- 输出形状:
(None, 32, 32, 32)
说明:池化窗口为 2×2,没有改变输出形状。
3.Dropout (丢弃层)
- 丢弃部分神经元,防止过拟合。
4.第二个 Conv2D (卷积层)
- 输出形状:
(None, 32, 32, 64)
说明:64 个 3×3 的卷积核。 - 参数数量:18,496
计算:$(3 \times 3 \times 32 + 1) \times 64 = 18,496$。
5.Flatten (拉伸层)
- 输出形状:
(None, 65536)
说明:将 32×32×64 的特征图展平为一维向量。
6.Dense (全连接层)
- 输出形状:
(None, 512)
参数数量:33,554,944
计算:$65536 \times 512 + 512 = 33,554,944$。
7.Dense (全连接层)
- 输出形状:
(None, 256)
参数数量:131,328
计算:$512 \times 256 + 256 = 131,328$。
8.输出层 (Dense)
- 输出形状:
(None, 10)
说明:10 个神经元,表示 10 个分类类别。 - 参数数量:2,570
计算:$256 \times 10 + 10 = 2,570$。
(2)模型总参数:
- 总参数数量:33,708,234
- 可训练参数数量:33,708,234
- 非可训练参数:0
2.3 编译、训练和评估 CNN 模型
2.3.1 实现目的:
- 编译模型:配置优化器、损失函数和评估指标,为训练过程做好准备。
- 训练模型:用训练集训练模型,并在训练过程中监控验证集性能。
- 评估模型:在测试集上评估模型性能,确保模型的泛化能力。
2.3.3 代码片段:
'''步骤三,编译、训练和评估 CNN 模型'''
# 编译网络模型
model.compile(optimizer='adam',
loss=tf.keras.losses.SparseCategoricalCrossentropy(),
metrics=['sparse_categorical_accuracy'])
os.environ["CUDA_VISIBLE_DEVICES"] = "-1"
# 训练网络模型
history = model.fit(x_train, y_train,
batch_size=128,
epochs=10,
validation_split=0.2)
# 评估网络模型
model.evaluate(x_test, y_test,
batch_size=64,
verbose=2)
2.3.3 代码解释:
1.模型编译 (model.compile
):
optimizer='adam'
:Adam 优化器,兼具自适应学习率和动量优化的特点。loss=tf.keras.losses.SparseCategoricalCrossentropy()
:用于多分类任务的损失函数。metrics=['sparse_categorical_accuracy']
:评估指标为稀疏分类准确率,用于处理标签是整数编码的情况。
2.训练模型 (model.fit
):
batch_size=128
:每次梯度更新时处理 128 个样本。epochs=10
:训练模型 10 个完整的周期(轮次)。validation_split=0.2
:将 20% 的训练数据用于验证模型性能。
3.评估模型 (model.evaluate
):
- 在测试集上评估模型性能,
batch_size=64
指定每次处理的样本数。 verbose=2
控制输出日志的详细程度。
2.3.4 结果与结果分析
(1)训练过程:
1.趋势分析:
1.Epoch 1/10:
- 训练损失(
loss
):1.3763 - 训练准确率(
sparse_categorical_accuracy
):51.22% - 验证损失(
val_loss
):1.1739 - 验证准确率(
val_sparse_categorical_accuracy
):59.02%
2.Epoch 10/10:
- 训练损失:0.3544
- 训练准确率:87.84%
- 验证损失:1.0411
- 验证准确率:69.57%
趋势分析:
训练损失和训练准确率逐渐优化,模型在训练数据上的表现不断提高,最终训练准确率达到 87.84%。
验证集的准确率逐渐提高到 69.57%,但验证损失(val_loss)在第 7-10 轮趋于不稳定(略有增加),表明模型可能出现了一些过拟合。
(2)测试集评估结果:
loss: 1.0738 sparse_categorical_accuracy: 69.16%
解释:
- 测试损失:1.0738
- 测试准确率:69.16%
- 在测试集上的准确率为 69.16%,与验证集准确率(69.57%)非常接近,表明模型的泛化能力良好。
- 测试损失与验证损失接近,进一步验证模型的性能稳定。
2.4 可视化训练的结果
2.4.1 实现目的:
使用 Matplotlib 将训练损失、验证损失以及训练准确率、验证准确率随轮次的变化情况绘制成折线图,帮助分析模型的训练过程和性能。
2.4.2 代码片段:
'''步骤四,可视化训练的结果'''
print(history.history)
# 确保 epochs 数组的定义与 loss 和 acc 的数据一致
epochs = range(1, len(history.history['loss']) + 1)
# 创建画布并设置画面大小
plt.figure(figsize=(10, 3))
# 在子图1中绘制损失函数的折线图
plt.subplot(121)
plt.plot(epochs, history.history['loss'], color='b', label='train') # 蓝色线表示训练集损失
plt.plot(epochs, history.history['val_loss'], color='r', label='validate') # 红色线表示验证集损失
plt.xlabel('Epochs') # 添加横轴标签
plt.ylabel('Loss') # 添加纵轴标签
plt.legend() # 显示图例
# 在子图2中绘制准确率的折线图
plt.subplot(122)
plt.plot(epochs, history.history['sparse_categorical_accuracy'], color='b', label='train') # 蓝色线表示训练集准确率
plt.plot(epochs, history.history['val_sparse_categorical_accuracy'], color='r', label='validate') # 红色线表示验证集准确率
plt.xlabel('Epochs') # 添加横轴标签
plt.ylabel('Accuracy') # 添加纵轴标签
plt.legend() # 显示图例
# 显示图像
plt.show()
2.4.3 代码解释:
1.打印训练历史:
history.history
中包含每一轮训练和验证的损失与准确率。
2.绘制损失函数曲线:
- 子图 1 展示训练和验证的损失随轮次的变化:
- 蓝线:训练集损失
- 红线:验证集损失
3.绘制准确率曲线:
- 子图 2 展示训练和验证的准确率随轮次的变化:
- 蓝线:训练集准确率
- 红线:验证集准确率
4.显示图像:
- 使用
plt.show()
将两张图同时显示出来。
2.4.4 结果与结果分析
(1)损失曲线(左图):
- 蓝色线(train):训练集的损失值逐渐下降,说明模型在逐步学习特征。
- 红色线(validate):验证集的损失在前几轮下降后逐渐趋于平稳并略有上升。
分析:
- 训练损失持续降低,而验证损失在后期上升,表明模型可能出现过拟合。
- 改进建议:
- 增加 Dropout 层的使用或增大丢弃率。
- 使用更多正则化(如 L2 正则化)。
- 尝试减少模型的复杂度或收集更多数据。
(2)准确率曲线(右图):
- 蓝色线(train):训练集的准确率逐渐提升,最终接近 90%。
- 红色线(validate):验证集的准确率在 70% 附近趋于平稳。
分析:
- 训练准确率不断上升,而验证准确率停滞在约 70% 左右。这进一步表明模型可能在训练集上表现良好,但在验证集上出现过拟合。
2.5 应用卷积神经网络模型
2.5.1 实现目的:
- 随机选取 10 张测试集中的图片,使用训练好的 CNN 模型对其进行预测,并将标签值和预测值显示在图像上方。
- 验证模型的效果,观察模型在测试集上的分类准确性。
2.5.2 代码片段:
'''步骤五,应用卷积神经网络模型'''
plt.figure() # 创建画布
for i in range(10): # 循环显示10张图片
n = np.random.randint(1, 10000) # 生成随机整数 n
plt.subplot(2, 5, i + 1) # 创建子图 (2行5列)
plt.axis("off") # 不显示坐标轴
plt.rcParams['font.sans-serif'] = ['SimHei'] # 设置字体为 SimHei,支持中文显示
# 显示测试集中随机图片,使用灰度颜色映射
plt.imshow(x_test[n], cmap='gray')
# 将图片 reshape 为模型输入的形状 (1, 32, 32, 3)
demo = tf.reshape(x_test[n], (1, 32, 32, 3))
# 使用模型预测该图片的类别
y_pred = np.argmax(model.predict(demo))
# 设置子图标题,显示标签值与预测值
title = "标签值: " + str((y_test.numpy())[n, 0]) + "\n预测值: " + str(y_pred)
plt.title(title)
# 展示图像
plt.show()
2.5.3 代码解释:
1.画布与子图:
plt.figure()
:创建一个用于显示图像的画布。plt.subplot(2, 5, i + 1)
:将画布分成 2 行 5 列的子图,每次在新的子图中显示一张图片。
2.随机选取测试图片:
- 使用
np.random.randint(1, 10000)
生成 1 到 9999 之间的随机数,作为图片索引。
3.显示图片:
- 使用
plt.imshow()
在子图中显示随机图片,使用 灰度颜色映射 (cmap='gray'
)。
4.模型预测:
- 将选中的图片 reshape 为模型的输入形状
(1, 32, 32, 3)
。 - 使用
model.predict()
对图片进行预测,并使用np.argmax()
获取预测的类别。
5.设置标题:
- 子图标题中显示标签值和预测值,标签值来自
y_test
,预测值来自模型的预测结果。
6.展示图像:
- 使用
plt.show()
展示 10 张图片及其标签与预测结果。
2.5.4 结果与分析
(1)展示内容:
每个子图显示了一张来自 CIFAR-10 测试集的随机图片,且每张图片的标题标注了:
- 标签值(真实类别):来自
y_test
,表示图片的真实类别标签。 - 预测值(模型预测结果):模型根据输入图像预测的类别。
(2)分析结果:
1.正确预测的示例:
- 标签值:6,预测值:6:模型正确地识别出图片中的类别。
- 标签值:7,预测值:7:另一个成功分类的示例。
2.错误预测的示例:
- 标签值:4,预测值:3:模型将真实类别 4 误判为 3。
- 标签值:3,预测值:2:该图片也被模型错误分类。
(3)模型表现观察:
- 部分图片的预测结果是正确的,说明模型对某些类别的识别效果较好。
- 某些类别的预测出现错误,这可能是因为:
- 类别之间的相似性导致分类困难(如狗和猫)。
- 数据不足或模型过拟合,使得模型在某些类别上泛化能力不足。
3.基于 CNN 的手写数字识别的实验
3.1 随机选取并保存测试图片
3.1.1 实现目的:
通过随机从 MNIST 测试集中选取一些图片,并将它们以 PNG 格式保存到指定目录。这有助于我们检查数据集的样本内容,并在后续使用这些图片进行预测和验证。
3.1.2 代码片段:
import numpy as np
import matplotlib.pyplot as plt
from tensorflow.keras.datasets import mnist
# 加载 MNIST 数据集
(x_train, y_train), (x_test, y_test) = mnist.load_data()
# 创建保存路径
import os
save_path = 'test_images'
os.makedirs(save_path, exist_ok=True)
# 保存一些图片为 PNG
def save_images(images, labels, indices):
for i in indices:
img = images[i]
label = labels[i]
file_name = f"{save_path}/{label}_{i}.png"
plt.imsave(file_name, img, cmap='gray')
print(f"保存图片: {file_name}")
# 从测试集里保存 3 张图片 (你可以指定其他索引)
save_images(x_test, y_test, [0, 3, 9]) # 保存索引 0, 3, 9 处的图片
3.1.3 代码解释:
1.导入库:
numpy
:用于处理数组和数值操作。matplotlib.pyplot
:用于保存和显示图片。tensorflow.keras.datasets
:用于加载 MNIST 数据集。
2.加载数据集:
- 使用
mnist.load_data()
加载 MNIST 手写数字数据集,该数据集包含 60,000 张训练图片和 10,000 张测试图片。x_train
、x_test
:保存图片的 numpy 数组,每张图片大小为28x28
像素。y_train
、y_test
:保存图片对应的标签(0-9)。
3.创建保存路径:
- 使用
os.makedirs()
创建test_images
文件夹,如果文件夹不存在则会自动创建。
4.定义 save_images()
函数:
- 遍历传入的索引列表
indices
,根据索引获取图片和对应标签。 - 使用
plt.imsave()
将图片保存为 PNG 格式,并以{标签}_{索引}.png
命名。
5.调用 save_images()
函数:
- 选取测试集中的第 0、3、9 张图片,并将其保存到
test_images
文件夹中。
3.1.4 结果与分析:
保存图片: test_images/7_0.png
保存图片: test_images/0_3.png
保存图片: test_images/9_9.png
1.图片内容检查:
- 打开
test_images
文件夹,检查是否存在 3 张 PNG 图片。 - 图片的名称格式为
{标签}_{索引}.png
,其中标签
是图片的真实类别。
2.潜在问题与解决方案:
- 路径问题:确保有权限在当前工作目录下创建文件夹和保存文件。
- 图片内容错误:如果保存的图片标签与预期不符,请检查代码中的索引是否正确。
3.实际应用:
- 这些保存的图片可以用于模型的预测测试,验证训练模型的准确性。
- 在接下来的步骤中,我们将使用这些保存的图片,并通过训练好的模型进行预测。
3.2 数据的读取与预处理
3.2.1 实现目的:
本步骤的目的是加载 MNIST 数据集,并对其进行预处理。具体目标包括:
- 检查本地是否存在数据集,如果没有则自动从网上下载。
- 为每张图片增加通道维度,以适配 CNN 模型的输入需求。
- 将图片的像素值缩放至
[0, 1]
区间,提高模型的训练性能。 - 打印数据集的形状,并验证每张图片是否为单通道(灰度图)。
3.2.2 代码片段:
import os
import tensorflow as tf
from tensorflow.keras.datasets.mnist import load_data
class DataSource:
def __init__(self):
# 数据集路径配置(使用当前目录)
data_path = os.path.join(os.getcwd(), 'data', 'mnist.npz')
# 检查本地是否存在数据集
if os.path.exists(data_path):
(x_train, y_train), (x_test, y_test) = load_data(path=data_path)
else:
print("mnist.npz 文件不存在,从网上下载数据集...")
(x_train, y_train), (x_test, y_test) = load_data()
# 增加一个通道维度
x_train = x_train[..., tf.newaxis]
x_test = x_test[..., tf.newaxis]
# 像素值缩放到 [0, 1] 之间
x_train, x_test = x_train / 255.0, x_test / 255.0
self.train_images, self.train_labels = x_train, y_train
self.test_images, self.test_labels = x_test, y_test
# 创建 DataSource 实例,加载数据
data = DataSource()
# 打印训练集和测试集的形状
print(f"训练集图片形状: {data.train_images.shape}")
print(f"训练集标签形状: {data.train_labels.shape}")
print(f"测试集图片形状: {data.test_images.shape}")
print(f"测试集标签形状: {data.test_labels.shape}")
# 验证是否为单通道
print(f"训练集单张图片通道数: {data.train_images.shape[-1]}")
print(f"测试集单张图片通道数: {data.test_images.shape[-1]}")
3.2.3 代码解释:
1.导入库:
os
:用于文件和目录的路径操作。tensorflow
:用于加载 MNIST 数据集并进行张量操作。
2.定义 DataSource
类:
- 检查数据集是否存在于
data
目录中。如果存在则加载本地数据集,否则自动从网上下载。 - 增加通道维度:将每张图片从
(28, 28)
形状扩展为(28, 28, 1)
,以适应 CNN 模型的输入格式。 - 像素值缩放:将图片的像素值从
[0, 255]
缩放到[0, 1]
区间,提高模型训练的效率和收敛速度。
3.创建 DataSource
实例:
- 实例化
DataSource
类并加载数据,将数据集保存在train_images
、train_labels
、test_images
和test_labels
中。
4.打印数据集的形状:
- 输出训练集和测试集的图片形状与标签形状。
- 检查每张图片的通道数是否为 1,确保数据为灰度图。
3.2.4 结果与分析:
训练集图片形状: (60000, 28, 28, 1) 训练集标签形状: (60000,) 测试集图片形状: (10000, 28, 28, 1) 测试集标签形状: (10000,) 训练集单张图片通道数: 1 测试集单张图片通道数: 1
1.预期结果分析:
- 形状正确:训练集有 60,000 张图片,测试集有 10,000 张图片,每张图片大小为
(28, 28, 1)
。 - 通道数:每张图片应为单通道(灰度图)。
2.潜在问题与解决方案:
- 数据集不存在:如果程序提示 "mnist.npz 文件不存在",确保程序有网络访问权限以从网上下载数据集。
- 维度错误:如果图片形状不符合
(28, 28, 1)
,检查是否正确添加了新维度。
3.实际应用:
- 预处理后的数据可直接用于训练 CNN 模型,提高模型的训练效率。
- 数据集结构清晰,确保了模型输入格式的一致性。
3.3 搭建卷积神经网络(CNN模型)
3.3.1 实现目的:
本步骤旨在构建一个卷积神经网络(CNN),用于对 MNIST 手写数字数据集进行分类。卷积神经网络是一种适用于图像数据的深度学习模型,通过卷积层提取特征,并通过全连接层进行分类。本模型设计为一个简单的多层 CNN,适用于 MNIST 数据集。
3.3.2 代码片段:
from tensorflow.keras import layers, models
class CNN:
def __init__(self):
# 构建 Sequential 模型,按层堆叠模型结构
model = models.Sequential([
# 第1层卷积层 + 最大池化层
layers.Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)),
layers.MaxPooling2D((2, 2)),
# 第2层卷积层 + 最大池化层
layers.Conv2D(64, (3, 3), activation='relu'),
layers.MaxPooling2D((2, 2)),
# 第3层卷积层
layers.Conv2D(64, (3, 3), activation='relu'),
# 展平层 + 全连接层
layers.Flatten(),
layers.Dense(64, activation='relu'),
# 输出层(10类,用 softmax 激活函数)
layers.Dense(10, activation='softmax')
])
# 打印模型结构
model.summary()
self.model = model # 将模型存储为类的属性
3.3.3 代码解释:
1.导入所需模块:
layers
和models
:来自 TensorFlow 的 Keras API,分别用于创建网络层和模型。
2.模型构建:
Sequential
:顺序模型,按层堆叠每一层。- 卷积层:提取图像的局部特征,通过
ReLU
激活函数引入非线性。- 第1层:32 个 3x3 卷积核,输入形状为
(28, 28, 1)
。 - 第2层:64 个 3x3 卷积核。
- 第3层:64 个 3x3 卷积核。
- 第1层:32 个 3x3 卷积核,输入形状为
3.池化层:
- 最大池化层:每次将 2x2 区域的最大值作为特征,减少特征维度。
4.展平层:
- 将卷积层输出的多维张量展平为一维向量,便于输入到全连接层。
5.全连接层:
- Dense:全连接层,包含 64 个神经元,并使用
ReLU
激活函数。
6.输出层:
- Dense:输出层包含 10 个神经元,对应 10 个类别,使用
softmax
激活函数进行多分类。
7.模型摘要:
model.summary()
:打印模型结构,包括每层的输出形状和参数数量。
3.3.4 结果(此结果在下一段代码打印)与分析:
1.Conv2D (卷积层):
- 功能:提取图像特征,如边缘、纹理等。
- 输出形状:
(None, 26, 26, 32)
,表示卷积后的输出尺寸为 26×26,每个像素点有 32 个通道。 - 参数数量 (Param #):320。这由卷积核的数量和大小决定。
2.MaxPooling2D (最大池化层):
- 功能:缩减图像尺寸,保留重要特征,减少参数。
- 输出形状:
(None, 13, 13, 32)
,池化层将图像尺寸缩小了一半。
3.Flatten (展平层):
- 功能:将多维的特征图展平为一维,供全连接层使用。
- 输出形状:
(None, 576)
,将图像的特征展平为 576 维向量。
4.Dense (全连接层):
- 功能:用于分类或回归任务,将展平的特征映射到输出类别上。
- 输出形状:第一层为
(None, 64)
,第二层为(None, 10)
,表示 10 个输出类别。
5.总参数数量:
- Total params (总参数数量): 93,322
- Trainable params (可训练参数): 93,322
- Non-trainable params (不可训练参数): 0
3.4 训练模型、保存结果与可视化
3.4.1 实现目的:
本部分的目的是对构建的卷积神经网络(CNN)进行训练,并在训练过程中记录日志、保存模型检查点,以便可视化和模型的复用。训练结束后,打印测试结果以评估模型性能。
3.4.2 代码片段:
import numpy as np
from datetime import datetime
from tensorflow.keras.callbacks import TensorBoard, ModelCheckpoint
# 忽略警告
import warnings
warnings.filterwarnings('ignore', category=UserWarning)
class Train:
def __init__(self):
self.network = CNN() # 创建CNN实例
self.data = DataSource() # 加载数据集
def train(self):
# 设置 TensorBoard 日志路径
logdir = "./logs/scalars/" + datetime.now().strftime("%Y%m%d-%H%M%S")
tensorboard_cb = TensorBoard(log_dir=logdir)
# # 设置模型检查点路径
# check_path = './ckpt/cp-{epoch:04d}.ckpt'
# save_model_cb = ModelCheckpoint(check_path, save_weights_only=True, verbose=1)
# 设置模型检查点路径(每5个epoch保存一次)
check_path = './ckpt/cp-{epoch:04d}.weights.h5'
save_model_cb = tf.keras.callbacks.ModelCheckpoint(
filepath=check_path, # 修改文件后缀为 .weights.h5
save_weights_only=True,
verbose=1
)
# 编译模型
self.network.model.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
# 训练模型并保存日志
training_history = self.network.model.fit(
self.data.train_images,
self.data.train_labels,
epochs=10,
validation_data=(self.data.test_images, self.data.test_labels),
callbacks=[tensorboard_cb, save_model_cb]
)
# 保存模型为 .keras 格式
os.makedirs('./keras', exist_ok=True)
self.network.model.save('./keras/model.keras')
print("模型已保存为 './keras/model.keras'")
# 打印最终测试结果
test_loss, test_acc = self.network.model.evaluate(self.data.test_images,
self.data.test_labels)
print(f"准确率:{test_acc * 100:.2f}%,共测试了 {len(self.data.test_labels)} 张图片")
print("平均误差:", np.average(training_history.history['loss']))
if __name__ == "__main__":
mnist_train = Train()
mnist_train.train()
3.4.3 代码解释:
1.引入必要的库:
TensorBoard
和ModelCheckpoint
用于训练过程的监控和模型权重保存。warnings
模块用于忽略不必要的警告信息,保持输出清晰。
2.初始化训练实例:
Train
类的__init__()
方法创建了 CNN 模型实例,并加载了数据源。
3.训练配置:
- TensorBoard 可视化: 配置 TensorBoard 的日志路径,每次运行生成新的日志目录,使用当前时间戳命名。
- 模型检查点: 使用
ModelCheckpoint
在每个 epoch 结束后保存模型权重。文件名以cp-{epoch:04d}.weights.h5
形式保存,确保每个 epoch 都有独立的权重文件。
4.模型编译:
- 使用 Adam 优化器和 sparse_categorical_crossentropy 损失函数。
- 指定
accuracy
为评估指标,用于监控训练和验证过程的分类准确率。
5.模型训练:
- 训练数据和标签通过
fit()
方法传入,训练 10 个 epoch。 - 每个 epoch 结束时,验证集上的损失和准确率会被计算并打印。
- 使用回调函数记录训练过程的 TensorBoard 日志,并在每 5 个 epoch 保存一次模型权重。
6.模型保存:
- 训练完成后,将模型保存为
.keras
格式,方便后续加载和预测。
7.模型评估:
- 使用
evaluate()
方法在测试集上评估模型性能,并打印准确率和平均损失。
3.4.4 结果与分析:
Epoch 1/10 1871/1875 ━━━━━━━━━━━━━━━━━━━━ 0s 5ms/step - accuracy: 0.8945 - loss: 0.3399 Epoch 1: saving model to ./ckpt/cp-0001.weights.h5 1875/1875 ━━━━━━━━━━━━━━━━━━━━ 14s 6ms/step - accuracy: 0.8947 - loss: 0.3394 - val_accuracy: 0.9853 - val_loss: 0.0450 Epoch 2/10 1867/1875 ━━━━━━━━━━━━━━━━━━━━ 0s 5ms/step - accuracy: 0.9863 - loss: 0.0462 Epoch 2: saving model to ./ckpt/cp-0002.weights.h5 1875/1875 ━━━━━━━━━━━━━━━━━━━━ 11s 6ms/step - accuracy: 0.9863 - loss: 0.0462 - val_accuracy: 0.9860 - val_loss: 0.0424 Epoch 3/10 1871/1875 ━━━━━━━━━━━━━━━━━━━━ 0s 5ms/step - accuracy: 0.9899 - loss: 0.0324 Epoch 3: saving model to ./ckpt/cp-0003.weights.h5 1875/1875 ━━━━━━━━━━━━━━━━━━━━ 11s 6ms/step - accuracy: 0.9899 - loss: 0.0324 - val_accuracy: 0.9875 - val_loss: 0.0379 Epoch 4/10 1871/1875 ━━━━━━━━━━━━━━━━━━━━ 0s 5ms/step - accuracy: 0.9918 - loss: 0.0257 Epoch 4: saving model to ./ckpt/cp-0004.weights.h5 1875/1875 ━━━━━━━━━━━━━━━━━━━━ 11s 6ms/step - accuracy: 0.9918 - loss: 0.0257 - val_accuracy: 0.9916 - val_loss: 0.0264 Epoch 5/10 1870/1875 ━━━━━━━━━━━━━━━━━━━━ 0s 5ms/step - accuracy: 0.9948 - loss: 0.0178 Epoch 5: saving model to ./ckpt/cp-0005.weights.h5 1875/1875 ━━━━━━━━━━━━━━━━━━━━ 11s 6ms/step - accuracy: 0.9948 - loss: 0.0178 - val_accuracy: 0.9906 - val_loss: 0.0293 Epoch 6/10 1875/1875 ━━━━━━━━━━━━━━━━━━━━ 0s 5ms/step - accuracy: 0.9952 - loss: 0.0138 Epoch 6: saving model to ./ckpt/cp-0006.weights.h5 1875/1875 ━━━━━━━━━━━━━━━━━━━━ 11s 6ms/step - accuracy: 0.9952 - loss: 0.0138 - val_accuracy: 0.9919 - val_loss: 0.0270 Epoch 7/10 1868/1875 ━━━━━━━━━━━━━━━━━━━━ 0s 5ms/step - accuracy: 0.9962 - loss: 0.0119 Epoch 7: saving model to ./ckpt/cp-0007.weights.h5 1875/1875 ━━━━━━━━━━━━━━━━━━━━ 11s 6ms/step - accuracy: 0.9962 - loss: 0.0119 - val_accuracy: 0.9925 - val_loss: 0.0293 Epoch 8/10 1874/1875 ━━━━━━━━━━━━━━━━━━━━ 0s 5ms/step - accuracy: 0.9977 - loss: 0.0079 Epoch 8: saving model to ./ckpt/cp-0008.weights.h5 1875/1875 ━━━━━━━━━━━━━━━━━━━━ 11s 6ms/step - accuracy: 0.9977 - loss: 0.0079 - val_accuracy: 0.9894 - val_loss: 0.0419 Epoch 9/10 1870/1875 ━━━━━━━━━━━━━━━━━━━━ 0s 5ms/step - accuracy: 0.9972 - loss: 0.0080 Epoch 9: saving model to ./ckpt/cp-0009.weights.h5 1875/1875 ━━━━━━━━━━━━━━━━━━━━ 11s 6ms/step - accuracy: 0.9972 - loss: 0.0080 - val_accuracy: 0.9921 - val_loss: 0.0317 Epoch 10/10 1870/1875 ━━━━━━━━━━━━━━━━━━━━ 0s 5ms/step - accuracy: 0.9977 - loss: 0.0072 Epoch 10: saving model to ./ckpt/cp-0010.weights.h5 1875/1875 ━━━━━━━━━━━━━━━━━━━━ 11s 6ms/step - accuracy: 0.9977 - loss: 0.0072 - val_accuracy: 0.9921 - val_loss: 0.0382 模型已保存为 './keras/model.keras' 313/313 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step - accuracy: 0.9886 - loss: 0.0526 准确率:99.21%,共测试了 10000 张图片 平均误差: 0.03288608025759458
1.结果分析:
- 训练表现:随着 epoch 数的增加,模型的训练准确率逐渐提高,损失函数逐渐降低。
- 测试集表现:在测试集上的准确率达到了 99.21%,表明模型在 MNIST 数据集上有非常好的表现。
- 平均误差:训练的平均误差较低,表明模型已成功拟合训练数据。
2.优化建议:
- 正则化:可以引入 Dropout 或 L2 正则化来防止过拟合。
- 数据增强:通过随机裁剪或旋转图片,提高模型的泛化能力。
3.5 小标题:加载模型并进行预测
3.5.1 实现目的:
本步骤的目标是使用训练好的卷积神经网络(CNN)模型,对手写数字图片进行预测,验证模型的分类能力。通过加载保存的模型,读取测试图片,并显示模型的预测结果,判断模型是否能够准确识别输入图片中的数字。
3.5.2 代码片段:
# 步骤4:加载模型并进行预测
from PIL import Image
class Predict:
def __init__(self):
# 加载已训练的模型
self.model = tf.keras.models.load_model('./keras/model.keras')
def predict(self, image_path):
# 读取并预处理图片
img = Image.open(image_path).convert('L')
img = img.resize((28, 28)) # 确保图片大小为 28x28
img_array = np.array(img).reshape(1, 28, 28, 1) / 255.0 # 归一化
# 进行预测
prediction = self.model.predict(img_array)
predicted_label = np.argmax(prediction[0])
print(f"{image_path} -> 预测数字为:{predicted_label}")
# 测试预测
if __name__ == "__main__":
app = Predict()
app.predict('./test_images/7_0.png')
app.predict('./test_images/0_3.png')
app.predict('./test_images/9_9.png')
3.5.3 代码解释:
1.加载模型:
- 使用
tf.keras.models.load_model()
加载保存为model.keras
格式的训练模型。该模型已经通过训练,并保存在目录./keras
下。
2.读取和预处理图片:
- 使用
PIL
库中的Image.open()
读取指定路径的图片,并将其转换为灰度模式(L
)。 - 使用
img.resize((28, 28))
将图片大小调整为 28x28,以符合模型的输入要求。 - 使用
np.array()
将图片转换为 NumPy 数组,并 reshape 为(1, 28, 28, 1)
,以便输入到模型中进行预测。最后对像素值归一化到[0, 1]
范围内。
3.模型预测:
- 使用
self.model.predict()
对图片数据进行预测,得到一个概率数组。 - 使用
np.argmax()
获取预测结果中概率最高的类别标签,即模型认为的数字。
4.测试代码:
- 代码通过调用
app.predict()
,对三张测试图片进行预测,并打印结果到控制台。
3.5.4 结果与分析:
1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 95ms/step ./test_images/7_0.png -> 预测数字为:7 1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 20ms/step ./test_images/0_3.png -> 预测数字为:0 1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 19ms/step ./test_images/9_9.png -> 预测数字为:9
1.预测分析:
这些结果表明模型对这几张测试图片的识别非常准确。
- 对于输入图片
7_0.png
,模型成功预测为数字 7。 - 对于输入图片
0_3.png
,模型成功预测为数字 0。 - 对于输入图片
9_9.png
,模型成功预测为数字 9。
2.模型表现:
- 模型在这些测试图片上的预测结果全部正确,说明模型在训练过程中已经学习到了有效的特征。
- 预测速度较快,每次预测耗时在 20ms-95ms 之间,表明模型推理效率较高。
3.优化建议:
- 异常处理: 在加载图片时增加异常捕获,以避免文件缺失或路径错误导致程序崩溃:
try: img = Image.open(image_path).convert('L') except FileNotFoundError: print(f"文件未找到: {image_path}")
- 批量预测: 如果需要对多张图片进行预测,可以编写循环批量加载和预测,提升效率。