天天看點

【深度學習】經典網絡-(InceptionV3)GoogLeNet網絡複現(使用Tensorflow實作)論文閱讀Tensorflow代碼實作

論文位址:(V3)https://arxiv.org/abs/1512.00567

本文所包含代碼GitHub位址:https://github.com/shankezh/DL_HotNet_Tensorflow

如果對機器學習有興趣,不僅僅滿足将深度學習模型當黑盒模型使用的,想了解為何機器學習可以訓練拟合最佳模型,可以看我過往的部落格,使用數學知識推導了機器學習中比較經典的案例,并且使用了python撸了一套簡單的神經網絡的代碼架構用來加深了解:https://blog.csdn.net/shankezh/article/category/7279585

項目幫忙或工作機會請郵件聯系:[email protected]

資料集下載下傳位址:https://www.kaggle.com/c/cifar-10 or http://www.cs.toronto.edu/~kriz/cifar.html

--------------------- 

本文對論文進行了代碼複現;

論文閱讀

關鍵資訊提取

1.AlexNet對于提升卷積網絡性能的選項基本是沒有競争力的,參數太大;

2.VGG的結構簡單特性非常吸引人,也非常有價值;

3.Inception結構的GoogLeNet可以很好的執行在記憶體和計算資源有限的機器上;GoogLeNet隻有500w個參數,是具有6000w參數的AlexNet的1/12,更不用說VGG是AlexNet的3倍參數;

4.提出了一些在搭建高品質模型的指導規則:a)避免表征瓶頸,尤其在網絡初期(個人釋義:一些從輸出量剪開的輸入量,一方面會有近乎大量的資訊被剪掉,另一方面也應該避免瓶頸和過度壓縮); b)在網絡中,更高次元的代表者更容易處理本地資訊(個人了解:增加網絡寬度,訓練将會更快);c)空間聚合可以在低維嵌入上進行,而不會造成很大的或任何的表現力損失;d)平衡網絡深度和寬度;

5.GoogLeNet網絡的直接收益的提升大多是因為慷慨的使用了次元減少方法;也可以看作是一種分解卷積的計算手段;例如1x1卷積後面接個3x3;

6.研究了其他對于分解卷積的一系列方法;

7.分解成更小的卷積,5x5是3x3的2.78倍計算量,研究鄰近激活單元的相關性後,讨論5x5是否已可被分解使用多重層組織代替,是以結果是5x5可分解成兩個3x3,這種方式可讓計算代價變為(9+9)/25,減少了近乎28%;

8.那麼可不可以使用更小的卷積拆分來替代呢?也許可以,如2x2;但是,發現使用n x 1的效果也很棒,例如3x1接一個1x3,這樣和3x3卷及效果一樣,但是卻可以比後者省掉33%的性能,而通過壓縮網絡,3x3使用兩個2x2的替代,也僅僅可以省下11%的性能;

9.理論上,可以使用這樣的方法将nxn的卷積通過1xn和nx1來進行代替;實踐過程中,發現這樣的分解方式,在網絡的前部分的層上表現不佳,但卻在中等網格尺寸表現不錯(格子尺寸指的是圖的分辨率,在mxm的特征圖上,m的的尺寸是12到20);

10.使用輔助訓練器的網絡在訓練尾部階段比不适用輔助訓練器的網絡會效果更好,正确率更高;

11.傳統做法中,卷積網絡需要使用池化操作來減少特征圖的網格尺寸;為了避免表征瓶頸,在網絡濾波器使用最大或者均值池化前是展開;使用兩個并列stride2的塊:P和C,P是池化層,所有都是stride為2的濾波團;

網絡結構

1.給出新的網絡結構,見Figure1;

2.将以前的7x7分解後,使用了兩個3x3代替(section3.1有說明);

3.在35x35和288個濾波器的地方,使用了3個傳統的inception子產品;

4.在17x17和768濾波器的地方,使用了使用了網格減少技巧(Section5有說明),這裡使用了5個分解後的Inception子產品(其實就是InceptionV2的子產品,但是這篇論文把這個子產品叫V2,其實叫V3),圖見Figure2;

5.在8x8和1280濾波器的地方,使用了如Figure3.1的技巧,使用了兩個如Figure3.2圖的Inception結構;

6.雖然網絡深度為42,但計算代價僅僅是2.5倍的GoogLeNet(應該指的是V1),并且比VGGNet有效;

訓練方法:

1.使用了TensorFlow的随機梯度進行訓練,使用了分布式機器學習系統,在50台NVidia kepler架構的GPU上訓練;

2.BatchSize為32,運作了100個批次;

3.早期的經驗都是使用Momentum方法,deacy設為0.9,後來使用了RMSProp,将decay設定為0.9,E設定為1.0,學習速率設定為0.045,每兩個批次衰減一次,衰減率為0.94;

其他細節:

1.使用更高分辨率的感受野對于提高性能十分識别顯著;

2.如果僅僅改變輸入的分辨率而不去更進一步的調節模型,那麼就會使用計算能力更弱的模型去處理更難得任務;

3.多大分辨率的輸入才可以讓計算效果保持穩定?團隊給出了三個經驗觀點(Figure4.):

a)299x299   感受野為Stride=2,使用最大池化在第一層後;

b)151x151   感受野為Stride=1,使用最大池化在第一層後;

c)79x79       感受野為Stride=1,第一層後不使用池化;

【深度學習】經典網絡-(InceptionV3)GoogLeNet網絡複現(使用Tensorflow實作)論文閱讀Tensorflow代碼實作

Figure1.GoogLeNet使用InceptionV3打建,紅色為修正數字,因為源碼中這裡是三層,而論文中隻有兩層;

【深度學習】經典網絡-(InceptionV3)GoogLeNet網絡複現(使用Tensorflow實作)論文閱讀Tensorflow代碼實作

Figure2,即Figure1中的3xInception

【深度學習】經典網絡-(InceptionV3)GoogLeNet網絡複現(使用Tensorflow實作)論文閱讀Tensorflow代碼實作

Figure3.1 論文中提到對于減少分辨率的子產品,即将需要減少分辨率的第一個Inception子產品替換即可

【深度學習】經典網絡-(InceptionV3)GoogLeNet網絡複現(使用Tensorflow實作)論文閱讀Tensorflow代碼實作

Figure3.2,對應Figure1中的5xInception

【深度學習】經典網絡-(InceptionV3)GoogLeNet網絡複現(使用Tensorflow實作)論文閱讀Tensorflow代碼實作

Fiugre3.3,對應Figure1中的2xInception

【深度學習】經典網絡-(InceptionV3)GoogLeNet網絡複現(使用Tensorflow實作)論文閱讀Tensorflow代碼實作

Figure4. 提高輸入圖像分辨率後的正确率

Tensorflow代碼實作

說明

使用Tensorflow搭建論文網絡,搭建過程遵循論文原意,并且确認google官方給出的IncetpionV3的代碼與我的差別,論文的結構和谷歌官方給出的結構存在一定的差異性,是以我在搭建的時候,最大限度的按照了論文複現,除了論文中最後的2 x incepion 被我變成了3 x inception,同時沒有使用輔助分類器進行訓練,其他的基本都是按照論文進行複現,不遵循官方源碼,但大同小異,沒多大差別;

代碼

模型

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Created by InceptionV3 on 19-4-4

import tensorflow as tf
import tensorflow.contrib.slim as slim


"""
論文中提到的第一種子產品結構,stride都是1,等效于三層
但目前google源碼可以看到和這裡是不一樣的,而是用了inceptionv1的結構
但論文的圖表中則明确指出了是拆分了5x5的卷積為兩個3x3,網上基本上全部抄的google源碼
我這裡就按照論文複現,這裡展現了原則3
"""
def inception_module_v3_1(net, scope, filter_num, stride=1):
    with tf.variable_scope(scope):
        with tf.variable_scope('bh1'):
            bh1 = slim.conv2d(net, filter_num[0], [1, 1], stride=stride, scope="bh1_conv1_1x1")
        with tf.variable_scope('bh2'):
            bh2 = slim.avg_pool2d(net, [3, 3], stride=stride, scope="bh2_avg_3x3")
            bh2 = slim.conv2d(bh2, filter_num[1], [1, 1], stride=stride, scope="bh2_conv_1x1")
        with tf.variable_scope('bh3'):
            bh3 = slim.conv2d(net, filter_num[2], [1, 1], stride=stride, scope="bh3_conv1_1x1")
            bh3 = slim.conv2d(bh3, filter_num[3], [3, 3], stride=stride, scope="bh3_conv2_3x3")
        with tf.variable_scope('bh4'):
            bh4 = slim.conv2d(net, filter_num[4], [1, 1], stride=stride, scope="bh4_conv1_1x1")
            bh4 = slim.conv2d(bh4, filter_num[5], [3, 3], stride=stride, scope="bh4_conv2_3x3")
            bh4 = slim.conv2d(bh4, filter_num[6], [3, 3], stride=stride, scope="bh4_conv3_3x3")
        net = tf.concat([bh1, bh2, bh3, bh4], axis=3)
    return net


'''
論文中提到的第二種結構,使用了1xn和nx1,論文中将n=7用來處理17x17的grid,五層
這裡展現了原則3
'''
def inception_moudle_v3_2(net, scope, filter_num, stride=1):
    with tf.variable_scope(scope):
        with tf.variable_scope("bh1"):
            bh1 = slim.conv2d(net, filter_num[0], [1, 1], stride=stride, scope="bh1_conv_1x1")
        with tf.variable_scope("bh2"):
            bh2 = slim.avg_pool2d(net, [3, 3], stride=stride, scope='bh2_avg_3x3')
            bh2 = slim.conv2d(bh2, filter_num[1], [1, 1], stride=stride, scope='bh2_conv_1x1')
        with tf.variable_scope("bh3"):
            bh3 = slim.conv2d(net, filter_num[2], [1, 1], stride=stride, scope='bh3_conv1_1x1')
            bh3 = slim.conv2d(bh3, filter_num[3], [1, 7], stride=stride, scope='bh3_conv2_1x7')
            bh3 = slim.conv2d(bh3, filter_num[4], [7, 1], stride=stride, scope='bh3_conv3_7x1')
        with tf.variable_scope("bh4"):
            bh4 = slim.conv2d(net, filter_num[5], [1, 1], stride=stride, scope='bh4_conv1_1x1')
            bh4 = slim.conv2d(bh4, filter_num[6], [1, 7], stride=stride, scope='bh4_conv2_1x7')
            bh4 = slim.conv2d(bh4, filter_num[7], [7, 1], stride=stride, scope='bh4_conv3_7x1')
            bh4 = slim.conv2d(bh4, filter_num[8], [1, 7], stride=stride, scope='bh4_conv4_1x7')
            bh4 = slim.conv2d(bh4, filter_num[9], [7, 1], stride=stride, scope='bh4_conv5_7x1')
        net = tf.concat([bh1, bh2, bh3, bh4], axis=3)
    return net


'''
論文提到的第三種結構,增加了寬度,三層
展現了原則2
'''
def inception_moudle_v3_3(net, scope, filter_num, stride=1):
    with tf.variable_scope(scope):
        with tf.variable_scope("bh1"):
            bh1 = slim.conv2d(net, filter_num[0], [1, 1], stride=stride, scope='bh1_conv_1x1')
        with tf.variable_scope("bh2"):
            bh2 = slim.avg_pool2d(net, [3, 3], stride=stride, scope='bh2_avg_3x3')
            bh2 = slim.conv2d(bh2, filter_num[1], [1, 1], stride=stride, scope='bh2_conv_1x1')
        with tf.variable_scope("bh3"):
            bh3 = slim.conv2d(net, filter_num[2], [1, 1], stride=stride, scope='bh3_conv1_1x1')
            bh3_1 = slim.conv2d(bh3, filter_num[3], [3, 1], stride=stride, scope='bh3_conv2_3x1')
            bh3_2 = slim.conv2d(bh3, filter_num[4], [1, 3], stride=stride, scope='bh3_conv2_1x3')
        with tf.variable_scope("bh4"):
            bh4 = slim.conv2d(net, filter_num[5], [1, 1], stride=stride, scope='bh4_conv1_1x1')
            bh4 = slim.conv2d(bh4, filter_num[6], [3, 3], stride=stride, scope='bh4_conv2_3x3')
            bh4_1 = slim.conv2d(bh4, filter_num[7], [3, 1], stride=stride, scope='bh4_conv3_3x1')
            bh4_2 = slim.conv2d(bh4, filter_num[8], [1, 3], stride=stride, scope='bh4_conv3_1x3')
        net = tf.concat([bh1, bh2, bh3_1, bh3_2, bh4_1, bh4_2], axis=3)
    return net


'''
論文中提到用來減少grid-size的inception子產品
等效三層,pad為VALID
展現了原則1
'''
def inception_moudle_v3_reduce(net, scope, filter_num):
    with tf.variable_scope(scope):
        with tf.variable_scope("bh1"):
            bh1 = slim.max_pool2d(net, [3, 3], stride=2, padding='VALID',scope="bh1_max_3x3")
        with tf.variable_scope("bh2"):
            bh2 = slim.conv2d(net, filter_num[0], [1, 1], stride=1, scope='bh2_conv1_1x1')
            bh2 = slim.conv2d(bh2, filter_num[1], [3, 3], stride=2, padding='VALID', scope='bh2_conv2_3x3')
        with tf.variable_scope("bh3"):
            bh3 = slim.conv2d(net, filter_num[2], [1, 1], stride=1, scope='bh3_conv1_1x1')
            bh3 = slim.conv2d(bh3, filter_num[3], [3, 3], stride=1, scope='bh3_conv2_3x3')
            bh3 = slim.conv2d(bh3, filter_num[4], [3, 3], stride=2, padding='VALID', scope='bh3_conv3_3x3')
        net = tf.concat([bh1, bh2, bh3], axis=3)
    return net


def V3_slim(inputs, num_cls, keep_prob=0.8, is_training=True, spatital_squeeze=True):
    batch_norm_params = {
        'decay': 0.998,
        'epsilon': 0.001,
        'scale': False,
        'updates_collections': tf.GraphKeys.UPDATE_OPS,
    }

    net = inputs
    with tf.name_scope('reshape'):
        net = tf.reshape(net, [-1, 299, 299, 3])

    with tf.variable_scope('GoogLeNet_V3'):
        with slim.arg_scope(
                [slim.conv2d, slim.fully_connected],
                weights_regularizer=slim.l2_regularizer(0.00004)):
            with slim.arg_scope(
                    [slim.conv2d],
                    weights_initializer=slim.xavier_initializer(),
                    normalizer_fn=slim.batch_norm,
                    normalizer_params=batch_norm_params):
                with slim.arg_scope(
                        [slim.batch_norm, slim.dropout], is_training=is_training):
                    with slim.arg_scope([slim.conv2d, slim.max_pool2d, slim.avg_pool2d], stride=1, padding='VALID'):
                        net = slim.conv2d(net,32,[3,3], stride=2,scope="layer1")             #149x149
                        net = slim.conv2d(net,32,[3,3], scope='layer2')                      #147x147
                        net = slim.conv2d(net,64,[3,3], padding='SAME',scope='layer3')       #147x147
                        net = slim.max_pool2d(net,[3,3], stride=2,scope='layer4')            #73x73
                        net = slim.conv2d(net,80,[3,3], scope='layer5')                      #71x71
                        net = slim.conv2d(net,192,[3,3], stride=2,scope='layer6')            #35x35

                    with slim.arg_scope([slim.conv2d, slim.max_pool2d, slim.avg_pool2d], stride=1, padding='SAME'):
                        net = slim.conv2d(net, 288, [3,3], scope='layer7')
                        # 3 x inception
                        net = inception_module_v3_1(net, scope='layer8',filter_num=[64,32,48,64,64,96,96])       #35x35
                        net = inception_module_v3_1(net, scope='layer11',filter_num=[64,64,48,64,64,96,96])
                        net = inception_module_v3_1(net, scope='layer14',filter_num=[64,64,48,64,64,96,96])
                        print(net)
                        # 5 x inception
                        net = inception_moudle_v3_reduce(net, scope='layer17',filter_num=[192,384,64,96,96])  #17x17
                        net = inception_moudle_v3_2(net, scope='layer20',filter_num=[192,192,128,128,192,128,128,128,128,192])
                        net = inception_moudle_v3_2(net, scope='layer25',filter_num=[192,192,160,160,192,160,160,160,160,192])
                        net = inception_moudle_v3_2(net, scope='layer30',filter_num=[192,192,160,160,192,160,160,160,160,192])
                        net = inception_moudle_v3_2(net, scope='layer35',filter_num=[192,192,160,160,192,160,160,160,160,192])
                        print(net)
                        # 3 x inception
                        net = inception_moudle_v3_reduce(net, scope='layer40',filter_num=[192,320,192,192,192])  #8x8
                        net = inception_moudle_v3_3(net,scope='layer43',filter_num=[320,192,384,384,384,448,384,384,384])
                        net = inception_moudle_v3_3(net,scope='layer46',filter_num=[320,192,384,384,384,448,384,384,384])
                        print(net)
                        net = slim.avg_pool2d(net,[8,8],padding='VALID',scope='layer49')
                        net = slim.dropout(net)
                        net = slim.conv2d(net,num_cls,[1,1],activation_fn=None,normalizer_fn=None,scope='layer50')
                        print(net)
                        if spatital_squeeze:
                            net = tf.squeeze(net,[1,2],name='squeeze')

                        net = slim.softmax(net,scope='softmax')
                    return net



class testInceptionV3(tf.test.TestCase):
    def testBuildClassifyNetwork(self):
        inputs = tf.random_uniform((5,299,299,3))
        logits = V3_slim(inputs,10)
        print(logits)

if __name__ == '__main__':
    tf.test.main()
           

為了減少篇幅,訓練代碼我就不粘貼了,請參考之前的文章,或者直接去我的github拉取,接下來我們來看訓練過程;

訓練結果

訓練誤差如下圖

【深度學習】經典網絡-(InceptionV3)GoogLeNet網絡複現(使用Tensorflow實作)論文閱讀Tensorflow代碼實作

 測試資料集正确率如下圖:

【深度學習】經典網絡-(InceptionV3)GoogLeNet網絡複現(使用Tensorflow實作)論文閱讀Tensorflow代碼實作

結果說明,本次訓練正确率為84%,對于使用cifar10的資料集來說,已經算是很不錯的結果了;

測試結果 

本次使用了十張從百度下載下傳的照片,且分辨率都是遠遠大于訓練資料集的32x32:

【深度學習】經典網絡-(InceptionV3)GoogLeNet網絡複現(使用Tensorflow實作)論文閱讀Tensorflow代碼實作

檢測結果如下:

【深度學習】經典網絡-(InceptionV3)GoogLeNet網絡複現(使用Tensorflow實作)論文閱讀Tensorflow代碼實作

本次測試結果為10個結果全對,同時需要額外說明的是,本次檢測,沒有對圖檔進行resize32x32後再resize成299x299,而是直接resize299x299,但是檢測結果意外的好,這和我們再InceptionV2的結論是相悖的,按理應該是需要進行二次轉換的,其實道理也很簡單,inceptionv3使用了1xn,nx1這樣的卷積,這是橫豎條紋,對于特征提取是方向性的,而不是之前的,利用卷積模闆,提取的是區域性特征,是以在檢測結果上才可以達到如下的效果;

至此,InceptionV3論文複現完成;

訓練模型已經上傳至百度網盤,GitHub可以下到我的源碼,位址在最上部分,感興趣的可以下載下傳學習,或者試用;

權重檔案,百度雲位址為連結:https://pan.baidu.com/s/1BdMZYvkiYT9Fts0dLIgrog

提取碼:0rmi 

繼續閱讀