基于多模型的手写数字识别

目录

开发环境

引言

MNIST 数据集介绍

全连接神经网络详解

代码实现

 模型讲解

​卷积神经网络

代码实现

 模型讲解

深度残差网络

代码实现

模型讲解

 总结

开发环境

作者:嘟粥yyds

时间:2023年6月21日

集成开发工具:PyCharm Professional 2021.1

集成开发环境:Python 3.10.6

第三方库:tensorflow-gpu 2.10.0、numpy、matplotlib、opencv-python 4.7.0.72

引言

        手写数字识别一直是计算机视觉领域中的一项重要任务。该任务旨在利用给定一系列手写数字图像及其对应的数字标签,构建模型进行学习,以实现自动识别出新的手写数字图像所代表的数字。图像识别是指利用计算机进行图像处理、分析和理解,以识别各种模式和目标的技术。在机器学习领域,通常将这类识别问题转化为分类问题。
        而手写数字识别任务之所以受到如此高的关注,是因为数字类别仅限于0-9这10个数字,相比其他字符识别率较高,因此,手写数字识别任务常被用于验证新的理论或进行深入的分析研究。许多机器学习、模式识别和深度学习领域的新理论和算法往往会首先在手写数字识别任务上进行验证,以验证其理论的有效性,然后再将其应用到更为复杂的领域当中。
        首先建立识别模型意味着构建一个适用于手写数字识别任务的模型。本文中使用的模型包括全连接神经网络模型、卷积神经网络( LeNet-5 模型)和深度残差网络( ResNet-18和 ResNet-34 )。

MNIST 数据集介绍

        MNIST 数据集是一个经典的手写数字图像数据集,常用于测试和评估机器学习算法在手写数字识 别任务上的性能。它由美国国家标准与技术研究所 (National Institute of Standards and Technology,简称 NIST) 收集和发布。
        MNIST 数据集包含了大量的手写数字图像,其中包括了 0 9 10 个数字的灰度图像样本。每个 样本的图像尺寸为 28x28 像素,图像中的每个像素值表示了该位置的灰度强度,取值范围为 0 255
        图像样本经过预处理和标准化,使得每个像素的数值范围在 0 1 之间,以便更好地适应机器学习算法 的处理要求。
        MNIST 数据集总共包含了 6 万个用于训练的图像样本和 1 万个用于测试的图像样本。这些样本已 经经过人工标注,每个图像都有对应的数字标签,表示图像中所示的手写数字。这些标签从 0 9 的数 字编码了对应的类别信息,用于训练和评估模型的性能。
        MNIST 数据集在机器学习领域被广泛应用,特别是在手写数字识别任务的研究和评估中。研究者 们可以利用 MNIST 数据集来开发和测试各种机器学习算法和模型,包括全连接神经网络、卷积神经网络等。通过使用 MNIST 数据集,研究者们可以比较不同算法在相同数据集上的性能,评估模型的准确度、泛化能力和鲁棒性,并推动手写数字识别算法和技术的发展。
        虽然 MNIST 数据集已经存在多年,但它仍然是一个重要的基准数据集,被广泛应用于教学、研究和算法验证等领域。同时,基于 MNIST 数据集的研究也为更复杂的图像识别和模式识别问题提供了有 益的启示和参考。
                                                           图1:MNIST 数据集样例图片。
                                                图2:MNIST 数据集中内容为8的数字图片矩阵。

全连接神经网络详解

        从网络结构上看,如下图所示,函数的嵌套表现为网络层的前后相连,每堆叠一个(非)线性环节, 网络层数增加一层。我们把输入节点 x 所在的层叫作输入层,每一个非线性模块的输出 h_i 连同它的网络层参数 W_i 和 b_i 称为一层网络层,特别地,对于网络中间的层,叫作隐藏层,最后一层叫作输出层。这种由大量神经元模型连接形成的网络结构称为神经网络 (Neural Network)。我们可以看到,神经网络并不难理解,神经网络的每层的节点数和神经网络的层数决定了神经网络的复杂度。
                                                       图3: 全连接神经网络结构。
                                         图4: 全连接神经网络结构中首末两层的信号传递。

代码实现

import tensorflow as tf
from tensorflow.keras import layers, losses, optimizers
import cv2
import numpy as np
import os
import matplotlib.pyplot as plt
import matplotlib as mpl
mpl.use('TKAgg')

save_dir = 'save_model/mnist_classifier_model'
if not os.path.exists(save_dir):
    gpus = tf.config.experimental.list_physical_devices("GPU")
    if gpus:
        try:
            # 设置GPU显存占用为按需分配,增长式
            for gpu in gpus:
                tf.config.experimental.set_memory_growth(gpu, True)
        except RuntimeError as e:
            print(e)

    # 数据加载和预处理
    (x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()

    x_train = x_train / 255.0
    x_test = x_test / 255.0

    train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))
    test_dataset = tf.data.Dataset.from_tensor_slices((x_test, y_test))

    batch_size = 128

    train_dataset = train_dataset.shuffle(buffer_size=1024).batch(batch_size)
    test_dataset = test_dataset.batch(batch_size)

    # 构建模型
    class MNISTClassifier(tf.keras.Model):
        def __init__(self):
            super(MNISTClassifier, self).__init__()
            self.flatten = layers.Flatten()
            self.fc1 = layers.Dense(256, activation='relu', kernel_regularizer=tf.keras.regularizers.l2(0.001))
            self.dropout = layers.Dropout(0.2)
            self.fc2 = layers.Dense(128, activation='relu', kernel_regularizer=tf.keras.regularizers.l2(0.001))
            self.output_layer = layers.Dense(10)

        def call(self, inputs, training=None):
            x = self.flatten(inputs)
            x = self.fc1(x)
            x = self.dropout(x, training=training)
            x = self.fc2(x)
            x = self.output_layer(x)
            return x


    model = MNISTClassifier()

    # 定义优化器和损失函数
    optimizer = optimizers.Adam(learning_rate=0.001)
    loss_fn = losses.SparseCategoricalCrossentropy(from_logits=True)

    # 定义评估指标
    train_loss = tf.keras.metrics.Mean(name='train_loss')
    train_accuracy = tf.keras.metrics.SparseCategoricalAccuracy(name='train_accuracy')

    test_loss = tf.keras.metrics.Mean(name='test_loss')
    test_accuracy = tf.keras.metrics.SparseCategoricalAccuracy(name='test_accuracy')

    # 定义TensorBoard回调函数
    log_dir = './MNIST_Classifier_logs'
    tensorboard_callback = tf.keras.callbacks.TensorBoard(log_dir=log_dir, histogram_freq=1)

    # build一次网络模型,输入形状为(28, 28)
    model.build(input_shape=(batch_size, 28, 28))
    # 查看模型结构
    model.summary()
    # 编译模型
    model.compile(optimizer=optimizer, loss=loss_fn, metrics=[train_accuracy, test_accuracy])

    # 开始训练
    epochs = 10

    model.fit(train_dataset, epochs=epochs, validation_data=test_dataset, callbacks=[tensorboard_callback])

    # 保存模型为TensorFlow SavedModel格式
    model.save('save_model/mnist_classifier_model')
    print("模型保存成功")

 模型讲解

  • 输入的手写数字图像经过展平层,从二维形式转换为一维向量。
  • 一维向量作为输入进入第一个全连接层(fc1),通过权重和偏置进行线性变换,并通过ReLU激活函数进行非线性处理。
  • 经过第一个全连接层后,得到一个具有128个元素的向量。
  • 第一个全连接层的输出再传递给第二个全连接层(fc2),进行线性变换但没有激活函数。
  • 最终的输出是一个具有10个元素的向量,代表了输入图像属于每个数字类别的概率。

本文所有的评测指标的可视化均在TensorBoard中完成。

                                        图 5: 模型的权重和偏差直方图。

                                        图 6: 模型在训练集和测试集上的训练准确率。 

                                                            图7 :模型结构图

卷积神经网络

        卷积神经网络(Convolutional Neural Network ,CNN)是一种在计算机视觉和图像处理领域广泛应用的神经网络模型。它专门设计用于处理具有网格结构的数据,如图像。卷积神经网络通过卷积层、池化层 和全连接层等组成,以有效地提取图像特征并进行分类或识别。
        卷积神经网络的核心思想是局部感知和参数共享。通过卷积操作,网络可以在图像的局部区域提取特征,并通过参数共享来减少模型的参数数量。这种设计使得卷积神经网络在处理大规模图像数据时具有优势,同时能够保留空间结构和位置信息。
  1.  卷积层(Convolutional Layer):卷积层是CNN的核心组成部分。它使用卷积核(滤波器)对输入 图像进行卷积操作,提取不同位置的特征。卷积层可以通过设置多个卷积核来提取多个不同的特征图。 卷积操作可以有效地捕捉局部特征,同时减少模型参数。

  2. 池化层 (Pooling Layer):池化层用于降低特征图的空间维度,减少计算量。常见的池化操作有最 大池化 (Max Pooling) 和平均池化 (Average Pooling) 。池化层通过对特征图进行下采样,保留主要特征并减少噪声干扰。
  3. 激活层 (Activation Layer):激活层引入非线性变换,增加模型的表达能力。常用的激活函数有 ReLU(Rectified Linear Unit) Sigmoid Tanh 等。

                                             图8: 常见卷积核及其效果。 

        卷积神经网络(Convolutional Neural Network CNN)是一种前馈神经网络,其设计灵感来自于生物学中视觉皮层的工作原理。 CNN 在处理具有网格结构的数据上表现出色,尤其适用于图像和视频数据的处理和分析。
CNN 的核心原理是局部感知和参数共享。
  1. 局部感知:CNN 通过卷积操作来实现局部感知,即将卷积核(也称为滤波器)应用于输入数据的局部区域,从而提取特征。卷积核的大小通常小于输入数据的尺寸,因此它可以在不同位置进行滑动, 并对每个位置进行局部特征提取。通过卷积操作,CNN 能够捕捉到图像中的边缘、纹理等局部特征。
  2. 参数共享:CNN 利用参数共享来减少模型的参数数量。在卷积层中,卷积核的参数被共享使用, 即同一卷积核在不同位置的应用使用相同的参数。这样做的好处是减少了需要训练的参数数量,提高了模型的泛化能力。参数共享还使得 CNN 对平移、旋转和缩放等图像变化具有一定的不变性。
        CNN 的训练过程通常使用反向传播算法来更新网络参数。在训练过程中,通过与实际标签进行比较,计算预测结果的损失,并使用优化算法(如梯度下降)来更新卷积核的权重。通过多次迭代训练, CNN 可以学习到输入数据中的特征并进行准确的分类或识别。
        总结而言,卷积神经网络通过局部感知和参数共享的机制,能够有效地处理图像和视频等具有网格结构的数据。它的结构设计使得它在图像处理和计算机视觉任务中表现出色,并成为了深度学习中的重要模型之一。
                                                       图9: 卷积神经网络结构。

本文实现的卷积神经网络为 LeNet-5 模型。 LeNet-5是一个经典的深度卷积神经网络,由Yann LeCun 在1998年提出,旨在解决手写数字识别问题,被认为是卷积神经网络的开创性工作之一。该网络是第一个被广泛应用于数字图像识别的神经网络之一,也是深度学习领域的里程碑之一。

                                                  图10:原LeNet-5模型结构。                               

 而本文实现的LeNet-5模型在原LeNet-5的基础上进行了些许调整,使得它更容易在现代深度学习框架上实现。首先我们将输入X的形状由32x32调整为28 x 28,然后将2个下采样层实现为最大池化层(用于降低特征图的高、宽),最后利用全连接层替换掉Gaussian Connections层。

                                             图11:针对MNIST数据集调整后LeNet-5模型结构。

代码实现

import tensorflow as tf
import tensorflow.keras
from tensorflow.keras import layers, Sequential, losses, datasets, losses, optimizers
import cv2
import numpy as np
import os
import matplotlib.pyplot as plt
import matplotlib as mpl
mpl.use('TKAgg')

if not os.path.exists('save_model/mnist_LeNet-5_model'):
    gpus = tf.config.experimental.list_physical_devices("GPU")
    if gpus:
        try:
            # 设置GPU显存占用为按需分配,增长式
            for gpu in gpus:
                tf.config.experimental.set_memory_growth(gpu, True)
        except RuntimeError as e:
            print(e)

    # 数据预处理
    def preprocess(x, y):
        x = tf.expand_dims(x, axis=-1)  # 添加通道维度
        x = 2 * tf.cast(x, dtype=tf.float32) / 255. - 1
        y = tf.cast(y, dtype=tf.int32)
        y = tf.one_hot(y, depth=10)
        return x, y


    # 训练集   验证集
    (x, y), (x_val, y_val) = tf.keras.datasets.mnist.load_data()
    x_train, x_test = tf.split(x, num_or_size_splits=[45000, 15000], axis=0)
    y_train, y_test = tf.split(y, num_or_size_splits=[45000, 15000], axis=0)
    print(
        f"datasets:\nx_train:{x_train.shape}  x_test:{x_test.shape}  x_val:{x_val.shape}\ny_train:{y_train.shape}         y_test:{y_test.shape}         y_val:{y_val.shape}")
    # 训练数据集
    train_db = tf.data.Dataset.from_tensor_slices((x_train, y_train))
    train_db = train_db.map(preprocess).shuffle(45000).batch(128)
    # 验证数据集
    ds_val = tf.data.Dataset.from_tensor_slices((x_val, y_val))
    ds_val = ds_val.map(preprocess).batch(128)
    # 测试数据集
    test_db = tf.data.Dataset.from_tensor_slices((x_test, y_test))
    test_db = test_db.map(preprocess).batch(128)

    # 创建TensorBorad环境
    log_dir = './MNIST_LeNet-5_logs'
    tensorboard_callback = tf.keras.callbacks.TensorBoard(log_dir=log_dir, histogram_freq=1)

    network = Sequential([
        layers.Conv2D(6, kernel_size=3, strides=1),  # 第一个卷积层,6个 3x3 卷积核
        layers.MaxPooling2D(pool_size=2, strides=2),  # 高宽各减半的池化层
        layers.ReLU(),  # 激活函数
        layers.Conv2D(16, kernel_size=3, strides=1),  # 第二个卷积层,16个 3x3 卷积核
        layers.MaxPooling2D(pool_size=3, strides=1),  # 高宽各减半的池化层
        layers.ReLU(),  # 激活函数
        layers.Flatten(),  # 打平层,方便全连接层处理
        # 全连接层
        layers.Dense(120, activation='relu'),
        layers.Dense(84, activation='relu'),
        layers.Dense(10)
    ])

    # 创建损失函数的类,在实际计算时直接调用类实例即可
    criteon = losses.CategoricalCrossentropy(from_logits=True)
    optimizer = tf.keras.optimizers.RMSprop(0.01)

    # build一次网络模型,给输入X的形状,其中128为随意给的batch_size
    network.build(input_shape=(128, 28, 28, 1))
    network.summary()

    # 编译模型
    network.compile(optimizer=optimizer, loss=criteon, metrics=['accuracy'])

    # 开始训练
    epochs = 10
    network.fit(train_db, epochs=epochs, validation_data=ds_val, callbacks=[tensorboard_callback])

    # 保存模型为TensorFlow SavedModel格式
    network.save('save_model/mnist_LeNet-5_model')
    print("模型保存成功")

 模型讲解

模型由一系列层组成,包括卷积层、池化层和全连接层。它的输入是图像数据,经过一系列的层操
作,最终输出图像对应的类别。
  • 卷积层:使用`Conv2D`层进行卷积操作。第一个卷积层有63x3大小的卷积核,第二个卷积层有16个3x3大小的卷积核。这些卷积核将提取输入图像的不同特征。
  • 池化层:使用`MaxPooling2D`层进行最大池化操作。第一个池化层的池化窗口大小为2x2,步幅为2,将图像的高度和宽度各减半。第二个池化层的池化窗口大小为3x3,步幅为1。
  • ReLU激活层:使用`ReLU`层作为激活函数,对卷积层的输出进行非线性映射,增加网络的表达能力。
  • 打平层:使用`Flatten`层将多维的输入数据转换为一维,以便后续的全连接层处理。
  • 全连接层:使用`Dense`层进行全连接操作。第一个全连接层有120个神经元,第二个全连接层有84个神经元。最后一层是一个有10个神经元的全连接层,用于输出对应10个类别的概率分布。 
        模型的训练使用了交叉熵损失函数(`CategoricalCrossentropy`)和 RMSprop优化器(`RMSprop`)。模型在训练数据集上进行多个epoch的训练,并使用验证集进行验证。训练过程中会记录训练过程的指标(如准确率)和损失,并将这些信息保存在 TensorBoard 日志文件中。
        模型对图像进行前向传播,输出图像所属的类别,并通过`argmax`函数找出概率最大的类别作为预测结果。

                                             图12:模型的训练准确率和损失值。

                                                    图13:模型结构图 

深度残差网络

        深度残差网络(Deep Residual Network ,简称 ResNet)是一种深度学习模型,旨在解决深层网络训练过程中的梯度消失和梯度爆炸问题。 ResNet 通过引入残差连接 (residual connection)来构建深层网络,使得网络可以更好地学习输入与输出之间的残差,从而提升网络的性能和训练效果。
        传统的深层神经网络在训练过程中容易出现梯度消失或梯度爆炸的问题,导致网络的性能下降。 ResNet 通过引入残差块 (residual block) 来解决这一问题。残差块包含了跳跃连接 (skip connection),允许信息直接从前一层传递到后续层,即通过残差路径来学习输入与输出之间的残差。
        一个典型的残差块由两个卷积层组成。设输入为x ,经过第一个卷积层后得到特征图 H,然后经过激活函数(如 ReLU )进行非线性变换,再经过第二个卷积层得到特征图 F。这时,可以定义残差块的输出为F + H ,即将输入与输出相加作为最终的输出。
        深度残差网络由多个残差块堆叠而成。其中,引入了一个重要的设计原则,即在残差块中保持特征图的大小不变,同时通过卷积操作改变特征图的通道数。这样可以保持网络的信息流动,并降低网络的复杂度。此外,为了进一步减少参数量和计算量,常常使用一个1x1的卷积层作为降维层,将通道数减少一半。
        在训练过程中,ResNet通过反向传播和梯度下降来更新网络中的参数。在每个训练迭代中,通过最小化损失函数来优化网络,以使网络的预测结果与真实标签尽可能接近。常用的损失函数可以是交叉熵损失函数。

代码实现

import tensorflow as tf
from tensorflow.keras import layers, Sequential, losses, optimizers
import os
import cv2
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
mpl.use('TKAgg')

# 动态分配GPU内存
gpus = tf.config.experimental.list_physical_devices("GPU")
if gpus:
    try:
        # 设置GPU显存占用为按需分配,增长式
        for gpu in gpus:
            tf.config.experimental.set_memory_growth(gpu, True)
    except RuntimeError as e:
        print(e)


# 首先实现中间两个卷积层,Skip Connection 1x1 卷积层的残差模块
class BasicBlock(tf.keras.layers.Layer):
    # 残差模块
    def __init__(self, filter_num, stride=1):
        super(BasicBlock, self).__init__()
        # 第一个卷积单元
        self.conv1 = layers.Conv2D(filter_num, (3, 3), strides=stride, padding='same')
        self.bn1 = layers.BatchNormalization()
        self.relu = layers.Activation('relu')
        # 第二个卷积单元
        self.conv2 = layers.Conv2D(filter_num, (3, 3), strides=1, padding='same')
        self.bn2 = layers.BatchNormalization()
        if stride != 1:  # 通过 1x1 卷积完成 shape 匹配
            self.downsample = Sequential()
            self.downsample.add(layers.Conv2D(filter_num, (1, 1), strides=stride))
        else:  # shape 匹配,直接短接
            self.downsample = lambda x: x

    def call(self, inputs, training=None):
        # 前向计算函数
        # [b, h, w, c],通过第一个卷积单元
        out = self.conv1(inputs)
        out = self.bn1(out)
        out = self.relu(out)
        # 通过第二个卷积单元
        out = self.conv2(out)
        out = self.bn2(out)
        # 通过 identity 模块
        identity = self.downsample(inputs)
        # 2 条路径输出直接相加
        output = layers.add([out, identity])
        output = tf.nn.relu(output)  # 通过激活函数
        return output


# 实现ResNet网络模型
class ResNet(tf.keras.Model):
    # 通用的 ResNet 实现类
    def __init__(self, layer_dims, num_classes=10):  # [2, 2, 2, 2]
        super(ResNet, self).__init__()
        # 根网络,预处理
        self.stem = Sequential([
            layers.Conv2D(64, (3, 3), strides=(1, 1)),
            layers.BatchNormalization(),
            layers.Activation('relu'),
            layers.MaxPool2D(pool_size=(2, 2), strides=(1, 1), padding='same')
        ])
        # 堆叠 4 个 Block,每个 Block 包含了多个 BasicBlock,设置步长不一样
        self.layer1 = self.build_resblock(64, layer_dims[0])
        self.layer2 = self.build_resblock(128, layer_dims[1], stride=2)
        self.layer3 = self.build_resblock(256, layer_dims[2], stride=2)
        self.layer4 = self.build_resblock(512, layer_dims[3], stride=2)
        # 通过 Pooling 层将高宽降低为 1x1
        self.avgpool = layers.GlobalAveragePooling2D()
        # 最后连接一个全连接层分类
        self.fc = layers.Dense(num_classes)

    def call(self, inputs, training=None):
        x = self.stem(inputs)
        # 一次通过 4 个模块
        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)
        # 通过池化层
        x = self.avgpool(x)
        # 通过全连接层
        x = self.fc(x)
        return x

    def build_resblock(self, filter_num, blocks, stride=1):
        # 辅助函数,堆叠 filter_num 个 BasicBlock
        res_blocks = Sequential()
        # 只有第一个 BasicBlock 的步长可能不为 1,实现下采样
        res_blocks.add(BasicBlock(filter_num, stride))
        for _ in range(1, blocks):  # 其他 BasicBlock 步长都为 1
            res_blocks.add(BasicBlock(filter_num, stride=1))
        return res_blocks


def resnet18():
    # 通过调整模块内部BasicBlock的数量和配置实现不同的ResNet
    return ResNet([2, 2, 2, 2])


def resnet34():
    # 通过调整模块内部BasicBlock的数量和配置实现不同的ResNet
    return ResNet([3, 4, 6, 3])


# 数据预处理
def preprocess(x, y):
    # 将x缩放到区间[-1, 1]上
    x = 2 * tf.cast(x, dtype=tf.float32) / 255. - 1
    y = tf.cast(y, dtype=tf.int32)
    y = tf.one_hot(y, depth=10)
    return x, y


if __name__ == '__main__':
    save_dir = 'save_model/mnist_ResNet-18_model'
    if not os.path.exists(save_dir):
        # 训练集   验证集
        (x, y), (x_val, y_val) = tf.keras.datasets.mnist.load_data()
        x_train, x_test = tf.split(x, num_or_size_splits=[45000, 15000], axis=0)
        y_train, y_test = tf.split(y, num_or_size_splits=[45000, 15000], axis=0)
        print(
            f"datasets:\nx_train:{x_train.shape}  x_test:{x_test.shape}  x_val:{x_val.shape}\ny_train:{y_train.shape}         y_test:{y_test.shape}         y_val:{y_val.shape}")
        # 训练数据集
        train_db = tf.data.Dataset.from_tensor_slices((x_train, y_train))
        train_db = train_db.map(preprocess).shuffle(45000).batch(128)
        # 验证数据集
        ds_val = tf.data.Dataset.from_tensor_slices((x_val, y_val))
        ds_val = ds_val.map(preprocess).batch(128)
        # 测试数据集
        test_db = tf.data.Dataset.from_tensor_slices((x_test, y_test))
        test_db = test_db.map(preprocess).batch(128)
        # 采样一个样本
        sample = next(iter(train_db))
        print('sample:', sample[0].shape, sample[1].shape,
              tf.reduce_min(sample[0]), tf.reduce_max(sample[0]))

        # 创建TensorBorad环境(保存路径因人而异)
        log_dir = './MNIST_ResNet18_logs'
        tensorboard_callback = tf.keras.callbacks.TensorBoard(log_dir=log_dir, histogram_freq=1)

        optimizer = optimizers.Adam(learning_rate=1e-4)
        loss_fn = losses.CategoricalCrossentropy(from_logits=True,
                                                 reduction=tf.keras.losses.Reduction.SUM_OVER_BATCH_SIZE)
        train_accuracy = tf.keras.metrics.CategoricalAccuracy(name='train_accuracy')
        test_accuracy = tf.keras.metrics.CategoricalAccuracy(name='test_accuracy')

        model = resnet18()
        model.build(input_shape=(None, 28, 28, 1))  # 输入尺寸改为28x28x1
        # 打印网络参数信息
        model.summary()
        # 编译模型
        model.compile(optimizer=optimizer, loss=loss_fn, metrics=[train_accuracy, test_accuracy])
        # 开始训练
        epochs = 5
        model.fit(train_db, epochs=epochs, validation_data=ds_val, callbacks=[tensorboard_callback])

        # 保存模型为TensorFlow SavedModel格式
        model.save('save_model/mnist_ResNet-18_model')
        print("模型保存成功")

模型讲解

  • Stem 部分: ResNet-18以一个卷积层作为模型的初始部分,用于对输入图像进行特征提取。该 卷积层后面跟着批归一化层、 ReLU激活函数和最大池化层,以减小特征图的大小。
  • Layer 部分: ResNet-18 共包含4个 Layer ,每个 Layer 由若干个 BasicBlock 组成。第一个 Layer的BasicBlock 输出通道数为64,后续的 Layer 依次翻倍,分别为 128 256 512。
  • 全局平均池化层:在经过4个 Layer 后, ResNet-18使用全局平均池化层将特征图转换为一维向 量,以便进行分类。
  • 全连接层:最后, ResNet-18 使用全连接层将特征向量映射到预测类别的输出。

                                          图14: 模型的训练准确率和损失值。

                                                          图15:模型结构图。

 总结

        本文旨在验证通过在 MNIST 数据集上训练得到的模型的泛化能力。为了使泛化能力测试更加具有学术专业性,我们选择了从互联网下载的图像数据集,其中包含了各种不同风格的手写数字图像。每个数字类别都有不同的风格作为对照。
        为了确保泛化能力测试的专业性,我们选择了具有不同尺寸的手写数字图像,这些图像的尺寸都大于原始 MNIST 数据集的尺寸 28x28。这样的选择使得我们更贴近实际应用场景,并且能够更全面地评估模型的泛化能力。

                                                 图 16: 仿真数据集图片。

                                         图17: 三个模型的预测成功率汇总对比。

猜你喜欢

转载自blog.csdn.net/zzp20031120/article/details/131595100