深入理解ResNet原理解析及代码实现
github地址:https://github.com/pytorch/vision/blob/master/torchvision/models/resnet.py
論文地址:https://arxiv.org/pdf/1512.03385.pdf
解決什么問題
??? Is learning better networks as easy as stacking more layers? An obstacle to answering this question was the notorious problem of vanishing/exploding gradients [1, 9], which hamper convergence(收斂) from the beginning.
??? This problem, however, has been largely addressed by normalized initialization [23, 9, 37, 13] and intermediate normalization layers [16], which enable networks with tens of layers to start converging for stochastic gradient descent (SGD) with backpropagation [22].
梯度消失和梯度爆炸的問題阻止了剛開始的收斂,這一問題通過初始化歸一化和中間層歸一化得到了解決。
??? a degradation problem has been exposed: with the network depth increasing, accuracy gets saturated (which might be unsurprising) and then degrades rapidly. Unexpectedly, such degradation is not caused by overfitting, and adding more layers to a suitably deep model leads to higher training error.
解決了收斂的問題后又出現了退化的現象:隨著層數加深,準確率升高然后急劇下降。且這種退化不是由過擬合造成,且向網絡中添加適當多層導致了更大的訓練誤差。
總結一下,隨著網絡深度的增加,模型精度并不總是提升,并且這個問題并不是由過擬合(overfitting)造成的,因為網絡加深后不僅測試誤差變高了,它的訓練誤差竟然也變高了。作者提出,這可能是因為更深的網絡會伴隨梯度消失/爆炸問題,從而阻礙網絡的收斂。這種加深網絡深度但網絡性能卻下降的現象被稱為退化問題。
也就是說,隨著深度的增加出現了明顯的退化,網絡的訓練誤差和測試誤差均出現了明顯的增長,ResNet就是為了解決這種退化問題而誕生的。
如何解決
??? Let us consider a shallower architecture and its deeper counterpart that adds more layers onto it. There exists a solution by construction to the deeper model: the added layers are identity mapping and the other layers are copied from the learned shallower model. The existence of this constructed solution indicates that a deeper model should produce no higher training error than its shallower counterpart.
于是作者提出了解決方案:在一個較淺的架構上添加更多層。 對于更深層次的模型:添加的層是恒等映射(identity mapping),其他層則從學習的淺層模型復制。在這種情況下,更深的模型不應該產生比其對應的較淺的網絡更高的訓練誤差。
殘差學習基本單元:
?
??? Formally, denoting the desired underlying mapping as H(x), we let the stacked nonlinear layers fit another mapping of F(x) := H(x)?x. The original mapping is recast into F(x)+x. We hypothesize that it is easier to optimize the residual mapping than to optimize the original, unreferenced mapping.
原先的網絡輸入x,希望輸出H(x)。現在我們令H(x)=F(x)+x,那么我們的網絡就只需要學習輸出一個殘差F(x)=H(x)-x。
假設輸入為 x,有兩層全連接層學習到的映射為H(x),也就是說這兩層可以漸進(asymptotically)擬合H(x)。假設 H(x)與x維度相同,那么擬合 H(x) 與擬合殘差函數 H(x)-x 等價,令殘差函數 F(x)=H(x)-x,則原函數變為 F(x)+x ,于是直接在原網絡的基礎上加上一個跨層連接,這里的跨層連接也很簡單,就是 將x的**恒等映射(Identity Mapping)**傳遞過去。
本質也就是不改變目標函數 H(x) ,將網絡結構拆成兩個分支,一個分支是殘差映射F(x),一個分支是恒等映射x ,于是網絡僅需學習殘差映射F(x) 即可。
為何有效
??? 自適應深度:網絡退化問題就體現了多層網絡難以擬合恒等映射這種情況,也就是說 H(x)難以擬合 x ,但使用了殘差結構之后,擬合恒等映射變得很容易,直接把網絡參數全學習到為0,只留下那個恒等映射的跨層連接即可。于是當網絡不需要這么深時,中間的恒等映射就可以多一點,反之就可以少一點。
??? “差分放大器”:假設最優 H(x)更接近恒等映射,那么網絡更容易發現除恒等映射之外微小的波動
??? 緩解梯度消失:針對一個殘差結構對輸入 x 求導就可以知道,由于跨層連接的存在,總梯度在 F(x) 對 x 的導數基礎上還會加1
普通網絡與深度殘差網絡的最大區別在于**,深度殘差網絡有很多旁路的支線將輸入直接連到后面的層,使得后面的層可以直接學習殘差**,這些支路就叫做shortcut。傳統的卷積層或全連接層在信息傳遞時,或多或少會存在信息丟失、損耗等問題。ResNet 在某種程度上解決了這個問題,通過直接將輸入信息繞道傳到輸出,保護信息的完整性,整個網絡則只需要學習輸入、輸出差別的那一部分,簡化學習目標和難度。
網絡實現
ResNet主要有五種主要形式:Res18,Res34,Res50,Res101,Res152;
如下圖所示,每個網絡都包括三個主要部分:輸入部分、輸出部分和中間卷積部分(中間卷積部分包括如圖所示的Stage1到Stage4共計四個stage)。盡管ResNet的變種形式豐富,但都遵循上述的結構特點,網絡之間的不同主要在于中間卷積部分的block參數和個數存在差異。下面我們以ResNet18為例,看一下整個網絡的實現代碼是怎樣的。
class ResNet(nn.Module):def forward(self, x):# 輸入x = self.conv1(x)x = self.bn1(x)x = self.relu(x)x = self.maxpool(x)# 中間卷積x = self.layer1(x)x = self.layer2(x)x = self.layer3(x)x = self.layer4(x)# 輸出x = self.avgpool(x)x = x.view(x.size(0), -1)x = self.fc(x)return x# 生成一個res18網絡 def resnet18(pretrained=False, **kwargs):model = ResNet(BasicBlock, [2, 2, 2, 2], **kwargs)if pretrained:model.load_state_dict(model_zoo.load_url(model_urls['resnet18']))return model?
(1)數據進入網絡后先經過輸入部分(conv1, bn1, relu, maxpool);
(2)然后進入中間卷積部分(layer1, layer2, layer3, layer4,這里的layer對應我們之前所說的stage);
(3)最后數據經過一個平均池化和全連接層(avgpool, fc)輸出得到結果;
具體來說,resnet18和其他res系列網絡的差異主要在于layer1~layer4,其他的部件都是相似的。
網絡輸入部分
所有的ResNet網絡輸入部分是一個size=7x7, stride=2的大卷積核,以及一個size=3x3, stride=2的最大池化組成,通過這一步,一個224x224的輸入圖像就會變56x56大小的特征圖,極大減少了存儲所需大小。
self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False) self.bn1 = nn.BatchNorm2d(64) self.relu = nn.ReLU(inplace=True) self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)?
網絡中間卷積部分
中間卷積部分主要是下圖中的藍框部分,通過3*3卷積的堆疊來實現信息的提取。紅框中的[2, 2, 2, 2]和[3, 4, 6, 3]等則代表了bolck的重復堆疊次數。
?
剛剛我們調用的resnet18( )函數中有一句 ResNet(BasicBlock, [2, 2, 2, 2], *kwargs),這里的[2, 2, 2, 2]與圖中紅框是一致的,如果你將這行代碼改為 ResNet(BasicBlock, [3, 4, 6, 3], *kwargs), 那你就會得到一個res34網絡。
殘差塊實現
下面我們來具體看一下一個殘差塊是怎么實現的,如下圖所示的basic-block,輸入數據分成兩條路,一條路經過兩個3*3卷積,另一條路直接短接,二者相加經過relu輸出,十分簡單。
?
class BasicBlock(nn.Module):expansion = 1def __init__(self, inplanes, planes, stride=1, downsample=None):super(BasicBlock, self).__init__()self.conv1 = conv3x3(inplanes, planes, stride)self.bn1 = nn.BatchNorm2d(planes)self.relu = nn.ReLU(inplace=True)self.conv2 = conv3x3(planes, planes)self.bn2 = nn.BatchNorm2d(planes)self.downsample = downsampleself.stride = stridedef forward(self, x):identity = xout = self.conv1(x)out = self.bn1(out)out = self.relu(out)out = self.conv2(out)out = self.bn2(out)if self.downsample is not None:identity = self.downsample(x)out += identityout = self.relu(out)return out???
網絡輸出部分
?
網絡輸出部分很簡單,通過全局自適應平滑池化,把所有的特征圖拉成1*1,對于res18來說,就是1x512x7x7 的輸入數據拉成 1x512x1x1,然后接全連接層輸出,輸出節點個數與預測類別個數一致。
self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
self.fc = nn.Linear(512 * block.expansion, num_classes)
網絡特點
?
整個ResNet不使用dropout,全部使用BN。此外:
- ??? 受VGG的啟發,卷積層主要是3×3卷積;
- ??? 對于相同的輸出特征圖大小的層,即同一stage,具有相同數量的3x3濾波器;
- ??? 如果特征地圖大小減半,濾波器的數量加倍以保持每層的時間復雜度;
- ??? 每個stage通過步長為2的卷積層執行下采樣,而卻這個下采樣只會在每一個stage的第一個卷積完成,有且僅有一次。
- ??? 網絡以平均池化層和softmax的1000路全連接層結束,實際上工程上一般用自適應全局平均池化 (Adaptive Global Average Pooling);
從圖中的網絡結構來看,在卷積之后全連接層之前有一個全局平均池化 (Global Average Pooling, GAP) 的結構。
??? In this paper, we propose another strategy called global average pooling to replace the traditional fully connected layers in CNN. The idea is to generate one feature map for each corresponding category of the classification task in the last mlpconv layer. Instead of adding fully connected layers on top of the feature maps, we take the average of each feature map, and the resulting vector is fed directly into the softmax layer. One advantage of global average pooling over the fully connected layers is that it is more native to the convolution structure by enforcing correspondences between feature maps and categories. Thus the feature maps can be easily interpreted as categories confidence maps. Another advantage is that there is no parameter to optimize in the global average pooling thus overfitting is avoided at this layer. Futhermore, global average pooling sums out the spatial information, thus it is more robust to spatial translations of the input.
??? We can see global average pooling as a structural regularizer that explicitly enforces feature maps to be con?dence maps of concepts (categories).This is made possible by the mlpconv layers, as they makes better approximation to the con?dence maps than GLMs.
總結如下:
??? 相比傳統的分類網絡,這里接的是池化,而不是全連接層。池化是不需要參數的,相比于全連接層可以砍去大量的參數。對于一個7x7的特征圖,直接池化和改用全連接層相比,可以節省將近50倍的參數,作用有二:一是節省計算資源,二是防止模型過擬合,提升泛化能力;
??? 這里使用的是全局平均池化,一些論文的**實驗結果表明平均池化的效果略好于最大池化,但最大池化的效果也差不到哪里去。**實際使用過程中,可以根據自身需求做一些調整,比如多分類問題更適合使用全局最大池化(需試驗)。
ResNet的常見改進
??? 改進一:改進downsample部分,減少信息流失。
??? 改進二:ResNet V2。論文地址:https://arxiv.org/abs/1603.05027
————————————————
版權聲明:本文為CSDN博主「圖南www」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/qq_42372629/article/details/100115649
總結
以上是生活随笔為你收集整理的深入理解ResNet原理解析及代码实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 深度残差网络和Highway网络
- 下一篇: 高速服务区应急处理方案怎么写