《零基础实践深度学习》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)

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

相关文章
|
3天前
|
机器学习/深度学习 人工智能 自然语言处理
深度学习的奥秘:探索神经网络的核心原理
本文将深入浅出地介绍深度学习的基本概念,包括神经网络的结构、工作原理以及训练过程。我们将从最初的感知机模型出发,逐步深入到现代复杂的深度网络架构,并探讨如何通过反向传播算法优化网络权重。文章旨在为初学者提供一个清晰的深度学习入门指南,同时为有经验的研究者回顾和巩固基础知识。
25 11
|
4天前
|
机器学习/深度学习 人工智能 自然语言处理
深度学习的魔法:如何用神经网络解锁数据的奥秘
在人工智能的璀璨星空中,深度学习犹如一颗最亮的星,它以其强大的数据处理能力,改变了我们对世界的认知方式。本文将深入浅出地介绍深度学习的核心概念、工作原理及其在不同领域的应用实例,让读者能够理解并欣赏到深度学习技术背后的奇妙和强大之处。
15 3
|
5天前
|
机器学习/深度学习 人工智能 算法
深度学习的奥秘:探索神经网络的魔法
在本文中,我们将一起踏上一场奇妙的旅程,探索深度学习背后的科学奥秘。通过简单易懂的语言和有趣的比喻,我们将解锁神经网络的强大力量,并了解它们如何改变我们的世界。无论你是科技爱好者还是对人工智能充满好奇的朋友,这篇文章都将为你打开一扇通往未来的大门。
|
3天前
|
机器学习/深度学习 数据采集 数据可视化
深度学习实践:构建并训练卷积神经网络(CNN)对CIFAR-10数据集进行分类
本文详细介绍如何使用PyTorch构建并训练卷积神经网络(CNN)对CIFAR-10数据集进行图像分类。从数据预处理、模型定义到训练过程及结果可视化,文章全面展示了深度学习项目的全流程。通过实际操作,读者可以深入了解CNN在图像分类任务中的应用,并掌握PyTorch的基本使用方法。希望本文为您的深度学习项目提供有价值的参考与启示。
|
3天前
|
编解码 人工智能 文件存储
卷积神经网络架构:EfficientNet结构的特点
EfficientNet是一种高效的卷积神经网络架构,它通过系统化的方法来提升模型的性能和效率。
9 1
|
11天前
|
机器学习/深度学习 算法 TensorFlow
深入探索强化学习与深度学习的融合:使用TensorFlow框架实现深度Q网络算法及高效调试技巧
【8月更文挑战第31天】强化学习是机器学习的重要分支,尤其在深度学习的推动下,能够解决更为复杂的问题。深度Q网络(DQN)结合了深度学习与强化学习的优势,通过神经网络逼近动作价值函数,在多种任务中表现出色。本文探讨了使用TensorFlow实现DQN算法的方法及其调试技巧。DQN通过神经网络学习不同状态下采取动作的预期回报Q(s,a),处理高维状态空间。
25 1
|
11天前
|
机器学习/深度学习 数据采集
深度学习的魔法:用神经网络识别手写数字
【8月更文挑战第31天】在这个数字时代,手写数字的识别技术显得尤为重要。本文将通过构建一个简单的神经网络模型来演示如何使用深度学习进行手写数字的识别。我们将一步步引导你了解数据预处理、模型搭建、训练和测试的过程,让你体会到深度学习的魅力。无论你是初学者还是有一定基础的学习者,这篇文章都将为你打开深度学习的大门。
|
3天前
|
机器学习/深度学习 人工智能 监控
深度学习浪潮中的轻舟:探索卷积神经网络的奥秘
在这个数据泛滥的时代,深度学习如同一艘巨轮,在知识的海洋中破浪前行。然而,在这艘巨轮上,有一个小小的角落常常被人忽视—那就是卷积神经网络(CNN)。本文将带领读者一探究竟,从CNN的核心概念到其在实际中的应用,我们将用通俗易懂的语言,揭开这一技术神秘面纱,让每一位对深度学习感兴趣的朋友都能轻松理解并应用CNN。
10 0
|
9天前
|
机器学习/深度学习 算法 开发者
深度学习的魔法:用神经网络识别手写数字
【9月更文挑战第2天】在这篇技术文章中,我们将一起探索深度学习的奥秘,并尝试使用神经网络来识别手写数字。通过简单的代码示例,我们将了解如何构建和训练一个深度学习模型,以及如何使用它来进行手写数字的识别。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的见解和实践技巧。
|
10天前
|
机器学习/深度学习 人工智能 自然语言处理
深度学习中的艺术:探索神经网络的奥秘
【9月更文挑战第2天】 在人工智能的宏伟画卷中,深度学习以其独特的魅力和强大的能力占据了中心舞台。本文将深入浅出地探讨深度学习的核心——神经网络,揭示其如何模拟人脑处理信息的方式,以及它在图像识别、自然语言处理等领域的应用。我们将从基础概念出发,逐步深入到网络结构的设计思想,最后探讨深度学习面临的挑战与未来发展方向。通过本文,读者将获得对深度学习基本原理的理解,并激发进一步探索这一领域的好奇心。