日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 综合教程 >内容正文

综合教程

tensorflow学习笔记——ResNet

發(fā)布時(shí)間:2024/4/24 综合教程 47 生活家
生活随笔 收集整理的這篇文章主要介紹了 tensorflow学习笔记——ResNet 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

  自2012年AlexNet提出以來(lái),圖像分類、目標(biāo)檢測(cè)等一系列領(lǐng)域都被卷積神經(jīng)網(wǎng)絡(luò)CNN統(tǒng)治著。接下來(lái)的時(shí)間里,人們不斷設(shè)計(jì)新的深度學(xué)習(xí)網(wǎng)絡(luò)模型來(lái)獲得更好的訓(xùn)練效果。一般而言,許多網(wǎng)絡(luò)結(jié)構(gòu)的改進(jìn)(例如從VGG到ResNet可以給很多不同的計(jì)算機(jī)視覺(jué)領(lǐng)域帶來(lái)進(jìn)一步性能的提高。

  ResNet(Residual Neural Network)由微軟研究員的 Kaiming He 等四位華人提出,通過(guò)使用 Residual Uint 成功訓(xùn)練152層深的神經(jīng)網(wǎng)絡(luò),在 ILSVRC 2015比賽中獲得了冠軍,取得了 3.57%的top-5 的錯(cuò)誤率,同時(shí)參數(shù)量卻比 VGGNet低,效果非常突出,因?yàn)樗?ldquo;簡(jiǎn)單與實(shí)用”并存,之后很多方法都建立在ResNet50或者ResNet101的基礎(chǔ)上完成的,檢測(cè),分割,識(shí)別等領(lǐng)域都紛紛使用ResNet,Alpha zero 也使用了ResNet,所以可見(jiàn)ResNet確實(shí)很好用。ResNet的結(jié)構(gòu)可以極快的加速超深神經(jīng)網(wǎng)絡(luò)的訓(xùn)練,模型的準(zhǔn)確率也有非常大的提升。之前我們學(xué)習(xí)了Inception V3,而Inception V4則是將 Inception Module和ResNet相結(jié)合。可以看到ResNet是一個(gè)推廣性非常好的網(wǎng)絡(luò)結(jié)構(gòu),甚至可以直接應(yīng)用到 Inception Net中。

1,Highway Network簡(jiǎn)介

  在ResNet之前,瑞士教授 Schmidhuber 提出了 Highway Network,原理與ResNet很相似。這位Schmidhuber 教授同時(shí)也是 LSTM網(wǎng)絡(luò)的發(fā)明者,而且是早在1997年發(fā)明的,可謂是神經(jīng)網(wǎng)絡(luò)領(lǐng)域元老級(jí)的學(xué)者。通常認(rèn)為神經(jīng)網(wǎng)絡(luò)的深度對(duì)其性能非常重要,但是網(wǎng)絡(luò)越深其訓(xùn)練難度越大,Highway Network的目標(biāo)就是解決極深的神經(jīng)網(wǎng)絡(luò)難以訓(xùn)練的問(wèn)題。Highway Network相當(dāng)于修改了每一層的激活函數(shù),此前的激活函數(shù)只是對(duì)輸入做一個(gè)非線性變換 y = H(x, WH) ,Highway Network 則允許保留一定比例的原始輸入 x,即 y =H(x, WH) .T(x, WT) + x . C(x, WC) ,其中 T是變換系數(shù),C為保留系數(shù)。論文中令 C= 1 - T。這樣前面一層的信息,有一定比例可以不經(jīng)過(guò)矩陣乘法和非線性變換,直接傳輸?shù)较乱粚樱路鹨粭l信息高速公路,因而得名 Highway Network。Highway Network主要通過(guò) gating units 學(xué)習(xí)如何控制網(wǎng)絡(luò)中的信息流,即學(xué)習(xí)原理信息應(yīng)保留的比例。這個(gè)可學(xué)習(xí)的 gating機(jī)制,正是借鑒自Schmidhuber 教授早年的 LSTM 訓(xùn)練神經(jīng)網(wǎng)絡(luò)中的gating。幾百乃至上千層深的 Highway Network可以直接使用梯度下降算法訓(xùn)練,并可以配合多種非線性激活函數(shù),學(xué)習(xí)極深的神經(jīng)網(wǎng)絡(luò)現(xiàn)在變得可行了。事實(shí)上,Highway Network 的設(shè)計(jì)在理論上允許其訓(xùn)練任意深的網(wǎng)絡(luò),其優(yōu)化方法基本上與網(wǎng)絡(luò)的深度獨(dú)立,而傳統(tǒng)的神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu)則對(duì)深度非常敏感,訓(xùn)練復(fù)雜度隨著深度增加而急劇增加。

2,模型加深存在的問(wèn)題

  ResNet 和 HighWay Network非常類似,也就是允許原始輸入信息直接傳輸?shù)胶竺娴膶又小esNet最初的靈感來(lái)自這個(gè)問(wèn)題:在不斷加神經(jīng)網(wǎng)絡(luò)的深度時(shí),會(huì)出現(xiàn)一個(gè) Degradation 的問(wèn)題,即準(zhǔn)確率會(huì)先上升然后達(dá)到飽和,再持續(xù)增加深度則會(huì)導(dǎo)致準(zhǔn)確率下降。這并不是一個(gè)過(guò)擬合的問(wèn)題,因?yàn)椴还庠跍y(cè)試機(jī)上誤差增大,訓(xùn)練集本身誤差也會(huì)增大。假設(shè)有一個(gè)比較淺的網(wǎng)絡(luò)達(dá)到了飽和的準(zhǔn)確率,那么后面再加上幾個(gè) y=x 的全等映射層,起碼誤差不會(huì)增加,即更深的網(wǎng)絡(luò)不應(yīng)該帶來(lái)訓(xùn)練集上誤差上升。而這里提到的使用全等映射直接將前一層輸出傳到后面的思想,就是 ResNet的靈感來(lái)源。假定某段神經(jīng)網(wǎng)絡(luò)的輸入是 x,期望輸出是 H(x),如果我們直接把輸入 x 傳到輸出作為初始結(jié)果,那么此時(shí)我們需要學(xué)習(xí)的目標(biāo)就是 F(x) = H(x) - x。如下圖所示,這就是一個(gè)ResNet的殘差學(xué)習(xí)單元(Residual Unit),ResNet相當(dāng)于將學(xué)習(xí)目標(biāo)改變了,不再是學(xué)習(xí)一個(gè)完整的輸出 H(x),只是輸出和輸入的差別 H(x) - x,即殘差。

  如下圖所示,CIFIR10 數(shù)據(jù)的一個(gè)實(shí)驗(yàn),左側(cè)為訓(xùn)練誤差,右側(cè)是測(cè)試誤差,不光在測(cè)試集上誤差比較大,訓(xùn)練集本身的誤差也非常大。

  隨著網(wǎng)絡(luò)越深,精準(zhǔn)度的變化如下圖:

  通過(guò)實(shí)驗(yàn)可以發(fā)現(xiàn):隨著網(wǎng)絡(luò)層級(jí)的不斷增加,模型精度不斷得到提升,而當(dāng)網(wǎng)絡(luò)層級(jí)增加到一定的數(shù)目以后,訓(xùn)練精度和測(cè)試精度迅速下降,這說(shuō)明當(dāng)網(wǎng)絡(luò)變得很深以后,深度網(wǎng)絡(luò)變得更加難以訓(xùn)練了

3,為什么深度模型難以訓(xùn)練

  為什么隨著網(wǎng)絡(luò)層級(jí)越深,模型效果卻變差了呢?

3.1 鏈?zhǔn)椒▌t與梯度彌散

  下圖是一個(gè)簡(jiǎn)單的神經(jīng)網(wǎng)絡(luò)圖,由輸入層,隱含層,輸出層構(gòu)成:

  回想一下神經(jīng)網(wǎng)絡(luò)反向傳播的原理,先通過(guò)正向傳播計(jì)算出結(jié)果 output,然后通過(guò)與樣本比較得出誤差值 Etotal:

  根據(jù)誤差結(jié)果,利用著名的“鏈?zhǔn)椒▌t”求偏導(dǎo),使結(jié)果誤差反向傳播從而得出權(quán)重w調(diào)整的梯度。下圖是輸出結(jié)果到隱含層的反向傳播過(guò)程(隱含層到輸入層的反向傳播過(guò)程也是類似):

  通過(guò)不斷迭代,對(duì)參數(shù)矩陣進(jìn)行不斷調(diào)整后,使得輸出結(jié)果的誤差值更小,使輸出結(jié)果與事實(shí)更加接近。

  從上面的過(guò)程來(lái)看,神經(jīng)網(wǎng)絡(luò)在反向傳播過(guò)程中要不斷地傳播梯度,而當(dāng)網(wǎng)絡(luò)層數(shù)加深時(shí),梯度在傳播過(guò)程中會(huì)逐漸消失(假如采用Sigmoid函數(shù),對(duì)于幅度為1的信號(hào),每向后傳遞一層,梯度就衰減為原來(lái)的 0.25,層數(shù)越多,衰減越厲害),導(dǎo)致無(wú)法對(duì)前面網(wǎng)絡(luò)層的權(quán)重進(jìn)行有效的調(diào)整。

3.2 1.01365 = 37.783 與 0.99 365 = 0.0255

4,ResNet的特點(diǎn)

  假設(shè):假如有一個(gè)比較淺網(wǎng)絡(luò)(Shallow Net)的準(zhǔn)確率達(dá)到了飽和,那么后面再加上幾個(gè) y = x 的恒等映射(Identity Mappings),按理說(shuō),即使準(zhǔn)確率不能再提速了,起碼誤差不會(huì)增加(也即更深的網(wǎng)絡(luò)不應(yīng)該帶來(lái)訓(xùn)練集上誤差的上升),但是實(shí)驗(yàn)證明準(zhǔn)確率下降了,這說(shuō)明網(wǎng)絡(luò)越深,訓(xùn)練難度越大。而這里提到的使用恒等映射直接將前一層輸出傳到后面的思想,便是著名深度殘差網(wǎng)絡(luò)ResNet的靈感來(lái)源。

  ResNet引入了殘差網(wǎng)絡(luò)結(jié)構(gòu)(residual Network),通過(guò)這種殘差網(wǎng)絡(luò)結(jié)構(gòu),可以把網(wǎng)絡(luò)層弄得很深(據(jù)說(shuō)目前可以達(dá)到1000多層),并且最終的分類效果也非常好,殘差網(wǎng)絡(luò)的基本結(jié)構(gòu)如下圖所示,很明顯,該圖示帶有跳躍結(jié)構(gòu)的:

  F(x) 是一個(gè)殘差映射 w, r, t 恒等,如果說(shuō)恒等是理想,很容易將權(quán)重值設(shè)定為0,如果理想化映射更接近于恒等映射,便更容易發(fā)現(xiàn)微小波動(dòng)。

  殘差網(wǎng)絡(luò)借鑒了高速網(wǎng)絡(luò)(Highway Network)的跨層鏈接思想,但對(duì)其進(jìn)行修改(殘差項(xiàng)原本是帶權(quán)值的,但是ResNet用恒等映射代替之)

假定某段神經(jīng)網(wǎng)絡(luò)的輸入是x,期望輸出是H(x),即H(x)是期望的復(fù)雜潛在映射,如果是要學(xué)習(xí)這樣的模型,則訓(xùn)練難度會(huì)比較大;

回想前面的假設(shè),如果已經(jīng)學(xué)習(xí)到較飽和的準(zhǔn)確率(或者當(dāng)發(fā)現(xiàn)下層的誤差變大時(shí)),那么接下來(lái)的學(xué)習(xí)目標(biāo)就轉(zhuǎn)變?yōu)楹愕扔成涞膶W(xué)習(xí),也就是使輸入x近似于輸出H(x),以保持在后面的層次中不會(huì)造成精度下降。

在上圖的殘差網(wǎng)絡(luò)結(jié)構(gòu)圖中,通過(guò)“shortcut connections(捷徑連接)”的方式,直接把輸入x傳到輸出作為初始結(jié)果,輸出結(jié)果為H(x)=F(x)+x,當(dāng)F(x)=0時(shí),那么H(x)=x,也就是上面所提到的恒等映射。于是,ResNet相當(dāng)于將學(xué)習(xí)目標(biāo)改變了,不再是學(xué)習(xí)一個(gè)完整的輸出,而是目標(biāo)值H(X)和x的差值,也就是所謂的殘差F(x) = H(x)-x,因此,后面的訓(xùn)練目標(biāo)就是要將殘差結(jié)果逼近于0,使到隨著網(wǎng)絡(luò)加深,準(zhǔn)確率不下降。

這種殘差跳躍式的結(jié)構(gòu),打破了傳統(tǒng)的神經(jīng)網(wǎng)絡(luò)n-1層的輸出只能給n層作為輸入的慣例,使某一層的輸出可以直接跨過(guò)幾層作為后面某一層的輸入,其意義在于為疊加多層網(wǎng)絡(luò)而使得整個(gè)學(xué)習(xí)模型的錯(cuò)誤率不降反升的難題提供了新的方向。

至此,神經(jīng)網(wǎng)絡(luò)的層數(shù)可以超越之前的約束,達(dá)到幾十層、上百層甚至千層,為高級(jí)語(yǔ)義特征提取和分類提供了可行性。

  下面感受一下34層的深度殘差網(wǎng)絡(luò)的結(jié)構(gòu)圖:

  從圖中可以看出,怎么有一些“shortcut connections(捷徑連接)”是實(shí)現(xiàn),有一些是虛線,有什么區(qū)別呢?

  因?yàn)榻?jīng)過(guò)“shortcut-connections(捷徑連接)”后,H(x) = F(x) + x,如果 F(x) 和 x 通道相同,則可直接相加,那么通道不同怎么相加呢。上圖的實(shí)線,虛線就是為了區(qū)分這兩種情況的:

實(shí)線的Connection部分,表示通道相同,如上圖的第一個(gè)粉色矩形和第三個(gè)粉色矩形,都是 3*3*64 的特征圖,由于通道相同,所以采用計(jì)算方式為H(x) = F(x) + x;
虛線的 Connection 部分,表示通道不同,如上圖的第一個(gè)綠色矩形和第三個(gè)粉色矩形,分別為 3*3*64 和 3*3*128 的特征圖,通道不同,采用的計(jì)算方式為H(x) = F(x) + Wx,其中 W 為卷積操作,用來(lái)調(diào)整x維度的

 下圖是兩層及三層的ResNet殘差學(xué)習(xí)模塊:

  兩種結(jié)構(gòu)分別針對(duì) ResNet34(左圖)和 ResNet50/101/152(右圖),其目的主要就是為了降低參數(shù)的數(shù)目,左圖是兩個(gè) 3*3*256 的卷積,參數(shù)數(shù)目:3*3*256*256*2 = 1179648,右圖是第一個(gè)1*1的卷積把256維通道降到64維,然后在最后通過(guò)1*1卷積恢復(fù),整體上用的參數(shù)數(shù)目為:1*1*256*64 + 3*3*64*64 + 1*1*64*256 = 69632,右圖的參數(shù)數(shù)量比左圖減少 16.94倍,因此,右圖的主要目的就是為了減少參數(shù)量,從而減少計(jì)算量

  對(duì)于常規(guī)的ResNet,可以用于34層或者更少的網(wǎng)絡(luò)中(左圖);對(duì)于更深的網(wǎng)絡(luò)(如101層),則使用右圖,其目的是減少計(jì)算和參數(shù)量。

  經(jīng)檢驗(yàn),深度殘差網(wǎng)絡(luò)的確解決了退化問(wèn)題,如下圖所示,上圖為平原網(wǎng)絡(luò)(plain network)網(wǎng)絡(luò)層次越深(34層)比網(wǎng)絡(luò)層次淺的(18層)的誤差率更高;右圖為殘差網(wǎng)絡(luò)ResNet的網(wǎng)絡(luò)層次越深(34層)比網(wǎng)絡(luò)層次淺(18層)的誤差率更低。

5,VGGNet-19 VS ResNet-34(ResNet的創(chuàng)新點(diǎn))

  在提出殘差學(xué)習(xí)的思想,傳統(tǒng)的卷積網(wǎng)絡(luò)或者全連接網(wǎng)絡(luò)在信息傳遞的時(shí)候或多或少會(huì)存在信息丟失,損耗等問(wèn)題,同時(shí)還有導(dǎo)致梯度小時(shí)或梯度爆炸,導(dǎo)致很深的網(wǎng)絡(luò)無(wú)法訓(xùn)練。ResNet在一定程度上解決了這個(gè)問(wèn)題,通過(guò)直接將輸入信息繞道傳到輸出,保護(hù)信息的完整性,整個(gè)網(wǎng)絡(luò)只需要學(xué)習(xí)輸入,輸出差別的那一部分,簡(jiǎn)化學(xué)習(xí)目標(biāo)和難度。

  下圖所示為 VGGNet-19,以及一個(gè)34層深的普通卷積網(wǎng)絡(luò),和34層深的ResNet網(wǎng)絡(luò)的對(duì)比圖。可以看到普通直連的卷積神經(jīng)網(wǎng)絡(luò)和ResNet的最大區(qū)別在于,ResNet有很多旁路的支線將輸入直接連到后面的層,使得后面的層可以直接學(xué)習(xí)殘差,這種結(jié)構(gòu)也被稱為 shortcut或 skip connections。

  傳統(tǒng)的卷積層或全連接層在信息傳遞時(shí),或多或少的會(huì)存在信息丟失,損耗等問(wèn)題。ResNet 在某種程度上解決了這個(gè)問(wèn)題,通過(guò)直接將輸入信息繞道傳到輸出,保護(hù)信息的完整性,整個(gè)網(wǎng)絡(luò)則需要學(xué)習(xí)輸入,輸出差別的那一部分,簡(jiǎn)化學(xué)習(xí)目標(biāo)和難度。

  在ResNet的論文中,處理下圖中的兩層殘差學(xué)習(xí)單元,還有三層的殘差學(xué)習(xí)單元。兩層的殘差學(xué)習(xí)單元中包含兩個(gè)相同輸出通道數(shù)(因?yàn)闅埐畹扔谀繕?biāo)輸出減去輸入,即 H(x) - x,因此輸入,輸出維度需保持一致)的 3*3 卷積;而3層的殘差網(wǎng)絡(luò)則使用了 Network In Network 和 Inception Net中的 1*1 卷積,并且是在中間 3*3 的卷積前后都使用了 1*1 卷積,有先降維再升維的操作。另外,如果有輸入,輸出維度不同的情況,我們可以對(duì) x 做一個(gè)線性映射變換維度,再連接到后面的層。

   下圖為 VGG-19 ,直連的 34層網(wǎng)絡(luò),和ResNet的34層網(wǎng)絡(luò)的結(jié)構(gòu)對(duì)比:

6,ResNet不同層數(shù)的網(wǎng)絡(luò)配置

  下圖是ResNet 不同層數(shù)時(shí)的網(wǎng)絡(luò)配置(這里我們特別提出ResNet50和ResNet101,主要是因?yàn)樗麄兊某鲧R率很高,所以需要做特別的說(shuō)明):

  上表中,我們一共提出了五種深度的ResNet,分別是18, 34, 50, 101和152,首先看圖2最左側(cè),我們發(fā)現(xiàn)所有的網(wǎng)絡(luò)都分為五部分,分別是 conv1, conv2_x, conv3_x, conv4_x , conv5_x,之后的其他論文也會(huì)專門用這個(gè)稱呼指代 ResNet 50 或者 101 的每部分。

  拿 101-layer 那列,我們先看看 101-layer 是不是真的是 101 層網(wǎng)絡(luò),首先有個(gè) 輸入 7*7*64的卷積,然后經(jīng)過(guò) 3 + 4 + 23+ 3 = 33 個(gè) building block ,每個(gè) block 為3層,所以有 33*3 = 99 層,最后有個(gè) fc 層(用于分類),所有有 1+99+1=101層,確實(shí)有101層網(wǎng)絡(luò);

  注意1:101 層網(wǎng)絡(luò)僅僅指卷積或者全連接層,而激活層或者 Pooling 層并沒(méi)有計(jì)算在內(nèi);

  注意2:這里我們關(guān)注50-layer 和 101-layer 這兩列,可以發(fā)現(xiàn),他們唯一的不同在于 conv4_x, ResNet50有6個(gè)block,而 ResNet101有 23 個(gè) block,插了17個(gè)block,也就是 17*3=51層。

  在使用了ResNet的結(jié)構(gòu)后,可以發(fā)現(xiàn)層數(shù)不斷加深導(dǎo)致的訓(xùn)練集上誤差增大的現(xiàn)象被消除了,ResNet 網(wǎng)絡(luò)的訓(xùn)練誤差會(huì)隨著層數(shù)增大而逐漸減小,并且在測(cè)試機(jī)上的表現(xiàn)也會(huì)變好。在ResNet推出后不久,Google就借鑒了ResNet的精髓,提出了 Inception V4和 Inception-ResNet-V2,并通過(guò)融合這兩個(gè)模型,在 ILSVRC數(shù)據(jù)集上取得了驚人的 3.08%的錯(cuò)誤率。可見(jiàn),ResNet及其思想對(duì)卷積神經(jīng)網(wǎng)絡(luò)研究的貢獻(xiàn)確實(shí)非常顯著,具有很強(qiáng)的推廣性。在ResNet的作者的第二篇相關(guān)論文 Identity Mappings in Deep Rsidual Networks中,ResNet V2被提出。ResNet V2和 ResNet V1 的主要區(qū)別在于,作者通過(guò)研究 ResNet 殘差學(xué)習(xí)單元的傳播公式,發(fā)現(xiàn)前饋和反饋信息可以直接傳輸,因此 skip connection 的非線性激活函數(shù)(如ReLU)替換為 Identity Mappings(y = x)。同時(shí),ResNet V2在每一層中都使用了Batch Normalization。這樣處理之后,新的殘差學(xué)習(xí)單元將比以前更容易訓(xùn)練且泛化性更強(qiáng)。

  根據(jù) Schmidhuber 教授的觀點(diǎn),ResNet 類似于一個(gè)沒(méi)有Gates 的LSTM 網(wǎng)絡(luò),即將輸入 x 傳遞到后面層的過(guò)程是一直發(fā)生的,而不是學(xué)習(xí)出來(lái)的。同時(shí),最近也有兩篇論文表示,ResNet 基本等價(jià)于 RNN且ResNet的效果類似于在多層網(wǎng)絡(luò)間的集成方法(ensemble)。ResNet在加深網(wǎng)絡(luò)層數(shù)上做出來(lái)重大貢獻(xiàn),而另一篇論文 The Power of Depth for Feedforward Neural Networks 則從理論上證明了加深網(wǎng)絡(luò)比加寬網(wǎng)絡(luò)更有效,算是給ResNet 提供了聲援,也是給深度學(xué)習(xí)為什么要深才有效提供合理的解釋。

7,TensorFlow 實(shí)現(xiàn)ResNet V2網(wǎng)絡(luò)

  在ResNet的作者的第二篇相關(guān)論文《Identity Mappings in Deep Residual Networks》中,提出了ResNet V2。ResNet V2 和 ResNet V1 的主要區(qū)別在于,作者通過(guò)研究 ResNet 殘差學(xué)習(xí)單元的傳播公式,發(fā)現(xiàn)前饋和反饋信號(hào)可以直接傳輸,因此“shortcut connection”(捷徑連接)的非線性激活函數(shù)(如ReLU)替換為 Identity Mappings。同時(shí),ResNet V2 在每一層中都使用了 Batch Normalization。這樣處理后,新的殘差學(xué)習(xí)單元比以前更容易訓(xùn)練且泛化性更強(qiáng)。

  下面我們使用TensorFlow實(shí)現(xiàn)一個(gè)ResNet V2 網(wǎng)絡(luò)。我們依然使用方便的 contrib.slim 庫(kù)來(lái)輔助創(chuàng)建 ResNet,其余載入的庫(kù)還有原生的 collections。本文代碼主要來(lái)自于TensorFlow的開(kāi)源實(shí)現(xiàn)。

  我們使用 collections.namedtuple 設(shè)計(jì)ResNet 基本Block 模塊組的 named tuple,并用它創(chuàng)建 Block 的類,但只包含數(shù)據(jù)結(jié)構(gòu),不包含具體方法。我們要定義一個(gè)典型的 Block,需要輸入三個(gè)參數(shù),分別是 scope,unit_fn 和 args。以Block('block1', bottleneck, [(256, 64, 1]) x 2 + [(256, 64, 2 )]) 這一行代碼為例,它可以定義一個(gè)典型的Block,其中 block1 就是我們這個(gè)Block 的名稱(或 scope);bottleneck 是ResNet V2中的殘差學(xué)習(xí)單元;而最后一個(gè)參數(shù)[(256, 64, 1]) x 2 + [(256, 64, 2 )] 則是這個(gè)Block 的 args,args 是一個(gè)列表,其中每個(gè)元素都對(duì)應(yīng)一個(gè) bottleneck殘差學(xué)習(xí)單元,前面兩個(gè)元素都是 (256,64,1),最后一個(gè)是(256,64,2)。每一個(gè)元素都是一個(gè)三元 tuple,即 (depth,depth_bottleneck, stride)。比如(256, 64, 3)代表構(gòu)建的 bottleneck 殘差學(xué)習(xí)單元(每個(gè)殘差學(xué)習(xí)單元包含三個(gè)卷積層)中,第三層輸出通道數(shù) depth 為 256,前兩層輸出通道數(shù) depth_bottleneck 為64,且中間那層的步長(zhǎng) stride 為3。這個(gè)殘差學(xué)習(xí)單元結(jié)構(gòu)即為 [(1x1/s1, 64), (3x3/s2, 64), (1x1/s1, 256)]。而在這個(gè)Block中,一共有3個(gè)bottleneck殘差學(xué)習(xí)單元,除了最后一個(gè)的步長(zhǎng)由3變?yōu)?,其余都一致。

#_*_coding:utf-8_*_
import collections
import tensorflow as tf

slim = tf.contrib.slim

class Block(collections.namedtuple('Block', ['scope', 'uint_fn', 'args'])):
    'A named tuple describing a ResNet block'

  下面定義一個(gè)降采樣 subsample的方法,參數(shù)包括 inputs(輸入),factor(采樣因子)和scope。這個(gè)函數(shù)也非常簡(jiǎn)單,如果factor為1,則不做修改直接返回 inputs;如果不為1,則使用 slim.max_pool2d 最大池化來(lái)實(shí)現(xiàn),通過(guò)1x1的池化尺寸,stride作步長(zhǎng),即可實(shí)現(xiàn)降采樣。

def subsample(inputs, factor, scope=None):
    if factor == 1:
        return inputs
    else:
        return slim.max_pool2d(inputs, [1, 1], stride=factor, scope=scope)
   

  再定義一個(gè) conv2d_same函數(shù)創(chuàng)建卷積層。先判斷 stride 是否為1,如果為1,則直接使用 slim.conv2d 并令 padding 模式為SAME。如果 stride 不為1,則顯式地 pad zero,要pad zero 的總數(shù)為 Kernel_size -1 ,pad_beg 為 pad/2,pad_end 為余下的部分。接下來(lái)使用 tf.pad 對(duì)輸入變量進(jìn)行補(bǔ)零操作。最后,因?yàn)橐呀?jīng)進(jìn)行了 zero padding ,所以只需要使用一個(gè) padding 模式為VALID 的 slim.conv2d 創(chuàng)建這個(gè)卷積層。

def conv2d_same(inputs, num_outputs, kernel_size, stride, scope=None):
    if stride == 1:
        return slim.conv2d(inputs, num_outputs, kernel_size, stride=1,
                           padding='SAME', scope=scope)
    else:
        pad_total = kernel_size - 1
        pad_beg = pad_total // 2
        pad_end = pad_total - pad_beg
        inputs = tf.pad(inputs, [[0, 0], [pad_beg, pad_end],
                                 [pad_beg, pad_end], [0, 0]])
        return slim.conv2d(inputs, num_outputs, kernel_size, stride=stride,
                           padding='VALID', scope=scope)

  接下來(lái)定義堆疊Blocks的函數(shù),參數(shù)中的 net 即為輸入,blocks是之前定義的Block 的class 的列表,而 outputs_collections 則是用來(lái)收集各個(gè) end_points 的 collections。下面使用兩層循環(huán),逐個(gè)Block,逐個(gè)Residual Uint 地堆疊,先使用兩個(gè) tf.variable_scope 將殘差學(xué)習(xí)單元命名為 block1 / uint_1 的形式。在第二層循環(huán)中,我們拿到每個(gè)Block中每個(gè)Residual Unit的args,并展開(kāi)為 depth,depth_bottleneck 和 stide,其含義在前面定義Blocks類時(shí)已經(jīng)學(xué)習(xí)過(guò)。然后使用 unit_fn 函數(shù)(即殘差學(xué)習(xí)單元的生成函數(shù))順序地創(chuàng)建并連接所有的殘差學(xué)習(xí)單元。最后,我們使用 slim.utils.collect_named_outpouts 函數(shù)將輸出 net 添加到 collection 中 。最后,當(dāng)所有 Block 中的所有Residual Unit 都堆疊完之后,我們?cè)俜祷刈詈蟮?net 作為 stack_blocks_dense 函數(shù)的結(jié)果。

@slim.add_arg_scope
def stack_blocks_dense(net, blocks, outputs_collections=None):

    for block in blocks:
        with tf.variable_scope(block.scope, 'block', [net]) as sc:
            for i, unit in enumerate(block.args):
                with tf.variable_scope('unit_%d' % (i+1), values=[net]):
                    unit_depth, unit_depth_bottleneck, unit_stride = unit
                    net = block.unit_fn(net,
                                        depth=unit_depth,
                                        unit_depth_bottleneck=unit_depth_bottleneck,
                                        steide=unit_stride)
            net = slim.utils.collect_named_outputs(outputs_collections, sc.name, net)
    return net

  這里創(chuàng)建 ResNet通用的 arg_scope,關(guān)于 arg_scope,我們已經(jīng)知道其功能——用來(lái)定義某些函數(shù)的參數(shù)默認(rèn)值。這里定義訓(xùn)練標(biāo)記 is_training 默認(rèn)為TRUE,權(quán)重衰減速率 weight_decay 默認(rèn)為 0.0001,BN的衰減速率默認(rèn)為 0.997,BN的 epsilon默認(rèn)為 1e-5,BN的 scale默認(rèn)為 TRUE,和Inception V3定義 arg_scope一樣,先設(shè)置好BN的各項(xiàng)參數(shù),然后通過(guò)slim.arg_scope將 slim.conv2d的幾個(gè)默認(rèn)參數(shù)設(shè)置好:權(quán)重正則器設(shè)置為 L2正則,權(quán)重初始化器設(shè)為 slim.variance_scaling_initializer(),激活函數(shù)設(shè)為 ReLU,標(biāo)準(zhǔn)化器設(shè)為 BN。并將最大池化 的padding模式默認(rèn)設(shè)為 SAME(注意,ResNet原論文中使用的 VALID模式,設(shè)為SAME可讓特征對(duì)其更簡(jiǎn)單,大家可以嘗試改為 VALID)。最后將幾層嵌套的 arg_scope 作為結(jié)果返回。

def resnet_arg_scope(is_training=True,
                     weight_decay=0.0001,
                     batch_norm_decay=0.997,
                     batch_norm_epsilon=1e-5,
                     batch_norm_scale=True):
    batch_norm_params = {
        'is_training': is_training,
        'decay': batch_norm_decay,
        'epsilon': batch_norm_epsilon,
        'scale': batch_norm_scale,
        'updates_collections': tf.GraphKeys.UPDATE_OPS,
    }

    with slim.arg_scope(
        [slim.conv2d],
        weights_regularizer=slim.l2_regularizer(weight_decay),
        weights_initializer=slim.variance_scaling_initializer(),
        activation_fn=tf.nn.relu,
        normalizer_fn=slim.batch_norm,
        normalizer_params=batch_norm_params
    ):
        with slim.arg_scope([slim.batch_norm], **batch_norm_params):
            with slim.arg_scope([slim.max_pool2d], padding='SAME') as arg_sc:
                return arg_sc

  接下來(lái)定義核心的 bottleneck 殘差學(xué)習(xí)單元,它是ResNet V2 的論文中提到的 Full Preactivation Residual Unit 的一個(gè)變種。它和ResNet V1 中的殘差學(xué)習(xí)單元的主要區(qū)別有兩點(diǎn),一是在每層前都用了Batch Bormalization,而是對(duì)輸入進(jìn)行 practivation,而不是在卷積進(jìn)行激活函數(shù)處理。我們來(lái)看一下bottleneck 函數(shù)的參數(shù),inputs是輸入,depth,depth_bottleneck和stride這三個(gè)參數(shù)前面的 Batch Normalization,并使用 ReLU函數(shù)進(jìn)行預(yù)激活Preactivate。然后定義 shortcut(即直連的 x):如果殘差單元的輸入通道數(shù) depth_in和輸出通道數(shù) depth一致,那么使用 subsample按步長(zhǎng)為 stride 對(duì) Inputs 進(jìn)行空間上的降采樣(確保空間尺寸和殘差一致,因?yàn)闅埐钪虚g那層的卷積步長(zhǎng)為 stride);如果輸入,輸出通道數(shù)不一樣,我們用步長(zhǎng)為 stride 的1*1 卷積改變其通道數(shù),使得與輸出通道數(shù)一致。然后定義 Residual(殘差),residual這里有3層,先是一個(gè)1*1尺寸,步長(zhǎng)為1,輸出通道數(shù)為depth_bottleneck的卷積,然后是一個(gè)3*3尺寸,步長(zhǎng)為 stride,輸出通道數(shù)為 depth_bottleneck的卷積,最后是一個(gè)1*1的卷積,步長(zhǎng)為1,輸出通道數(shù)為depth的卷積,得到最終的 residual,這里注意最后一層沒(méi)有正則項(xiàng)也沒(méi)有激活函數(shù)。然后將residual 和 shortcut 相加,得到最后結(jié)果 output,再使用 slim.utils.collect_named_outpouts 將結(jié)果添加進(jìn) collection并返回 output 作為函數(shù)結(jié)果。

@slim.add_arg_scope
def bottleneck(inputs, depth, depth_bottleneck, stride,
               outputs_collections=None, scope=None):
    with tf.variable_scope(scope, 'bottleneck_v2', [inputs]) as sc:
        depth_in = slim.utils.last_dimension(inputs.get_shape(), min_rank=4)
        preact = slim.batch_norm(inputs, activation_fn=tf.nn.relu, scope='preact')

        if depth == depth_in:
            shortcut = subsample(inputs, stride, 'shortcut')
        else:
            shortcut = slim.conv2d(preact, depth, [1, 1], stride=stride,
                                   normalizer_fn=None, activation_fn=None,
                                   scope='shortcut')
        residual = slim.conv2d(preact, depth_bottleneck, [1, 1], stride=1,
                               scope='conv1')
        residual = conv2d_same(residual, depth_bottleneck, 3, stride,
                               scope='conv2')
        residual = slim.conv2d(residual, depth, [1, 1], stride=1,
                               normalozer_fn=None, activation_fn=None,
                               scope='conv3')
        output = shortcut + residual

        return slim.utils.collect_named_outputs(outputs_collections, sc.name, output)

  下面定義生成ResNet V2 的主函數(shù),我們只需要預(yù)先定義好網(wǎng)絡(luò)的殘差學(xué)習(xí)模塊組blocks,它就可以生成對(duì)應(yīng)的完整的ResNet。先看看這個(gè)函數(shù)的參數(shù),Inputs 即輸入,blocks為定義好的Block類的列表,num_classes是最后輸出的類數(shù)。global_pool 標(biāo)志是否加上最后的一層全局平均池化,include_root_block 標(biāo)志是否加上ResNet網(wǎng)絡(luò)最前面通常使用的7*7卷積和最大池化,reuse標(biāo)志是否重用,scope是整個(gè)網(wǎng)絡(luò)的名稱。在函數(shù)體內(nèi),我們先定義好variable_scope及 end_points_collection,再通過(guò) slim.arg_scope 將(slim.con2d,bottleneck, stack_block_dense)這三個(gè)函數(shù)的參數(shù) outputs_collections默認(rèn)設(shè)為 end_points_collection。然后根據(jù) include_root_block標(biāo)記,創(chuàng)建ResNet最前面的 64輸出通道的步長(zhǎng)為2的7*7卷積,然后再接一個(gè)步長(zhǎng)為2的3*3的最大池化。經(jīng)歷兩個(gè)步長(zhǎng)為2的層,圖片尺寸已經(jīng)被縮小為1/4。然后,使用前面定義好的 stack_blocks_dense 將殘差學(xué)習(xí)模塊組生成好,再根據(jù)標(biāo)記添加全局池化層,這里用 tf.reduce_mean 實(shí)現(xiàn)全局平均池化,效率比直接用 avg_pool高。下面根據(jù)是否有分類數(shù),添加一個(gè)輸出通道數(shù)為 Num_classes的1*1卷積(該卷積層無(wú)激活函數(shù)和正則項(xiàng)),再添加一個(gè) Softmax層輸出網(wǎng)絡(luò)結(jié)果。同時(shí)使用 slim.utils.convert_collection_to_dict 將 collection 轉(zhuǎn)化為Python的 dict,最后返回 net 和 end_points。

def resnet_v2(inputs,
              blocks,
              num_classes=None,
              global_pool=True,
              include_root_block=True,
              reuse=None,
              scope=None):
    with tf.variable_scope(scope, 'resnet_v2', [inputs], reuse=reuse) as sc:
        end_points_collection = sc.original_name_scope + '_end_points'
        with slim.arg_scope([slim.conv2d, bottleneck,
                             stack_blocks_dense],
                            outputs_collections=end_points_collection):
            net = inputs
            if include_root_block:
                with slim.arg_scope([slim.conv2d],
                                    activation_fn=None, normalizer_fn=None):
                    net = conv2d_same(net, 64, 7, stride=2, scope='conv1')
                net = slim.max_pool2d(net, [3, 3], stride=2, scope='pool1')
            net = stack_blocks_dense(net, blocks)
            net = slim.batch_norm(net, activation_fn=tf.nn.relu, scope='postnorm')
            if global_pool:
                net = tf.reduce_mean(net, [1, 2], name='pool5', keep_dims=True)
            if num_classes is not None:
                net = slim.conv2d(net, num_classes, [1, 1], activation_fn=None,
                                  normalizer_fn=None, scope='logits')
            end_points = slim.utils.convert_collection_to_dict(
                end_points_collection
            )
            if num_classes is not None:
                end_points['predictions'] = slim.softmax(net, scope='predictions')
            return net, end_points

  至此,我們就將 ResNet 的生成函數(shù)定義好了。下面根據(jù)ResNet不同層數(shù)時(shí)的網(wǎng)絡(luò)配置圖中推薦的幾個(gè)不同深度的ResNet網(wǎng)絡(luò)配置,來(lái)設(shè)計(jì)層數(shù)分別為 50, 101, 152 和 200 的ResNet。我們先來(lái)看 50層的ResNet,其嚴(yán)格遵守了圖中的設(shè)置,4個(gè)殘差學(xué)習(xí)Blocks 的 units數(shù)量分別為3, 4, 6和3,總層數(shù)即為 (3+4+6+3)x3+2=50。需要注意的時(shí),殘差學(xué)習(xí)模塊之前的卷積,池化已經(jīng)將尺寸縮小為4倍,我們前3個(gè)Blocks又都包含步長(zhǎng)為2的層,因此總尺寸縮小了 4*8=32倍,輸入圖片尺寸最后變?yōu)?224/32=7 。和 Inception V3很像,ResNet 不斷使用步長(zhǎng)為2的層來(lái)縮減尺寸,但同時(shí)輸出通道數(shù)也在持續(xù)增加,最后達(dá)到了 2048。

def resnet_v2_50(inputs,
                 num_classes=None,
                 global_pool=True,
                 reuse=None,
                 scope='resnet_v2_50'):
    blocks = [
        Block('block1', bottleneck, [(256, 64, 1)] * 2 + [(256, 64, 2)]),
        Block('block2', bottleneck, [(512, 128, 1)] * 3 + [(512, 128, 2)]),
        Block('block3', bottleneck, [(1024, 256, 1)] * 5 + [(1024, 256, 2)]),
        Block('block4', bottleneck, [(2048, 512, 1)] * 3)
    ]
    return resnet_v2(inputs, blocks, num_classes, global_pool,
                     include_root_block=True, reuse=reuse, scope=scope)

  101 層的ResNet 和50層相比,主要變化就是把4個(gè)Blocks的units 數(shù)量從3, 4, 6,3提升到了3, 4, 23, 3 。即將第三個(gè)殘差學(xué)習(xí)Block 的units 數(shù)增加到接近4倍。

def resnet_v2_101(inputs,
                 num_classes=None,
                 global_pool=True,
                 reuse=None,
                 scope='resnet_v2_101'):
    blocks = [
        Block('block1', bottleneck, [(256, 64, 1)] * 2 + [(256, 64, 2)]),
        Block('block2', bottleneck, [(512, 128, 1)] * 3 + [(512, 128, 2)]),
        Block('block3', bottleneck, [(1024, 256, 1)] * 22 + [(1024, 256, 2)]),
        Block('block4', bottleneck, [(2048, 512, 1)] * 3)
    ]
    return resnet_v2(inputs, blocks, num_classes, global_pool,
                     include_root_block=True, reuse=reuse, scope=scope)

  然后152層的ResNet,則是將第二個(gè)Block 的units數(shù)提高到8,將第三個(gè) Block的 units 數(shù)提高到36。Units數(shù)量提升的主要場(chǎng)所依然是第三個(gè)Block。

def resnet_v2_152(inputs,
                 num_classes=None,
                 global_pool=True,
                 reuse=None,
                 scope='resnet_v2_152'):
    blocks = [
        Block('block1', bottleneck, [(256, 64, 1)] * 2 + [(256, 64, 2)]),
        Block('block2', bottleneck, [(512, 128, 1)] * 7 + [(512, 128, 2)]),
        Block('block3', bottleneck, [(1024, 256, 1)] * 35 + [(1024, 256, 2)]),
        Block('block4', bottleneck, [(2048, 512, 1)] * 3)
    ]
    return resnet_v2(inputs, blocks, num_classes, global_pool,
                     include_root_block=True, reuse=reuse, scope=scope)

  最后,200層的Resnet 相比152層的ResNet ,沒(méi)有繼續(xù)提升第三個(gè)Block的units數(shù),而是將第二個(gè)Block的 units 數(shù)一下子提升到了23。

def resnet_v2_200(inputs,
                 num_classes=None,
                 global_pool=True,
                 reuse=None,
                 scope='resnet_v2_200'):
    blocks = [
        Block('block1', bottleneck, [(256, 64, 1)] * 2 + [(256, 64, 2)]),
        Block('block2', bottleneck, [(512, 128, 1)] * 23 + [(512, 128, 2)]),
        Block('block3', bottleneck, [(1024, 256, 1)] * 35 + [(1024, 256, 2)]),
        Block('block4', bottleneck, [(2048, 512, 1)] * 3)
    ]
    return resnet_v2(inputs, blocks, num_classes, global_pool,
                     include_root_block=True, reuse=reuse, scope=scope)

  最后我們使用一直以來(lái)的測(cè)評(píng)函數(shù) timne_tensorflow_run,來(lái)測(cè)試 152層深的 ResNet(即獲得 ILSVRC 2015 冠軍的版本)的forward 性能。圖片尺寸回歸到AlexNet ,VGGNet的 224*224,batch_size 為32。我們將 is_training 這個(gè) FLAG置為FALSE。然后使用 resnet_v2_152 創(chuàng)建網(wǎng)絡(luò),再由 time_tensorflow_run 函數(shù)測(cè)評(píng)其 forward 性能。這里不再對(duì)訓(xùn)練時(shí)的性能進(jìn)行測(cè)試了,大家可以自行測(cè)試求解ResNet全部參數(shù)的梯度所需要的時(shí)間。

def time_tensorflow_run(session, target, info_string):
    num_steps_burn_in = 10
    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)
        duration = time.time() - start_time
        if i >= num_steps_burn_in:
            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
    vr = total_duration_squared / num_batches - mn * mn
    sd = math.sqrt(vr)
    print('%s: %s across %d steps, %.3f +/- %.3f sec / batch'% (datetime.now(),
                                                               info_string, num_batches, mn, sd))

if __name__ == '__main__':
    batch_size = 32
    height, width = 224, 224
    inputs = tf.random_uniform((batch_size, height, width, 3))
    with slim.arg_scope(resnet_arg_scope(is_training=False)):
        net, endpoints = resnet_v2_152(inputs, 1000)

    init = tf.global_variables_initializer()
    sess = tf.Session()
    sess.run(init)
    num_batches = 100
    time_tensorflow_run(sess, net, 'Forward')

  這里可以看到,雖然這個(gè)ResNet有152層深,但其forward計(jì)算耗時(shí)并沒(méi)有特別夸張,相比 VGGNet 和 Inception_v3,大概只增加了 50%,每batch為 0.122 秒。這說(shuō)明 ResNet也是一個(gè)實(shí)用的卷積神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu),不僅支持超深網(wǎng)絡(luò)的訓(xùn)練,同時(shí)在實(shí)際工業(yè)應(yīng)用時(shí)也有不差的forward 性能。

2019-09-17 13:40:28.111221: step 0, duration=0.124
2019-09-17 13:40:29.336873: step 10, duration=0.122
2019-09-17 13:40:30.555401: step 20, duration=0.122
2019-09-17 13:40:31.774261: step 30, duration=0.122
2019-09-17 13:40:32.993206: step 40, duration=0.122
2019-09-17 13:40:34.210301: step 50, duration=0.122
2019-09-17 13:40:35.426938: step 60, duration=0.122
2019-09-17 13:40:36.644774: step 70, duration=0.122
2019-09-17 13:40:37.861877: step 80, duration=0.122
2019-09-17 13:40:39.078488: step 90, duration=0.122
2019-09-17 13:40:40.173907: Forward across 100 steps, 0.012 +/- 0.037 sec / batch

  本文我們完整的學(xué)習(xí)了ResNet的基本原理及Tensorflow實(shí)現(xiàn),也設(shè)計(jì)了一系列不同深度的 ResNet。如果大家感興趣可以自行探索不同深度,乃至不同殘差單元結(jié)構(gòu)的ResNet的分類性能。例如,ResNet 原論文中主要增加的時(shí)第二個(gè)和第三個(gè)Block的 units數(shù),大家可以嘗試增加其余兩個(gè)Block的 units數(shù),或者修改bottleneck單元中的 depth,depth_bottleneck等參數(shù),可對(duì)其參數(shù)設(shè)置的意義加深理解。ResNet 可以算是深度學(xué)習(xí)中的一個(gè)里程碑式的圖片,真正意義上支持極深神經(jīng)網(wǎng)絡(luò)的訓(xùn)練。其網(wǎng)絡(luò)結(jié)構(gòu)值得反復(fù)思索,如Google等已將其融合到自家的 Inception Net中,并取得了非常好的效果。相信ResNet的成功也會(huì)啟發(fā)其他在深度學(xué)習(xí)領(lǐng)域研究的靈感。

  完整代碼如下:

import collections
import tensorflow as tf

slim = tf.contrib.slim


class Block(collections.namedtuple('Block', ['scope', 'unit_fn', 'args'])):
    """A named tuple describing a ResNet block.
    Its parts are:
      scope: The scope of the `Block`.
      unit_fn: The ResNet unit function which takes as input a `Tensor` and
        returns another `Tensor` with the output of the ResNet unit.
      args: A list of length equal to the number of units in the `Block`. The list
        contains one (depth, depth_bottleneck, stride) tuple for each unit in the
        block to serve as argument to unit_fn.
    """


def subsample(inputs, factor, scope=None):
    """Subsamples the input along the spatial dimensions.
    Args:
      inputs: A `Tensor` of size [batch, height_in, width_in, channels].
      factor: The subsampling factor.
      scope: Optional variable_scope.
    Returns:
      output: A `Tensor` of size [batch, height_out, width_out, channels] with the
        input, either intact (if factor == 1) or subsampled (if factor > 1).
    """
    if factor == 1:
        return inputs
    else:
        return slim.max_pool2d(inputs, [1, 1], stride=factor, scope=scope)


def conv2d_same(inputs, num_outputs, kernel_size, stride, scope=None):
    """Strided 2-D convolution with 'SAME' padding.
    When stride > 1, then we do explicit zero-padding, followed by conv2d with
    'VALID' padding.
    Note that
       net = conv2d_same(inputs, num_outputs, 3, stride=stride)
    is equivalent to
       net = slim.conv2d(inputs, num_outputs, 3, stride=1, padding='SAME')
       net = subsample(net, factor=stride)
    whereas
       net = slim.conv2d(inputs, num_outputs, 3, stride=stride, padding='SAME')
    is different when the input's height or width is even, which is why we add the
    current function. For more details, see ResnetUtilsTest.testConv2DSameEven().
    Args:
      inputs: A 4-D tensor of size [batch, height_in, width_in, channels].
      num_outputs: An integer, the number of output filters.
      kernel_size: An int with the kernel_size of the filters.
      stride: An integer, the output stride.
      rate: An integer, rate for atrous convolution.
      scope: Scope.
    Returns:
      output: A 4-D tensor of size [batch, height_out, width_out, channels] with
        the convolution output.
    """
    if stride == 1:
        return slim.conv2d(inputs, num_outputs, kernel_size, stride=1,
                           padding='SAME', scope=scope)
    else:
        # kernel_size_effective = kernel_size + (kernel_size - 1) * (rate - 1)
        pad_total = kernel_size - 1
        pad_beg = pad_total // 2
        pad_end = pad_total - pad_beg
        inputs = tf.pad(inputs,
                        [[0, 0], [pad_beg, pad_end], [pad_beg, pad_end], [0, 0]])
        return slim.conv2d(inputs, num_outputs, kernel_size, stride=stride,
                           padding='VALID', scope=scope)


@slim.add_arg_scope
def stack_blocks_dense(net, blocks,
                       outputs_collections=None):
    """Stacks ResNet `Blocks` and controls output feature density.
    First, this function creates scopes for the ResNet in the form of
    'block_name/unit_1', 'block_name/unit_2', etc.
    Args:
      net: A `Tensor` of size [batch, height, width, channels].
      blocks: A list of length equal to the number of ResNet `Blocks`. Each
        element is a ResNet `Block` object describing the units in the `Block`.
      outputs_collections: Collection to add the ResNet block outputs.
    Returns:
      net: Output tensor 
    """
    for block in blocks:
        with tf.variable_scope(block.scope, 'block', [net]) as sc:
            for i, unit in enumerate(block.args):
                with tf.variable_scope('unit_%d' % (i + 1), values=[net]):
                    unit_depth, unit_depth_bottleneck, unit_stride = unit
                    net = block.unit_fn(net,
                                        depth=unit_depth,
                                        depth_bottleneck=unit_depth_bottleneck,
                                        stride=unit_stride)
            net = slim.utils.collect_named_outputs(outputs_collections, sc.name, net)

    return net


def resnet_arg_scope(is_training=True,
                     weight_decay=0.0001,
                     batch_norm_decay=0.997,
                     batch_norm_epsilon=1e-5,
                     batch_norm_scale=True):
    """Defines the default ResNet arg scope.
    TODO(gpapan): The batch-normalization related default values above are
      appropriate for use in conjunction with the reference ResNet models
      released at https://github.com/KaimingHe/deep-residual-networks. When
      training ResNets from scratch, they might need to be tuned.
    Args:
      is_training: Whether or not we are training the parameters in the batch
        normalization layers of the model.
      weight_decay: The weight decay to use for regularizing the model.
      batch_norm_decay: The moving average decay when estimating layer activation
        statistics in batch normalization.
      batch_norm_epsilon: Small constant to prevent division by zero when
        normalizing activations by their variance in batch normalization.
      batch_norm_scale: If True, uses an explicit `gamma` multiplier to scale the
        activations in the batch normalization layer.
    Returns:
      An `arg_scope` to use for the resnet models.
    """
    batch_norm_params = {
        'is_training': is_training,
        'decay': batch_norm_decay,
        'epsilon': batch_norm_epsilon,
        'scale': batch_norm_scale,
        'updates_collections': tf.GraphKeys.UPDATE_OPS,
    }

    with slim.arg_scope(
            [slim.conv2d],
            weights_regularizer=slim.l2_regularizer(weight_decay),
            weights_initializer=slim.variance_scaling_initializer(),
            activation_fn=tf.nn.relu,
            normalizer_fn=slim.batch_norm,
            normalizer_params=batch_norm_params):
        with slim.arg_scope([slim.batch_norm], **batch_norm_params):
            # The following implies padding='SAME' for pool1, which makes feature
            # alignment easier for dense prediction tasks. This is also used in
            # https://github.com/facebook/fb.resnet.torch. However the accompanying
            # code of 'Deep Residual Learning for Image Recognition' uses
            # padding='VALID' for pool1. You can switch to that choice by setting
            # slim.arg_scope([slim.max_pool2d], padding='VALID').
            with slim.arg_scope([slim.max_pool2d], padding='SAME') as arg_sc:
                return arg_sc


@slim.add_arg_scope
def bottleneck(inputs, depth, depth_bottleneck, stride,
               outputs_collections=None, scope=None):
    """Bottleneck residual unit variant with BN before convolutions.
    This is the full preactivation residual unit variant proposed in [2]. See
    Fig. 1(b) of [2] for its definition. Note that we use here the bottleneck
    variant which has an extra bottleneck layer.
    When putting together two consecutive ResNet blocks that use this unit, one
    should use stride = 2 in the last unit of the first block.
    Args:
      inputs: A tensor of size [batch, height, width, channels].
      depth: The depth of the ResNet unit output.
      depth_bottleneck: The depth of the bottleneck layers.
      stride: The ResNet unit's stride. Determines the amount of downsampling of
        the units output compared to its input.
      rate: An integer, rate for atrous convolution.
      outputs_collections: Collection to add the ResNet unit output.
      scope: Optional variable_scope.
    Returns:
      The ResNet unit's output.
    """
    with tf.variable_scope(scope, 'bottleneck_v2', [inputs]) as sc:
        depth_in = slim.utils.last_dimension(inputs.get_shape(), min_rank=4)
        preact = slim.batch_norm(inputs, activation_fn=tf.nn.relu, scope='preact')
        if depth == depth_in:
            shortcut = subsample(inputs, stride, 'shortcut')
        else:
            shortcut = slim.conv2d(preact, depth, [1, 1], stride=stride,
                                   normalizer_fn=None, activation_fn=None,
                                   scope='shortcut')

        residual = slim.conv2d(preact, depth_bottleneck, [1, 1], stride=1,
                               scope='conv1')
        residual = conv2d_same(residual, depth_bottleneck, 3, stride,
                               scope='conv2')
        residual = slim.conv2d(residual, depth, [1, 1], stride=1,
                               normalizer_fn=None, activation_fn=None,
                               scope='conv3')

        output = shortcut + residual

        return slim.utils.collect_named_outputs(outputs_collections,
                                                sc.name,
                                                output)


def resnet_v2(inputs,
              blocks,
              num_classes=None,
              global_pool=True,
              include_root_block=True,
              reuse=None,
              scope=None):
    """Generator for v2 (preactivation) ResNet models.
    This function generates a family of ResNet v2 models. See the resnet_v2_*()
    methods for specific model instantiations, obtained by selecting different
    block instantiations that produce ResNets of various depths.
    Args:
      inputs: A tensor of size [batch, height_in, width_in, channels].
      blocks: A list of length equal to the number of ResNet blocks. Each element
        is a resnet_utils.Block object describing the units in the block.
      num_classes: Number of predicted classes for classification tasks. If None
        we return the features before the logit layer.
      include_root_block: If True, include the initial convolution followed by
        max-pooling, if False excludes it. If excluded, `inputs` should be the
        results of an activation-less convolution.
      reuse: whether or not the network and its variables should be reused. To be
        able to reuse 'scope' must be given.
      scope: Optional variable_scope.
    Returns:
      net: A rank-4 tensor of size [batch, height_out, width_out, channels_out].
        If global_pool is False, then height_out and width_out are reduced by a
        factor of output_stride compared to the respective height_in and width_in,
        else both height_out and width_out equal one. If num_classes is None, then
        net is the output of the last ResNet block, potentially after global
        average pooling. If num_classes is not None, net contains the pre-softmax
        activations.
      end_points: A dictionary from components of the network to the corresponding
        activation.
    Raises:
      ValueError: If the target output_stride is not valid.
    """
    with tf.variable_scope(scope, 'resnet_v2', [inputs], reuse=reuse) as sc:
        end_points_collection = sc.original_name_scope + '_end_points'
        with slim.arg_scope([slim.conv2d, bottleneck,
                             stack_blocks_dense],
                            outputs_collections=end_points_collection):
            net = inputs
            if include_root_block:
                # We do not include batch normalization or activation functions in conv1
                # because the first ResNet unit will perform these. Cf. Appendix of [2].
                with slim.arg_scope([slim.conv2d],
                                    activation_fn=None, normalizer_fn=None):
                    net = conv2d_same(net, 64, 7, stride=2, scope='conv1')
                net = slim.max_pool2d(net, [3, 3], stride=2, scope='pool1')
            net = stack_blocks_dense(net, blocks)
            # This is needed because the pre-activation variant does not have batch
            # normalization or activation functions in the residual unit output. See
            # Appendix of [2].
            net = slim.batch_norm(net, activation_fn=tf.nn.relu, scope='postnorm')
            if global_pool:
                # Global average pooling.
                net = tf.reduce_mean(net, [1, 2], name='pool5', keep_dims=True)
            if num_classes is not None:
                net = slim.conv2d(net, num_classes, [1, 1], activation_fn=None,
                                  normalizer_fn=None, scope='logits')
            # Convert end_points_collection into a dictionary of end_points.
            end_points = slim.utils.convert_collection_to_dict(end_points_collection)
            if num_classes is not None:
                end_points['predictions'] = slim.softmax(net, scope='predictions')
            return net, end_points


def resnet_v2_50(inputs,
                 num_classes=None,
                 global_pool=True,
                 reuse=None,
                 scope='resnet_v2_50'):
    """ResNet-50 model of [1]. See resnet_v2() for arg and return description."""
    blocks = [
        Block('block1', bottleneck, [(256, 64, 1)] * 2 + [(256, 64, 2)]),
        Block(
            'block2', bottleneck, [(512, 128, 1)] * 3 + [(512, 128, 2)]),
        Block(
            'block3', bottleneck, [(1024, 256, 1)] * 5 + [(1024, 256, 2)]),
        Block(
            'block4', bottleneck, [(2048, 512, 1)] * 3)]
    return resnet_v2(inputs, blocks, num_classes, global_pool,
                     include_root_block=True, reuse=reuse, scope=scope)


def resnet_v2_101(inputs,
                  num_classes=None,
                  global_pool=True,
                  reuse=None,
                  scope='resnet_v2_101'):
    """ResNet-101 model of [1]. See resnet_v2() for arg and return description."""
    blocks = [
        Block(
            'block1', bottleneck, [(256, 64, 1)] * 2 + [(256, 64, 2)]),
        Block(
            'block2', bottleneck, [(512, 128, 1)] * 3 + [(512, 128, 2)]),
        Block(
            'block3', bottleneck, [(1024, 256, 1)] * 22 + [(1024, 256, 2)]),
        Block(
            'block4', bottleneck, [(2048, 512, 1)] * 3)]
    return resnet_v2(inputs, blocks, num_classes, global_pool,
                     include_root_block=True, reuse=reuse, scope=scope)


def resnet_v2_152(inputs,
                  num_classes=None,
                  global_pool=True,
                  reuse=None,
                  scope='resnet_v2_152'):
    """ResNet-152 model of [1]. See resnet_v2() for arg and return description."""
    blocks = [
        Block(
            'block1', bottleneck, [(256, 64, 1)] * 2 + [(256, 64, 2)]),
        Block(
            'block2', bottleneck, [(512, 128, 1)] * 7 + [(512, 128, 2)]),
        Block(
            'block3', bottleneck, [(1024, 256, 1)] * 35 + [(1024, 256, 2)]),
        Block(
            'block4', bottleneck, [(2048, 512, 1)] * 3)]
    return resnet_v2(inputs, blocks, num_classes, global_pool,
                     include_root_block=True, reuse=reuse, scope=scope)


def resnet_v2_200(inputs,
                  num_classes=None,
                  global_pool=True,
                  reuse=None,
                  scope='resnet_v2_200'):
    """ResNet-200 model of [2]. See resnet_v2() for arg and return description."""
    blocks = [
        Block(
            'block1', bottleneck, [(256, 64, 1)] * 2 + [(256, 64, 2)]),
        Block(
            'block2', bottleneck, [(512, 128, 1)] * 23 + [(512, 128, 2)]),
        Block(
            'block3', bottleneck, [(1024, 256, 1)] * 35 + [(1024, 256, 2)]),
        Block(
            'block4', bottleneck, [(2048, 512, 1)] * 3)]
    return resnet_v2(inputs, blocks, num_classes, global_pool,
                     include_root_block=True, reuse=reuse, scope=scope)


from datetime import datetime
import math
import time


def time_tensorflow_run(session, target, info_string):
    num_steps_burn_in = 10
    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)
        duration = time.time() - start_time
        if i >= num_steps_burn_in:
            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
    vr = total_duration_squared / num_batches - mn * mn
    sd = math.sqrt(vr)
    print('%s: %s across %d steps, %.3f +/- %.3f sec / batch' %
          (datetime.now(), info_string, num_batches, mn, sd))


if __name__ == '__main__':
    batch_size = 32
    height, width = 224, 224
    inputs = tf.random_uniform((batch_size, height, width, 3))
    with slim.arg_scope(resnet_arg_scope(is_training=False)):
        net, end_points = resnet_v2_152(inputs, 1000)

    init = tf.global_variables_initializer()
    sess = tf.Session()
    sess.run(init)
    num_batches = 100
    time_tensorflow_run(sess, net, "Forward")

  本文是學(xué)習(xí)ResNet網(wǎng)絡(luò)的筆記,參考了《tensorflow實(shí)戰(zhàn)》這本書中關(guān)于ResNet的章節(jié),寫的非常好,所以在此做了筆記,侵刪。

  而且本文在學(xué)習(xí)中,摘抄了下面博客的ResNet筆記,也寫的通俗易通:

  https://my.oschina.net/u/876354/blog/1634322     

  https://www.zybuluo.com/rianusr/note/1419006

  https://my.oschina.net/u/876354/blog/1622896

參考文獻(xiàn):https://blog.csdn.net/u013181595/article/details/80990930

     https://blog.csdn.net/lanran2/article/details/79057994

ResNet的論文文獻(xiàn): https://arxiv.org/abs/1512.03385

強(qiáng)烈建議學(xué)習(xí)何凱文關(guān)于深度殘差網(wǎng)絡(luò)的兩篇經(jīng)典論文,深度殘差網(wǎng)絡(luò)的主要思想,便是來(lái)自下面兩篇論文:

《Deep Residual Learning for Image Recognition》(基于深度殘差學(xué)習(xí)的圖像識(shí)別)

《Identity Mappings in Deep Residual Networks》(深度殘差網(wǎng)絡(luò)中的特征映射)

  在學(xué)習(xí)后,確實(shí)對(duì)ResNet 理解了不少,在此很感謝。

總結(jié)

以上是生活随笔為你收集整理的tensorflow学习笔记——ResNet的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。

狠狠夜夜 | 日本高清中文字幕有码在线 | 亚洲精品 在线视频 | 91精品国产91久久久久 | 欧美日韩在线视频观看 | 国精产品999国精产品岳 | 日日爱影视 | 久久久午夜精品福利内容 | 国产高清不卡一区二区三区 | 在线日韩| 久久久国产一区二区三区 | 婷婷伊人综合亚洲综合网 | 久久国产a | 五月激情av | 日韩在线中文字幕视频 | 91av网站在线观看 | 欧美污网站| 一级黄色免费网站 | 国产精品va在线播放 | 日韩在线观看视频免费 | 日韩免费视频观看 | 精品久久一区二区三区 | 国产福利av | a久久久久| 视频99爱 | www.久久成人| 97色婷婷人人爽人人 | 久久69精品| 玖玖在线视频观看 | 色综合久久久久久久 | 夜夜躁狠狠躁日日躁 | 日韩精品一区二区三区电影 | 九月婷婷人人澡人人添人人爽 | 国产白浆在线观看 | 成人av在线网址 | 国产精品一区二区三区电影 | 亚洲精品资源在线观看 | 超碰人人草 | av中文字幕av | 亚洲精品午夜一区人人爽 | 免费色网| 日韩欧美xx| a黄色一级片| 2023天天干 | 天堂av高清 | 久草在线免费新视频 | 国产精品一区二区三区免费视频 | 麻豆精品传媒视频 | 美女av免费 | 国产亚洲va综合人人澡精品 | 久久久久综合网 | www.久久久精品 | 日韩一区二区在线免费观看 | 国产高清精品在线 | 国产精品毛片久久久久久久久久99999999 | 视频直播国产精品 | 国产精品毛片一区二区三区 | 中文字幕成人在线 | 黄色影院在线免费观看 | 99资源网 | 精品一区av | 色综合亚洲精品激情狠狠 | 日韩在线不卡 | 午夜久久影视 | 久久经典视频 | 天天射天天射天天射 | 色丁香色婷婷 | 91视频高清完整版 | 99在线精品视频观看 | 成人香蕉视频 | 97在线观| 97看片网| 日韩美精品视频 | 中文字幕在线视频一区 | 色综合久久88 | 精品视频在线视频 | 99爱国产精品 | 国产91影院 | 欧美男男tv网站 | 日本最新一区二区三区 | 综合色久 | 成人免费观看大片 | 人人干天天射 | 日韩中文字幕a | 欧美在线视频一区二区三区 | 久久精品99国产精品 | 国产精品婷婷午夜在线观看 | 天天爱天天| 蜜臀av麻豆 | 亚洲婷婷伊人 | 亚洲动漫在线观看 | 天天天干天天天操 | 99精品免费久久久久久久久 | 一区二区av| 五月天天色 | 国产美女精品在线 | 国产精品影音先锋 | 成人av影视观看 | 国产色拍拍拍拍在线精品 | 成人网在线免费视频 | 国产精品第二十页 | 福利区在线观看 | 久久久香蕉视频 | 国产成人av综合色 | 国产在线91在线电影 | 最新久久免费视频 | 成人av影视观看 | 激情影音先锋 | 五月视频 | 亚洲黄色精品 | 最新中文字幕在线播放 | 黄色午夜 | 最新国产在线视频 | 婷婷色在线资源 | 亚洲专区路线二 | 91精品国产乱码 | 精品九九久久 | 黄色软件视频大全免费下载 | 草久在线| 91精品国产91久久久久福利 | 中文字幕免费在线看 | 午夜美女福利 | 精品高清美女精品国产区 | 天天操综合网站 | 日本中文字幕电影在线免费观看 | 人人插人人玩 | www天天操 | 精品国产一区二区三区av性色 | 午夜精品久久久久久久久久久 | 探花视频免费观看 | 99精品国产免费久久久久久下载 | a级国产乱理伦片在线播放 久久久久国产精品一区 | 久久五月精品 | 中文字幕 二区 | 色噜噜狠狠狠狠色综合 | 国产99久久久国产精品成人免费 | 国产视频九色蝌蚪 | 国产日产高清dvd碟片 | 国产高清在线免费 | 久久免费视频这里只有精品 | 91激情 | 99国产一区 | 999久久久国产精品 高清av免费观看 | 精品国产三级a∨在线欧美 免费一级片在线观看 | 美女精品在线观看 | 五月婷婷另类国产 | 亚洲精品tv久久久久久久久久 | 久久久麻豆视频 | 国产片免费在线观看视频 | 天天操天天射天天舔 | 中文区中文字幕免费看 | 久久avav | 国产超碰在线 | 国产在线色视频 | 日韩中文字幕在线观看 | 久久精品3 | 99在线精品视频在线观看 | 久久一区二区三区国产精品 | zzijzzij亚洲成熟少妇 | 免费黄色激情视频 | 欧美一级片 | 九九欧美| 国产亚洲精品久久久久久久久久久久 | 国产精品久久久区三区天天噜 | 日韩电影一区二区在线观看 | 国产一区在线视频播放 | 人人插人人 | 欧美成年人在线观看 | 韩国av免费看 | 国内精品久久久久影院男同志 | 综合五月 | 日韩欧美国产免费播放 | 精品亚洲午夜久久久久91 | 日韩电影一区二区三区在线观看 | 亚洲激情在线观看 | 国产成人黄色片 | 国产在线视频资源 | 国产成人精品久久久 | 成人精品电影 | 91香蕉视频色版 | 黄色毛片网站在线观看 | 开心色插 | 在线看成人av | 91在线产啪 | 欧美日韩精品免费观看视频 | 天天操欧美 | 亚洲国产中文字幕 | 日韩在线观看视频免费 | 99久久99久久精品 | 国产在线视频一区二区 | 成片免费观看视频999 | 九九爱免费视频 | 色网站黄 | 亚洲精品久久久久58 | 国内视频| 在线97| 国产一区二区日本 | 欧美成人影音 | 亚洲免费av片 | 97在线精品 | 欧美极度另类性三渗透 | 天天天天天天天天操 | 成人黄色电影免费观看 | 免费午夜视频在线观看 | 天堂av在线免费 | 天天操天天吃 | 激情综合色综合久久综合 | 国产成人福利在线观看 | 国产馆在线播放 | 国产精品永久久久久久久www | 免费福利在线 | 成人黄色电影在线播放 | 五月婷婷在线观看视频 | 久久久久久久久福利 | 欧美日韩91 | 色噜噜日韩精品一区二区三区视频 | 日韩视频www | 成人小视频免费在线观看 | 国产精品久久久久久吹潮天美传媒 | 欧美精品一区二区三区四区在线 | 国产精品久久久久久久免费 | 日韩av视屏 | 九九欧美视频 | 美女国产| 91桃色国产在线播放 | 婷婷色在线观看 | 国产免费一区二区三区最新 | 夜夜操天天操 | 亚洲片在线资源 | 亚洲va欧美va人人爽 | 国内精品久久久久久久97牛牛 | 欧美极品少妇xxxx | jizz18欧美18 | 免费看黄色91 | 国产一区二区不卡视频 | 最近日韩中文字幕中文 | 国产精品丝袜 | 一区二区三区在线不卡 | 久久久国产精品成人免费 | 香蕉视频4aa| 久久久久久99精品 | 9在线观看免费高清完整版在线观看明 | 夜夜摸夜夜爽 | 蜜桃av人人夜夜澡人人爽 | 婷婷黄色片 | 热久久在线视频 | 久久99精品国产麻豆宅宅 | 欧美999| 国内毛片毛片 | 色综合天天综合 | 欧美日韩视频免费看 | 91精品啪| 国产精品久久久久久久久久久杏吧 | www.夜夜操.com| 国产午夜一区 | 国产又粗又猛又色又黄视频 | 97在线免费视频观看 | 国产在线一区二区 | www久久久| 亚洲精品久久久久中文字幕m男 | 国产精品入口传媒 | av线上免费观看 | 欧美综合在线视频 | 97超碰资源| 国内精品久久久精品电影院 | 精品视频专区 | 免费在线一区二区三区 | 久久好看| 狠狠色综合网站久久久久久久 | 美女网站免费福利视频 | 超碰97久久| 日韩 | zzijzzij亚洲日本少妇熟睡 | 精品国产一区二区三区日日嗨 | 在线黄色观看 | 在线黄网站 | 日韩av看片 | 天天色天天搞 | 日韩av二区 | 成人免费观看视频网站 | 97激情影院| 国产精品99视频 | 亚洲免费不卡 | 国产97视频在线 | 精品国产亚洲一区二区麻豆 | 一性一交视频 | 人人插人人艹 | 亚洲精品视频在线观看免费视频 | 一区二区三区在线观看免费 | 一区二区视频欧美 | 婷婷色中文网 | 美女久久久久久久久久久 | 久久国产美女视频 | 精品国产一区二区三区久久久久久 | 亚洲免费高清视频 | 精品久久久久久久久久 | 91最新国产 | 国产精品福利av | 亚洲激情久久 | 国产一区高清在线观看 | 欧美色综合天天久久综合精品 | 国产伦精品一区二区三区无广告 | 麻豆国产视频下载 | 久热av在线| 国产91精品久久久久 | 夜夜躁日日躁狠狠躁 | 免费开视频 | 亚洲精品麻豆 | 99在线精品视频观看 | 国产日产在线观看 | 91av在线播放视频 | 久久久视频在线 | www毛片com| 日韩激情在线 | 超碰在线天天 | 欧美亚洲一级片 | 国产小视频福利在线 | 日韩在线观看中文字幕 | 亚洲国产日韩精品 | 午夜天使| 99久久影视 | 色综合天天综合网国产成人网 | 免费激情在线电影 | 日韩不卡高清视频 | av日韩av| 午夜私人影院久久久久 | 久久九九影院 | 一区二区三区观看 | 亚洲久草在线 | 91精品国产成 | 四川bbb搡bbb爽爽视频 | 成人蜜桃视频 | 五月开心婷婷 | 亚洲国产精品成人女人久久 | 五月天激情综合 | 久久欧美综合 | 日韩在线不卡视频 | 亚洲激情av | 日韩欧美一区二区在线 | 一区二区久久久久 | 天天色天天上天天操 | 中文在线a∨在线 | 欧美视频xxx| 天天色天天色 | 久久草在线视频国产 | 91成人精品一区在线播放69 | 激情久久久久久久久久久久久久久久 | 天天摸日日摸人人看 | 亚洲最大色 | 九九免费在线观看 | 国产麻豆精品在线观看 | 91精品入口| 亚洲干视频在线观看 | 久插视频| 在线天堂日本 | 久久国产精品精品国产色婷婷 | 久久伊人综合 | 国产美女视频网站 | 久久精品日产第一区二区三区乱码 | 午夜视频在线瓜伦 | 日韩国产精品久久久久久亚洲 | 国产精品自在线 | 日韩高清黄色 | 成人免费共享视频 | 97色婷婷成人综合在线观看 | 免费a级黄色毛片 | 九九影视理伦片 | 久久久久电影 | 久久精品视频在线观看 | 欧美一区二区免费在线观看 | 麻豆传媒视频在线播放 | 亚洲一级二级三级 | 日日干美女 | 91插插插免费视频 | 91久久久久久久 | 91在线你懂的 | 国产不卡精品 | 久草爱视频 | 一区二区三区四区五区在线 | 国产在线91在线电影 | 久久精品视频国产 | 久人人 | 天天干天天草天天爽 | 欧美在线视频不卡 | 日韩国产欧美视频 | 国产成人精品女人久久久 | 最近免费中文字幕mv在线视频3 | 日韩超碰在线 | 国产精品v a免费视频 | 亚洲一区二区三区精品在线观看 | 久艹视频在线免费观看 | 丁香六月在线 | 免费v片 | 国产亚洲精品成人av久久影院 | 中文字幕成人一区 | 欧美在线观看视频免费 | 亚洲视频2 | 色综合久久久久综合99 | 亚洲第一区在线观看 | 久久精品欧美日韩精品 | 国产精品va最新国产精品视频 | 亚洲免费视频在线观看 | 日本最新中文字幕 | 国产精品乱码高清在线看 | 丁香六月国产 | 日韩三级中文字幕 | 色国产在线 | 国产99久久久国产精品成人免费 | 成年人免费观看在线视频 | 国产精品v a免费视频 | 亚洲一级在线观看 | 国产免费av一区二区三区 | 亚洲国产大片 | 色资源中文字幕 | 黄色软件视频大全免费下载 | 欧美在线观看小视频 | 日韩一区二区三区高清在线观看 | 国产成人精品一区二区三区免费 | 99视频在线精品免费观看2 | 黄色三级免费看 | 五月婷婷视频在线 | 国模视频一区二区三区 | 蜜臀久久99精品久久久无需会员 | 欧美国产精品久久久久久免费 | 在线观看国产 | 国产一级特黄毛片在线毛片 | 狠狠色综合欧美激情 | 午夜国产福利在线 | 成人性生爱a∨ | 久久精品在线免费观看 | 在线 影视 一区 | 日韩二区三区在线观看 | 天天躁日日躁狠狠 | www五月天婷婷 | 一区二区不卡高清 | 色婷婷综合久久久中文字幕 | 有码中文字幕在线观看 | 色婷婷久久久 | 超碰97国产在线 | 久久免费在线观看 | 亚洲欧洲久久久 | 探花在线观看 | 中文字幕第 | 99色免费视频| 91亚洲网站 | 久久66热这里只有精品 | 欧美国产精品久久久久久免费 | 国产免费成人 | 激情五月在线观看 | 免费精品在线观看 | 伊人久久国产 | 综合色婷婷 | 成人蜜桃 | avwww在线观看| 久青草电影 | 五月开心六月伊人色婷婷 | 国产一级黄色电影 | 超碰九九 | 日韩视频1区 | 亚洲乱码在线观看 | 日本精品久久久久中文字幕 | 天天综合中文 | 久久经典国产 | 97超碰人 | 免费看的黄网站软件 | 成年人视频在线观看免费 | 在线观看中文字幕dvd播放 | 欧美天天综合 | 91在线播放国产 | 中文字幕欲求不满 | 国产视频九色蝌蚪 | 日韩精品一卡 | 91视频首页| 色黄www小说 | 久久国产色 | 国产精品毛片一区视频播 | 亚洲jizzjizz日本少妇 | 911av视频 | 国产精品18久久久久白浆 | 国产v在线播放 | 久久 在线 | 婷婷丁香色| 九九精品在线观看 | 国内精品久久影院 | 99热精品视 | 国产精品一区二区久久精品爱微奶 | 日韩欧美69 | 久久香蕉国产精品麻豆粉嫩av | 夜夜躁天天躁很躁波 | 在线观看一级 | 精品女同一区二区三区在线观看 | 综合国产在线 | 国产成人黄色网址 | 96久久欧美麻豆网站 | 在线视频精品播放 | 国产高清福利在线 | av播放在线| 午夜久久久精品 | www.av免费观看| 亚州av成人| 国产做a爱一级久久 | 黄色官网在线观看 | 国产高清永久免费 | 久久精品系列 | 嫩嫩影院理论片 | 久久久久久网址 | 五月天婷亚洲天综合网精品偷 | 国产精品久久久久久吹潮天美传媒 | 三级av在线免费观看 | 九九精品视频在线观看 | 日韩成人一级大片 | 久草在线免费资源 | 国产中文字幕三区 | 久久成年人网站 | 中文字幕免费一区 | 99久免费精品视频在线观看 | 日韩激情综合 | 在线免费观看一区二区三区 | 中文字幕成人网 | 久久久久久久久电影 | 欧美精品在线观看免费 | 麻豆va一区二区三区久久浪 | 欧洲一区二区在线观看 | 国产精品视频全国免费观看 | 黄色免费在线视频 | 国产高清在线观看 | 日韩电影在线观看一区二区 | 久久久www成人免费毛片 | 四虎精品成人免费网站 | ww亚洲ww亚在线观看 | 亚洲永久精品在线 | 国产精品99久久久久人中文网介绍 | 久久视频国产精品免费视频在线 | 日日干夜夜爱 | 曰本免费av | 成人黄色小说在线观看 | 99精品在线看 | 欧美成人在线免费 | 国产一区免费 | 人人玩人人添人人澡97 | 有码中文字幕在线观看 | 亚洲欧美日韩精品一区二区 | 欧美精品做受xxx性少妇 | 手机成人在线 | 国产欧美精品在线观看 | 国产精品久久久久久久久久99 | 久久国产精品小视频 | a天堂一码二码专区 | 国产激情电影综合在线看 | 天天色宗合 | 日韩精品免费一区二区三区 | 成人电影毛片 | 日韩夜夜爽 | 三级黄色片在线观看 | 国产一区电影在线观看 | 国产精品麻豆视频 | 麻豆精品91 | 久久久久国产a免费观看rela | 国内精品视频一区二区三区八戒 | 国产91影院 | 永久av免费在线观看 | 色视频网址 | 中文字幕人成不卡一区 | a电影在线观看 | 91人人人| 久久久久女人精品毛片九一 | 国产精品久免费的黄网站 | 日韩黄色免费 | 久草在线资源免费 | 精品视频专区 | 激情网站| 天天视频色 | 国产成人一区二区三区影院在线 | 精品久久久久久久久久久院品网 | 日韩电影在线一区二区 | 欧美日韩中文国产一区发布 | 亚洲一区视频在线播放 | 国产中文在线字幕 | 日韩免费观看高清 | 精品国产不卡 | 国产91学生| 一区二区三区日韩视频在线观看 | 在线观看黄av | 人人射人人爱 | 五月婷在线 | 国产免费不卡av | 天天爽人人爽夜夜爽 | 亚洲一级电影视频 | 99久久综合国产精品二区 | 爱射综合 | 国产综合久久 | 综合久久网 | 日批视频国产 | 国产又粗又硬又爽视频 | 色天天综合久久久久综合片 | 三日本三级少妇三级99 | 日韩高清一区 | 成人av动漫在线 | 中文字幕在线日亚洲9 | 亚洲视频电影在线 | 日本精品二区 | 国产精品一区二区三区久久 | 日韩在线观看视频免费 | 欧美日韩啪啪 | 色综合久久久久综合体 | 玖玖视频免费在线 | 午夜精品一二三区 | 日韩一区精品 | 97超碰人人模人人人爽人人爱 | 免费看污网站 | 毛片1000部免费看 | 天天操天天操天天操天天操 | 国产成人精品久久久久 | 久久免费观看少妇a级毛片 久久久久成人免费 | 国产一区免费在线观看 | 日日干影院 | 波多野结衣在线视频一区 | 337p西西人体大胆瓣开下部 | 夜夜躁日日躁狠狠躁 | 国产欧美精品一区aⅴ影院 99视频国产精品免费观看 | 久久精品一二区 | 精品影院一区二区久久久 | 99在线精品视频在线观看 | 激情小说网站亚洲综合网 | 一区二区三区在线电影 | av电影免费在线看 | 亚洲激情在线视频 | 中文字幕一区二区在线观看 | 最新午夜 | 69国产盗摄一区二区三区五区 | 国产精品九九九九九九 | 欧美日韩午夜爽爽 | 日韩成人免费电影 | 96精品高清视频在线观看软件特色 | 久久99精品久久久久久三级 | 亚洲人成精品久久久久 | 欧美性受极品xxxx喷水 | 久久香蕉电影网 | 玖玖999 | 奇米四色影狠狠爱7777 | 国产亚洲欧洲 | 亚洲精品国产拍在线 | 黄色av网站在线观看 | 少妇搡bbbb搡bbb搡aa | 99久久精品久久久久久清纯 | 久久av在线播放 | 天天爱天天操天天干 | 狠狠干天天 | 国产高清在线 | 天天做天天爱天天综合网 | 国产精品一区二区中文字幕 | 亚洲一级理论片 | 成人av免费播放 | 久草在线高清视频 | 国产福利在线不卡 | 精品主播网红福利资源观看 | 在线欧美国产 | 国产日韩欧美视频在线观看 | 色五月激情五月 | 亚洲视频精选 | 96精品视频 | 综合色久| 在线观看不卡视频 | 91色网址 | 国产精品久久久久久久久久久久午夜 | 日本不卡一区二区三区在线观看 | 久久狠狠亚洲综合 | a√国产免费a | 少妇高潮流白浆在线观看 | 中文视频一区二区 | 男女啪啪网站 | 91精品久久久久 | 国产精品9999久久久久仙踪林 | 日本超碰在线 | 99免费国产 | 久久久精品欧美 | 手机成人av在线 | 日日操网| 成人久久久久久久久 | 99色在线播放 | 黄在线免费观看 | 成人黄色短片 | 狠狠操欧美 | 日韩精品视频在线观看免费 | www.色就是色 | 精品国产观看 | 国产高清视频在线观看 | 亚洲精品久久视频 | 精品一区精品二区高清 | 日韩aⅴ视频| 国产精品第72页 | 四虎天堂| 国产成人精品亚洲a | 麻豆一精品传二传媒短视频 | 欧美精品一区二区免费 | 午夜精品一区二区三区在线 | 日本黄色免费播放 | www.久久色 | 国产欧美在线一区 | 欧美性生活大片 | 亚洲欧美日韩精品久久奇米一区 | 伊人成人精品 | 成人黄色免费在线观看 | 久久国产二区 | 一区二区电影在线观看 | 又色又爽又黄高潮的免费视频 | 成人午夜剧场在线观看 | 久久成人18免费网站 | 色干干 | 免费日韩一区二区三区 | 精品视频久久久久久 | 久草在线久草在线2 | 91网站在线视频 | a级黄色片视频 | 成人av网站在线 | 亚洲欧美综合 | 国产精品欧美久久久久天天影视 | 高清一区二区 | 人人看人人草 | 免费 在线 中文 日本 | 中文字幕乱偷在线 | 国产精品久久久久久久久久久杏吧 | 久久96国产精品久久99漫画 | 久久久九九 | 黄色福利视频网站 | 免费在线黄色av | 欧美日韩中 | 天天草天天干天天 | 91日韩在线视频 | 三级a视频 | 成人免费观看av | 色婷婷国产精品一区在线观看 | av888.com| 亚洲涩涩涩 | 日韩欧美视频在线观看免费 | 在线你懂的视频 | 免费a视频在线观看 | 久久精品女人毛片国产 | 91丨九色丨91啦蝌蚪老版 | 国产色网站 | 久久婷婷精品 | 国内免费的中文字幕 | 久久艹在线观看 | 伊人在线视频 | 色姑娘综合 | 天天干,夜夜爽 | 91在线中文字幕 | 国产精品理论片在线观看 | 在线91观看 | 久久国产亚洲精品 | 天天操天天摸天天射 | 日日夜夜爱 | 日韩在线视频二区 | 成人亚洲网 | 亚州中文av | 99久久这里有精品 | 久久久精品国产一区二区 | 成人免费网站视频 | 中文字幕在线观看91 | 日韩av看片 | 久久精品国产久精国产 | 久操视频在线免费看 | 丰满少妇对白在线偷拍 | 奇米网777 | 精品久久久久久久久久久久久久久久 | 日韩高清 一区 | 国产剧情在线一区 | 欧美日韩一区二区免费在线观看 | 一区二区不卡高清 | 天天爱综合| 久久99精品久久久久婷婷 | 国产h在线播放 | 夜夜澡人模人人添人人看 | 丁香 久久 综合 | 亚洲人成精品久久久久 | 国内精品久久天天躁人人爽 | 嫩草av影院 | 国产精品毛片久久久久久 | 中文字幕制服丝袜av久久 | 99精品免费久久久久久日本 | 国产美女被啪进深处喷白浆视频 | 日韩免费视频线观看 | 国产一二区精品 | 99免费精品视频 | 欧美日韩高清在线观看 | 日韩羞羞| www.神马久久| 久久久福利视频 | 亚洲日本精品视频 | 国产精品久久久久久久久久妇女 | 99国产视频 | 久草在线最新免费 | a视频免费| 91av在线免费观看 | 国产精品美女久久久久久久 | 久久久久麻豆v国产 | 日韩一区在线免费观看 | 国产亚洲精品美女久久 | www在线免费观看 | 国产精品自产拍在线观看桃花 | www久 | 四虎影视成人永久免费观看视频 | 一级成人免费视频 | 不卡精品 | 99麻豆视频| 国产中文字幕在线播放 | 美女黄频免费 | 女人18片 | 91成人观看| 中文字幕第| 国产精品国产精品 | 中文字幕在线中文 | 欧美性色综合网站 | 特黄特色特刺激视频免费播放 | 国产在线视频不卡 | 在线观看精品国产 | 亚洲桃花综合 | 一区二区欧美激情 | 国产黄大片 | 免费在线观看亚洲视频 | 激情网站网址 | 国产日韩精品一区二区 | 91精品在线观看入口 | 香蕉日日| 国产五月色婷婷六月丁香视频 | 久久一区二区三区国产精品 | av中文字幕免费在线观看 | 成人app在线免费观看 | 欧美性免费 | www.超碰 | 91精品视频观看 | av资源网在线播放 | 99 久久久久 | 91爱爱免费观看 | 四虎成人在线 | av三级在线免费观看 | 日本中文字幕在线一区 | 丁香高清视频在线看看 | 在线你懂的视频 | 欧美美女视频在线观看 | 久久高清片 | 高清一区二区三区av | 国内精品99 | 久草影视在线观看 | 国产在线精品视频 | 中文字幕在线网址 | 国产亚洲精品久久久久久久久久 | 国产理论片在线观看 | 91在线观看黄| 日韩一级电影在线观看 | 色综合久久88色综合天天免费 | 国产精品一区二区免费在线观看 | 精品美女在线观看 | 成人精品视频久久久久 | 在线精品视频免费播放 | 欧美日韩免费一区二区三区 | 黄色高清视频在线观看 | 国产精品成人久久久久 | 国产黄色美女 | 国产视频91在线 | 国产高清视频免费在线观看 | 国产精品都在这里 | 日韩欧美一区二区在线观看 | 色噜噜日韩精品一区二区三区视频 | 久久国产一区二区 | 区一区二区三在线观看 | 成人黄色免费在线观看 | 手机av看片 | 国产精品99久久99久久久二8 | 日韩3区 | 久久国产热 | 国内成人精品视频 | 日韩成人精品一区二区 | 99视频精品免费观看, | 99久e精品热线免费 99国产精品久久久久久久久久 | 最近中文字幕在线 | 日韩免费在线观看视频 | 日韩动漫免费观看高清完整版在线观看 | 亚洲人成人天堂h久久 | 久久久久久久久久久久久国产精品 | 欧美韩国日本在线观看 | 人人爽人人爽人人爽 | 欧美成人a在线 | 91视频在线自拍 | 四虎8848免费高清在线观看 | 九色最新网址 | 在线观看亚洲视频 | 久草在线视频资源 | 亚洲污视频 | 99久久久国产精品 | 久久久99国产精品免费 | 98超碰在线| 午夜精品电影 | 国产色拍拍拍拍在线精品 | 青青久草在线视频 | 一区二区三区日韩在线观看 | 国产人成在线观看 | 欧美精品一区二区蜜臀亚洲 | 黄色a级片在线观看 | 精品国产aⅴ麻豆 | 亚洲精品久久久久久中文传媒 | 中文一区二区三区在线观看 | 婷婷天天色 | 国产中文视频 | 久久网站最新地址 | 久久99国产精品视频 | 黄色国产在线观看 | 日本久久久影视 | 性色大片在线观看 | 在线а√天堂中文官网 | 欧美与欧洲交xxxx免费观看 | 国产精品青草综合久久久久99 | 日本不卡一区二区三区在线观看 | 日韩免费高清在线 | 色www免费视频 | 亚洲国产精品资源 | 欧美成年黄网站色视频 | 人人涩 | 在线观看视频在线 | 欧美日高清视频 | 毛片久久久 | 最新国产精品久久精品 | 精品久久久久久久久久久久久久久久久久 | 亚洲1区 在线 | 在线观看免费视频你懂的 | 亚洲精品在线观看视频 | 中文字幕传媒 | 久久久久一区二区三区四区 | 中文字幕 91 | 日韩极品视频在线观看 | 亚洲男男gaygay无套同网址 | 五月婷婷在线观看视频 | 综合久久久久久久 | 久久久久99精品成人片三人毛片 | 免费成人av| 波多野结依在线观看 | 精品国产伦一区二区三区观看说明 | 亚洲国产日韩精品 | 在线成人中文字幕 | 欧美一级免费在线 | 久久久久久久久久影视 | 色福利网站 | 中文字幕精品久久 | 国产九九在线 | 天天操夜夜操 | 国产99免费视频 | 狠狠干综合网 | 亚洲理论片在线观看 | 日本三级中文字幕在线观看 | 日韩欧美视频 | 久久成人18免费网站 | 国产精品欧美一区二区 | 深爱激情久久 | 91麻豆免费看 | 欧美日韩免费一区 | 亚洲一区二区黄色 | 日韩三级精品 | 999国内精品永久免费视频 | 玖玖在线精品 | 亚洲一区二区三区四区在线视频 | 成人精品一区二区三区电影免费 | 在线黄色国产电影 | 国产精品日韩欧美 | 色美女在线 | 一二区电影 | 美女国产 | 狠狠操天天干 | 免费的黄色的网站 | 国产在线自| 亚洲传媒在线 | 国产精品va在线观看入 | 免费观看的av | 国内精品视频在线 | 女人久久久久 | 成人啪啪18免费游戏链接 | 成 人 黄 色 片 在线播放 | 欧美污网站 | 国产中文欧美日韩在线 | 欧洲高潮三级做爰 | 色综合天天色 | 成人观看| 久久精品视频在线播放 | 天天天色 | 成人免费91 | 欧美极品少妇xbxb性爽爽视频 | 亚洲视频一级 | 高清不卡一区二区三区 | 日韩av线观看 | 国内精品亚洲 | 国产亚洲综合性久久久影院 | 天天搞夜夜骑 | 国产91综合一区在线观看 | 色综久久 | 中文字幕在线观看网站 | 国产中文字幕91 | 特级毛片网 | 免费高清在线观看电视网站 | 久久成人高清 |