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

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

相关文章
|
24天前
|
机器学习/深度学习 自然语言处理 搜索推荐
深度学习的魔法:如何用神经网络解决复杂问题
在这篇文章中,我们将探讨深度学习的基本原理和它在各种领域中的应用。通过一些实际的例子,我们将看到深度学习如何帮助我们解决复杂的问题,如图像识别、自然语言处理和推荐系统等。我们还将讨论一些最新的研究成果和技术趋势,以及深度学习在未来可能面临的挑战和机遇。
|
7天前
|
机器学习/深度学习 人工智能 自然语言处理
深度学习中的卷积神经网络:从理论到实践
【10月更文挑战第35天】在人工智能的浪潮中,深度学习技术以其强大的数据处理能力成为科技界的宠儿。其中,卷积神经网络(CNN)作为深度学习的一个重要分支,在图像识别和视频分析等领域展现出了惊人的潜力。本文将深入浅出地介绍CNN的工作原理,并结合实际代码示例,带领读者从零开始构建一个简单的CNN模型,探索其在图像分类任务中的应用。通过本文,读者不仅能够理解CNN背后的数学原理,还能学会如何利用现代深度学习框架实现自己的CNN模型。
|
6天前
|
机器学习/深度学习 人工智能 算法框架/工具
深度学习中的卷积神经网络(CNN)及其在图像识别中的应用
【10月更文挑战第36天】探索卷积神经网络(CNN)的神秘面纱,揭示其在图像识别领域的威力。本文将带你了解CNN的核心概念,并通过实际代码示例,展示如何构建和训练一个简单的CNN模型。无论你是深度学习的初学者还是希望深化理解,这篇文章都将为你提供有价值的见解。
|
4天前
|
机器学习/深度学习 人工智能 算法
基于Python深度学习的【垃圾识别系统】实现~TensorFlow+人工智能+算法网络
垃圾识别分类系统。本系统采用Python作为主要编程语言,通过收集了5种常见的垃圾数据集('塑料', '玻璃', '纸张', '纸板', '金属'),然后基于TensorFlow搭建卷积神经网络算法模型,通过对图像数据集进行多轮迭代训练,最后得到一个识别精度较高的模型文件。然后使用Django搭建Web网页端可视化操作界面,实现用户在网页端上传一张垃圾图片识别其名称。
21 0
基于Python深度学习的【垃圾识别系统】实现~TensorFlow+人工智能+算法网络
|
19天前
|
机器学习/深度学习 搜索推荐 安全
深度学习之社交网络中的社区检测
在社交网络分析中,社区检测是一项核心任务,旨在将网络中的节点(用户)划分为具有高内部连接密度且相对独立的子群。基于深度学习的社区检测方法,通过捕获复杂的网络结构信息和节点特征,在传统方法基础上实现了更准确、更具鲁棒性的社区划分。
33 7
|
20天前
|
机器学习/深度学习 自然语言处理 TensorFlow
深度学习的奥秘:探索神经网络背后的魔法
【10月更文挑战第22天】本文将带你走进深度学习的世界,揭示神经网络背后的神秘面纱。我们将一起探讨深度学习的基本原理,以及如何通过编程实现一个简单的神经网络。无论你是初学者还是有一定基础的学习者,这篇文章都将为你提供有价值的信息和启示。让我们一起踏上这段奇妙的旅程吧!
|
21天前
|
机器学习/深度学习 人工智能 自动驾驶
深度学习中的卷积神经网络(CNN)及其应用
【10月更文挑战第21天】本文旨在深入探讨深度学习领域的核心组成部分——卷积神经网络(CNN)。通过分析CNN的基本结构、工作原理以及在图像识别、语音处理等领域的广泛应用,我们不仅能够理解其背后的技术原理,还能把握其在现实世界问题解决中的强大能力。文章将用浅显的语言和生动的例子带领读者一步步走进CNN的世界,揭示这一技术如何改变我们的生活和工作方式。
|
22天前
|
机器学习/深度学习 计算机视觉 网络架构
【YOLO11改进 - C3k2融合】C3k2融合YOLO-MS的MSBlock : 分层特征融合策略,轻量化网络结构
【YOLO11改进 - C3k2融合】C3k2融合YOLO-MS的MSBlock : 分层特征融合策略,轻量化网络结构
|
28天前
|
机器学习/深度学习 人工智能 监控
深入理解深度学习中的卷积神经网络(CNN):从原理到实践
【10月更文挑战第14天】深入理解深度学习中的卷积神经网络(CNN):从原理到实践
81 1
|
7天前
|
机器学习/深度学习 人工智能 自动驾驶
深入解析深度学习中的卷积神经网络(CNN)
深入解析深度学习中的卷积神经网络(CNN)
21 0