深度学习(GoogLeNet)

2022-09-21 11:02:45 浏览数 (1)

大家好,又见面了,我是你们的朋友全栈君

本篇文章采用百度paddlepaddle深度学习框架,并在百度Ai Studio平台上运行。

本篇文章基于PaddlePaddle2.0-构建卷积网络GoogLeNet,不了解2.0API的读者可以参考深度学习(paddle2.0 API)_无意2121的博客-CSDN博客

目录

1 GoogLeNet的介绍

1.1 GoogLeNet的简介

1.2 GoogLeNet 原始论文

1.2.1 论文标题的来源

1.2.2 GoogLeNet 提出的原因

2 GoogLeNet 创新点

2.1 inception(原生模块)的结构

​2.2 inception(原生模块)的优势

2.3 inception(具有降维功能)的结构

2.4 1*1卷积核的作用

2.5 GAP

2.6 两个辅助分类器

3 GoogLeNet的定义

3.1 GoogLeNet的总体架构

3.2 GoogLeNet的变种

3.3 GoogLeNet的完整代码定义

4 构建GoogLeNet做CIFAR图像识别


1 GoogLeNet的介绍

1.1 GoogLeNet的简介

GoogLeNet模型是由谷歌(Google)团队开发出来的卷积神经网络,它是2014年ImageNet挑战赛的冠军模型。相比于AlexNet模型,GoogLeNet模型的网络结构更深,共包括87层。尽管模型结构变得更复杂,但参数量更少了。GoogLeNet模型的参数量为5941552个,仅为AlexNet模型参数量的1/10。这主要归功于GoogLeNet创新性地采用了Inception模块。感兴趣的读者可以阅读原始顶会顶刊http://arxiv.org/abs/1409.4842。

1.2 GoogLeNet 原始论文

1.2.1 论文标题的来源

有趣的是原始论文的标题《Going deeper with convolutions》来自这个表情包

1.2.2 GoogLeNet 提出的原因

从 LeNet 开始,卷积神经网络通常有一个标准范式(结构) – 堆叠卷积层(后跟归一化和最大池化),然后跟一个或多个全连接层,这种模式及其优化后的模式确实在图像分类中取得较好的结果。

但为了获得更好的神经网络性能,这时候我们就需要扩大网络规模(增加网络层数及其宽度),但是更大的规模意味着更多的参数,容易出现过拟合,尤其是在训练集中标记示例的数量有限的情况下。增加网络规模的另一个缺点是计算资源的使用急剧增加

为了解决这两个问题,他们将原本密集连接的神经网络转向稀疏连接的神经网络。Aroar基于模仿生物系统的神经网络模型研究结论提出,一个基于稀疏结构的深度、宽度神经网络经过数据信息的输入后,末端神经节点中反映该数据信息特征的节点将总是处于激活状态。假如要识别一只猫,有些神经元是识别猫眼睛,有些是识别猫鼻子、猫尾巴等,如果图像真的是一只猫,这些特征会一起激活。虽然严格的数学证明需要很强的条件,但事实上这个陈述与赫布理论类似。这表明稀疏连接的神经网络具有一定科学性,但是,计算机软硬件对非均匀稀疏数据的计算效率很差

现在的问题是有没有一种方法,既能保持网络结构的稀疏性,又能利用密集矩阵的高计算性能。大量的文献表明可以将稀疏矩阵聚类为较为密集的子矩阵来提高计算性能,据此论文提出了名为inception的结构来实现此目的。

2 GoogLeNet 创新点

2.1 inception(原生模块)的结构

通过赫布理论与多尺度处理,论文提出了一种多通路 (inception) 的设计方案,即在网络同一层将不同尺寸的卷积核并联放置

(1) 将不同尺寸的卷积核放在同一层,意味着有不同大小的感受野,可以提取到不同特征,最后拼接特征,相当于对不同特征进行融合。

(2)之所以卷积核大小采用1、3、5,主要是为了输出维度一致。设定卷积核移动步长stride=1之后,只要分别设定padding=0、1、2,那么卷积之后便可以得到相同尺寸的特征,然后这些特征就可以直接聚合在一起了。

(3)论文表明pooling在实验中是有效的,所以Inception里面也有。 (4)网络越到后面,特征越抽象,而且每个特征所涉及的感受野也更大了,因此随着层数的增加,3×3和5×5卷积的比例也要增加。

2.2 inception(原生模块)的优势

多个尺寸上进行卷积再聚合的优势

  • 直观感觉上在多个尺度上同时进行卷积,能提取到不同尺度的特征。特征更为丰富也意味着最后分类判断时更加准确。
  • 利用稀疏矩阵分解成密集矩阵计算的原理可以加快收敛速度。用稀疏的分散的网络,对它们进行汇总,就可以取代一些庞大臃肿的网络。稀疏矩阵的分解示意图如下

这个原理在inception中就是在特征维度上进行分解

传统的卷积层的输入数据只与一种尺度(例如3*3)的卷积核进行卷积,输出固定维度(例如192个特征)的数据。而这192个特征是平均分布在3*3的尺度范围上的,这可以理解为输出了一个稀疏均匀分布的特征集。而在inception模块中在多个尺寸的卷积核下提取特征,输出的192个特征不是均匀分布的,而是相关性强的特征聚合在一起。比如3*3的96个特征聚合在一起,5*5的96个特征聚合在一起,这也可以理解为多个密集分布的子集合。由于相关性强的特征聚合在一起,同时相关性弱的特征就被弱化,因此冗余的数据比较少,也就是数据的有效性非常强,这能够加快收敛速度。

换句话说,即神经元同时激活的情况下,将联合评判做出结论!意味着神经元传递信息可能不是简单的复制逐层传递,而是并行的同时传递,这也反映着inception更逼近真实生物系统的神经网络。

2.3 inception(具有降维功能)的结构

简单的多通路拼接会造成通道数迅速增长,导致计算量和复杂度增高。在3*3与5*5的卷积核之前,先经过1*1的卷积核,这样的设置能够把通道数减少。在3*3的池化层之后,用1*1卷积核降维。

因此,我们可以看到具有降维功能的inception的最大创新之处是加入了1*1的卷积核。最终GooLeNet 用的inception 是改进后的具有降维功能的inception。代码如下

代码语言:javascript复制
class Inception(paddle.nn.Layer):
    def __init__(self, in_channels, c1, c2, c3, c4):
        '''
        c1:第一条支路1x1卷积的输出通道数,数据类型是整数
        c2:第二条支路卷积的输出通道数,数据类型是tuple或list,  
            其中c2[0]是1x1卷积的输出通道数,c2[1]是3x3
        c3: 第三条支路卷积的输出通道数,数据类型是tuple或list,  
            其中c3[0]是1x1卷积的输出通道数,c3[1]是3x3
        c4:第四条支路1x1卷积的输出通道数,数据类型是整数
        '''
        super(Inception, self).__init__()
        #路线1,卷积核1x1
        self.route1x1_1 = paddle.nn.Conv2D(in_channels, c1, kernel_size=1)
        #路线2,卷积层1x1、卷积层3x3
        self.route1x1_2 = paddle.nn.Conv2D(in_channels, c2[0], kernel_size=1)
        self.route3x3_2 = paddle.nn.Conv2D(c2[0], c2[1], kernel_size=3, padding=1)
        #路线3,卷积层1x1、卷积层5x5
        self.route1x1_3 = paddle.nn.Conv2D(in_channels, c3[0], kernel_size=1)
        self.route5x5_3 = paddle.nn.Conv2D(c3[0], c3[1], kernel_size=5, padding=2)
        #路线4,池化层3x3、卷积层1x1
        self.route3x3_4 = paddle.nn.MaxPool2D(kernel_size=3, stride=1, padding=1)
        self.route1x1_4 = paddle.nn.Conv2D(in_channels, c4, kernel_size=1)

    def forward(self, x):
        route1 = F.relu(self.route1x1_1(x))
        route2 = F.relu(self.route3x3_2(F.relu(self.route1x1_2(x))))
        route3 = F.relu(self.route5x5_3(F.relu(self.route1x1_3(x))))
        route4 = F.relu(self.route1x1_4(self.route3x3_4(x)))
        out = [route1, route2, route3, route4]
        return paddle.concat(out, axis=1)  #在通道维度(axis=1)上进行连接

def BasicConv2d(in_channels, out_channels, kernel, stride=1, padding=0):
    layer = paddle.nn.Sequential(
                paddle.nn.Conv2D(in_channels, out_channels, kernel, stride, padding), 
                paddle.nn.BatchNorm2D(out_channels, epsilon=1e-3),
                paddle.nn.ReLU())
    return layer

2.4 1*1卷积核的作用

(1)1*1的卷积核相当于对所有特征进行一次相同权重的全连接的计算

(2)减少通道数,而不改变输出的宽度与高度,起到降维的作用,减少计算复杂度

(3)只要最后输出的特征数不变,中间的降维类似于压缩的效果,并不影响最终训练的结果

第一种没有1*1卷积核的参数量=256*3*3*256=589824

第二种有1*1卷积核的参数量=256*1*1*32 32*3*3*256=81920

可见大大减少参数量

2.5 GAP

无论是 AlexNet 还是 VGG,最后的卷积层和全连接层相连,参数量是巨大的(占网络参数量的 70-90% 左右)。GoogLeNet 采用了全局平均池化 Global Average Pooling 代替全连接

2.6 两个辅助分类器

通过添加连接到这些中间层的辅助分类器,作用是尽快学到可分类的特征,相当于是起到了一个正则化的作用,防止梯度消失。这些分类器采用较小卷积网络的形式,放置在Inception (原生模块) 和 Inception (具有降维功能)模块的输出之上。

3 GoogLeNet的定义

3.1 GoogLeNet的总体架构

拥有上述9个inception

局部响应归一化(Local Response Normalization,LRN)技术是首次在 AlexNet 模型中提出这个概念

3.2 GoogLeNet的变种

本篇文章主要讲解inception v1,改进的版本这里不再赘述

3.3 GoogLeNet的完整代码定义

代码语言:javascript复制
#构建模型
class Inception(paddle.nn.Layer):
    def __init__(self, in_channels, c1, c2, c3, c4):
        '''
        c1:第一条支路1x1卷积的输出通道数,数据类型是整数
        c2:第二条支路卷积的输出通道数,数据类型是tuple或list,  
            其中c2[0]是1x1卷积的输出通道数,c2[1]是3x3
        c3: 第三条支路卷积的输出通道数,数据类型是tuple或list,  
            其中c3[0]是1x1卷积的输出通道数,c3[1]是3x3
        c4:第四条支路1x1卷积的输出通道数,数据类型是整数
        '''
        super(Inception, self).__init__()
        #路线1,卷积核1x1
        self.route1x1_1 = paddle.nn.Conv2D(in_channels, c1, kernel_size=1)
        #路线2,卷积层1x1、卷积层3x3
        self.route1x1_2 = paddle.nn.Conv2D(in_channels, c2[0], kernel_size=1)
        self.route3x3_2 = paddle.nn.Conv2D(c2[0], c2[1], kernel_size=3, padding=1)
        #路线3,卷积层1x1、卷积层5x5
        self.route1x1_3 = paddle.nn.Conv2D(in_channels, c3[0], kernel_size=1)
        self.route5x5_3 = paddle.nn.Conv2D(c3[0], c3[1], kernel_size=5, padding=2)
        #路线4,池化层3x3、卷积层1x1
        self.route3x3_4 = paddle.nn.MaxPool2D(kernel_size=3, stride=1, padding=1)
        self.route1x1_4 = paddle.nn.Conv2D(in_channels, c4, kernel_size=1)

    def forward(self, x):
        route1 = F.relu(self.route1x1_1(x))
        route2 = F.relu(self.route3x3_2(F.relu(self.route1x1_2(x))))
        route3 = F.relu(self.route5x5_3(F.relu(self.route1x1_3(x))))
        route4 = F.relu(self.route1x1_4(self.route3x3_4(x)))
        out = [route1, route2, route3, route4]
        return paddle.concat(out, axis=1)  #在通道维度(axis=1)上进行连接

def BasicConv2d(in_channels, out_channels, kernel, stride=1, padding=0):
    layer = paddle.nn.Sequential(
                paddle.nn.Conv2D(in_channels, out_channels, kernel, stride, padding), 
                paddle.nn.BatchNorm2D(out_channels, epsilon=1e-3),
                paddle.nn.ReLU())
    return layer

class GoogLeNet(paddle.nn.Layer):
    def __init__(self, in_channel, num_classes):
        super(GoogLeNet, self).__init__()
        self.b1 = paddle.nn.Sequential(
                    BasicConv2d(in_channel, out_channels=64, kernel=7, stride=2, padding=3),
                    paddle.nn.MaxPool2D(3, 2))
        self.b2 = paddle.nn.Sequential(
                    BasicConv2d(64, 64, kernel=1),
                    BasicConv2d(64, 192, kernel=3, padding=1),
                    paddle.nn.MaxPool2D(3, 2))
        self.b3 = paddle.nn.Sequential(
                    Inception(192, 64, (96, 128), (16, 32), 32),
                    Inception(256, 128, (128, 192), (32, 96), 64),
                    paddle.nn.MaxPool2D(3, 2))
        self.b4 = paddle.nn.Sequential(
                    Inception(480, 192, (96, 208), (16, 48), 64),
                    Inception(512, 160, (112, 224), (24, 64), 64),
                    Inception(512, 128, (128, 256), (24, 64), 64),
                    Inception(512, 112, (144, 288), (32, 64), 64),
                    Inception(528, 256, (160, 320), (32, 128), 128),
                    paddle.nn.MaxPool2D(3, 2))
        self.b5 = paddle.nn.Sequential(
                    Inception(832, 256, (160, 320), (32, 128), 128),
                    Inception(832, 384, (182, 384), (48, 128), 128),
                    paddle.nn.AvgPool2D(2))
        self.flatten=paddle.nn.Flatten()
        self.b6 = paddle.nn.Linear(1024, num_classes)
        
    def forward(self, x):
        x = self.b1(x)
        x = self.b2(x)
        x = self.b3(x)
        x = self.b4(x)
        x = self.b5(x)
        x = self.flatten(x)
        x = self.b6(x)
        return x

4 构建GoogLeNet做CIFAR图像识别

自行调整超参数

代码语言:javascript复制
import paddle
import paddle.nn.functional as F
import numpy as np
from paddle.vision.transforms import Compose, Resize, Transpose, Normalize

t = Compose([Resize(size=96),Normalize(mean=[127.5, 127.5, 127.5], std=[127.5, 127.5, 127.5], data_format='HWC'),Transpose()]) #数据转换

cifar10_train = paddle.vision.datasets.cifar.Cifar10(mode='train', transform=t, backend='cv2')
cifar10_test = paddle.vision.datasets.cifar.Cifar10(mode="test", transform=t, backend='cv2')

#构建模型
class Inception(paddle.nn.Layer):
    def __init__(self, in_channels, c1, c2, c3, c4):
        '''
        c1:第一条支路1x1卷积的输出通道数,数据类型是整数
        c2:第二条支路卷积的输出通道数,数据类型是tuple或list,  
            其中c2[0]是1x1卷积的输出通道数,c2[1]是3x3
        c3: 第三条支路卷积的输出通道数,数据类型是tuple或list,  
            其中c3[0]是1x1卷积的输出通道数,c3[1]是3x3
        c4:第四条支路1x1卷积的输出通道数,数据类型是整数
        '''
        super(Inception, self).__init__()
        #路线1,卷积核1x1
        self.route1x1_1 = paddle.nn.Conv2D(in_channels, c1, kernel_size=1)
        #路线2,卷积层1x1、卷积层3x3
        self.route1x1_2 = paddle.nn.Conv2D(in_channels, c2[0], kernel_size=1)
        self.route3x3_2 = paddle.nn.Conv2D(c2[0], c2[1], kernel_size=3, padding=1)
        #路线3,卷积层1x1、卷积层5x5
        self.route1x1_3 = paddle.nn.Conv2D(in_channels, c3[0], kernel_size=1)
        self.route5x5_3 = paddle.nn.Conv2D(c3[0], c3[1], kernel_size=5, padding=2)
        #路线4,池化层3x3、卷积层1x1
        self.route3x3_4 = paddle.nn.MaxPool2D(kernel_size=3, stride=1, padding=1)
        self.route1x1_4 = paddle.nn.Conv2D(in_channels, c4, kernel_size=1)

    def forward(self, x):
        route1 = F.relu(self.route1x1_1(x))
        route2 = F.relu(self.route3x3_2(F.relu(self.route1x1_2(x))))
        route3 = F.relu(self.route5x5_3(F.relu(self.route1x1_3(x))))
        route4 = F.relu(self.route1x1_4(self.route3x3_4(x)))
        out = [route1, route2, route3, route4]
        return paddle.concat(out, axis=1)  #在通道维度(axis=1)上进行连接

def BasicConv2d(in_channels, out_channels, kernel, stride=1, padding=0):
    layer = paddle.nn.Sequential(
                paddle.nn.Conv2D(in_channels, out_channels, kernel, stride, padding), 
                paddle.nn.BatchNorm2D(out_channels, epsilon=1e-3),
                paddle.nn.ReLU())
    return layer

class GoogLeNet(paddle.nn.Layer):
    def __init__(self, in_channel, num_classes):
        super(GoogLeNet, self).__init__()
        self.b1 = paddle.nn.Sequential(
                    BasicConv2d(in_channel, out_channels=64, kernel=7, stride=2, padding=3),
                    paddle.nn.MaxPool2D(3, 2))
        self.b2 = paddle.nn.Sequential(
                    BasicConv2d(64, 64, kernel=1),
                    BasicConv2d(64, 192, kernel=3, padding=1),
                    paddle.nn.MaxPool2D(3, 2))
        self.b3 = paddle.nn.Sequential(
                    Inception(192, 64, (96, 128), (16, 32), 32),
                    Inception(256, 128, (128, 192), (32, 96), 64),
                    paddle.nn.MaxPool2D(3, 2))
        self.b4 = paddle.nn.Sequential(
                    Inception(480, 192, (96, 208), (16, 48), 64),
                    Inception(512, 160, (112, 224), (24, 64), 64),
                    Inception(512, 128, (128, 256), (24, 64), 64),
                    Inception(512, 112, (144, 288), (32, 64), 64),
                    Inception(528, 256, (160, 320), (32, 128), 128),
                    paddle.nn.MaxPool2D(3, 2))
        self.b5 = paddle.nn.Sequential(
                    Inception(832, 256, (160, 320), (32, 128), 128),
                    Inception(832, 384, (182, 384), (48, 128), 128),
                    paddle.nn.AvgPool2D(2))
        self.flatten=paddle.nn.Flatten()
        self.b6 = paddle.nn.Linear(1024, num_classes)
        
    def forward(self, x):
        x = self.b1(x)
        x = self.b2(x)
        x = self.b3(x)
        x = self.b4(x)
        x = self.b5(x)
        x = self.flatten(x)
        x = self.b6(x)
        return x

epoch_num = 20
batch_size = 256
learning_rate = 0.001

val_acc_history = []
val_loss_history = []

def train(model):
    #启动训练模式
    model.train()

    opt = paddle.optimizer.Adam(learning_rate=learning_rate, parameters=model.parameters())
    train_loader = paddle.io.DataLoader(cifar10_train, shuffle=True, batch_size=batch_size)
    valid_loader = paddle.io.DataLoader(cifar10_test, batch_size=batch_size)

    for epoch in range(epoch_num):
        for batch_id, data in enumerate(train_loader()):
            x_data = paddle.cast(data[0], 'float32')
            y_data = paddle.cast(data[1], 'int64')
            y_data = paddle.reshape(y_data, (-1, 1))

            y_predict = model(x_data)
            loss = F.cross_entropy(y_predict, y_data)
            loss.backward()
            opt.step()
            opt.clear_grad()
        
        print("训练轮次: {}; 损失: {}".format(epoch, loss.numpy()))

        #启动评估模式
        model.eval()
        accuracies = []
        losses = []
        for batch_id, data in enumerate(valid_loader()):
            x_data = paddle.cast(data[0], 'float32')
            y_data = paddle.cast(data[1], 'int64')
            y_data = paddle.reshape(y_data, (-1, 1))

            y_predict = model(x_data)
            loss = F.cross_entropy(y_predict, y_data)
            acc = paddle.metric.accuracy(y_predict, y_data)
            accuracies.append(np.mean(acc.numpy()))
            losses.append(np.mean(loss.numpy()))

        avg_acc, avg_loss = np.mean(accuracies), np.mean(losses)
        print("评估准确度为:{};损失为:{}".format(avg_acc, avg_loss))
        val_acc_history.append(avg_acc)
        val_loss_history.append(avg_loss)
        model.train()

model = GoogLeNet(3, 10)
train(model)

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/170454.html原文链接:https://javaforall.cn

0 人点赞