深度学习实战——卷积神经网络/CNN实践(LeNetResnet)

Posted @李忆如

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深度学习实战——卷积神经网络/CNN实践(LeNetResnet)相关的知识,希望对你有一定的参考价值。

      忆如完整项目/代码详见github:https://github.com/yiru1225(转载标明出处 勿白嫖 star for projects thanks)

系列文章目录

本系列博客重点在深度学习相关实践(有问题欢迎在评论区讨论指出,或直接私信联系我)。

第一章  深度学习实战——不同方式的模型部署(CNN、Yolo)_如何部署cnn_@李忆如的博客

第二章  深度学习实战——卷积神经网络实践


目录

系列文章目录

一、实验综述

1.实验工具及内容

2.实验数据

3.实验目标

4.实验步骤

二、卷积神经网络综述

1.卷积神经网络概念与原理

2.卷积神经网络发展历程

三、LeNet原理、实现与优化

1.LeNet原理

1.1 简介

1.2 数据集介绍

1.3 网络发展

1.4 网络解析

2.LeNet实现

2.1 代码实现与解析

2.2 代码测试

3.LeNet优化

3.1 超参数

3.2 优化与消融实验

Ⅰ、lr/Batch_size

Ⅱ、epoch

Ⅲ、激活函数/池化方式

Ⅳ、网络超参数

Ⅴ、最优参数组合

四、高级架构介绍与选择实现

1.AlexNet

2.VGG

3.NiN

4.ResNet

5.ResNet的实践

五、图像显著性与可视化诊断

1.图像显著性

2.特征可视化

2.1 CAM

2.2 Grad-CAM

2.3 其他特征可视化

3.CAM可视化诊断实践

4.其他可视化诊断拓展

六、总结 

1.实验结论 

2. 参考资料


梗概

本篇博客主要介绍几种卷积神经网络的原理,并进行了代码实践与优化,另外,使用了CAM、图像显著性检测等方法进行了模型的可视化诊断。(内附代码与数据集)。


一、实验综述

本章主要对实验思路、环境、步骤进行综述,梳理整个实验报告架构与思路,方便定位。

1.实验工具及内容

本次实验主要使用Pycharm完成几种卷积神经网络的代码编写与优化,并通过不同参数的消融实验采集数据分析后进行性能对比。另外,分别尝试使用CAM与其他MIT工具包中的显著性预测算法对模型进行可视化诊断,并同时在一些在线平台上做了预测。

2.实验数据

本次实验大部分数据来自卷积神经网络模型官方数据集,部分测试数据来源于网络

3.实验目标

本次实验目标主要是深度剖析卷积神经网络的原理与模型定义,并了解不同参数的意义与对模型的贡献度(性能影响),通过实践完成不同模型、参数情况的性能对比与可视化诊断,指导真实项目开发中应用。

4.实验步骤

本次实验大致流程如表1所示:

表1 实验流程

1.实验思路综述

2.卷积神经网络综述

3.LetNet原理、实现与优化

4.高级架构介绍与选择实现

5.图像显著性与可视化诊断

二、卷积神经网络综述

本实验无论是实践LetNet、AlexNet还是其他高级架构,都属于卷积神经网络,故本章先对神经网络的概念与原理做一定综述,并简述其发展历程。

1.卷积神经网络概念与原理

卷积神经网络(Convolutional Neural Network,简称CNN)是一类前馈神经网络,是基于神经认知机和权重共享的卷积神经层(感受野衍生概念)被提出的,由于其具有局部区域连接、权值共享、降采样的结构特点,如今在图像处理领域有较好效果并并大量应用。

在此之前,一般使用全连接神经网络进行图像处理与其他人工智能任务,两种网络结构对比如图1所示,而全连接神经网络处理大图像图像时有以下三个明显的缺点:

  1. 首先将图像展开为向量会丢失空间信息;
  2. 其次参数过多效率低下,训练困难;
  3. 同时大量的参数也很快会导致网络过拟合。

图1 两种神经网络结构对比

如图1所示,CNN中的各层中的神经元是3维排列的:宽度、高度和深度(深度指激活数据体的第三个维度),而将CNN的结构拆解,在原理实现上一般由输入层、卷积层(一般包含激活函数层)、池化(Pooling)层和全连接层各层叠加构建,各层简介如图表1:

图表1 CNN各层简介

输入: 相关数据(集)的输入与读取

过程

  

  1. 卷积层与激活函数层(应用中统称卷积层)

主要就是使用卷积核进行特征提取和特征映射,由于卷积一般为线性运算,故需要使用激活函数(目前常用ReLU)增加非线性映射

  1. 池化层

    主要作用体现在降采样:保留显著特征、降低特征维度,增大kernel的感受野,同时提供一定的旋转不变性。

  1. 全连接层(输出层)

    经过若干次卷积+激励+池化后,将多个特征图进行通过softmax等函数全连接并输出(与全连接神经网络类似),对于过拟合等现象会引入dropout、正则化等操作,还可以进行局部归一化(LRN)、数据增强,交叉验证,提前终止训练等操作,来增加鲁棒性。

根据实验1中ML/DL任务综述我们知道人工智能任务中最重要的就是数据和模型,而对于CNN的模型训练,核心流程(与其他网络训练类似)如图2所示:

图2 CNN模型训练核心流程

Tips:CNN大类概念与核心原理解析到此结束,特定网络的解析在后文实践中详述。

2.卷积神经网络发展历程

本部分对CNN的发展历程做一个简述,经典的CNN网络发展可见图3:

图3 经典CNN的发展历程

Tips:本图仅统计2016及之前经典CNN网络。

根据图3,广义上最早的卷积神经网络是1998年的LeNet,但随着SVM等手工设计特征的出现沉寂。而因ReLU和dropout的提出,以及GPU和大数据带来的历史机遇,CNN在2012年迎来重大突破——提出AlexNet,至此,大量CNN网络结构被提出并广泛应用。而如今Transformer(Vision)在人工智能各领域的高速发展与优秀效果正使CNN面临危机。

三、LeNet原理、实现与优化

在上一章中我们对卷积神经网络进行了简单介绍,在本章正式进入CNN的实现与优化,主要将以最经典的CNN——LeNet为例探究不同实现/参数对效果的影响。

1.LeNet原理

1.1 简介

论文:Gradient-Based Learning Applied to Document Recognition

链接:Gradient-Based_Learning_Applied_to_Document_Recognition.pdf 

    根据论文、网络资料、个人理解,首先在本节对LeNet原理进行介绍与解析。

    背景:LeNet-5的设计主要是为了解决手写识别问题。那时传统的识别方案很多特征都是hand-crafted,识别的准确率很大程度上受制于所设计的特征,而且最大的问题在于手动设计特征对领域性先验知识的要求很高还耗时耗力,更别谈什么泛化能力,基本上只能针对特定领域。故本节同样以手写数字识别任务来介绍其原理。

1.2 数据集介绍

由于我们的原理介绍与后续实验都是基于手写数字识别任务,故在本部分对经典的数据集做一定介绍,以MNISTFashion-MNIST为例。

Ⅰ、MNIST:MNIST是一个著名的计算机视觉数据集,其包含各种手写数字图片(训练集中有60000个样本,测试集中有10000个样本,每个样本都是一张 28x28 像素的灰度手写数字图片),部分数据可视化如图4所示:

数据集下载:MNIST handwritten digit database,Ys 

图4 MNIST数据可视化(部分)

Ⅱ、Fashion-MNIST:Fashion-MNIST 是一个替代 MNIST 手写数字集的图像数据集,涵盖了来自 10 种类别的共 7 万个不同商品的正面图片,数据格式与MNIST完全一致,不需要改动任何网络代码,部分数据可视化如图5所示:

数据集下载:github-Fashion-MNIST.com

图5 Fashion-MNIST数据可视化(部分)

1.3 网络发展

一般我们常说的LeNet是LeNet-5,而LeNet是一类网络结构(含1、4、5),在此对其网络发展及其之前发展做一定回顾(以手写数字识别为例),如图表2所示:

图表2 手写数字识别发展(至LeNet-5)

非CNN:

非CNN的识别过程在第一章第一节有简述,大致经历了简单线性分类器(每个输入像素值构成每个输出单元的加权和)->单隐藏层网络->双(多)隐藏层网络三个过程。

LeNet

    LeNet发展大致经历了LeNet-1、LeNet-4、LeNet-5三个部分(下图由上至下),网络层数变化为5->6->7,据图分析处理部分(卷积、池化)并未发生大变动,主要是输入、输出部分的格式与处理的变化,网络解析在后文详述

  

1.4 网络解析

本部分我们对LeNet-5的网络架构进行深入解析,如图6所示,以便后续的实现与优化。

图6 LeNet-5网络架构

如图6所示,LeNet-5是一个7层卷积神经网络,包含C1、S2、C3、S4、C5/F5、F6、F7(C为卷积层,S为池化(pooling)层,F为全连接层),各层简介总结如图表3:

图表3 LeNet-5各层简介

输入: 相关数据(集)的输入与读取,尺寸统一归一化为32*32

过程: 

1、C1层:

    Tips:LeNet激活函数默认为Sigmoid

  1. 参数:num_kernels=6, kernel_size=5×5, padding=0, stride=1

补充:特征图大小计算如式1:

式1 特征图大小计算公式

对输入图像进行第一次卷积运算,得到6个C1特征图(6个大小为28*28的 feature maps, 32-5+1=28)。

2、S2层

Tips:LeNet默认使用平均池化

  1. 参数:kernel_size=2×2, padding=0, stride=2

第一次卷积之后紧接着就是池化运算,得到了6个14*14的特征图(28/2=14)。S2这个pooling层是对C1中的2*2区域内的像素求和乘以一个权值系数再加上一个偏置,然后将这个结果再做一次映射。

3、C3层

  1. 参数:num_kernels=16, kernel_size=5×5, padding=0, stride=1

然后进行第二次卷积,输出是16个10x10的特征图. 我们知道S2 有6个 14*14 的特征图,这里是通过对S2 的特征图特殊组合计算得到的16个特征图。

4、S4层

  1. 参数:kernel_size=2×2, padding=0, stride=2

     处理和连接过程与S2类似,得到16个5x5的特征图。

5、C5/F5层

  1. 参数:num_kernels=120,kernel_size=5×5, padding=0, stride=1,out_features=120

C5层是一个卷积层(实际上可以理解为全连接层)。由于S4层的16个图的大小为5x5,与卷积核的大小相同,所以卷积后形成的图的大小为1x1。形成120个卷积结果。

6、F6层

  1. 参数:out_features=84

    计算方法:计算输入向量和权重向量之间的点积,再加上一个偏置,结果通过sigmoid函数输出。

F6层有84个节点,对应于一个7x12的比特图,-1表示白色,1表示黑色,这样每个符号的比特图的黑白色就对应于一个ASCII编码。

7、F7层

  1. 参数:out_features=10

Output层(F7层)共有10个节点,分别代表数字(类别)0到9,且如果节点i的值为0,则网络识别的结果是数字i。采用的是径向基函数(RBF)的网络连接方式,即为其损失函数。假设x是上一层的输入,y是RBF的输出,RBF输出的计算方式如式2:

式2 RBF输出计算方式

补充:原论文中的损失函数采用MSE,并添加了一个惩罚项,计算公式如式3:

式3 MSE + 惩罚项损失函数

至此,LeNet-5的核心网络解析完成,根据图表3与网络结构我们将手写数字识别过程深化到各层,如图7所示:

图7 基于LeNet-5的手写数字识别过程拆解

2.LeNet实现

2.1 代码实现与解析

上一节对LeNet的背景、发展、网络进行了深入剖析,本节根据原理在代码层面进行实现,并检测在分类数据集上的效果。

    Tips:由于算法年代比较久远且意义重大,故LeNet代码在网上有各种不同实现,但未必是对论文的完全还原(大部分是基于后续的tricks进行了优化),在此贴出一类优秀、简洁的代码实现(去掉了最后的高斯激活,其余与论文保持一致),来自李沐老师:6.6.卷积神经网络(LeNet) — 动手学深度学习 2.0.0 documentation (d2l.ai)

在卷积神经网络的实现上,核心是网络的定义,根据LeNet-5的原理与各层参数,代码实现(Pytorch版)如下:

import torch
from torch import nn
from d2l import torch as d2l

net = nn.Sequential(
    nn.Conv2d(1, 6, kernel_size=5, padding=2), nn.Sigmoid(),
    nn.AvgPool2d(kernel_size=2, stride=2),
    nn.Conv2d(6, 16, kernel_size=5), nn.Sigmoid(),
    nn.AvgPool2d(kernel_size=2, stride=2),
    nn.Flatten(),
    nn.Linear(16 * 5 * 5, 120), nn.Sigmoid(),
    nn.Linear(120, 84), nn.Sigmoid(),
    nn.Linear(84, 10))

X = torch.rand(size=(1, 1, 28, 28), dtype=torch.float32)
for layer in net:
    X = layer(X)
    print(layer.__class__.__name__,'output shape: \\t',X.shape)

batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size=batch_size)

def evaluate_accuracy_gpu(net, data_iter, device=None): #@save
    """使用GPU计算模型在数据集上的精度"""
    if isinstance(net, nn.Module):
        net.eval()  # 设置为评估模式
        if not device:
            device = next(iter(net.parameters())).device
    # 正确预测的数量,总预测的数量
    metric = d2l.Accumulator(2)
    with torch.no_grad():
        for X, y in data_iter:
            if isinstance(X, list):
                # BERT微调所需的(之后将介绍)
                X = [x.to(device) for x in X]
            else:
                X = X.to(device)
            y = y.to(device)
            metric.add(d2l.accuracy(net(X), y), y.numel())
    return metric[0] / metric[1]

#@save
def train_ch6(net, train_iter, test_iter, num_epochs, lr, device):
    """用GPU训练模型(在第六章定义)"""
    def init_weights(m):
        if type(m) == nn.Linear or type(m) == nn.Conv2d:
            nn.init.xavier_uniform_(m.weight)
    net.apply(init_weights)
    print('training on', device)
    net.to(device)
    optimizer = torch.optim.SGD(net.parameters(), lr=lr)
    loss = nn.CrossEntropyLoss()
    animator = d2l.Animator(xlabel='epoch', xlim=[1, num_epochs],
                            legend=['train loss', 'train acc', 'test acc'])
    timer, num_batches = d2l.Timer(), len(train_iter)
    for epoch in range(num_epochs):
        # 训练损失之和,训练准确率之和,样本数
        metric = d2l.Accumulator(3)
        net.train()
        for i, (X, y) in enumerate(train_iter):
            timer.start()
            optimizer.zero_grad()
            X, y = X.to(device), y.to(device)
            y_hat = net(X)
            l = loss(y_hat, y)
            l.backward()
            optimizer.step()
            with torch.no_grad():
                metric.add(l * X.shape[0], d2l.accuracy(y_hat, y), X.shape[0])
            timer.stop()
            train_l = metric[0] / metric[2]
            train_acc = metric[1] / metric[2]
            if (i + 1) % (num_batches // 5) == 0 or i == num_batches - 1:
                animator.add(epoch + (i + 1) / num_batches,
                             (train_l, train_acc, None))
        test_acc = evaluate_accuracy_gpu(net, test_iter)
        animator.add(epoch + 1, (None, None, test_acc))
    print(f'loss train_l:.3f, train acc train_acc:.3f, '
          f'test acc test_acc:.3f')
    print(f'metric[2] * num_epochs / timer.sum():.1f examples/sec '
          f'on str(device)')

lr, num_epochs = 0.9, 10
train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
d2l.plt.show()

 分析:根据Code我们可以看到代码实现与1中LeNet的论文网络定义保持一致,其中Conv2D为卷积层,AvgPool2D为平均池化(pooling)层,Dense为全连接层。每层输入参数与图表3/论文均保持一致。

而LeNet用于手写数字识别或其他分类任务时与实验1提到的ML/DL一般流程保持一致,即网络定义->数据导入与处理->模型训练->模型评估

2.2 代码测试

在2.1根据原理编写好LeNet-5代码后,在本部分对其的原始分类效果进行测试,测试数据集为MNIST和Fashion-MNIST

数据集可选择自己下载导入或使用代码导入,如Code2,代码导入样例如图8:

Code2 MNIST和Fashion-MNIST代码导入

from d2l import xx(框架) as d2l

# Fashion-MNIST

train_data, test_data = d2l.load_data_fashion_mnist(batch_size)

# MNIST

 train_dataset = datasets.MNIST(

        root=r'./mnist', #此处为自己定义的下载存在目录

        train=True,

        download=True,

        transform=transform #自定义转换方式)

图8 代码导入数据集样例

在数据集准备好后,输入网络进行训练和评估即可,本实验中的训练样例与结果样例如图9、图10所示:

图9 LeNet训练过程样例

 图10 LeNet测试结果样例(Fashion-MNIST)

分析:根据图10,我们可以看到在Fashion-MNIST数据集上使用LeNet网络的train loss为0.469,train acc为0.821,test acc为0.809,同时验证了代码编写的正确性。

Tips:由于在MNIST数据集上acc达到98%+,故后续优化的效果比较不明显,后续优化对比测试均基于Fashion-MNIST。

3.LeNet优化

在上一节我们实现了与论文网络结构保持一致的LeNet,并通过两个数据集上的分类测试验证了代码的正确性,但在Fashion-MNIST的分类准确率只有80%左右(epoch=10,lr=0.9为例),仍有优化的空间,故本节以LeNet为例来探究深度模型优化的范式

3.1 超参数

    深度学习很重要的一步是“调参”,而这个参数,一般指模型的超参数。重要/常见参超数总结如表2所示:

表2 重要/常见超参数总结

1、损失函数:

损失可以衡量模型的预测值和真实值的不一致性,由一个非负实值函数损失函数定义

2、优化器:

为使损失最小,定义loss后可根据不同优化方式定义对应的优化器

3、epoch:

学习回合数,表示整个训练过程要遍历多少次训练集

4、学习率:

学习率描述了权重参数每次训练之后以多大的幅度(step)沿梯下降的方向移动

5、归一化:

    在训练神经神经网络中通常需要对原始数据进行归一化,以提高网络的性能

6、Batchsize:

每次计算损失loss使用的训练数据数量

7、网络超参数:

    包括输入图像的大小,各层的超参数(卷积核数、尺寸、步长,池化尺寸、步长、方法,激活函数等)

3.2 优化与消融实验

Tips:所有单次的消融实验均不做网络架构的改变,对比的样例均(Batch_size=256,lr=0.9,epoch=10),并不做叠加处理对比,便于数据分析与结论的提出。

在进入优化与消融实验前,我们首先根据表2定义与过往实践对LeNet中待优化的超参数进行解析,并对效果做一定预测。

  1. lr、Batch_size通过影响梯度影响效果(得确保在合适区间),同时影响模型收敛速度。
  2. 效果一般会随着Epoch的增大先增大后稳定。
  3. 网络超参数、损失函数、优化器等的不同选择一般对效果有较大影响,需要人为确定。

下面正式开始消融实验,尝试对经典LeNet进行优化。

Ⅰ、lr/Batch_size

第一Part我们首先分别对lr与Batch_size进行改变,并进行对比实验。

首先是lr,本实验从0-1.7,step=0.1,每个lr进行10次实验取平均值,部分数据汇总于表3(详见excel),lr对LeNet效果的影响如图11:

表3 lr对LeNet的效果影响数据汇总(部分)

lr

0.9

1

1.1

1.2

1.3

1.4

1.5

1.6

1.7

loss

0.469

0.459

0.444

0.425

0.411

0.423

0.438

0.446

0.461

tr_acc

0.821

0.827

0.835

0.843

0.847

0.842

0.835

0.822

0.81

te_acc

0.809

0.828

0.766

0.83

0.825

0.831

0.772

0.764

0.758

图11 lr对LeNet效果的影响

分析:根据lr的消融实验我们可以发现LeNet的acc随lr的增长不断上升(一定波动),最后波动下降,本实验中较好的lr为1.2-1.4,而非0.9,test_acc可从0.809上升至0.83

再泛化一点讲,学习率对模型影响是存在范式的,如图12所示(图源cs231n):

图12 lr对模型的一般影响

实际上在LeNet中写死lr(lr不变)的做法是不合适的,在速度和效果上均不是最优选择(受限于当时的技术发展),在此对学习率的选择做简单补充。

首先我们要知道LeNet中使用的(优化器)是经典SGD算法,目前可以使用Adagrad,Adam等为代表的自适应学习策略优化器,如Code3,同样可以针对SGD进行精细调优,一般可以得到更好的效果。

Code3 优化器学习率改进

#经典SGD

optimizer = torch.optim.SGD(net.parameters(), lr=lr)

#自适应学习优化器

torch.optim.Adagrad(params, lr=lr, lr_decay=0, weight_decay=0, initial_accumulator_value=0)

然后是Batch_size,本实验从2-1024,取值均为2的幂,每个Batch_size进行10次实验取平均值,数据汇总于表4,Batch_size对LeNet效果的影响如图13:

表4 Batch_size对LeNet的效果影响数据汇总

batch_size

2

4

8

16

32

64

128

256

512

1024

loss

0.54

0.316

0.27

0.269

0.306

0.329

0.376

0.469

0.599

0.916

train_acc

0.811

0.882

0.898

0.9

0.885

0.878

0.861

0.821

0.767

0.637

test_acc

0.82

0.862

0.887

0.888

0.861

0.869

0.856

0.809

0.737

0.618

图13 Batch_size对LeNet效果的影响

分析:根据Batch_size的笑容实验我们可以发现LeNet的acc随Batch_size的增长不断上升再下降(但Batch_size小的时候收敛速度大大降低),本实验中较好的Batch_size为8或16,而非256,test_acc可从0.809上升至0.888

而在现实情况中,一般不会单独考虑/改变lr或Batch_size,两者是有很强联系的。所以目前深度学习模型多采用批量随机梯度下降算法进行优化,原理如式4:

式4 批量随机梯度下降算法原理

n是批量大小(batch_size),η是学习率(lr)。可知道除了梯度本身,这两个因子直接决定了模型的权重更新,从优化本身来看它们是影响模型性能收敛最重要参数。

学习率直接影响模型的收敛状态,batchsize则影响模型的泛化性能,两者又是分子分母的直接关系,相互也可影响。

Ⅱ、epoch

第二Part我们对epoch进行改变,并进行对比实验。

Tips:由于d2l.plt.show()与animator.show()可以静态/动态输出LeNet效果与epoch的关系,故直接使用其图进行数据分析。

本实验epoch取值区间为从0-100(测点为10、50、100),epoch对LeNet效果的影响如图10、14、15:

 

图14 测点epoch为50时LeNet的变化趋势

 图15 测点epoch为100时LeNet的变化趋势

分析:根据epoch的消融实验我们可以发现LeNet的acc随epoch的增长先上升后稳定(模型收敛),本实验中较好的epoch为50左右,而非10,test_acc可从0.809上升至0.89

    Tips:epoch带来的模型优化是基于时间和算力资源的,真实情况下需做权衡。

Ⅲ、激活函数/池化方式

我们知道网络参数中卷积类型、池化方式、激活函数等选择都会影响模型效果,故第三Part我们对激活函数和池化方式进行改变,并进行对比实验。

根据图表3我们知道LeNet-5的激活函数为Sigmoid,池化方式为平均池化

首先我们改变激活函数为目前主流的Relu(当时并没有),代码如Code4,不同激活函数的定义对比如图16,激活函数改变后进行10次实验取平均数据,对LeNet效果影响如图17,数据汇总于表5:

Code4 激活函数改变

#sigmoid

nn.Conv2d(1, 6, kernel_size=5, padding=2), nn.Sigmoid()

#Relu

nn.Conv2d(1, 6, kernel_size=5, padding=2), nn.ReLU()

Tips:不要修改F6层的Sigmoid,尽量只对卷积层的激活函数操作。

图16 常用激活函数定义对比

表5 激活函数改变前后对LeNet的效果影响数据汇总

Sigmoid

Relu

loss

0.469

0.28

train_acc

0.821

0.894

test_acc

0.809

0.884

图17 激活函数改变对LeNet的影响

分析:根据图17与表5,我们发现卷积层的激活函数从Sigmoid改成Relu后LeNet的acc从0.809上升至0.884,验证了Relu的优化能力(更优激活函数)。

然后我们再将池化方式改为目前主流的最大池化(当时并没有),代码如Code5,定义如图18,池化方式改变后进行10次实验取平均数据,对LeNet效果影响如图19,数据汇总于表6:

图18 平均池化和最大池化的定义

Code5 池化方式改变

#平均池化

nn.AvgPool2d(kernel_size=2, stride=2)

#最大池化

nn.MaxPool2d(kernel_size=2, stride=2)

表6 池化方式改变前后对LeNet的效果影响数据汇总

AvgPool

MaxPool

loss

0.469

0.429

train_acc

0.821

0.841

test_acc

0.809

0.811

图19 池化方式改变对LeNet的影响

分析:根据图19与表6,我们发现池化方式从平均池化改成最大池化后LeNet的指标均相对稳定,故本实验两种池化方式均可,当然有时间的情况下可以继续测试混合池化、随机池化等方法,在此不做展开。

Ⅳ、网络超参数

    第四Part我们针对网络超参数中带数量的参数(卷积与池化层的kernel_size、stride等)进行更改与对比实验,以Kernel_size为例。

    首先是卷积层的Kernel_size,默认为5,本实验从图像适配性上出发(Kernel_size范围合适)探究Kernel_size的变化对LeNet的影响,选取Cov2D(C1层)的Kernel_size为3-5,每个Kernel_size进行10次实验取平均值,数据汇总于表7,Kernel_size对LeNet效果的影响如图20:

表7 Cov2D Kernel_size对LeNet的效果影响数据汇总

Cov Kernel_size

3

4

5

loss

0.486

0.47

0.469

train_acc

0.818

0.823

0.821

test_acc

0.792

0.782

0.809

图20 Cov2D Kernel_size对LeNet的效果影响

分析:根据Cov2D Kernel_size的消融实验我们可以发现LeNet的acc随Kernel_size的增长不断上升,故本实验中较好的Cov2D的Kernel_size为5

然后我们改变Pooling_size,从2->1,改变后进行10次实验取平均数据,对LeNet效果影响数据汇总于表8:

表8 Pooling_size改变前后对LeNet的效果影响数据汇总

 Pooling_size

1

2

loss

0.435

0.469

train_acc

0.84

0.821

test_acc

0.791

0.809

分析:根据表8,我们发现Pooling_size从2变为1后acc下降,loss增大,故本实验中较优Pooling_size仍为论文给出的2

Ⅴ、最优参数组合

    根据前四Part测试出的最优参数,进行组合检测优化效果,本实验的最优参数(只列举测试过变化的参数)总结于表9,利用表9的参数组合在Fashion-MNIST进行十次实验取数据平均值,与论文/实验默认参数进行效果对比,数据汇总于表10,效果对比如图21:

表9 LeNet在Fashion-MNIST上的最优参数组合(实验自测)

参数

最优选择

Lr

1.2-1.4

Batch_size

8或16

Epoch

50左右即可

激活函数

ReLU

池化方法

AvgPool或MaxPool

Kernel_size(Cov)

5

Pooling_size

2

Tips:不同模型/算法没有固定的最优参数,且多个最优参数的组合未必是最优参数组合,本组合仅做优化效果验证,真实项目中得进行更精细的调参。

表10 参数优化前后对LeNet的效果影响数据汇总

最优参数

默认参数

loss

0.175

0.469

train_acc

0.938

0.821

test_acc

0.906

0.809

图21 参数优化前后对LeNet的效果影响

分析:根据表10与图21,我们发现参数优化后LeNet在Fashion-MNIST的test_acc从0.809上升至0.906,验证了参数优化的效果。

四、高级架构介绍与选择实现

在上一章中我们深度解析了最经典CNN——LeNet的背景、网络架构,进行了在MNIST和Fashion-MNIST数据集上的测试,并通过不同超参数的消融实验介绍了深度模型中的超参数,并得到了一组较优参数组合,优化了模型效果。

但技术不断发展,太过古早的模型即使通过各种精细优化也很难与目前主流的卷积神经网络相提并论,故我们在本章根据图3选取LeNet之后的一些高级架构介绍并选取一种进行实验并进行性能对比。

1.AlexNet

参考论文:AlexNet.pdf    

LeNet的出现是爆炸性的,在MNIST数据集上的表现十分优秀,但很遗憾由于其在更大、更真实的数据集上的性能难以与当时的其他类模型(如SVM)比较,而在AlexNet出现后,CNN的时代才可谓正式来临。

  1. 背景:AlexNet以很大的优势赢得了2012年ImageNet图像识别挑战赛(训练集包含120万张图片,验证集包含5万张图片,测试集包含15万张图片,这些图片分为了1000个类别,并且有多种不同的分辨率)。

接下来我们来讲讲AlexNet的原理(网络设计),AlexNet是一个八层深度卷积神经网络,论文作者使用双GPU训练,网络架构与细节如图22与图23。但实际上AlexNet与LeNet设计是很相似的,主要区别如下:

  1. AlexNet比相对较小的LeNet5要深得多。AlexNet由八层组成:五个卷积层、两个全连接隐藏层和一个全连接输出层。
  2. AlexNet使用ReLU而不是sigmoid作为其激活函数。

图22 AlexNet网络架构(论文)

图23 AlexNet网络架构(细化) 

综述:AlexNet相较LeNet进行了网络的深化,且使用了数据增强、ReLU、局部相应归一化、Dropout、重叠池化、双GPU训练、端到端训练等方法提升模型效果与性能,在此不做展开。

2.VGG

参考论文:Very Deep Convolutional Networks for Large-Scale Image Recognition)

    虽然AlexNet证明深层神经网络卓有成效,但它没有提供一个通用的模板来指导后续的研究人员设计新的网络,直到2014年牛津大学团队提出了使用块的网络——VGG,其通过使用循环和子程序,可以很容易地在任何现代深度学习框架的代码中实现这些重复的架构

经典卷积神经网络的基本组成部分是下面的这个序列:

①带填充以保持分辨率的卷积层

②非线性激活函数,如ReLU

③池化(Pooling)层,如最大池化

而一个VGG块与之类似,由一系列卷积层组成,后面再加上用于空间下采样的最大汇聚层。在最初的VGG论文中 (Simonyan and Zisserman, 2014),作者使用了带有3x3卷积核、填充为1(保持高度和宽度)的卷积层,和带有2x2池化窗口、步幅为2(每个块后的分辨率减半)的最大汇聚层。

故VGG相比AlexNet的一个改进是采用连续的几个3x3的卷积核代替AlexNet中的较大卷积核(11x11,7x7,5x5),原理如图24所示。对于给定的感受野,采用堆积的小卷积核更优,因为多层非线性层可以增加网络深度以较小代价来保证学习更复杂的模式。

图24 卷积核替代原理

然后让我们回到VGG的网络设计,与AlexNet、LeNet一样,VGG网络可以分为两部分:第一部分主要由卷积层和汇聚层组成,第二部分由全连接层组成,只不过使用了块设计实现,详细架构如图25所示,对比架构如图26所示:

图25 VGG网络架构 

图26 VGG vs AlexNet

综述:VGG主要是提出了块的概念,方便了后人进行CNN的设计与实现,同时VGG使用可复用的卷积块构造网络,非常高效的同时网络定义非常简洁。

3.NiN

参考论文:Network In Network.pdf (endnote.com)

LeNet、AlexNet和VGG都有一个共同的设计模式:通过一系列的卷积层与汇聚层来提取空间结构特征;然后通过全连接层对特征的表征进行处理。然而,如果使用了全连接层,可能会完全放弃表征的空间结构。新国立团队提出的NiN提供了一个非常简单的解决方案。

NiN的两个主要创新如下:

Ⅰ、将简单线性卷积层替换为多层感知机:如图27,多层感知机为多层全连接层和非线性函数的组合,提供了网络层间映射的一种新可能,同时增加了网络卷积层的非线性能力。

图27 N

深度学习算法实践10---卷积神经网络(CNN)原理

其实从本篇博文开始,我们才算真正进入深度学习领域。在深度学习领域,已经经过验证的成熟算法,目前主要有深度卷积网络(DNN)和递归网络(RNN),在图像识别、视频识别、语音识别领域取得了巨大的成功,正是由于这些成功,能促成了当前深度学习的大热。与此相对应的,在深度学习研究领域,最热门的是AutoEncoder、RBM、DBN等产生式网络架构,但是这些研究领域,虽然论文比较多,但是重量级应用还没有出现,是否能取得成功还具有不确定性。但是有一些比较初步的迹象表明,这些研究领域还是非常值得期待的。比如AutoEncoder在图像、视频搜索领域的应用,RBM对非结构化数据的处理方面,DBN网络在结合人工智能领域两大流派连接主义和符号主义,都具有巨大的前景,有理由期待产生重量级成果。我们在后续会对这些网络逐一进行介绍和实现,除了给出重构后的Theano实现代码外,还会逐步补充这些算法在实际应用的中的实例,我们会主要将这些算法应用在创业公司数据中,从几万家创业公司及投融资数据中,希望能挖掘出哪些公司更可能获得投资,特定公司更有可能获得哪家投资机构的投资。
好了,转入正题。我们今天将要研究的是卷积网络(CNN),这是深度学习算法应用最成功的领域之一,主要用于图像和视频识别领域。

在讨论卷积网络(CNN)之前,我们先来根据我们的常识,来讨论一下怎样可以提高图像识别的准确率。我们以印刷字母识别为例,假设我们需要网络识别大写字母A,我们给网络的训练样本可能是这样的:


但是我们实际需要识别的图片却可能是这样的:


根据我们的经验,如果可以把字母移到视野的中心去,识别的难度将下降很多,有利于提高识别率。


在这种情况下,如果我们能把图像变为标准大小,则可以提高相应的识别率。

对于真识的物体,从不同角度来看,会有不同的表现,即使对于字母识别而言,字母也能出现旋转的情况:


如果可以将图像旋转过来,将可以极大的提高识别率。

以上各种方式,在实际的图像识别领域,通常是以组合形式出现的,即图像中的元素,需要经过一系列的平移、旋转、缩放后,才能得到与训练样本相似的标准图像,因此在传统的图像识别中,需要对图像进行预处理,达到这一目的。在神经网络进行图像识别中,我们也希望神经网络可以自动处理这些变换,用学术术语来讲,就是具有平移、旋转、缩放的不变性,卷积网络(CNN)就是为解决这一问题而提出一种架构。

那么怎样才能让神经网络具有我所希望的这种变换不变性呢?我们知道,神经网络的兴起,很大程度上是仿生学在人工智能领域的应用,我们用人工神经元模型及其连接,来模仿人类大脑,解决一些常规方法不能解决的复杂问题。对于图像识别而言,神经网络的研究人员,也希望通过模拟大脑视觉皮层的处理机制,来提高图像识别的准确率。

根据Hubel and Wiesel对猫的视觉皮层的研究表明,视觉皮层细胞会组成视觉接收域,只负责对一部分图像信号的处理,处理局部的空间信息,例如图像在的边缘识别等。同时视觉皮层中存在两类细胞,一类细胞是简单细胞,主要用于识别图像边缘等基本信息,还有一类复杂细胞,具有位置不变性,可以识别各种高级的图像信息。

以上述发现为指导,研究人员提出了卷积神经网络(CNN)模型,主要包括两大方面特性:第一是层间稀疏连接,第二是共享连接权值。

层间稀疏连接主要是想要模拟大脑视觉皮层的接收域,以具有简单细胞和复杂细胞两类不同细胞,分别处理局部细节和全局空间不变性。首先我们将图像像素分为3*3的区域,所以对于l=1的输入层而言,这9个像素连接到9个输入层神经元,而这9个神经元,只连接到l=2上的一个神经元,如下图所示:注意由于我们画的是二维图,因此只显示面对我们的三个神经元,如图所示:


如上图所示,每上一层,都只与其底层3*3的神经元相连接,这样对最上层神经元,其对应的视觉接收域将变为9*9。利用上述结构,可以采用多层来表示原来的图像信息,底层神经元主要负责边缘等基本信息的识别,而越往高层走,其识别的级别越高,最上层则可以表达为我们希望区分的类别。这其实与传统的数字图像处理中,金字塔模型比较类似,解决的是同一类问题。

卷积网络的第二个特征是共享连接权值,如图所示:


图中不同颜色的连接,具有相同的连接权值。因此,在我们的卷积神经网络(CNN)实现中,考虑到这种情况,需要对原来的算法进行修改,将以单个权值求导变为对三个权值之和进行求导,将在下一篇博文中详述。

通过共享权值模式,可以使卷积神经网络(CNN)识别出图像中的物体,而与物体的空间位置无关,即实现本文开头所提到的旋转、平移、缩放的不变性,这对于在图像识别领域经常出的物体在图像中的位置变化,大小变化,观察角度变化,所造成了识别困难,具有非常好的解决效果。同时,由于权值共享,减少了网络的参数个数,也大提高了网络的学习效率,因此成为卷积神经网络(CNN)的一个事实上的标准。

在讲完了卷积神经网络(CNN)的基本原理之后,自然就是怎么利用Theano这样的平台,来实现自己的卷积神经网络(CNN),对于这一问题,我们将在下一篇博文中进行讨论。


以上是关于深度学习实战——卷积神经网络/CNN实践(LeNetResnet)的主要内容,如果未能解决你的问题,请参考以下文章

深度学习算法实践12---卷积神经网络(CNN)实现

Keras深度学习实战——卷积神经网络详解与实现

Keras深度学习实战——使用卷积神经网络实现性别分类

深度学习时间序列预测:卷积神经网络(CNN)算法构建单变量时间序列预测模型预测空气质量(PM2.5)+代码实战

深度学习多变量时间序列预测:卷积神经网络(CNN)算法构建时间序列多变量模型预测交通流量+代码实战

深度学习与图神经网络核心技术实践应用高级研修班-Day1卷积神经网络(CNN详解)