經典卷積神經網絡算法(3):VGG

 

VGG的實質是AlexNet結構的加強版,它將卷積層的深度提高到了19層,而且在2014年的ImageNet大賽中的定位問題中得到了亞軍(冠軍是GoogLeNet,將在下一篇博客中介紹)。整個網絡向人們證實了咱們是能夠用很小的卷積核取得很好地效果,前提是咱們要把網絡的層數加深,這也論證了咱們要想提升整個神經網絡的模型效果,一個較爲有效的方法即是將它的深度加深,雖然計算量會大大提升,可是整個複雜度也上升了,更能解決複雜的問題。雖然VGG網絡已經誕生好幾年了,可是不少其餘網絡上效果並非很好地狀況下,VGG有時候還可以發揮它的優點,讓人有意想不到的收穫。javascript

與AlexNet網絡很是相似,VGG共有五個卷積層,而且每一個卷積層以後都有一個池化層。當時在ImageNet大賽中,做者分別嘗試了六種網絡結構。這六種結構大體相同,只是層數不一樣,少則11層,多達19層。網絡結構的輸入是大小爲224*224的RGB圖像,最終將分類結果輸出。固然,在輸入網絡時,圖片要進行預處理。css

VGG網絡相比AlexNet網絡,在網絡的深度以及寬度上作了必定的拓展,具體的卷積運算仍是與AlexNet網絡相似。咱們主要說明一下VGG網絡所作的改進。
第一點,因爲不少研究者發現歸一化層的效果並非很好,並且佔用了大量的計算資源,因此在VGG網絡中做者取消了歸一化層;
第二點,VGG網絡用了更小的3x3的卷積核,而兩個連續的3x3的卷積核至關於5x5的感覺野,由此類推,三個3x3的連續的卷積核也就至關於7x7的感覺野。這樣的變化使得參數量更小,節省了計算資源,將資源留給後面的更深層次的網絡。
第三點是VGG網絡中的池化層特徵池化核改成了2x2,而在AlexNet網絡中池化核爲3x3。
這三點改進無疑是使得整個參數運算量降低,這樣咱們在有限的計算平臺上可以得到更多的資源留給更深層的網絡。因爲層數較多,卷積核比較小,這樣使得整個網絡的特徵提取效果很好。其實因爲VGG的層數較多,因此計算量仍是至關大的,卷積層比較多成了它最顯著的特色。另外,VGG網絡的拓展性能比較突出,結構比較簡潔,因此它的遷移性能比較好,遷移到其餘數據集的時候泛化性能好。到如今爲止,VGG網絡還常常被用來提出特徵。因此當如今不少較新的模型效果很差時,使用VGG可能會解決這些問題。html

In [5]:
import os
import tensorflow as tf
from tensorflow.keras import layers, optimizers, datasets, Sequential
import matplotlib.pyplot as plt
In [6]:
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
tf.random.set_seed(2345)
 

先來加載圖像數據集。這裏,咱們使用tensorflow自帶的cifar100數據集。html5

In [16]:
(x_train, y_train), (x_test, y_test) = datasets.cifar100.load_data()
 

查看數據大體狀況:java

In [17]:
print(x_train.shape, y_train.shape)
print(x_test.shape, y_test.shape)
 
(50000, 32, 32, 3) (50000, 1)
(10000, 32, 32, 3) (10000, 1)
In [18]:
index = 1
fig, axes = plt.subplots(4, 3, figsize=(8, 4), tight_layout=True)
for row in range(4):
    for col in range(3):
        axes[row, col].imshow(x_train[index])
        axes[row, col].axis('off')
        axes[row, col].set_title(y_train[index][0])
        index += 1
plt.show()
 
In [13]:
y_train = tf.squeeze(y_train, axis=1)
y_test = tf.squeeze(y_test, axis=1)
In [7]:
def preprocess(x, y):
    x = tf.cast(x, dtype=tf.float32) / 255.  # 將每一個像素值映射到[0, 1]內
    y = tf.cast(y, dtype=tf.float32)
    return x, y
 

將數據集用TensorFlow的dataset存儲並打亂:node

In [13]:
train_db = tf.data.Dataset.from_tensor_slices((x_train, y_train))
train_db = train_db.shuffle(1000).map(preprocess).batch(64)
test_db = tf.data.Dataset.from_tensor_slices((x_test, y_test))
test_db = test_db.shuffle(1000).map(preprocess).batch(64)
 

如今來建立卷積部分網絡,共10個卷積層,每兩層之間添加一層最大池化層。在設計卷積網絡時,通常使核的數量保持增長,但每一個核輸出的特徵圖大小下降或保持不變。python

 

在前面實現LeNet和AlexNet網絡博客中,咱們是直接使用模型的fit方法訓練模型,在卷積網絡與全鏈接網絡的過渡部分經過TensorFlow的flatten層進行過渡,在本文中,爲更好演示網絡的各個細節,對這兩個功能咱們均手動實現。jquery

In [14]:
conv_layers = [ # 5層卷積,每兩層卷積後添加一層最大池化

    layers.Conv2D(64, kernel_size=[3,3],padding='same', activation=tf.nn.relu),  # 64是指核的數量,
    layers.Conv2D(64, kernel_size=[3,3],padding='same', activation=tf.nn.relu),  # same是指輸入於輸出保持相同size
    layers.MaxPool2D(pool_size=[2, 2], strides=2, padding='same'),
    
    layers.Conv2D(128, kernel_size=[3,3],padding='same', activation=tf.nn.relu),
    layers.Conv2D(128, kernel_size=[3,3],padding='same', activation=tf.nn.relu),
    layers.MaxPool2D(pool_size=[2, 2], strides=2, padding='same'),
    
    layers.Conv2D(256, kernel_size=[3,3],padding='same', activation=tf.nn.relu),
    layers.Conv2D(256, kernel_size=[3,3],padding='same', activation=tf.nn.relu),
    layers.MaxPool2D(pool_size=[2, 2], strides=2, padding='same'),
    
    layers.Conv2D(512, kernel_size=[3,3],padding='same', activation=tf.nn.relu),
    layers.Conv2D(512, kernel_size=[3,3],padding='same', activation=tf.nn.relu),
    layers.MaxPool2D(pool_size=[2, 2], strides=2, padding='same'),
    
    layers.Conv2D(512, kernel_size=[3,3],padding='same', activation=tf.nn.relu),
    layers.Conv2D(512, kernel_size=[3,3],padding='same', activation=tf.nn.relu),
    layers.MaxPool2D(pool_size=[2, 2], strides=2, padding='same')
]
 

假如咱們輸入網絡中的圖像大小爲32*32,包含3通道,檢測一下輸出大小:linux

In [15]:
conv_net = Sequential(conv_layers)
conv_net.build(input_shape=[None, 32, 32, 3])  # 指定輸入
x = tf.random.normal([1, 32, 32, 3])  # 1是指輸入一張圖像,兩個32是圖像長寬,3是指3通道
out = conv_net(x)
out.shape
Out[15]:
TensorShape([1, 1, 1, 512])
 

可知,通過5層卷積核池化以後,最終的輸出大小爲[1, 1, 1, 512],根據這一信息,咱們就能夠進一步設計全鏈接網絡。在設計全鏈接網絡時須要注意,由於數據集圖像有100個類別,因此全鏈接層中最後一層節點數量爲100.android

In [16]:
fc_layers = [  # 全鏈接層
    layers.Dense(256, activation=tf.nn.relu),
    layers.Dense(128, activation=tf.nn.relu),
    layers.Dense(100, activation=None)
]
 

指定輸入全鏈接層數據大小,並建立模型:

In [17]:
fc_net = Sequential(fc_layers)

fc_net.build(input_shape=[None, 512])
 

將卷積層和全鏈接層參數同一存儲,方便後續方便後續更新:

In [18]:
variables = conv_net.trainable_variables + fc_net.trainable_variables
 

建立優化器:

In [19]:
optimizer = optimizers.Adam(lr=1e-4)
 

手動實現fit功能,進行模型訓練。

In [28]:
for epoch in range(5):
    for step , (x, y) in enumerate(train_db):
        with tf.GradientTape() as tape:
            # 第一步, 將圖像數據傳入卷積層網絡
            # [batch, 32, 32, 3] -->  [batch, 1, 1, 512]
            out = conv_net(x)
            # 第二步, 卷卷積層輸出的特徵圖輸出到全鏈接層網絡
            # 須要先將特徵圖進行變形
            out = tf.reshape(out, [-1, 512])   # [batch, 1, 1, 512] --> [Batch, 512]
            # 全鏈接層:[batch, 512]  --> [b, 100]
            logits = fc_net(out)
            # 對輸出進行獨熱編碼:
            # y_onehot = tf.keras.one_hot(y, depth=100)  # 直接使用tf.one_hot()報錯Could not find valid device for node.
            y_onehot = tf.keras.utils.to_categorical(y, num_classes=100)  # 因此使用這種方式進行獨熱編碼
            # 計算損失函數
            loss = tf.losses.categorical_crossentropy(y_onehot, logits, from_logits=True)
            loss = tf.reduce_mean(loss)  # 損失均值
            
        grads = tape.gradient(loss, variables)  # 對卷積層和全鏈接層參數進行求導
        optimizer.apply_gradients(zip(grads, variables))  # 更新參數
        if step % 100 == 0: # 每1000次傳播輸出一次
            print(epoch , step, 'loss:', float(loss))
    total_num = 0 
    total_correct = 0
    for x, y in test_db:
        out = conv_net(x)
        out = tf.reshape(out, [-1, 512])
        logits = fc_net(out)
        prob = tf.nn.softmax(logits, axis=1)
        pred = tf.argmax(prob, axis=1)
        pred = tf.cast(pred, dtype=tf.int32)
        y = tf.cast(y, dtype=tf.int32)
        correct = tf.cast(tf.equal(pred , y), dtype=tf.int32)
        correct = tf.reduce_sum(correct)
        
        total_num += x.shape[0]
        total_correct += int(correct)
    acc = total_correct / total_num
    print(epoch, 'acc:', acc)
        
 
0 0 loss: 3.2746524810791016
0 100 loss: 3.1227006912231445
0 200 loss: 3.3354835510253906
0 300 loss: 3.5577585697174072
0 400 loss: 3.4442076683044434
0 500 loss: 3.5540578365325928
0 600 loss: 2.993718385696411
0 700 loss: 3.398216724395752
0 acc: 0.2156
1 0 loss: 3.2784264087677
1 100 loss: 2.915174961090088
1 200 loss: 3.1731386184692383
1 300 loss: 2.9105772972106934
1 400 loss: 2.889545202255249
1 500 loss: 3.0817737579345703
1 600 loss: 2.8999242782592773
1 700 loss: 2.847750186920166
1 acc: 0.2577
2 0 loss: 3.0487632751464844
2 100 loss: 2.961989164352417
2 200 loss: 2.810255527496338
2 300 loss: 2.921875476837158
2 400 loss: 3.0022480487823486
2 500 loss: 2.8648478984832764
2 600 loss: 2.313401222229004
2 700 loss: 2.9197773933410645
2 acc: 0.2714
3 0 loss: 3.2561397552490234
3 100 loss: 2.6284666061401367
3 200 loss: 2.611253499984741
3 300 loss: 2.6300625801086426
3 400 loss: 2.8262720108032227
3 500 loss: 2.4057717323303223
3 600 loss: 2.365994691848755
3 700 loss: 2.552517890930176
3 acc: 0.3038
4 0 loss: 2.8142364025115967
4 100 loss: 2.7545413970947266
4 200 loss: 2.5179195404052734
4 300 loss: 2.093433380126953
4 400 loss: 2.763005256652832
4 500 loss: 2.2059311866760254
4 600 loss: 2.128242015838623
4 700 loss: 2.2914538383483887
4 acc: 0.3382
 

上述訓練通過了5次迭代,準確率到達33.82%,增長迭代次數可進一步提升準確率。對比上篇博客的AlexNet網絡,咱們發現VGG網絡明顯收斂速度更快,模型西能更佳。

參考:

https://baijiahao.baidu.com/s?id=1636567480736287260&wfr=spider&for=pc

相關文章
相關標籤/搜索