在何大佬的文章中承耿,提出了下面兩種殘差塊:
左邊的稱為building block墓捻,右邊的稱為bottleneck building block李请。
- 左邊的輸入和輸出都是64個(gè)channel的藤抡,四四方方的,像個(gè)建筑物硝枉;
- 右邊的就好像通過(guò)了一個(gè)瓶頸一樣廉丽,輸入殘差塊的網(wǎng)絡(luò)通道數(shù)會(huì)先從256變成64,然后最終再升到256妻味,其中降維和升維使用的是1x1的卷積正压,可以減少參數(shù)量;
代碼
Building Block弧可,就是之前寫(xiě)的(換了激活函數(shù)):
class ResidualBlock(nn.Module):
def __init__(self, in_channels):
super(ResidualBlock, self).__init__()
self.conv1 = nn.Conv2d(in_channels, in_channels, kernel_size=3, padding=1)
self.bn1 = nn.BatchNorm2d(in_channels)
self.relu = nn.ReLU()
self.conv2 = nn.Conv2d(in_channels, in_channels, kernel_size=3, padding=1)
self.bn2 = nn.BatchNorm2d(in_channels)
def forward(self, x):
residual = self.conv1(x)
residual = self.bn1(residual)
residual = self.relu(residual)
residual = self.conv2(residual)
residual = self.bn2(residual)
out = self.relu(x + residual)
return out
BottleNeck的代碼稍微改下就行:
class BottleNeck(nn.Module):
def __init__(self,in_channels):
super(BottleNeck, self).__init__()
self.main = nn.Sequential(
nn.Conv2d(in_channels,64,kernel_size=1,stride=1,padding=0),
nn.BatchNorm2d(64),
nn.ReLU(),
nn.Conv2d(64,64,kernel_size=3,stride=1,padding=1),
nn.BatchNorm2d(64),
nn.ReLU(),
nn.Conv2d(63,in_channels,kernel_size=1,stride=1,padding=0),
nn.BatchNorm2d(in_channels),
)
self.shortcut = nn.Sequential()
def forward(self,x):
shortcut = self.shortcut(x)
residual = self.main(x)
out = nn.ReLU(shortcut + residual )
return out
幾點(diǎn)經(jīng)驗(yàn)
- 在網(wǎng)絡(luò)中蔑匣,如果層數(shù)比較多的時(shí)候盡可能使用容器來(lái)寫(xiě)(比如上面的Sequential)劣欢,這樣子看起來(lái)更加的清晰;
- 在殘差塊的最后加上輸入之后記得要加上激活函數(shù)裁良;
- 要有積木思想凿将,就是盡可能的把網(wǎng)絡(luò)中的結(jié)構(gòu)搭建為可復(fù)用的塊,就比如上面的殘差塊价脾;
- BottleNeck用在較深的網(wǎng)絡(luò)層中可以減少參數(shù)量牧抵;
ResNet18
在何大佬的文章中提出了幾種不同的殘差網(wǎng)絡(luò),主要是網(wǎng)絡(luò)層的不同侨把,最少的為18層:
我們根據(jù)上面的信息復(fù)現(xiàn)一下ResNet18犀变。先分析其結(jié)構(gòu):
- 原文中用的圖像輸入是3*224*224,先通過(guò)一個(gè)7*7*64的卷積秋柄,但是步長(zhǎng)設(shè)置為2获枝,使得圖像的大小縮小了一半;
- 在con2_x的剛開(kāi)始骇笔,通過(guò)一個(gè)最大值池化省店,步長(zhǎng)設(shè)置為2,使得圖像又縮小了一半笨触;
- 然后是con2_x懦傍、con3_x、con4_x芦劣、con5_x一共8個(gè)殘差塊粗俱;
- 按照作者說(shuō)的,在con3_1虚吟、con4_1寸认、con5_1都進(jìn)行了2倍的下采樣;
- 最后一層先經(jīng)過(guò)一個(gè)自適應(yīng)平均池化層稍味,然后一個(gè)全連接層映射到輸出废麻;
那么根據(jù)上面的過(guò)程寫(xiě)代碼即可,但是寫(xiě)之前有幾點(diǎn)需要注意:
- 原文章中說(shuō)了每個(gè)卷積層后面跟上一個(gè)批歸一化層(BN層)模庐;
- 特征圖的尺寸減半的時(shí)候,特征圖的數(shù)量要增加一倍油宜;
- 原文說(shuō)的是直接用的步長(zhǎng)為2的卷積層進(jìn)行下采樣掂碱;
整體代碼:
class ResidualBlock(nn.Module):
def __init__(self, in_channels):
super(ResidualBlock, self).__init__()
self.conv1 = nn.Conv2d(in_channels, in_channels, kernel_size=3, padding=1)
self.bn1 = nn.BatchNorm2d(in_channels)
self.relu = nn.ReLU()
self.conv2 = nn.Conv2d(in_channels, in_channels, kernel_size=3, padding=1)
self.bn2 = nn.BatchNorm2d(in_channels)
def forward(self, x):
residual = self.conv1(x)
residual = self.bn1(residual)
residual = self.relu(residual)
residual = self.conv2(residual)
residual = self.bn2(residual)
out = self.relu(x + residual)
return out
class ResNet18(nn.Module):
def __init__(self,in_channels,resblock,outputs=1000):
super(ResNet18, self).__init__()
self.block1 = nn.Sequential(
nn.Conv2d(in_channels,64,kernel_size=7,stride=2,padding=3),
nn.BatchNorm2d(64),
nn.ReLU(),
)
self.block2 = nn.Sequential(
nn.MaxPool2d(kernel_size=3,stride=2,padding=1),
resblock(in_channels=64),
resblock(in_channels=64)
)
self.block3 = nn.Sequential(
nn.Conv2d(64,128,kernel_size=3,stride=(2,2),padding=1),
resblock(in_channels=128),
resblock(in_channels=128),
)
self.block4 = nn.Sequential(
nn.Conv2d(128,256,kernel_size=3,stride=(2,2),padding=1),
resblock(in_channels=256),
resblock(in_channels=256),
)
self.block5 = nn.Sequential(
nn.Conv2d(256,512,kernel_size=3,stride=(2,2),padding=1),
resblock(in_channels=512),
resblock(in_channels=512),
)
self.block6 = nn.AdaptiveAvgPool2d(output_size=(1,1))
self.fc = nn.Linear(in_features=512,out_features=outputs)
def forward(self, x):
x = self.block1(x)
x = self.block2(x)
x = self.block3(x)
x = self.block4(x)
x = self.block5(x)
x = self.block6(x)
x = x.reshape(x.shape[0],-1)
x = self.fc(x)
return x
為什么叫做ResNet18?
如果打印出來(lái)看下(用之前說(shuō)的torchsummary)慎冤,可以發(fā)現(xiàn)其中帶有可學(xué)習(xí)參數(shù)的層數(shù)一共是18層疼燥,所以叫做ResNet18(除去那些BN層、激活函數(shù)層等)蚁堤。
小總結(jié)與注意項(xiàng)
- 上面的代碼我是嚴(yán)格按照參考【1】的論文進(jìn)行復(fù)現(xiàn)的醉者,可能跟網(wǎng)上的有些不一樣,比如跟參考【3】的就不太一樣;
- 網(wǎng)上各種ResNet18的復(fù)現(xiàn)撬即,但是也有一些跟論文不太一樣的地方立磁,比如參考【4】在最初的卷積層之后就沒(méi)有加激活層;
- 還有其他的一些網(wǎng)上的教程也是各不相同剥槐,但是都是大同小異唱歧,所以在使用的時(shí)候要自己仔細(xì)斟酌;
- 上面的復(fù)現(xiàn)跟Pytorch官方提供的基本一致粒竖,但是參數(shù)量有些不同颅崩,后面就沒(méi)有細(xì)細(xì)比較了;
- 使用別人的代碼的時(shí)候一定要先讀懂了原理蕊苗,不要無(wú)腦直接套用沿后,血的教訓(xùn);
- 酌情根據(jù)自己的需求修改其中可以修改的模塊(比如激活函數(shù)朽砰,卷積核的大小等)尖滚。
參考
【1】HE K, ZHANG X, REN S, et al. Deep Residual Learning for Image Recognition[C]//2016 IEEE Conference on Computer Vision and Pattern Recognition (CVPR).2016:770-778. 10.1109/CVPR.2016.90.
【2】https://blog.csdn.net/sazass/article/details/116864275
【3】https://towardsdev.com/implement-resnet-with-pytorch-a9fb40a77448
【4】https://blog.csdn.net/weixin_36979214/article/details/108879684?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522162374909216780265420718%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=162374909216780265420718&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduend~default-1-108879684.first_rank_v2_pc_rank_v29&utm_term=pytorch+resnet18&spm=1018.2226.3001.4187
本文由mdnice多平臺(tái)發(fā)布