殘差網(wǎng)絡(luò)ResNet網(wǎng)絡(luò)原理及實(shí)現(xiàn)

論文地址:https://arxiv.org/pdf/1512.03385.pdf

1、引言-深度網(wǎng)絡(luò)的退化問題

在深度神經(jīng)網(wǎng)絡(luò)訓(xùn)練中,從經(jīng)驗(yàn)來看,隨著網(wǎng)絡(luò)深度的增加,模型理論上可以取得更好的結(jié)果。但是實(shí)驗(yàn)卻發(fā)現(xiàn),深度神經(jīng)網(wǎng)絡(luò)中存在著退化問題(Degradation problem)??梢钥吹?,在下圖中56層的網(wǎng)絡(luò)比20層網(wǎng)絡(luò)效果還要差。

上面的現(xiàn)象與過擬合不同,過擬合的表現(xiàn)是訓(xùn)練誤差小而測試誤差大,而上面的圖片顯示訓(xùn)練誤差和測試誤差都是56層的網(wǎng)絡(luò)較大。

深度網(wǎng)絡(luò)的退化問題至少說明深度網(wǎng)絡(luò)不容易訓(xùn)練。我們假設(shè)這樣一種情況,56層的網(wǎng)絡(luò)的前20層和20層網(wǎng)絡(luò)參數(shù)一模一樣,而后36層是一個(gè)恒等映射( identity mapping),即輸入x輸出也是x,那么56層的網(wǎng)絡(luò)的效果也至少會和20層的網(wǎng)絡(luò)效果一樣,可是為什么出現(xiàn)了退化問題呢?因此我們在訓(xùn)練深層網(wǎng)絡(luò)時(shí),訓(xùn)練方法肯定存在的一定的缺陷。

正是上面的這個(gè)有趣的假設(shè),何凱明博士發(fā)明了殘差網(wǎng)絡(luò)ResNet來解決退化問題!讓我們來一探究竟!

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

ResNet中最重要的是殘差學(xué)習(xí)單元:

對于一個(gè)堆積層結(jié)構(gòu)(幾層堆積而成)當(dāng)輸入為x時(shí)其學(xué)習(xí)到的特征記為H(x),現(xiàn)在我們希望其可以學(xué)習(xí)到殘差F(x)=H(x)-x,這樣其實(shí)原始的學(xué)習(xí)特征是F(x)+x 。當(dāng)殘差為0時(shí),此時(shí)堆積層僅僅做了恒等映射,至少網(wǎng)絡(luò)性能不會下降,實(shí)際上殘差不會為0,這也會使得堆積層在輸入特征基礎(chǔ)上學(xué)習(xí)到新的特征,從而擁有更好的性能。一個(gè)殘差單元的公式如下:

后面的x前面也需要經(jīng)過參數(shù)Ws變換,從而使得和前面部分的輸出形狀相同,可以進(jìn)行加法運(yùn)算。

在堆疊了多個(gè)殘差單元后,我們的ResNet網(wǎng)絡(luò)結(jié)構(gòu)如下圖所示:

3、ResNet代碼實(shí)戰(zhàn)

我們來實(shí)現(xiàn)一個(gè)mnist手寫數(shù)字識別的程序。代碼中主要使用的是tensorflow.contrib.slim中定義的函數(shù),slim作為一種輕量級的tensorflow庫,使得模型的構(gòu)建,訓(xùn)練,測試都變得更加簡單。卷積層、池化層以及全聯(lián)接層都可以進(jìn)行快速的定義,非常方便。這里為了方便使用,我們直接導(dǎo)入slim。

import tensorflow.contrib.slim as slim

我們主要來看一下我們的網(wǎng)絡(luò)結(jié)構(gòu)。首先定義兩個(gè)殘差結(jié)構(gòu),第一個(gè)是輸入和輸出形狀一樣的殘差結(jié)構(gòu),一個(gè)是輸入和輸出形狀不一樣的殘差結(jié)構(gòu)。

下面是輸入和輸出形狀相同的殘差塊,這里slim.conv2d函數(shù)的輸入有三個(gè),分別是輸入數(shù)據(jù)、卷積核數(shù)量,卷積核的大小,默認(rèn)的話padding為SAME,即卷積后形狀不變,由于輸入和輸出形狀相同,因此我們可以在計(jì)算outputs時(shí)直接將兩部分相加。

def res_identity(input_tensor,conv_depth,kernel_shape,layer_name):
    with tf.variable_scope(layer_name):
        relu = tf.nn.relu(slim.conv2d(input_tensor,conv_depth,kernel_shape))
        outputs = tf.nn.relu(slim.conv2d(relu,conv_depth,kernel_shape) + input_tensor)
    return outputs

下面是輸入和輸出形狀不同的殘差塊,由于輸入和輸出形狀不同,因此我們需要對輸入也進(jìn)行一個(gè)卷積變化,使二者形狀相同。ResNet作者建議可以用1*1的卷積層,stride=2,來進(jìn)行變換:

def res_change(input_tensor,conv_depth,kernel_shape,layer_name):
    with tf.variable_scope(layer_name):
        relu = tf.nn.relu(slim.conv2d(input_tensor,conv_depth,kernel_shape,stride=2))
        input_tensor_reshape = slim.conv2d(input_tensor,conv_depth,[1,1],stride=2)
        outputs = tf.nn.relu(slim.conv2d(relu,conv_depth,kernel_shape) + input_tensor_reshape)
    return outputs

最后是整個(gè)網(wǎng)絡(luò)結(jié)構(gòu),對于x的輸入,我們先進(jìn)行一次卷積和池化操作,然后接入四個(gè)殘差塊,最后接兩層全聯(lián)接層得到網(wǎng)絡(luò)的輸出。

def inference(inputs):
    x = tf.reshape(inputs,[-1,28,28,1])
    conv_1 = tf.nn.relu(slim.conv2d(x,32,[3,3])) #28 * 28 * 32
    pool_1 = slim.max_pool2d(conv_1,[2,2]) # 14 * 14 * 32
    block_1 = res_identity(pool_1,32,[3,3],'layer_2')
    block_2 = res_change(block_1,64,[3,3],'layer_3')
    block_3 = res_identity(block_2,64,[3,3],'layer_4')
    block_4 = res_change(block_3,32,[3,3],'layer_5')
    net_flatten = slim.flatten(block_4,scope='flatten')
    fc_1 = slim.fully_connected(slim.dropout(net_flatten,0.8),200,activation_fn=tf.nn.tanh,scope='fc_1')
    output = slim.fully_connected(slim.dropout(fc_1,0.8),10,activation_fn=None,scope='output_layer')
    return output

完整的代碼地址在:https://github.com/princewen/tensorflow_practice/tree/master/CV/ResNet

參考文獻(xiàn):

1、論文:https://arxiv.org/pdf/1512.03385.pdf
2、https://blog.csdn.net/kaisa158/article/details/81096588?utm_source=blogxgwz4

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容