MXNET深度學習框架-18-使用gluon實現VGGNet

      VGGNet與上一章的AlexNet相比,模型更深,非線性表達能力更強,原因在於它將一系列7×77×75×55×5的卷積核變成了幾個反覆堆疊的3×33×3卷積,比如:2個3×33×3的卷積可以替代7×77×7的卷積、3個3×33×3的卷積可以替代5×55×5的卷積。這樣做不僅不會增加參數量,反而會增加模型的非線性表達能力。

      下面使用mxnet實現VGGNet:

1、VGG Block實現:
      比如我有一100個卷積層(當然VGG不可能成功,因爲會發生梯度消失等現象),難道說我要寫1000行代碼?所以需要定義一個Block,反正VGG全部用的是3×33×3的卷積。

def VGG_Block(num_convs,channels):
    net=gn.nn.Sequential()
    with net.name_scope():
        for i in range(num_convs):
            net.add(gn.nn.Conv2D(channels=channels,kernel_size=3,padding=1,
                                 activation="relu"))
        net.add(gn.nn.MaxPool2D(pool_size=(2,2),strides=2))
    return net

接下來實例化一個例子看看:

X=nd.random.normal(shape=(2,3,16,16))
blk=VGG_Block(2,128) # 2個堆疊的3X3卷積,相當於5X5
blk.initialize()
y=blk(X)
print(y.shape)

運行結果:
在這裏插入圖片描述
原維度是3,經過VGG Block之後,特徵維度變成了128,由於使用了最大池化,所以大小由(16,16)變成了(8,8)。爲什麼要這麼做?長寬減小,深就要增加,這樣能保持基本的信息不變。

2、VGG-11實現
      包含8個卷積層、3個全連接:

# 它有5個卷積塊,前2塊使用單卷積層,而後3塊使用雙卷積層。
# 第一塊的輸出通道是64,之後每次對輸出通道數翻倍,直到變爲512。
# 因爲這個網絡使用了8個卷積層和3個全連接層,所以經常被稱爲VGG-11。
architecture=((1,64),(1,128),(2,256),(2,512),(2,512))
def vgg(architecture):
    net=gn.nn.Sequential()
    with net.name_scope():
        for (num_conv,num_feature) in architecture:
            net.add(VGG_Block(num_conv,num_feature))
        # 全連接
        net.add(gn.nn.Flatten())
        net.add(gn.nn.Dense(4096,activation="relu"))
        net.add(gn.nn.Dropout(0.5))
        net.add(gn.nn.Dense(4096, activation="relu"))
        net.add(gn.nn.Dropout(0.5))
        net.add(gn.nn.Dense(10))
    return net

我們運行一個實例看看維度:

net=vgg(architecture)
net.initialize()
X = nd.random.uniform(shape=(1, 1, 224, 224))
for blk in net:
    X = blk(X)
    print(blk.name, 'output shape:\t', X.shape)

結果:
在這裏插入圖片描述
爲了便於訓練,我把全連接層的參數改小,圖片輸入大小也改小了,下面附上所有代碼:

import mxnet.gluon as gn
import mxnet.autograd as ag
import mxnet.ndarray as nd
import mxnet.initializer as init
import mxnet as mx

# 反覆堆疊的conv
def VGG_Block(num_convs,channels):
    net=gn.nn.Sequential()
    with net.name_scope():
        for i in range(num_convs):
            net.add(gn.nn.Conv2D(channels=channels,kernel_size=3,padding=1,
                                 activation="relu"))
        net.add(gn.nn.MaxPool2D(pool_size=(2,2),strides=2))
    return net
# 實例化一個例子看看
# X=nd.random.normal(shape=(2,3,16,16))
# blk=VGG_Block(2,128) # 2個堆疊的3X3卷積,相當於5X5
# blk.initialize()
# y=blk(X)
# print(y.shape)
# 現在我們構造一個VGG網絡。
# 它有5個卷積塊,前2塊使用單卷積層,而後3塊使用雙卷積層。
# 第一塊的輸出通道是64,之後每次對輸出通道數翻倍,直到變爲512。
# 因爲這個網絡使用了8個卷積層和3個全連接層,所以經常被稱爲VGG-11。
ctx=mx.gpu()
architecture=((1,64),(1,128),(2,256),(2,512),(2,512))
def vgg(architecture):
    net=gn.nn.Sequential()
    with net.name_scope():
        for (num_conv,num_feature) in architecture:
            net.add(VGG_Block(num_conv,num_feature))
        # 全連接
        net.add(gn.nn.Flatten())
        net.add(gn.nn.Dense(256,activation="relu"))  # VGG的全連接層神經元太大,爲便於訓練改小一點
        net.add(gn.nn.Dropout(0.5))
        net.add(gn.nn.Dense(128, activation="relu"))
        net.add(gn.nn.Dropout(0.5))
        net.add(gn.nn.Dense(10))
    return net
net=vgg(architecture)
net.initialize(ctx=ctx,init=init.Xavier())
# print(net)
# X = nd.random.uniform(shape=(1, 1, 28, 28))
# for blk in net:
#     X = blk(X)
#     print(blk.name, 'output shape:\t', X.shape)

'''---讀取數據和預處理---'''
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=64
train_iter,test_iter=load_data_fashion_mnist(batch_size,resize=32) # 32,因爲圖片加大的話訓練很慢,而且顯存會吃不消
# softmax和交叉熵損失函數
# 由於將它們分開會導致數值不穩定(前兩章博文的結果可以對比),所以直接使用gluon提供的API
cross_loss=gn.loss.SoftmaxCrossEntropyLoss()

# 定義準確率
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))

訓練結果:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章