什麼是ResNet,本項目給大家介紹殘差網路ResNet。
ResNet是一種殘差網路,咱們可以先簡單看一下ResNet的結構,再對它的結構進行詳細介紹。

從圖可以看出,殘差網路是由多個結構類似的塊堆疊起來的,這樣的塊是殘差網路的基本單元(稱為殘差塊),ResNet是由多個這樣的殘差塊堆疊起來的。
殘差塊長這樣:

那麼可能會有小夥伴疑問,幹嘛非要用殘差塊來構建這麼一個深層網路呢?幹嘛不直接用卷積層對網路進行一個堆疊呢?
為什麼要引入ResNet?
我們知道,網路越深,咱們能獲取的信息越多,而且特徵也越豐富。但是根據實驗表明,隨著網路的加深,優化效果反而越差,測試數據和訓練數據的準確率反而降低了。這是由於網路的加深會造成梯度爆炸和梯度消失的問題。

網路加深時測試錯誤率和訓練錯誤率顯示
目前針對這種現象已經有了解決的方法:對輸入數據和中間層的數據進行歸一化操作,這種方法可以保證網路在反向傳播中採用隨機梯度下降(SGD),從而讓網路達到收斂。但是,這個方法僅對幾十層的網路有用,當網路再往深處走的時候,這種方法就無用武之地了。
為了讓更深的網路也能訓練出好的效果,何凱明大神提出了一個新的網路結構——ResNet。這個網路結構的想法主要源於VLAD(殘差的想法來源)和Highway Network(跳躍連接的想法來源)。
ResNet詳細解說
再放一遍ResNet結構圖。要知道咱們要介紹的核心就是這個圖啦!(ResNet block有兩種,一種兩層結構,一種三層結構)
兩種ResNet block(代碼給出了兩種殘差塊以供選擇)
咱們要求解的映射為:H(x)
現在咱們將這個問題轉換為求解網路的殘差映射函數,也就是F(x),其中F(x) = H(x)-x。

殘差:觀測值與輸入值之間的差。
這裡H(x)就是觀測值,x就是輸入值(也就是上一層ResNet輸出的特徵映射)。
我們一般稱x為identity Function,它是一個跳躍連接;稱F(x)為殘差映射ResNet Function。
那麼咱們要求解的問題變成了H(x) = F(x)+x。
有小夥伴可能會疑惑,咱們幹嘛非要經過F(x)之後再求解H(x)啊?X的跳躍連接有什麼好處嗎?
因為如果是採用一般的卷積神經網路的化,原先咱們要求解的是H(x) = F(x)這個值對不?ResNet相當於將學習目標改變了,不再是學習一個完整的輸出H(x),只是輸出和輸入的差別H(x)-x,即殘差。學習一個微小的波動F(x)不比學習一個整個x更容易嗎?X的跳躍連接除了讓網路的學習有了基礎,在梯度反向傳播時也能更直接的傳到前面的層去。
殘差塊
殘差塊通過跳躍連接shortcut connection實現,通過shortcut將這個block的輸入和輸出進行一個逐點element-wise的加疊,這個簡單的加法並不會給網路增加額外的參數和計算量,同時卻可以大大增加模型的訓練速度、提高訓練效果,並且當模型的層數加深時,這個簡單的結構能夠很好的解決退化問題。
注意:如果殘差映射(F(x))的結果的維度與跳躍連接(x)的維度不同,那咱們是沒有辦法對它們兩個進行相加操作的,必須對x進行升維操作,讓他倆的維度相同時才能計算。
升維的方法有兩種:
1、用0填充;
2、採用1*1的卷積。一般都是採用1*1的卷積。
#以下是代碼:
#導入庫
mport torch
import torch.nn as nn
import torch.nn.functional as F
from torch.autograd import Variable
#定義殘差塊(BasicBlock是小殘差塊,Bottleneck是大殘差塊)
class BasicBlock(nn.Module):#定義block
expansion = 1
def __init__(self, in_channels, channels, stride=1, downsample=None):#輸入通道,輸出通道,stride,下採樣
super(BasicBlock, self).__init__()
self.conv1 = conv3x3(in_channels, channels, stride)
self.bn1 = nn.BatchNorm2d(channels)
self.relu = F.relu(inplace=True)
self.conv2 = conv3x3(channels, channels)
self.bn2 = nn.BatchNorm2d(channels)
self.downsample = downsample
self.stride = stride
def forward(self, x):
residual = x
out = 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:
residual = self.downsample(x)
out += residual
out = self.relu(out)
return out#block輸出
class Bottleneck(nn.Module):
expansion = 4
def __init__(self, in_planes, planes, stride=1):
super(Bottleneck, self).__init__()
self.conv1 = nn.Conv2d(in_planes, planes, kernel_size=1, bias=False)
self.bn1 = nn.BatchNorm2d(planes)
self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride, padding=1, bias=False)
self.bn2 = nn.BatchNorm2d(planes)
self.conv3 = nn.Conv2d(planes, self.expansion*planes, kernel_size=1, bias=False)
self.bn3 = nn.BatchNorm2d(self.expansion*planes)
self.shortcut = nn.Sequential()
if stride != 1 or in_planes != self.expansion*planes:
self.shortcut = nn.Sequential(
nn.Conv2d(in_planes, self.expansion*planes, kernel_size=1, stride=stride, bias=False),
nn.BatchNorm2d(self.expansion*planes)
)
def forward(self, x):
out = F.relu(self.bn1(self.conv1(x)))
out = F.relu(self.bn2(self.conv2(out)))
out = self.bn3(self.conv3(out))
out += self.shortcut(x)
out = F.relu(out)
return out
#定義殘差網路
class ResNet(nn.Module):
def __init__(self, block, num_blocks, num_classes=9,embedding_size=256):
super(ResNet, self).__init__()
self.in_planes = 64
self.conv1 = nn.Conv2d(1, 64, kernel_size=3, stride=1, padding=1, bias=False)
self.bn1 = nn.BatchNorm2d(64)
self.layer1 = self._make_layer(block, 64, num_blocks[0], stride=1)
self.layer2 = self._make_layer(block, 128, num_blocks[1], stride=2)
self.layer3 = self._make_layer(block, 256, num_blocks[2], stride=2)
self.layer4 = self._make_layer(block, 512, num_blocks[3], stride=2)
self.avg_pool = nn.AdaptiveAvgPool2d([4, 1])
self.fc=nn.Linear(512*4, embedding_size)
self.linear = nn.Linear(embedding_size, num_classes)
def _make_layer(self, block, planes, num_blocks, stride):
strides = [stride] + [1]*(num_blocks-1)
layers = []
for stride in strides:
layers.append(block(self.in_planes, planes, stride))
self.in_planes = planes * block.expansion
return nn.Sequential(*layers)
def forward(self, x):
x = torch.tensor(x, dtype=torch.float32)
out = F.relu(self.bn1(self.conv1(x)))
out = self.layer1(out)
out = self.layer2(out)
out = self.layer3(out)
out = self.layer4(out)
out =self.avg_pool(out)
out = out.view(out.size(0), -1)
embedding=self.fc(out)
out = self.linear(embedding)
return out,embedding
#從18層的到101層的,可以根據自己需要選擇網路大小,大的網路選用了大的殘差塊,
#第一個參數指明用哪個殘差塊,第二個參數是一個列表,指明殘差塊的數量。
def ResNet18():
return ResNet(BasicBlock, [2,2,2,2])
def ResNet34():
return ResNet(BasicBlock, [3,4,6,3])
def ResNet50():
return ResNet(Bottleneck, [3,4,6,3])
def ResNet101():
return ResNet(Bottleneck, [3,4,23,3])
def ResNet152():
return ResNet(Bottleneck, [3,8,36,3])
總結:在使用了ResNet的結構後,可以發現層數不斷加深導致的訓練集上誤差增大的現象被消除了,ResNet網路的訓練誤差會隨著層數增加而逐漸減少,並且在測試集上的表現也會變好。原因在於,Resnet學習的是殘差函數F(x) = H(x) – x, 這裡如果F(x) = 0, 那麼就是上面提到的恆等映射。事實上,resnet是「shortcut connections」的,在connections是在恆等映射下的特殊情況,學到的殘差為0時,它沒有引入額外的參數和計算複雜度,且不會降低精度。 在優化目標函數是逼近一個恆等映射 identity mapping, 而學習的殘差不為0時, 那麼學習找到對恆等映射的擾動會比重新學習一個映射函數要更容易。
參考論文: Deep Residual Learning for Image Recognition
殘差網路有兩個版本,ResNet_v1和ResNet_v2,這兩者有何區別,為啥大多用的是ResNet_v2,它有什麼優良的性質呢,下一篇殘差網路深度解析為您解答。
原創文章,作者:投稿專員,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/219112.html