Resnet-Deep residual network

摘要

Resnet(殘差網(wǎng)絡(luò))在ILSVRC2015比賽中取得冠軍,并取得了5項第一:

  • ImageNet分類第一
  • ImageNet檢測第一
  • ImageNet定位第一
  • COCO檢測第一
  • COCO分割第一

作者是來自微軟亞洲研究院的何凱明等人。主要貢獻在于解決了深度CNN模型難訓(xùn)練的問題妖混,提出恒等映射和殘差網(wǎng)絡(luò)的結(jié)構(gòu)冷离,并使得網(wǎng)絡(luò)深度有了更大的突破(從2014年VGG的19層书聚,Googlenet22層發(fā)展到Resnet的50層,152層)芬膝,是CNN圖像史上的一件里程碑事件悲没。

網(wǎng)絡(luò)由來

  • 深度網(wǎng)絡(luò)的退化問題
    從經(jīng)驗來看,當(dāng)卷積神經(jīng)網(wǎng)絡(luò)的層數(shù)增加時男图,網(wǎng)絡(luò)可以進行更加復(fù)雜的特征模式的提取示姿,理論上可以取得更好的結(jié)果(Alexnet8->VGG19->Google22)。然而實踐發(fā)現(xiàn)深度網(wǎng)絡(luò)出現(xiàn)了退化問題(Degradation problem):隨著網(wǎng)絡(luò)深度的增加逊笆,模型的準(zhǔn)確度會出現(xiàn)飽和栈戳,甚至下降。如下圖所示:56層的網(wǎng)絡(luò)比20層網(wǎng)絡(luò)效果還要差难裆。這不會是過擬合問題子檀,因為56層網(wǎng)絡(luò)的訓(xùn)練誤差同樣高镊掖。我們知道深層網(wǎng)絡(luò)存在著梯度消失或者爆炸的問題,這使得深度學(xué)習(xí)模型很難訓(xùn)練褂痰。但是現(xiàn)在已經(jīng)存在一些技術(shù)手段如BatchNorm來緩解這個問題亩进。因此,出現(xiàn)深度網(wǎng)絡(luò)的退化問題是非常令人詫異的缩歪。
模型精度vs 網(wǎng)絡(luò)深度
  • 恒等映射(Identity Mapping)
    深度網(wǎng)絡(luò)的退化問題至少說明深度網(wǎng)絡(luò)不容易訓(xùn)練归薛。但是我們考慮這樣一個事實:現(xiàn)在你有一個淺層網(wǎng)絡(luò),你想通過向上堆積新層來建立深層網(wǎng)絡(luò)匪蝙,一個極端情況是這些增加的層什么也不學(xué)習(xí)主籍,僅僅復(fù)制淺層網(wǎng)絡(luò)的特征,即這樣新層是恒等映射(Identity mapping)逛球。在這種情況下千元,深層網(wǎng)絡(luò)應(yīng)該至少和淺層網(wǎng)絡(luò)性能一樣,也不應(yīng)該出現(xiàn)退化現(xiàn)象颤绕。針對這個退化問題幸海,resnet作者提出了殘差學(xué)習(xí)來解決退化問題。對于一個堆積層結(jié)構(gòu)(幾層堆積而成)當(dāng)輸入為時其學(xué)習(xí)到的特征記為屋厘,現(xiàn)在我們希望其可以學(xué)習(xí)到殘差涕烧,這樣其實原始的學(xué)習(xí)特征是。之所以這樣是因為殘差學(xué)習(xí)相比原始特征直接學(xué)習(xí)更容易汗洒。當(dāng)殘差為0時议纯,此時堆積層僅僅做了恒等映射,至少網(wǎng)絡(luò)性能不會下降溢谤,實際上殘差不會為0瞻凤,這也會使得堆積層在輸入特征基礎(chǔ)上學(xué)習(xí)到新的特征,從而擁有更好的性能世杀。殘差學(xué)習(xí)的結(jié)構(gòu)如圖4所示阀参。這有點類似與電路中的“短路”,所以是一種短路連接(shortcutconnection)瞻坝。


    Identity Mapping

網(wǎng)絡(luò)結(jié)構(gòu)

Resnet網(wǎng)絡(luò)是參考了VGG19網(wǎng)絡(luò)蛛壳,在其基礎(chǔ)上進行了修改,并通過短路機制加入了殘差單元所刀,Resnet34如下圖所示衙荐。不同點在于除了第一層resnet采用7x7卷積并連接pool外,中間層都直接在采用stride=2的卷積進行下采樣浮创,在最后用global average pool替換了全連接層忧吟。

Resnet網(wǎng)絡(luò)以一個殘差塊為基礎(chǔ)單元,多個單元在深度上進行形成一組斩披,同一組中每個殘差塊的輸出通道數(shù)相同溜族,不同組的輸出通道以256為基礎(chǔ)讹俊,并以2倍遞增。從下圖中可以看到煌抒,ResNet相比普通網(wǎng)絡(luò)每兩層間增加了短路機制仍劈,這就形成了殘差學(xué)習(xí),其中實現(xiàn)部分表示常規(guī)的identity mapping(輸入輸出通道數(shù)相同)摧玫,虛線表示輸入輸出通道數(shù)不同時在shortcut上加了1x1卷積來改變輸入的通道數(shù)耳奕。其中Resnet34 和Resnet50除第一層7x7卷積和最后一層全連接外,共有4組殘差組诬像,每組各有3屋群,4,6坏挠,3個殘差單元芍躏,而resnet34中一個殘差單元含有2個3x3卷積,因此層數(shù)為1+(3+4+6+3)x2+1 = 34降狠,同理resnet50的層數(shù)為:1+(3+4+6+3)x3+1=50

resnet34 vs VGG19

  • 淺層殘差單元vs深層殘差單元
    ResNet使用兩種殘差單元对竣,如下圖所示。左圖對應(yīng)的是淺層網(wǎng)絡(luò)(34層及以下)榜配,而右圖對應(yīng)的是深層網(wǎng)絡(luò)(50層及以上)否纬。對于短路連接,當(dāng)輸入和輸出維度一致時蛋褥,可以直接將輸入加到輸出上临燃。但是當(dāng)維度不一致時(對應(yīng)的是維度增加一倍),這就不能直接相加烙心。有兩種策略:
    (1)采用zero-padding增加維度膜廊,此時一般要先做一個downsamp,可以采用strde=2的pooling淫茵,這樣不會增加參數(shù)爪瓜;
    (2)采用新的映射(projection shortcut),一般采用1x1的卷積匙瘪,這樣會增加參數(shù)铆铆,也會增加計算量。短路連接除了直接使用恒等映射丹喻,當(dāng)然都可以采用projection shortcut薄货。
殘差單元

代碼實現(xiàn)

本文采用tensorflow.contrib.layers 模塊來構(gòu)建Mobilenet網(wǎng)絡(luò)結(jié)構(gòu),關(guān)于tf.nn驻啤,tf.layers等api的構(gòu)建方式參見VGG網(wǎng)絡(luò)中的相關(guān)代碼。

# --------------------------Method 1 --------------------------------------------
import tensorflow as tf
import tensorflow.contrib.layers as tcl
from tensorflow.contrib.framework import arg_scope
class ResNet50:
    def __init__(self, resolution_inp=224, channel=3, name='resnet50'):
        self.name = name
        self.channel = channel
        self.resolution_inp = resolution_inp

    def __call__(self, x, dropout=0.5, is_training=True):
        with tf.variable_scope(self.name) as scope:
            with arg_scope([tcl.batch_norm], is_training=is_training, scale=True):
                with arg_scope([tcl.conv2d],
                               activation_fn=tf.nn.relu,
                               normalizer_fn=tcl.batch_norm,
                               padding="SAME"):
                    conv1 = tcl.conv2d(x, 64, 7, stride=2)
                    conv1 = tcl.max_pool2d(conv1, kernel_size=3, stride=2)

                    conv2 = self._res_blk(conv1, 256, 3, stride=1)
                    conv2 = self._res_blk(conv2, 256, 3, stride=1)
                    conv2 = self._res_blk(conv2, 256, 3, stride=1)

                    conv3 = self._res_blk(conv2, 512, 3, stride=2)
                    conv3 = self._res_blk(conv3, 512, 3, stride=1)
                    conv3 = self._res_blk(conv3, 512, 3, stride=1)
                    conv3 = self._res_blk(conv3, 512, 3, stride=1)

                    conv4 = self._res_blk(conv3, 1024, 3, stride=2)
                    conv4 = self._res_blk(conv4, 1024, 3, stride=1)
                    conv4 = self._res_blk(conv4, 1024, 3, stride=1)
                    conv4 = self._res_blk(conv4, 1024, 3, stride=1)
                    conv4 = self._res_blk(conv4, 1024, 3, stride=1)
                    conv4 = self._res_blk(conv4, 1024, 3, stride=1)

                    conv5 = self._res_blk(conv4, 2048, 3, stride=2)
                    conv5 = self._res_blk(conv5, 2048, 3, stride=1)
                    conv5 = self._res_blk(conv5, 2048, 3, stride=1)

                    avg_pool = tf.nn.avg_pool(conv5, [1, 7, 7, 1], strides=[1, 1, 1, 1], padding="VALID")
                    flatten = tf.layers.flatten(avg_pool)

                    self.fc6 = tf.layers.dense(flatten, units=1000, activation=tf.nn.relu)
                    # dropout = tf.nn.dropout(fc6, keep_prob=0.5)
                    predictions = tf.nn.softmax(self.fc6)
                    return predictions

    def _res_blk(self, x, num_outputs, kernel_size, stride=1, scope=None):
        with tf.variable_scope(scope, "resBlk"):
            small_ch = num_outputs // 4

            conv1 = tcl.conv2d(x, small_ch, kernel_size=1, stride=stride, padding="SAME")
            conv2 = tcl.conv2d(conv1, small_ch, kernel_size=kernel_size, stride=1, padding="SAME")
            conv3 = tcl.conv2d(conv2, num_outputs, kernel_size=1, stride=1, padding="SAME")

            shortcut = x
            if stride != 1 or x.get_shape()[-1] != num_outputs:
                shortcut = tcl.conv2d(x, num_outputs, kernel_size=1, stride=stride, padding="SAME",scope="shortcut")

            out = tf.add(conv3, shortcut)
            out = tf.nn.relu(out)
            return out

運行

該部分代碼包含2部分:計時函數(shù)time_tensorflow_run接受一個tf.Session變量和待計算的tensor以及相應(yīng)的參數(shù)字典和打印信息, 統(tǒng)計執(zhí)行該tensor100次所需要的時間(平均值和方差)荐吵;主函數(shù) run_benchmark中初始化了vgg16的3種調(diào)用方式骑冗,分別統(tǒng)計3中網(wǎng)絡(luò)在推理(predict) 和梯度計算(后向傳遞)的時間消耗赊瞬,詳細代碼如下:

# -------------------------- Demo and Test -------------------------------------------
from datetime import datetime
import math
import time
batch_size = 16
num_batches = 100


def time_tensorflow_run(session, target, feed, info_string):
    """
    calculate time for each session run
    :param session: tf.Session
    :param target: opterator or tensor need to run with session
    :param feed: feed dict for session
    :param info_string: info message for print
    :return: 
    """
    num_steps_burn_in = 10  # 預(yù)熱輪數(shù)
    total_duration = 0.0  # 總時間
    total_duration_squared = 0.0  # 總時間的平方和用以計算方差
    for i in range(num_batches + num_steps_burn_in):
        start_time = time.time()
        _ = session.run(target, feed_dict=feed)

        duration = time.time() - start_time

        if i >= num_steps_burn_in:  # 只考慮預(yù)熱輪數(shù)之后的時間
            if not i % 10:
                print('[%s] step %d, duration = %.3f' % (datetime.now(), i - num_steps_burn_in, duration))
            total_duration += duration
            total_duration_squared += duration * duration

    mn = total_duration / num_batches  # 平均每個batch的時間
    vr = total_duration_squared / num_batches - mn * mn  # 方差
    sd = math.sqrt(vr)  # 標(biāo)準(zhǔn)差
    print('[%s] %s across %d steps, %.3f +/- %.3f sec/batch' % (datetime.now(), info_string, num_batches, mn, sd))


# test demo
def run_benchmark():
    """
    main function for test or demo
    :return: 
    """
    with tf.Graph().as_default():
        image_size = 224  # 輸入圖像尺寸
        images = tf.Variable(tf.random_normal([batch_size, image_size, image_size, 3], dtype=tf.float32, stddev=1e-1))

        # method 0
        # prediction, fc = resnet50(images, training=True)
        model = ResNet50(224, 3)
        prediction = model(images, is_training=True)
        fc = model.fc6

        params = tf.trainable_variables()

        for v in params:
            print(v)
        init = tf.global_variables_initializer()

        print("out shape ", prediction)
        sess = tf.Session()
        print("init...")
        sess.run(init)

        print("predict..")
        writer = tf.summary.FileWriter("./logs")
        writer.add_graph(sess.graph)
        time_tensorflow_run(sess, prediction, {}, "Forward")

        # 用以模擬訓(xùn)練的過程
        objective = tf.nn.l2_loss(fc)  # 給一個loss
        grad = tf.gradients(objective, params)  # 相對于loss的 所有模型參數(shù)的梯度

        print('grad backword')
        time_tensorflow_run(sess, grad, {}, "Forward-backward")
        writer.close()

if __name__ == '__main__':
    run_benchmark()

注: 完整代碼可參見個人github工程

參數(shù)量

時間效率

參考

https://blog.csdn.net/u013709270/article/details/78838875

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市贼涩,隨后出現(xiàn)的幾起案子巧涧,更是在濱河造成了極大的恐慌,老刑警劉巖遥倦,帶你破解...
    沈念sama閱讀 221,430評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谤绳,死亡現(xiàn)場離奇詭異,居然都是意外死亡袒哥,警方通過查閱死者的電腦和手機缩筛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評論 3 398
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來堡称,“玉大人瞎抛,你說我怎么就攤上這事∪唇簦” “怎么了桐臊?”我有些...
    開封第一講書人閱讀 167,834評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長晓殊。 經(jīng)常有香客問我断凶,道長,這世上最難降的妖魔是什么巫俺? 我笑而不...
    開封第一講書人閱讀 59,543評論 1 296
  • 正文 為了忘掉前任认烁,我火速辦了婚禮,結(jié)果婚禮上识藤,老公的妹妹穿的比我還像新娘砚著。我一直安慰自己,他們只是感情好痴昧,可當(dāng)我...
    茶點故事閱讀 68,547評論 6 397
  • 文/花漫 我一把揭開白布稽穆。 她就那樣靜靜地躺著,像睡著了一般赶撰。 火紅的嫁衣襯著肌膚如雪舌镶。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,196評論 1 308
  • 那天,我揣著相機與錄音咕痛,去河邊找鬼晌块。 笑死,一個胖子當(dāng)著我的面吹牛否灾,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播鸣奔,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼墨技,長吁一口氣:“原來是場噩夢啊……” “哼惩阶!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起扣汪,我...
    開封第一講書人閱讀 39,671評論 0 276
  • 序言:老撾萬榮一對情侶失蹤断楷,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后崭别,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體冬筒,經(jīng)...
    沈念sama閱讀 46,221評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,303評論 3 340
  • 正文 我和宋清朗相戀三年茅主,在試婚紗的時候發(fā)現(xiàn)自己被綠了舞痰。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,444評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡暗膜,死狀恐怖匀奏,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情学搜,我是刑警寧澤娃善,帶...
    沈念sama閱讀 36,134評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站瑞佩,受9級特大地震影響聚磺,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜炬丸,卻給世界環(huán)境...
    茶點故事閱讀 41,810評論 3 333
  • 文/蒙蒙 一瘫寝、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧稠炬,春花似錦焕阿、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,285評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至毅桃,卻和暖如春褒纲,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背钥飞。 一陣腳步聲響...
    開封第一講書人閱讀 33,399評論 1 272
  • 我被黑心中介騙來泰國打工莺掠, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人读宙。 一個月前我還...
    沈念sama閱讀 48,837評論 3 376
  • 正文 我出身青樓彻秆,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子唇兑,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,455評論 2 359

推薦閱讀更多精彩內(nèi)容