MXNET深度学习框架-22-使用gluon的GoogleNet

        GoogleNet和VGG是2014年ImageNet挑战赛(ILSVRC14)的双雄,其中,GoogLeNet获得了冠军、VGGNet获得了亚军。对于上述两种结构来说,它们的共同点都是比较深的CNN模型,不同的是,GoogleNet更深,且模型大小比VGGNet还要小一写。

        下面我们来探讨一下GoogleNet,说到GoogleNet,先来说说Inception结构,它基于NiN思想做了很大的改进:
在这里插入图片描述
        通过上图可以看到,一个输入总共有4条路径到达“Concat”层。下面通过相关代码来实现一下:

class inception(gn.nn.Block):
    def __init__(self,n1_1,n2_1,n2_3,n3_1,n3_5,n4_1,**kwargs):
        '''
            :param n1_1:  第1条路径的卷积输出数(1X1卷积)
            :param n2_1: 第2条路径的卷积输出数(1X1卷积)
            :param n2_3:  第2条路径的卷积输出数(3X3卷积)
            :param n3_1: 第3条路径的卷积输出数(1X1卷积)
            :param n3_5: 第3条路径的卷积输出数(5X5卷积)
            :param n4_1: 第3条路径的卷积输出数(1X1卷积)
        '''
        super(inception,self).__init__(**kwargs)
        with self.name_scope():
            # path 1
            self.p1_conv_1=gn.nn.Conv2D(n1_1,kernel_size=1,activation="relu")
            # path 2
            self.p2_conv_1=gn.nn.Conv2D(n2_1,kernel_size=1,activation="relu")
            self.p2_conv_3=gn.nn.Conv2D(n2_3,kernel_size=3,padding=1,activation="relu") # padding=1说明输出和输入的h,w不变,如果变化了concat就不行了
            # path 3
            self.p3_conv_1 = gn.nn.Conv2D(n3_1, kernel_size=1, activation="relu")
            self.p3_conv_5 = gn.nn.Conv2D(n3_5, kernel_size=5, padding=2, activation="relu")
            # path 4
            self.p4_pool_3 = gn.nn.MaxPool2D(pool_size=3,padding=1,strides=1)
            self.p4_conv_5 = gn.nn.Conv2D(n4_1, kernel_size=1, activation="relu")
    def forward(self, x):
        p1=self.p1_conv_1(x)
        p2=self.p2_conv_3(self.p2_conv_1(x))
        p3=self.p3_conv_5(self.p3_conv_1(x))
        p4=self.p4_conv_5(self.p4_pool_3(x))
        return nd.concat(p1,p2,p3,p4,dim=1) # 融合

        为什么Inception结构里要使用1×1卷积?试想一下,1条大河里面有4条支流汇聚在一起,那么大河的水量是不是就会陡然增大?前一章NiN也讲过了1×1卷积可以改变通道数,也就是使支流的水量减少,这样大河的水量才不会增多。

接下来我们运行一个实例看看:

# 运行一个实例
test=inception(64,96,128,16,32,32) #
test.initialize()
x=nd.random_normal(shape=(32,3,64,64)) # NCHW
print(test(x).shape)

结果:
在这里插入图片描述
        我们可以看到,,除了通道数变了以外,其余的都保持不变。那么256是怎么来的:
        Inception结构共有4条路经,所以把所有路径上的最后一个块的通道数加起来就好了:64+128+32+32=256。

        下面我们按照Googlenet的结构来构建一下模型(模型结构参照这篇博文:GoogLeNet网络结构)

'''---模型定义---'''
def Googlenet():
    # GoogleNet 可以分为6块
    # block1
    b1=gn.nn.Sequential()
    b1.add(gn.nn.Conv2D(64,7,2,padding=3,activation="relu"),
           gn.nn.MaxPool2D(pool_size=3,strides=2,padding=1)) # 这里不使用LRN
    # block2
    b2=gn.nn.Sequential()
    b2.add(gn.nn.Conv2D(64,1,1,activation="relu"),
           gn.nn.Conv2D(192,3,1,padding=1,activation="relu"),
           gn.nn.MaxPool2D(pool_size=3,strides=2,padding=1))
    # block3 --inception
    b3=gn.nn.Sequential()
    b3.add(inception(64,96,128,16,32,32),
           inception(128,128,192,32,96,64),
           gn.nn.MaxPool2D(pool_size=3,strides=2,padding=1))
    # block4 --inception
    b4 = gn.nn.Sequential()
    b4.add(inception(192, 96, 208, 16, 48, 64),
           inception(160, 112, 224, 24, 64, 64),
           inception(128, 128, 256, 24, 64, 64),
           inception(112, 144, 288, 32, 64, 64),
           inception(256, 160, 320, 32, 128, 128),
           gn.nn.MaxPool2D(pool_size=3, strides=2, padding=1))
    # block5 --inception
    b5 = gn.nn.Sequential()
    b5.add(inception(256, 160, 320, 32, 128, 128),
           inception(384, 192, 384, 48, 128, 128),
           gn.nn.AvgPool2D())
    # block6 --Dense
    b6 = gn.nn.Sequential()
    b6.add(gn.nn.Flatten(),
           gn.nn.Dropout(0.5),
           gn.nn.Dense(10))
    net=gn.nn.Sequential()
    net.add(b1,b2,b3,b4,b5,b6)
    return net

        虽然有点长,但理解了其实也很简单,下面运行一个实例看看大致结构:

# 例子
net=Googlenet()
X = nd.random.uniform(shape=(1, 1, 96, 96))
net.initialize()
for layer in net:
    X = layer(X)
    print(layer.name, 'output shape:\t', X.shape)

结果:
在这里插入图片描述
接下来的训练部分与数据准备部分与前几章几乎一样,下面附上所有代码:

import mxnet.ndarray as nd
import mxnet.autograd as ag
import mxnet.gluon as gn
import mxnet as mx
import matplotlib.pyplot as plt
from mxnet import init

class inception(gn.nn.Block):
    def __init__(self,n1_1,n2_1,n2_3,n3_1,n3_5,n4_1,**kwargs):
        '''
            :param n1_1:  第1条路径的卷积输出数(1X1卷积)
            :param n2_1: 第2条路径的卷积输出数(1X1卷积)
            :param n2_3:  第2条路径的卷积输出数(3X3卷积)
            :param n3_1: 第3条路径的卷积输出数(1X1卷积)
            :param n3_5: 第3条路径的卷积输出数(5X5卷积)
            :param n4_1: 第3条路径的卷积输出数(1X1卷积)
        '''
        super(inception,self).__init__(**kwargs)
        with self.name_scope():
            # path 1
            self.p1_conv_1=gn.nn.Conv2D(n1_1,kernel_size=1,activation="relu")
            # path 2
            self.p2_conv_1=gn.nn.Conv2D(n2_1,kernel_size=1,activation="relu")
            self.p2_conv_3=gn.nn.Conv2D(n2_3,kernel_size=3,padding=1,activation="relu") # padding=1说明输出和输入的h,w不变,如果变化了concat就不行了
            # path 3
            self.p3_conv_1 = gn.nn.Conv2D(n3_1, kernel_size=1, activation="relu")
            self.p3_conv_5 = gn.nn.Conv2D(n3_5, kernel_size=5, padding=2, activation="relu")
            # path 4
            self.p4_pool_3 = gn.nn.MaxPool2D(pool_size=3,padding=1,strides=1)
            self.p4_conv_5 = gn.nn.Conv2D(n4_1, kernel_size=1, activation="relu")
    def forward(self, x):
        p1=self.p1_conv_1(x)
        p2=self.p2_conv_3(self.p2_conv_1(x))
        p3=self.p3_conv_5(self.p3_conv_1(x))
        p4=self.p4_conv_5(self.p4_pool_3(x))
        return nd.concat(p1,p2,p3,p4,dim=1) # 融合

# 运行一个实例
# test=inception(64,96,128,16,32,32) #
# test.initialize()
# x=nd.random_normal(shape=(32,3,64,64)) # NCHW
# print(test(x).shape)

'''---模型定义---'''
def Googlenet():
    # GoogleNet 可以分为6块
    # block1
    b1=gn.nn.Sequential()
    b1.add(gn.nn.Conv2D(64,7,2,padding=3,activation="relu"),
           gn.nn.MaxPool2D(pool_size=3,strides=2,padding=1)) # 这里不使用LRN
    # block2
    b2=gn.nn.Sequential()
    b2.add(gn.nn.Conv2D(64,1,1,activation="relu"),
           gn.nn.Conv2D(192,3,1,padding=1,activation="relu"),
           gn.nn.MaxPool2D(pool_size=3,strides=2,padding=1))
    # block3 --inception
    b3=gn.nn.Sequential()
    b3.add(inception(64,96,128,16,32,32),
           inception(128,128,192,32,96,64),
           gn.nn.MaxPool2D(pool_size=3,strides=2,padding=1))
    # block4 --inception
    b4 = gn.nn.Sequential()
    b4.add(inception(192, 96, 208, 16, 48, 64),
           inception(160, 112, 224, 24, 64, 64),
           inception(128, 128, 256, 24, 64, 64),
           inception(112, 144, 288, 32, 64, 64),
           inception(256, 160, 320, 32, 128, 128),
           gn.nn.MaxPool2D(pool_size=3, strides=2, padding=1))
    # block5 --inception
    b5 = gn.nn.Sequential()
    b5.add(inception(256, 160, 320, 32, 128, 128),
           inception(384, 192, 384, 48, 128, 128),
           gn.nn.AvgPool2D())
    # block6 --Dense
    b6 = gn.nn.Sequential()
    b6.add(gn.nn.Flatten(),
           gn.nn.Dropout(0.5),
           gn.nn.Dense(10))
    net=gn.nn.Sequential()
    net.add(b1,b2,b3,b4,b5,b6)
    return net
# # 例子
# net=Googlenet()
# X = nd.random.uniform(shape=(1, 1, 96, 96))
# net.initialize()
# for layer in net:
#     X = layer(X)
#     print(layer.name, 'output shape:\t', X.shape)
ctx=mx.gpu()
net=Googlenet()
net.initialize(init=init.Xavier(),ctx=ctx)
'''---读取数据和预处理---'''
def load_data_fashion_mnist(batch_size, resize=None):
    transformer = []
    if resize:
        transformer += [gn.data.vision.transforms.Resize(resize)]
    transformer += [gn.data.vision.transforms.ToTensor()]
    transformer = gn.data.vision.transforms.Compose(transformer)
    mnist_train = gn.data.vision.FashionMNIST(train=True)
    mnist_test = gn.data.vision.FashionMNIST(train=False)
    train_iter = gn.data.DataLoader(
        mnist_train.transform_first(transformer), batch_size, shuffle=True)
    test_iter = gn.data.DataLoader(
        mnist_test.transform_first(transformer), batch_size, shuffle=False)
    return train_iter, test_iter
batch_size=128
train_iter,test_iter=load_data_fashion_mnist(batch_size,resize=96) # 96,因为图片加大的话训练很慢,而且显存会吃不消

# 定义准确率
def accuracy(output,label):
    return nd.mean(output.argmax(axis=1)==label).asscalar()

def evaluate_accuracy(data_iter,net):# 定义测试集准确率
    acc=0
    for data,label in data_iter:
        data, label = data.as_in_context(ctx), label.as_in_context(ctx)
        label = label.astype('float32')
        output=net(data)
        acc+=accuracy(output,label)
    return acc/len(data_iter)

# softmax和交叉熵分开的话数值可能会不稳定
cross_loss=gn.loss.SoftmaxCrossEntropyLoss()
# 优化
train_step=gn.Trainer(net.collect_params(),'sgd',{"learning_rate":0.01})

# 训练
lr=0.1
epochs=20
for epoch in range(epochs):
    n=0
    train_loss=0
    train_acc=0
    for image,y in train_iter:
        image, y = image.as_in_context(ctx), y.as_in_context(ctx)
        y = y.astype('float32')
        with ag.record():
            output = net(image)
            loss = cross_loss(output, y)
        loss.backward()
        train_step.step(batch_size)
        train_loss += nd.mean(loss).asscalar()
        train_acc += accuracy(output, y)

    test_acc = evaluate_accuracy(test_iter, net)
    print("Epoch %d, Loss:%f, Train acc:%f, Test acc:%f"
          %(epoch,train_loss/len(train_iter),train_acc/len(train_iter),test_acc))

训练结果:

在这里插入图片描述

        当然,Goolenet有很多版本,本章实现的是最初的版本,而Inception块的通道数分配之比也是在ImageNet数据集上通过大量的实验得来的。

        结论:将原先1个大卷积变成4个小卷积,信息流动稍微能够大一些,特征更容易抓取,且计算量能有所降低。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值