《零基础实践深度学习》2.4手写数字识别之网络结构

简介: 这篇文章介绍了手写数字识别任务中网络结构设计的优化,比较了多层全连接神经网络和卷积神经网络两种模型结构,并展示了使用PaddlePaddle框架实现这些网络结构,训练并观察它们在MNIST数据集上的表现。

2.4 手写数字识别之网络结构

第2.2节我们尝试使用与房价预测相同的简单神经网络解决手写数字识别问题,但是效果并不理想。原因是手写数字识别的输入是28×28的像素值,输出是0~9的数字标签,而线性回归模型无法捕捉二维图像数据中蕴含的复杂信息,如图1所示。无论是牛顿第二定律任务,还是房价预测任务,输入特征和输出预测值之间的关系均可以使用“直线”刻画(使用线性方程来表达)。但手写数字识别任务的输入像素和输出数字标签之间的关系显然不是线性的,甚至这个关系复杂到我们靠人脑难以直观理解的程度。

图1:数字识别任务的输入和输出不是线性关系

因此,我们需要尝试使用其他更复杂、更强大的网络来构建手写数字识别任务,观察一下训练效果,即将“横纵式”教学法从横向展开,如图2所示。本节主要介绍两种常见的网络结构:经典的多层全连接神经网络和卷积神经网络

图2:“横纵式”教学法 — 网络结构优化

数据处理

数据处理已在前面内容中介绍,这里直接调用封装好的函数即可:

In [6]

from data_process import get_MNIST_dataloader
train_loader,_ = get_MNIST_dataloader()

2.4.1 经典的全连接神经网络

神经元是构成神经网络的基本单元,其基本结构如图3所示。

图3:神经元结构

经典的全连接神经网络来包含四层网络:输入层、两个隐含层和输出层,将手写数字识别任务通过全连接神经网络表示,如图4所示。

图4:手写数字识别任务的全连接神经网络结构

  • 输入层:将数据输入给神经网络。在该任务中,输入层的尺度为28×28的像素值。
  • 隐含层:增加网络深度和复杂度,隐含层的节点数是可以调整的,节点数越多,神经网络表示能力越强,参数量也会增加。在该任务中,中间的两个隐含层为10×10的结构,通常隐含层会比输入层的尺寸小,以便对关键信息做抽象激活函数使用常见的Sigmoid函数。
  • 输出层:输出网络计算结果,输出层的节点数是固定的。如果是回归问题,节点数量为需要回归的数字数量。如果是分类问题,则是分类标签的数量。在该任务中,模型的输出是回归一个数字,输出层的尺寸为1。

说明:

隐含层引入非线性激活函数Sigmoid是为了增加神经网络的非线性能力。

举例来说,如果一个神经网络采用线性变换,有四个输入x1~x4,一个输出y。假设第一层的变换是z1=x1−x2和z2=x3+x4,第二层的变换是y=z1+z2,则将两层的变换展开后得到y=x1−x2+x3+x4。也就是说,无论中间累积了多少层线性变换,原始输入和最终输出之间依然是线性关系。


Sigmoid是早期神经网络模型中常见的非线性变换函数,公式为

通过如下代码,绘制出Sigmoid的函数曲线。

In [2]

import numpy as np
import matplotlib.pyplot as plt
def sigmoid(x):
    # 直接返回sigmoid函数
    return 1. / (1. + np.exp(-x))
 
# param:起点,终点,间距
x = np.arange(-8, 8, 0.2)
y = sigmoid(x)
plt.plot(x, y)
plt.show()

针对手写数字识别的任务,网络层的设计如下:

  • 输入层的尺度为 28 × 28,但批次计算的时候会统一加1个维度(大小为batch size)。
  • 中间的两个隐含层为 10 × 10 的结构,激活函数使用Sigmoid函数。
  • 与房价预测模型一样,模型的输出是回归一个数字,输出层的尺寸设置成1

下述代码为经典全连接神经网络的实现。完成网络结构定义后,即可训练神经网络。


说明:

数据迭代器train_loader在每次迭代时的数据shape为[batch_size, 1, 28, 28],因此需要将该数据形式改变为向量形式。

In [3]

import paddle
import paddle.nn.functional as F
from paddle.nn import Linear
# 定义多层全连接神经网络
class MNIST(paddle.nn.Layer):
    def __init__(self):
        super(MNIST, self).__init__()
        # 定义两层全连接隐含层,输出维度是10,当前设定隐含节点数为10,可根据任务调整
        self.fc1 = Linear(in_features=784, out_features=10)
        self.fc2 = Linear(in_features=10, out_features=10)
        # 定义一层全连接输出层,输出维度是1
        self.fc3 = Linear(in_features=10, out_features=1)
    
    # 定义网络的前向计算,隐含层激活函数为sigmoid,输出层不使用激活函数
    def forward(self, inputs):
        inputs = paddle.reshape(inputs, [inputs.shape[0], 784])
        outputs1 = self.fc1(inputs)
        outputs1 = F.sigmoid(outputs1)
        outputs2 = self.fc2(outputs1)
        outputs2 = F.sigmoid(outputs2)
        outputs_final = self.fc3(outputs2)
        return outputs_final

paddle.summary(net, input_size, dtypes=None)函数能够打印网络的基础结构和参数信息。

paddle.summary(net, input_size=None, dtypes=None, input=None)

关键参数含义如下:

  • net (Layer) - 网络实例,必须是 Layer 的子类。
  • input_size (tuple|InputSpec|list[tuple|InputSpec) - 输入张量的大小。如果网络只有一个输入,那么该值需要设定为tuple或InputSpec。如果模型有多个输入。那么该值需要设定为list[tuple|InputSpec],包含每个输入的shape。
  • dtypes (str,可选) - 输入张量的数据类型,如果没有给定,默认使用 float32 类型。默认值:None

返回:字典,包含了总的参数量和总的可训练的参数量。

下面我们打印上面定义的全连接神经网络基础结构和参数信息:

In [4]

model = MNIST()
params_info = paddle.summary(model, (1, 1, 28, 28))
print(params_info)

W0901 17:12:48.648859 98 device_context.cc:447] Please NOTE: device: 0, GPU Compute Capability: 7.0, Driver API Version: 11.2, Runtime API Version: 10.1

W0901 17:12:48.653869 98 device_context.cc:465] device: 0, cuDNN Version: 7.6.

---------------------------------------------------------------------------

Layer (type) Input Shape Output Shape Param #

===========================================================================

Linear-1 [[1, 784]] [1, 10] 7,850

Linear-2 [[1, 10]] [1, 10] 110

Linear-3 [[1, 10]] [1, 1] 11

===========================================================================

Total params: 7,971

Trainable params: 7,971

Non-trainable params: 0

---------------------------------------------------------------------------

Input size (MB): 0.00

Forward/backward pass size (MB): 0.00

Params size (MB): 0.03

Estimated Total Size (MB): 0.03

---------------------------------------------------------------------------

{'total_params': 7971, 'trainable_params': 7971}

使用MNIST数据集训练定义好的经典全连接神经网络。

In [7]

#网络结构部分之后的代码,保持不变
def train(model):
    model.train()
    
    # 使用SGD优化器,learning_rate设置为0.01
    opt = paddle.optimizer.SGD(learning_rate=0.01, parameters=model.parameters())
    # 训练5轮
    EPOCH_NUM = 10
    loss_list = []
    
    for epoch_id in range(EPOCH_NUM):
        for batch_id, data in enumerate(train_loader()):
            #准备数据
            images, labels = data
            images = paddle.to_tensor(images)
            labels = paddle.to_tensor(labels, dtype="float32")
            
            #前向计算的过程
            predicts = model(images)
            
            #计算损失,取一个批次样本损失的平均值
            loss = F.square_error_cost(predicts, labels)
            avg_loss = paddle.mean(loss)
            #每训练200批次的数据,打印下当前Loss的情况
            if batch_id % 200 == 0:
                loss_list.append(avg_loss.numpy()[0])
                print("epoch: {}, batch: {}, loss is: {}".format(epoch_id, batch_id, avg_loss.numpy()))
            
            #后向传播,更新参数的过程
            avg_loss.backward()
            # 最小化loss,更新参数
            opt.step()
            # 清除梯度
            opt.clear_grad()
    #保存模型参数
    paddle.save(model.state_dict(), 'mnist.pdparams')
    return loss_list
model = MNIST()
loss_list = train(model)

epoch: 0, batch: 0, loss is: [26.372326]

epoch: 0, batch: 200, loss is: [5.287777]

epoch: 0, batch: 400, loss is: [3.5908165]

epoch: 0, batch: 600, loss is: [2.9941204]

epoch: 0, batch: 800, loss is: [2.546555]...

epoch: 9, batch: 0, loss is: [0.8851687]

epoch: 9, batch: 200, loss is: [1.3586344]

epoch: 9, batch: 400, loss is: [1.5049815]

epoch: 9, batch: 600, loss is: [2.061728]

epoch: 9, batch: 800, loss is: [1.1433309]

根据损失函数变化情况绘制曲线:

In [10]

from tools import plot
plot(loss_list)

2.4.2 卷积神经网络

虽然使用经典的全连接神经网络可以提升一定的准确率,但其输入数据的形式导致丢失了图像像素间的空间信息,这影响了网络对图像内容的理解。对于计算机视觉问题,效果最好的模型仍然是卷积神经网络。卷积神经网络针对视觉问题的特点进行了网络结构优化,可以直接处理原始形式的图像数据,保留像素间的空间信息,因此更适合处理视觉问题。

卷积神经网络由多个卷积层和池化层组成,如图5所示。卷积层负责对输入进行扫描以生成更抽象的特征表示,池化层对这些特征表示进行过滤保留最关键的特征信息。

图5:在处理计算机视觉任务中大放异彩的卷积神经网络


说明:

本节只简单介绍用卷积神经网络实现手写数字识别任务,以及它带来的效果提升。读者可以将卷积神经网络先简单的理解成是一种比经典的全连接神经网络更强大的模型即可,更详细的原理和实现在接下来的《计算机视觉-卷积神经网络基础》中讲述。


卷积在这个问题的loss更低。

两层卷积和池化的神经网络实现如下所示。

注:本实现与 图5不同,如本实现输出长度为1。

In [11]

# 定义 SimpleNet 网络结构
import paddle
from paddle.nn import Conv2D, MaxPool2D, Linear
import paddle.nn.functional as F
# 多层卷积神经网络实现
class MNIST(paddle.nn.Layer):
     def __init__(self):
         super(MNIST, self).__init__()
         
         # 定义卷积层,输出特征通道out_channels设置为20,卷积核的大小kernel_size为5,卷积步长stride=1,padding=2
         self.conv1 = Conv2D(in_channels=1, out_channels=20, kernel_size=5, stride=1, padding=2)
         # 定义池化层,池化核的大小kernel_size为2,池化步长为2
         self.max_pool1 = MaxPool2D(kernel_size=2, stride=2)
         # 定义卷积层,输出特征通道out_channels设置为20,卷积核的大小kernel_size为5,卷积步长stride=1,padding=2
         self.conv2 = Conv2D(in_channels=20, out_channels=20, kernel_size=5, stride=1, padding=2)
         # 定义池化层,池化核的大小kernel_size为2,池化步长为2
         self.max_pool2 = MaxPool2D(kernel_size=2, stride=2)
         # 定义一层全连接层,输出维度是1
         self.fc = Linear(in_features=980, out_features=1)
         
    # 定义网络前向计算过程,卷积后紧接着使用池化层,最后使用全连接层计算最终输出
    # 卷积层激活函数使用Relu,全连接层不使用激活函数
     def forward(self, inputs):
         x = self.conv1(inputs)
         x = F.relu(x)
         x = self.max_pool1(x)
         x = self.conv2(x)
         x = F.relu(x)
         x = self.max_pool2(x)
         x = paddle.reshape(x, [x.shape[0], -1])
         x = self.fc(x)
         return x

打印网络结构:

In [12]

model = MNIST()
params_info = paddle.summary(model, (1, 1, 28, 28))
print(params_info)

---------------------------------------------------------------------------

Layer (type) Input Shape Output Shape Param #

===========================================================================

Conv2D-1 [[1, 1, 28, 28]] [1, 20, 28, 28] 520

MaxPool2D-1 [[1, 20, 28, 28]] [1, 20, 14, 14] 0

Conv2D-2 [[1, 20, 14, 14]] [1, 20, 14, 14] 10,020

MaxPool2D-2 [[1, 20, 14, 14]] [1, 20, 7, 7] 0

Linear-10 [[1, 980]] [1, 1] 981

===========================================================================

Total params: 11,521

Trainable params: 11,521

Non-trainable params: 0

---------------------------------------------------------------------------

Input size (MB): 0.00

Forward/backward pass size (MB): 0.19

Params size (MB): 0.04

Estimated Total Size (MB): 0.23

---------------------------------------------------------------------------

{'total_params': 11521, 'trainable_params': 11521}

使用MNIST数据集训练定义好的卷积神经网络,如下所示。

In [13]

#网络结构部分之后的代码,保持不变
def train(model):
    model.train()
    
    learning_rate = 0.001 
    # 使用SGD优化器,设置learning_rate
    opt = paddle.optimizer.SGD(learning_rate=learning_rate, parameters=model.parameters())
    # 训练5轮
    EPOCH_NUM = 10
    # MNIST图像高和宽
    IMG_ROWS, IMG_COLS = 28, 28
    loss_list = []
    for epoch_id in range(EPOCH_NUM):
        for batch_id, data in enumerate(train_loader()):
            #准备数据
            images, labels = data
            images = paddle.to_tensor(images)
            labels = paddle.to_tensor(labels, dtype="float32")
            
            #前向计算的过程
            predicts = model(images) # [batch_size, 1]
            #计算损失,取一个批次样本损失的平均值
            loss = F.square_error_cost(predicts, labels)
            avg_loss = paddle.mean(loss)
            #每训练200批次的数据,打印下当前Loss的情况
            if batch_id % 200 == 0:
                loss_list.append(avg_loss.numpy()[0])
                print("epoch: {}, batch: {}, loss is: {}".format(epoch_id, batch_id, avg_loss.numpy()))
            
            #后向传播,更新参数的过程
            avg_loss.backward()
            # 最小化loss,更新参数
            opt.step()
            # 清除梯度
            opt.clear_grad()
    #保存模型参数
    paddle.save(model.state_dict(), 'mnist.pdparams')
    return loss_list
model = MNIST()
loss_list_conv = train(model)

epoch: 0, batch: 0, loss is: [33.812702]

epoch: 0, batch: 200, loss is: [2.4981875]

epoch: 0, batch: 400, loss is: [2.748225]

epoch: 0, batch: 600, loss is: [2.2872992]

epoch: 0, batch: 800, loss is: [2.0726104]....

epoch: 9, batch: 0, loss is: [1.5385004]

epoch: 9, batch: 200, loss is: [1.3261033]

epoch: 9, batch: 400, loss is: [0.9551685]

epoch: 9, batch: 600, loss is: [1.0667143]

epoch: 9, batch: 800, loss is: [1.651695]

同时绘制两个网络结构训练时损失函数变化曲线:

In [14]

def plot_two_losses(loss_list_1, loss_list_2):
    plt.figure(figsize=(10,5))
    
    freqs = [i for i in range(len(loss_list_1))]
    # 绘制训练损失变化曲线
    plt.plot(freqs, loss_list_1, color='#e4007f', label="Train loss1")
    plt.plot(freqs, loss_list_2, color='#f19ec2', linestyle='--', label="Train loss2")
    
    # 绘制坐标轴和图例
    plt.ylabel("loss", fontsize='large')
    plt.xlabel("freq", fontsize='large')
    plt.legend(loc='upper right', fontsize='x-large')
    
    plt.show()
plot_two_losses(loss_list, loss_list_conv)

从损失函数变化趋势看,全连接神经网络和卷积神经网络收敛速度相当。目前我们的卷积神经网络做的是一个回归任务,接下来我们尝试将回归任务替换成分类任务,看看卷积神经网络效果如何。

相关文章
|
9月前
|
机器学习/深度学习 人工智能 运维
网管不再抓头发:深度学习教你提前发现网络事故
网管不再抓头发:深度学习教你提前发现网络事故
239 2
|
5月前
|
机器学习/深度学习 数据采集 人工智能
深度学习实战指南:从神经网络基础到模型优化的完整攻略
🌟 蒋星熠Jaxonic,AI探索者。深耕深度学习,从神经网络到Transformer,用代码践行智能革命。分享实战经验,助你构建CV、NLP模型,共赴二进制星辰大海。
|
8月前
|
机器学习/深度学习 人工智能 算法
Wi-Fi老是卡?不如试试让“深度学习”来当网络管家!
Wi-Fi老是卡?不如试试让“深度学习”来当网络管家!
373 68
|
机器学习/深度学习 人工智能 算法
基于Python深度学习的眼疾识别系统实现~人工智能+卷积网络算法
眼疾识别系统,本系统使用Python作为主要开发语言,基于TensorFlow搭建卷积神经网络算法,并收集了4种常见的眼疾图像数据集(白内障、糖尿病性视网膜病变、青光眼和正常眼睛) 再使用通过搭建的算法模型对数据集进行训练得到一个识别精度较高的模型,然后保存为为本地h5格式文件。最后使用Django框架搭建了一个Web网页平台可视化操作界面,实现用户上传一张眼疾图片识别其名称。
665 5
基于Python深度学习的眼疾识别系统实现~人工智能+卷积网络算法
|
机器学习/深度学习 编解码 TensorFlow
RT-DETR改进策略【模型轻量化】| 替换骨干网络为EfficientNet v1 高效的移动倒置瓶颈结构
RT-DETR改进策略【模型轻量化】| 替换骨干网络为EfficientNet v1 高效的移动倒置瓶颈结构
676 0
RT-DETR改进策略【模型轻量化】| 替换骨干网络为EfficientNet v1 高效的移动倒置瓶颈结构
|
机器学习/深度学习 数据可视化 算法
PyTorch生态系统中的连续深度学习:使用Torchdyn实现连续时间神经网络
神经常微分方程(Neural ODEs)是深度学习领域的创新模型,将神经网络的离散变换扩展为连续时间动力系统。本文基于Torchdyn库介绍Neural ODE的实现与训练方法,涵盖数据集构建、模型构建、基于PyTorch Lightning的训练及实验结果可视化等内容。Torchdyn支持多种数值求解算法和高级特性,适用于生成模型、时间序列分析等领域。
656 77
PyTorch生态系统中的连续深度学习:使用Torchdyn实现连续时间神经网络
|
7月前
|
机器学习/深度学习 算法 数据库
基于GoogleNet深度学习网络和GEI步态能量提取的步态识别算法matlab仿真,数据库采用CASIA库
本项目基于GoogleNet深度学习网络与GEI步态能量图提取技术,实现高精度步态识别。采用CASI库训练模型,结合Inception模块多尺度特征提取与GEI图像能量整合,提升识别稳定性与准确率,适用于智能安防、身份验证等领域。
|
机器学习/深度学习 自动驾驶 计算机视觉
RT-DETR改进策略【模型轻量化】| 替换骨干网络为 GhostNet V1 基于 Ghost Module 和 Ghost Bottlenecks的轻量化网络结构
RT-DETR改进策略【模型轻量化】| 替换骨干网络为 GhostNet V1 基于 Ghost Module 和 Ghost Bottlenecks的轻量化网络结构
603 61
RT-DETR改进策略【模型轻量化】| 替换骨干网络为 GhostNet V1 基于 Ghost Module 和 Ghost Bottlenecks的轻量化网络结构
|
11月前
|
机器学习/深度学习 人工智能 运维
AI“捕风捉影”:深度学习如何让网络事件检测更智能?
AI“捕风捉影”:深度学习如何让网络事件检测更智能?
456 8
|
机器学习/深度学习 数据采集 算法
基于MobileNet深度学习网络的MQAM调制类型识别matlab仿真
本项目基于Matlab2022a实现MQAM调制类型识别,使用MobileNet深度学习网络。完整程序运行效果无水印,核心代码含详细中文注释和操作视频。MQAM调制在无线通信中至关重要,MobileNet以其轻量化、高效性适合资源受限环境。通过数据预处理、网络训练与优化,确保高识别准确率并降低计算复杂度,为频谱监测、信号解调等提供支持。

热门文章

最新文章