深度学习:TensorFlow实现CNN手写字识别

一、MNIST 手写数字介绍

MNIST 手写数字识别在深度学习发展历程中占据着重要地位,它不仅是学习卷积神经网络(CNN)等深度学习模型的绝佳切入点,也为众多复杂图像识别任务奠定了理论与实践基础。

1、MNIST 数据集

MNIST 数据集被誉为深度学习领域的 “Hello World”,由纽约大学柯伦实验室整理而成。它包含 60,000 张训练图像、10,000 张测试图像,每张图像均为 28×28 像素的灰度手写数字图片,涵盖数字 0 – 9 共 10 个类别。这些图像经过了归一化处理,像素值范围在 0 到 255 之间,且数字被居中放置在图像内,以减少额外干扰因素。数据集还附带对应的标签,明确标注了每张图像所代表的数字,方便模型训练和评估。​

MNIST 数据集具有高度标准化和易获取性的特点,其规模适中,既能让初学者快速上手实践,又能为复杂模型提供一定的挑战性。许多新提出的深度学习算法和模型,都会先在 MNIST 数据集上进行初步验证,测试模型的基本性能和泛化能力 。

2、MNIST 经典模型构建​

(一)全连接神经网络​

早期,全连接神经网络(FCN)是处理 MNIST 手写数字识别的常用方法。将 28×28 的图像展平为 784 维的向量后,输入到全连接层。通过多个全连接层的层层计算,对输入特征进行不断整合和变换,最终输出 10 个神经元的结果,分别对应 0 – 9 这 10 个数字类别的预测得分,再经过 Softmax 函数将得分转换为概率,选取概率最大的类别作为预测结果。不过,全连接神经网络由于参数众多,容易出现过拟合问题,且计算效率相对较低。​

(二)卷积神经网络(CNN)​

随着深度学习发展,CNN 逐渐成为 MNIST 手写数字识别的主流模型。CNN 利用卷积层和池化层进行特征提取和降维,有效减少了参数数量,同时保留了图像的空间结构信息。例如,在一个简单的 CNN 模型中,通常会包含 2 – 3 个卷积层,每个卷积层使用不同数量和尺寸的卷积核提取边缘、曲线等基础特征;卷积层之后搭配池化层,降低特征图维度,减少计算量;最后通过展平层将多维特征图转换为一维向量,输入全连接层进行分类。相比全连接神经网络,CNN 在 MNIST 数据集上不仅训练速度更快,识别准确率也大幅提升,通常可以达到 99% 以上。

3、MNIST 训练与评估

在 MNIST 手写数字识别模型训练过程中,常用的损失函数是交叉熵损失函数,它能够衡量预测概率分布与真实标签分布之间的差异,引导模型不断调整参数,使预测结果更接近真实值。优化器一般选用随机梯度下降(SGD)及其变种,如 Adagrad、Adam 等,它们可以根据不同参数的梯度情况,自适应地调整学习率,加快模型的收敛速度。​

模型训练完成后,需要使用测试集对其性能进行评估。除了准确率(Accuracy)这一最直观的指标外,还会计算精确率(Precision)、召回率(Recall)和 F1 值等指标,从不同角度评估模型在各个类别上的识别效果。例如,精确率反映了模型预测为某一类别的样本中,真正属于该类别的比例;召回率表示实际属于某一类别的样本中,被正确预测出来的比例。通过这些指标的综合分析,可以更全面地了解模型的优势与不足,进而进行针对性优化。

4、MNIST 拓展应用

MNIST 手写数字识别的研究成果不仅局限于数字分类本身,还为其他领域提供了宝贵经验。其背后的技术原理和模型结构,经过适当调整和优化,可以应用于其他图像识别任务,如字母识别、验证码识别等。此外,MNIST 数据集也常被用于教学和科研,帮助初学者理解深度学习的基本概念和流程,助力科研人员验证新算法和新模型的有效性。同时,围绕 MNIST 数据集开展的竞赛和研究,不断推动着图像识别技术向更高精度和更强泛化能力的方向发展 。

二、基于TensorFlow框架MNIST 手写数字识别

1、项目简介

案例名称:卷积神经网络CNN实现手写字MNIST图片识别

MNIST手写字识别是一个经典的机器学习任务,旨在通过卷积神经网络(CNN)对28×28像素的灰度手写数字图像进行分类。该数据集包含70,000张图片,分为60,000张训练图片和10,000张测试图片,每张图片代表一个0-9的手写数字。

本项目的目标是构建一个高精度的卷积神经网络模型,能够准确地识别手写数字,并且在测试集上达到较高的准确率。此外,项目还将探索如何优化模型结构、调整超参数以提高性能。

MNIST 数据集来源于 NIST(美国国家标准与技术研究院)的手写字符数据库。 它由 Yann LeCun、Corinna Cortes 和 Christopher J.C. Burges 于 1998 年整理并发布。

【1】MNIST 数据集
   – 样本数量:包含 70,000 张灰度图像,分为两个部分:
     —  训练集:60,000 张图像
     —  测试集:10,000 张图像

   – 图像尺寸:每张图像大小为 28×28 像素。

   – 类别:10 个类别(0 到 9 的手写数字)。

【2】格式
  – 每个图像是一个 28×28 的矩阵,像素值范围为 0 到 255,表示灰度值(0 表示黑  色,255 表示白色)。

  – 标签是单个整数(0 到 9),表示图像中的数字。

【3】数据集特点

 标准化:
   – 图像已经经过预处理,背景被归一化为纯黑色,手写数字居中对齐。
   – 这使得数据集相对简单且容易处理,适合初学者和基准测试。

 多样性:
   – 尽管数据集中的图像都是手写数字,但它们来自不同的书写者,具有不同的风格和笔迹,增加了数据集的多样性。

 平衡性:
   – 每个类别的样本数量大致相同,避免了类别不平衡的问题。

易于获取和使用:
   – MNIST 数据集可以通过多种方式轻松获取,例如通过 TensorFlow、PyTorch、scikit-learn 等库直接加载。

2、用到的技术和工具

开发工具:Anaconda Jupyter Notebook

深度学习框架:TensorFlow、Keras

Python工具包:Numpy、matplotlib

3、主要功能

(1) 数据预处理
   – 加载MNIST数据集。
   – 对数据进行归一化处理,将像素值从0-255缩放到0-1之间。
   – 将标签转换为独热编码(one-hot encoding)。

(2) 模型构建
   – 构建卷积神经网络模型,包括卷积层、池化层、全连接层等。
   – 使用适当的激活函数(如ReLU)和损失函数(如交叉熵)。
   – 添加正则化技术(如Dropout)以防止过拟合。

(3)  模型训练
   
   – 使用Adam优化器或其他合适的优化算法进行模型训练。
   – 设置批量大小(batch size)、迭代次数(epochs)等超参数。
   – 在训练过程中监控模型的损失和准确率。

(4) 模型评估
   
   – 在测试集上评估模型的性能,计算准确率、精确率、召回率等指标。
   – 可视化混淆矩阵,分析模型的分类错误情况。

(5) 模型优化
   
   – 调整网络结构,如增加或减少卷积层、池化层的数量。
   – 调整超参数,如学习率、批量大小、迭代次数等。
   – 使用数据增强技术(如旋转、平移)来扩充训练数据。

4、实验结果展示

Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz 11490434/11490434 ━━━━━━━━━━━━━━━━━━━━ 9s 1us/step Epoch 1/5 
D:myworkAnaconda2024Libsite-packageskerassrclayersconvolutionalase_conv.py:107: UserWarning: Do not pass an `input_shape`/`input_dim` argument to a layer. When using Sequential models, prefer using an `Input(shape)` object as the first layer in the model instead. super().__init__(activity_regularizer=activity_regularizer, **kwargs) 
750/750 ━━━━━━━━━━━━━━━━━━━━ 11s 12ms/step – accuracy: 0.8636 – loss: 0.4441 – val_accuracy: 0.9808 – val_loss: 0.0663 Epoch 2/5 750/750 ━━━━━━━━━━━━━━━━━━━━ 9s 12ms/step – accuracy: 0.9802 – loss: 0.0606 – val_accuracy: 0.9821 – val_loss: 0.0620 Epoch 3/5 750/750 ━━━━━━━━━━━━━━━━━━━━ 10s 13ms/step – accuracy: 0.9886 – loss: 0.0370 – val_accuracy: 0.9876 – val_loss: 0.0411 Epoch 4/5 750/750 ━━━━━━━━━━━━━━━━━━━━ 10s 13ms/step – accuracy: 0.9911 – loss: 0.0263 – val_accuracy: 0.9878 – val_loss: 0.0427 Epoch 5/5 750/750 ━━━━━━━━━━━━━━━━━━━━ 10s 13ms/step – accuracy: 0.9928 – loss: 0.0213 – val_accuracy: 0.9890 – val_loss: 0.0438 313/313 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step – accuracy: 0.9856 – loss: 0.0414 Test accuracy: 0.9900000095367432 313/313 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step 

5、TensorFlow实现过程

#步骤-1:导入必要的库

import numpy as np
import matplotlib.pyplot as plt
from tensorflow.keras.datasets import mnist
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense
from tensorflow.keras.utils import to_categorical

 

#步骤-2:加载MNIST数据集

(X_train, y_train), (X_test, y_test) = mnist.load_data()

#步骤-3:数据预处理

# 将图像数据从28×28转换为28x28x1以适应卷积层的要求
X_train = X_train.reshape((60000, 28, 28, 1))
X_test = X_test.reshape((10000, 28, 28, 1))

# 将像素值从0-255缩放到0-1之间
X_train = X_train.astype('float32') / 255
X_test = X_test.astype('float32') / 255

# 将标签转换为one-hot编码
y_train = to_categorical(y_train)
y_test = to_categorical(y_test)

#步骤-4:构建卷积神经网络模型

model = Sequential()

#步骤-5:添加第一个卷积层

# 添加第一个卷积层,输入形状为(28, 28, 1),32个滤波器,每个滤波器大小为3×3
model.add(Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)))

# 添加最大池化层,池化窗口大小为2×2
model.add(MaxPooling2D((2, 2)))

#步骤-6:添加第二个卷积层

# 添加第二个卷积层,64个滤波器
model.add(Conv2D(64, (3, 3), activation='relu'))
# 添加另一个最大池化层
model.add(MaxPooling2D((2, 2)))

#步骤-7:添加第三个卷积层

# 添加第三个卷积层,64个滤波器
model.add(Conv2D(64, (3, 3), activation='relu'))

#步骤-8:添加展平层

# 展平层,将多维特征图转换为一维向量
# 如果觉得没有必要可以不添加池化层
model.add(Flatten())

#步骤-9:添加全连接层

# 全连接层,128个神经元
model.add(Dense(128, activation='relu'))

#步骤-10:添加输出层

# 输出层,10个神经元(对应0-9这10个类别),使用softmax激活函数
model.add(Dense(10, activation='softmax'))

#步骤-11:编译模型

# 编译模型,使用交叉熵损失函数和Adam优化器
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])

#步骤-12:训练模型

# 训练模型,训练5个epoch,批量大小为64
model.fit(X_train, y_train, epochs=5, batch_size=64, validation_split=0.2)

#步骤-13:在测试集上评估模型

# 在测试集上评估模型
test_loss, test_acc = model.evaluate(X_test, y_test)
print(f'Test accuracy: {test_acc}')

#最后 可视化一些预测结果
predictions = model.predict(X_test)
plt.figure(figsize=(10, 10))
for i in range(25):
    plt.subplot(5, 5, i + 1)
    plt.xticks([])
    plt.yticks([])
    plt.grid(False)
    plt.imshow(X_test[i].reshape(28, 28), cmap=plt.cm.binary)
    predicted_label = np.argmax(predictions[i])
    true_label = np.argmax(y_test[i])
    if predicted_label == true_label:
        color = 'green'
    else:
        color = 'red'
    plt.xlabel(f'Predicted: {predicted_label}
True: {true_label}', color=color)

6、TensorFlow程序解说

(1)TensorFlow类库

# 导入必要的库
import numpy as np
import matplotlib.pyplot as plt
from tensorflow.keras.datasets import mnist
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense
from tensorflow.keras.utils import to_categorical

import numpy as np

– numpy是一个强大的科学计算库,提供了多维数组对象、各种派生对象(如掩码数组和矩阵)、以及用于数组快速操作的各种函数。
– 在深度学习中,numpy常用于处理数据、执行数学运算和生成随机数等。

import matplotlib.pyplot as plt

– matplotlib是Python中最常用的绘图库之一,pyplot是其中的一个子库,提供了类似于MATLAB的绘图接口。
– 使用matplotlib.pyplot可以方便地绘制图像、图表等可视化结果,例如展示训练过程中的损失曲线或预测结果。

from tensorflow.keras.datasets import mnist

– tensorflow.keras.datasets模块包含了多个常用的数据集,可以直接加载使用而无需手动下载和处理。
– mnist是手写数字识别的经典数据集,包含60,000张28×28像素的灰度训练图像和10,000张测试图像,标签为0到9之间的整数。
– 通过mnist.load_data()可以轻松加载MNIST数据集。

from tensorflow.keras.models import Sequential

– tensorflow.keras.models.Sequential用于创建顺序模型,即层按顺序堆叠的模型。
– 这种模型适合于简单的、线性堆叠的架构,其中每个层只有一个输入张量和一个输出张量。
– 使用Sequential模型可以方便地添加层、编译模型和进行训练。

from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense

– Conv2D:二维卷积层,用于对输入数据应用卷积操作。卷积操作可以提取图像中的局部特征,例如边缘、纹理等。
– MaxPooling2D:二维最大池化层,用于缩小特征图的空间尺寸,同时保留最重要的信息。它通过在每个局部区域取最大值来减少特征图的大小。
– Flatten:将多维输入(如卷积层的输出)展平为一维向量,以便连接到全连接层(Dense层)。
– Dense:全连接层,每个神经元与上一层的所有神经元连接。通常用于分类任务的最后一层,输出类别概率。

from tensorflow.keras.utils import to_categorical

– to_categorical函数用于将整数标签转换为one-hot编码形式。
– one-hot编码是一种表示类别标签的方法,其中每个类别的标签是一个长度为类别数的向量,只有对应位置为1,其余位置为0。
– 例如,标签3将被转换为[0, 0, 0, 1, 0, 0, 0, 0, 0, 0],适用于多分类任务的模型训练。

(2)加载数据集

# 加载MNIST数据集
(X_train, y_train), (X_test, y_test) = mnist.load_data()

'''
mnist.load_data() 并不是直接从网络端加载数据,而是依赖于本地缓存或预下载的数据文件。具体来说,当你第一次调用 mnist.load_data() 时,
Keras 或 TensorFlow 会检查本地是否有已经下载好的 MNIST 数据集文件。如果没有找到这些文件,它会从互联网上下载并保存到本地缓存目录中。
以后再次调用时,就会直接使用本地缓存的文件,而不再从网络下载。

在 TensorFlow/Keras 中,MNIST 数据集通常是从 [TensorFlow Datasets](https://www.tensorflow.org/datasets) 或者 
[Keras Datasets](https://keras.io/api/datasets/) 下载的,默认情况下存储路径通常是用户主目录下的 .keras/datasets/ 文件夹。

(3)数据预处理

# 数据预处理
# 将图像数据从28×28转换为28x28x1以适应卷积层的要求
X_train = X_train.reshape((60000, 28, 28, 1))
X_test = X_test.reshape((10000, 28, 28, 1))

# 将像素值从0-255缩放到0-1之间
X_train = X_train.astype('float32') / 255
X_test = X_test.astype('float32') / 255

# 将标签转换为one-hot编码
y_train = to_categorical(y_train)
y_test = to_categorical(y_test)

# 加载MNIST数据集
(X_train, y_train), (X_test, y_test) = mnist.load_data()

– 这行代码使用mnist.load_data()函数从Keras库中加载MNIST数据集。
– mnist.load_data()返回两个元组:一个是训练集(X_train, y_train),另一个是测试集(X_test, y_test)。
– X_train和X_test分别包含训练图像和测试图像,y_train和y_test分别包含训练标签和测试标签。

# 数据预处理

# 将图像数据从28×28转换为28x28x1以适应卷积层的要求
X_train = X_train.reshape((60000, 28, 28, 1))
X_test = X_test.reshape((10000, 28, 28, 1))

– MNIST数据集中的每个图像是28×28像素的灰度图像,但卷积神经网络通常需要输入图像具有明确的通道数(例如RGB图像有3个通道)。对于灰度图像,
  我们将其重塑为28x28x1的形式。
– X_train.reshape((60000, 28, 28, 1))将训练图像数据从形状(60000, 28, 28)重塑为(60000, 28, 28, 1),其中60000是样本数量,28×28是图像尺寸,
  1表示单通道(灰度图像)。
– 同样地,X_test.reshape((10000, 28, 28, 1))将测试图像数据从形状(10000, 28, 28)重塑为(10000, 28, 28, 1)。

# 将像素值从0-255缩放到0-1之间
X_train = X_train.astype('float32') / 255
X_test = X_test.astype('float32') / 255

– 图像数据的像素值通常是0到255之间的整数。为了使模型更容易学习,通常将这些值缩放到0到1之间。
– astype('float32')将图像数据类型从默认的uint8转换为float32,以便进行除法运算。
– / 255将每个像素值除以255,从而将其缩放到[0, 1]范围。

# 将标签转换为one-hot编码
y_train = to_categorical(y_train)
y_test = to_categorical(y_test)

– MNIST数据集中的标签是0到9之间的整数,表示数字类别。
– 卷积神经网络通常需要标签以one-hot编码的形式提供,即每个类别的标签是一个长度为10的向量,其中只有一个元素为1,其余为0。
– to_categorical函数将整数标签转换为one-hot编码形式。例如,标签3将被转换为[0, 0, 0, 1, 0, 0, 0, 0, 0, 0]。

(4)CNN建模

# 构建卷积神经网络模型
model = Sequential()

# 添加第一个卷积层,输入形状为(28, 28, 1),32个滤波器,每个滤波器大小为3×3
model.add(Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)))

# 添加最大池化层,池化窗口大小为2×2
model.add(MaxPooling2D((2, 2)))

# 构建卷积神经网络模型
model = Sequential()

– 创建一个顺序模型 Sequential,这是一个线性堆叠的模型,其中每个层只有一个输入张量和一个输出张量。
– Sequential 模型非常适合构建简单的、线性堆叠的架构,例如典型的卷积神经网络。

# 添加第一个卷积层,输入形状为(28, 28, 1),32个滤波器,每个滤波器大小为3×3
model.add(Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)))

– 使用 model.add() 方法向模型中添加一个新的层。
– Conv2D 是一个二维卷积层,用于对输入数据应用卷积操作,提取局部特征。
  – 参数解释:
    – 32:表示该层有32个滤波器(也称为卷积核),每个滤波器将生成一个特征图。
    – (3, 3):表示每个滤波器的大小为3×3像素。
    – activation='relu':激活函数使用ReLU(Rectified Linear Unit),即 ( f(x) = max(0, x) ),它将所有负值变为零,正值保持不变。
    ReLU是常用的激活函数,因为它可以加速训练过程并避免梯度消失问题。
    – input_shape=(28, 28, 1):指定输入图像的形状。对于MNIST数据集,每张图像是28×28像素的灰度图像,因此通道数为1(如果是RGB图像,
    则通道数为3)。这里指定了输入图像的形状为 (28, 28, 1)。

# 添加最大池化层,池化窗口大小为2×2
model.add(MaxPooling2D((2, 2)))

– 向模型中添加一个最大池化层 MaxPooling2D,用于缩小特征图的空间尺寸,同时保留最重要的信息。
  – 参数解释:
    – (2, 2):表示池化窗口的大小为2×2。最大池化层将在每个2×2的区域中取最大值,从而将特征图的宽度和高度减半。
    – 这种降采样操作有助于减少计算量和参数数量,同时防止过拟合。

(5)卷积层

# 添加第二个卷积层,64个滤波器
model.add(Conv2D(64, (3, 3), activation='relu'))

# 添加第二个卷积层,64个滤波器
# 在深度学习模型中,尤其是卷积神经网络(Convolutional Neural Network,CNN)里,卷积层起着关键的特征提取作用。
# 这里使用Keras框架(假设是基于Keras,因为常见的用法中 Conv2D 来自于Keras库)的 Conv2D 函数来添加第二个卷积层。

# 参数解释如下:
#【1】 第一个参数 64,表示滤波器(也常被称作卷积核)的数量为64个。每个滤波器都能够学习到输入图像(或者是上一层输出的特征图,如果不是
  输入层的话)不同的特征模式。例如,有的滤波器可能会学习到图像中的边缘特征,有的可能会学习到纹理特征等,随着滤波器数量增多,能提取到
的特征也就越丰富多样。

#【2】第二个参数 (3, 3),这是指定了滤波器(卷积核)的大小。意味着每个滤波器是一个 3 行 3 列的二维矩阵,它会在输入数据(比如图像数据,
  一般是由多个通道组成,例如彩色图像有红、绿、蓝三个通道)对应的通道上滑动进行卷积操作。在滑动过程中,计算滤波器与对应位置的数据元素
  的点积(对应元素相乘后求和),从而生成新的特征值,这样通过在整个输入数据的空间上滑动,就得到了新的特征图。
# – activation='relu',指定了该卷积层的激活函数为ReLU(Rectified Linear Unit,修正线性单元)。激活函数的作用是给神经网络引入非线性
因素,因为如果没有激活函数,无论多少层的神经网络,其本质上都可以等价于一个线性变换模型,表达能力有限。
而ReLU函数的表达式为 f(x) = max(0, x),也就是对于输入的 x 值,当 x 大于等于 0 时,输出就是 x 本身;当 x 小于 0 时,输出为 0。
它具有计算简单、能够加快训练速度、在一定程度上缓解梯度消失问题等优点,使得模型可以学习到更复杂的非线性关系,
所以在这里被选用作为激活函数。

model.add(Conv2D(64, (3, 3), activation='relu'))

(6)池化层

# 添加另一个最大池化层
model.add(MaxPooling2D((2, 2)))

# 添加另一个最大池化层
# 在卷积神经网络架构中,池化层通常紧跟在卷积层后面,用于对卷积层输出的特征图进行下采样操作,减少数据量的同时保留重要的特征信息,
并且能够增强模型对特征位置微小变化的鲁棒性(也就是平移不变性等特性)。这里使用的是Keras框架中的 MaxPooling2D 函数来添加最大池化层。
# 参数 (2, 2) 表示池化窗口的大小,即一个 2 行 2 列的小区域。在这个最大池化操作中,会在卷积层输出的特征图上滑动这个 2×2 的窗口,
对于每个窗口内的元素(比如一个 2×2 的特征值矩阵),选取其中的最大值作为这个小区域的代表值,输出到下一层,这样就使得特征图在高度
和宽度维度上都缩小为原来的一半(假设步长也是默认与池化窗口大小一致的情况)。例如,如果原来的特征图大小是 16×16,经过这个 (2, 2)
的最大池化层后,新的特征图大小就变为 8×8 了,大大减少了后续计算的数据量,同时突出了每个小区域内最显著的特征,有助于提升模型的泛
化能力和防止过拟合等情况。
model.add(MaxPooling2D((2, 2)))

(7)展平层

# 添加第三个卷积层,64个滤波器
model.add(Conv2D(64, (3, 3), activation='relu'))

# 展平层,将多维特征图转换为一维向量
model.add(Flatten())

在深度学习中,特别是在构建卷积神经网络(CNN)时,Flatten() 层是一个非常重要的组件。它用于将多维特征图(通常是三维的,例如 
(height, width, channels))转换为一维向量,以便可以将其传递给全连接层(Dense 层)。下面我将详细解释 Flatten() 层的作用和用法。

1. 为什么要使用 Flatten() 层?

在 CNN 中,卷积层和池化层通常会输出多维的特征图。这些特征图的形状通常是 (batch_size, height, width, channels),其中:
– batch_size 是批次大小,表示一次处理的样本数量。
– height 和 width 是特征图的高度和宽度。
– channels 是特征图的通道数,即卷积核的数量。

然而,当我们将这些特征图传递给全连接层(Dense 层)时,全连接层期望输入是一维的向量,而不是多维的张量。因此,我们需要一个步骤将多维
特征图展平成一维向量,这就是 Flatten() 层的作用。

2. Flatten() 层的工作原理

Flatten() 层不会改变数据的内容或顺序,它只是简单地将多维张量重新排列成一维向量。具体来说,假设你有一个形状为 
(batch_size, height, width, channels) 的张量,经过 Flatten() 层后,它的形状将变为 (batch_size, height * width * channels)。

例如:
– 如果输入张量的形状是 (32, 7, 7, 64)(即批次大小为 32,特征图的高和宽都是 7,通道数为 64),那么经过 Flatten() 层后,
输出的形状将是 (32, 3136),因为 7 * 7 * 64 = 3136。

(8)全连接层

# 全连接层,128个神经元
model.add(Dense(128, activation='relu'))

model.add(Dense(128, activation='relu'))

 解释:

1. model.add():
   – 这个方法将一个层(layer)添加到模型中。Keras 中的模型是按顺序堆叠的层结构,每一层都会对输入数据进行某种变换。

2. Dense:
   – Dense 是一种全连接层(也称为密集层或完全连接层)。在全连接层中,每个神经元都与上一层的所有神经元相连。它通常用于处理平坦化的
   特征向量,并且在网络的最后几层中非常常见。
   
3. 128:
   – 这是指该层中的神经元数量(即输出维度)。在这个例子中,有 128 个神经元。这意味着该层会输出一个长度为 128 的特征向量。每个神经元
   会对输入数据进行加权求和,并通过激活函数进行非线性变换。

4. activation='relu':
   – 激活函数(activation function)用于引入非线性。'relu' 表示 ReLU 激活函数,定义为 f(x) = max(0, x)。ReLU 函数有助于加速训练,
   并且能够有效处理梯度消失问题。它将所有负值设为零,而正值保持不变。

 背景信息:

– 全连接层的作用:
  – 在卷积神经网络(CNN)中,全连接层通常位于卷积层和池化层之后。卷积层和池化层负责提取局部特征,而全连接层则负责将这些局部特征组合
  成全局特征,从而进行分类或其他任务。
  
– 平坦化(Flattening):
  – 在使用全连接层之前,通常需要将多维张量(例如来自卷积层的特征图)展平成一维向量。这可以通过 Flatten 层来实现。例如:
    python
    model.add(Flatten())
    
  – 假设卷积层的输出形状是 (batch_size, height, width, channels),那么 Flatten 层会将其转换为 (batch_size, height  width  channels)。

– 为什么选择 128 个神经元?:
  – 神经元的数量是一个超参数,可以根据具体任务的需求进行调整。128 是一个常见的选择,因为它提供了足够的表达能力,同时不会使模型过于
  复杂。你可以根据实验结果和性能需求调整这个数字。

总结:
这行代码的作用是:
– 添加一个包含 128 个神经元的全连接层。
– 使用 ReLU 激活函数对每个神经元的输出进行非线性变换。
– 这一层通常位于卷积层和池化层之后,用于将提取到的特征进行进一步处理,以准备最终的分类或其他任务。

(9)输出层

# 输出层,10个神经元(对应0-9这10个类别),使用softmax激活函数
model.add(Dense(10, activation='softmax'))

# 编译模型,使用交叉熵损失函数和Adam优化器
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])

model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])

 解释:

1. model.compile():
   – compile 方法用于配置模型的训练过程。在 Keras 中,编译是将模型的结构与优化器、损失函数和评估指标联系起来的关键步骤。
   它为后续的训练做好准备。

2. optimizer='adam':
   – 优化器(Optimizer):优化器决定了如何更新模型权重以最小化损失函数。Adam 是一种非常流行的优化算法,结合了 Momentum 
   和 RMSprop 的优点。
     – Adam (Adaptive Moment Estimation):
       – Adam 使用了一种自适应学习率的方法,可以有效地处理稀疏梯度和非平稳目标。
       – 它通过估计一阶矩(均值)和二阶矩(方差)来动态调整每个参数的学习率。
       – Adam 通常比传统的 SGD(随机梯度下降)收敛更快且更稳定。
   
3. loss='categorical_crossentropy':
   – 损失函数(Loss Function):损失函数衡量的是模型预测值与真实标签之间的差异。选择合适的损失函数对于训练模型至关重要。
     – Categorical Crossentropy:
       – 这是一个多分类问题中常用的损失函数,适用于具有多个类别的分类任务。
       – 假设我们有 ( n ) 个类别,每个样本的真实标签是一个 one-hot 编码向量(长度为 ( n ),只有一个位置为 1,其余为 0),
       而模型的输出是一个概率分布(长度为 ( n ) 的向量,表示每个类别的概率)。
       – Categorical Crossentropy 计算的是这些概率分布之间的距离。公式如下:
         [
         L = -sum_{i=1}^{n} y_i log(hat{y}_i)
         ]
         其中 ( y_i ) 是真实标签,( hat{y}_i ) 是预测的概率。

4. metrics=['accuracy']:
   – 评估指标(Metrics):评估指标用于在训练和验证过程中监控模型性能。Keras 支持多种评估指标,例如准确率、精确率、召回率等。
     – Accuracy:
       – 准确率是最常用的评估指标之一,计算的是正确分类的样本数占总样本数的比例。
       – 对于多分类问题,准确率是所有类别的平均准确率。
       – 在训练过程中,Keras 会根据这个指标来报告模型的表现。

 背景信息:

– 为什么选择 Adam 优化器?:
  – Adam 是一个自适应学习率的优化器,能够有效处理复杂的优化问题,特别是在深度神经网络中表现良好。
  – 它在大多数情况下都能提供较快的收敛速度和较好的泛化性能,因此成为许多机器学习任务的默认选择。

– 为什么选择 Categorical Crossentropy 损失函数?:
  – 当你有一个多分类问题时,Categorical Crossentropy 是一个自然的选择。它能很好地处理多类别标签,并且对不同类别之间的概率分布敏感。
  – 如果你的问题是二分类问题,则可以选择 Binary Crossentropy。

– 为什么要使用 Accuracy 作为评估指标?:
  – 准确率是最直观和最容易理解的评估指标之一,特别是对于分类问题。
  – 然而,在某些不平衡数据集上,准确率可能不是最佳选择,这时你可以考虑其他指标如 F1 分数或 AUC-ROC。

总结:
这行代码的作用是:
– 配置模型的训练过程,包括选择优化器、损失函数和评估指标。
– 使用 Adam 优化器进行权重更新,确保模型能够快速且稳定地收敛。
– 使用 Categorical Crossentropy 作为损失函数,适用于多分类问题。
– 使用准确率(Accuracy)作为评估指标,以监控模型在训练和验证过程中的性能。

(10)训练模型

# 训练模型,训练5个epoch,批量大小为64
model.fit(X_train, y_train, epochs=5, batch_size=64, validation_split=0.2)

model.fit(X_train, y_train, epochs=5, batch_size=32, validation_split=0.2)

 参数解释:

1. X_train 和 y_train:
   – X_train: 这是训练数据集,通常是一个 NumPy 数组或 Pandas DataFrame,包含所有用于训练模型的输入特征。
   – y_train: 这是训练标签,通常也是一个 NumPy 数组或 Pandas Series,包含与 X_train 对应的输出标签(即目标变量)。

2. epochs=5:
   – epochs: 这是指定模型在整个训练集上进行训练的次数。每次遍历整个训练集称为一个 epoch。在这个例子中,模型将遍历训练集 5 次。
   – 例如,如果 X_train 包含 1000 个样本,那么在每个 epoch 中,模型会看到这 1000 个样本一次。经过 5 个 epoch 后,模型总共会看到
    这些样本 5  1000 = 5000 次。

3. batch_size=64:
   – batch_size: 这是指定每次更新模型权重时使用的样本数量。在每个 epoch 内,训练数据会被分成多个批次(batches),每个批次包含
     batch_size 个样本。模型会在每个批次上计算损失并更新权重。
   – 在这个例子中,batch_size=32 表示每次更新模型权重时,模型会处理 64 个样本。如果 X_train 有 1000 个样本,那么在一个 epoch 中,
      模型会处理大约 1000 / 32 ≈ 31.25 次(实际上是 31 次完整的批处理和 1 次剩余的批处理)。

4. validation_split=0.2:
   – validation_split: 这是指定从训练数据集中划分出一部分作为验证集的比例。验证集用于评估模型在未见过的数据上的性能,从而帮助防止过拟合。
   – 在这个例子中,validation_split=0.2 表示将训练数据的 20% 用作验证集。假设 X_train 有 1000 个样本,那么 800 个样本将用于训练,
    200 个样本将用于验证。

 总结:
– model.fit() 方法用于训练神经网络模型。
– X_train 和 y_train 分别是训练数据和对应的标签。
– epochs=5 表示模型将在训练集上训练 5 次。
– batch_size=32 表示每次更新模型权重时使用 32 个样本。
– validation_split=0.2 表示从训练集中划分出 20% 的数据作为验证集,用于监控模型的泛化能力。

(11)模型评估

# 在测试集上评估模型
test_loss, test_acc = model.evaluate(X_test, y_test)
print(f'Test accuracy: {test_acc}')
'''
test_loss, test_acc = model.evaluate(X_test, y_test)
print(f'Test accuracy: {test_acc}')

 参数解释:

1. model.evaluate(X_test, y_test):
   – X_test: 这是测试数据集,通常是一个 NumPy 数组或 Pandas DataFrame,包含所有用于评估模型性能的输入特征。
这些数据是模型之前未见过的数据,用于最终评估模型的泛化能力。
   – y_test: 这是测试标签,通常也是一个 NumPy 数组或 Pandas Series,包含与 X_test 对应的输出标签(即目标变量)。

2. 返回值:
   – model.evaluate() 返回一个包含多个指标的列表,默认情况下包括损失值(loss)和准确率(accuracy)。具体返回的
    指标取决于你在编译模型时指定的指标。
   – 在这个例子中,假设你编译模型时指定了 metrics=['accuracy'],那么 model.evaluate() 将返回两个值:test_loss 
和 test_acc。
     – test_loss: 模型在测试集上的损失值(loss),这是模型预测结果与实际标签之间的误差度量。
     – test_acc: 模型在测试集上的准确率(accuracy),表示模型正确分类的比例。

3. print(f'Test accuracy: {test_acc}'):
   – 这行代码使用 Python 的 f-string 格式化字符串来打印测试集上的准确率。
   – f'Test accuracy: {test_acc}' 表示将 test_acc 的值插入到字符串中,形成一个格式化的输出。

 总结:
– model.evaluate(X_test, y_test) 用于评估模型在测试集上的性能。
– X_test 和 y_test 分别是测试数据和对应的标签。
– model.evaluate() 返回两个值:test_loss 和 test_acc,分别表示测试集上的损失值和准确率。
– 最后一行代码将 test_acc 打印出来,显示模型在测试集上的准确率。

示例输出:
假设模型在测试集上的准确率为 0.85,那么运行这段代码后,你会看到如下输出:

Test accuracy: 0.85

(12)可视化展现

# 可视化一些预测结果
predictions = model.predict(X_test)
plt.figure(figsize=(10, 10))
for i in range(25):
    plt.subplot(5, 5, i + 1)
    plt.xticks([])
    plt.yticks([])
    plt.grid(False)
    plt.imshow(X_test[i].reshape(28, 28), cmap=plt.cm.binary)
    predicted_label = np.argmax(predictions[i])
    true_label = np.argmax(y_test[i])
    if predicted_label == true_label:
        color = 'green'
    else:
        color = 'red'
    plt.xlabel(f'Predicted: {predicted_label}
True: {true_label}', color=color)

 详细注释:

1. predictions = model.predict(X_test)
– model.predict(X_test): 使用训练好的模型对测试数据 X_test 进行预测。
– 返回值:返回一个包含所有预测结果的 NumPy 数组。对于分类问题,每个预测结果通常是各个类别的概率分布(softmax 输出)。例如,
如果是一个 10 类分类问题,predictions[i] 将是一个长度为 10 的数组,表示第 i 个样本属于每个类别的概率。

2. plt.figure(figsize=(10, 10))
– plt.figure(figsize=(10, 10)): 创建一个新的图形窗口,指定其大小为 10×10 英寸。这将用于绘制多个子图(subplot),以便同时展示多个
图像及其预测结果。

 3. for i in range(25):
– for i in range(25):: 开始一个循环,迭代 25 次。这意味着我们将展示前 25 个测试样本的预测结果。

4. plt.subplot(5, 5, i + 1)
– plt.subplot(5, 5, i + 1): 在当前图形窗口中创建一个 5×5 网格的子图,并选择第 i+1 个子图作为当前绘图区域。这样可以将 25 个图像整齐
地排列在一个 5×5 的网格中。

5. plt.xticks([]), plt.yticks([]), plt.grid(False)
– plt.xticks([]) 和 plt.yticks([]): 移除 x 轴和 y 轴上的刻度标记,使图像更简洁。
– plt.grid(False): 关闭网格线,进一步简化图像显示。

 6. plt.imshow(X_test[i].reshape(28, 28), cmap=plt.cm.binary)
– plt.imshow(X_test[i].reshape(28, 28), cmap=plt.cm.binary): 
  – X_test[i]: 获取第 i 个测试样本。
  – .reshape(28, 28): 假设输入数据是 28×28 的图像,但可能以扁平化的一维数组形式存储。使用 reshape 方法将其恢复为 28×28 的二维数组。
  – cmap=plt.cm.binary: 使用二值颜色映射(黑白)来显示图像。

7. predicted_label = np.argmax(predictions[i])
– np.argmax(predictions[i]): 找到 predictions[i] 中最大值的索引,即模型预测的概率最高的类别。这将给出模型对该样本的最终分类结果。

 8. true_label = np.argmax(y_test[i])
– np.argmax(y_test[i]): 找到 y_test[i] 中最大值的索引,即该样本的真实标签。假设 y_test 是 one-hot 编码的标签,np.argmax 可以将
one-hot 向量转换为类别索引。

9. if predicted_label == true_label: color = 'green' else: color = 'red'
– if predicted_label == true_label:: 如果模型的预测结果与真实标签一致,则设置文本颜色为绿色(正确分类)。
– else:: 否则,设置文本颜色为红色(错误分类)。

10. plt.xlabel(f'Predicted: {predicted_label}
True: {true_label}', color=color)
– plt.xlabel(…): 在每个子图的下方添加一个 x 轴标签,显示模型的预测结果和真实标签。
  – f'Predicted: {predicted_label}
True: {true_label}': 使用 f-string 格式化字符串,显示预测结果和真实标签。
  – color=color: 根据前面判断的颜色(绿色或红色)设置标签文本的颜色。

 总结:
这段代码的主要功能是:
1. 使用训练好的模型对测试集进行预测。
2. 创建一个 10×10 英寸的图形窗口,其中包含一个 5×5 的子图网格。
3. 对前 25 个测试样本进行可视化,显示每个样本的图像、模型的预测结果以及真实标签。
4. 使用不同颜色(绿色表示正确分类,红色表示错误分类)来突出显示模型的预测是否正确。

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容