【深度学习基础】PyTorch实现ResNet亲身实践
这篇博文字数不多,文字部分是复现时需要关注的。
1 论文关键信息
1. 1 核心-Residual Block
核心思想是:训练残差,传统cnn卷积层可以将y = F(w, x) 看做目标函数,而resnet可以的目标函数可视为 y = F (w, x) + x;凯明大神发现训练残差相比传统的结构,可以使得网络可以做得更深,更容易训练,并且减缓过拟合现象。
原文中提出了两种block,如上图,左边的我们不妨称作basic block,右边的称为bottle neck。结构都是在卷积层后面,添加一跳short cut,将输入与卷积层组的输出相加。注意观察结构,basic block中包含两个卷积层,卷积核数量相同,卷积核均为3x3; bottle neck的结构是前两组滤波核数量相同,第三层滤波核数量是前两组的4倍,第二层尺寸3x3,其余两层尺寸是1x1。这样一看,结构还是挺简单的(事后诸葛亮)。
1.2 BN 层和激活单元
原文中提出,在每个卷积层后面,ReLU激活单元前面,使用BN操作。但是,这里有一个地方不明确,也就是short cut部分——是先分别对卷积层输出和输入做BN,再相加;还是先相加,再对相加的结果做BN操作? 我没有从文中得到答案,并且我觉得这个地方影响没那么大,就使用了后者。
1.3 ResNet-18,ResNet-34,ResNet-50,ResNet-101,ResNet-152
原文提出了这五种网络,可以根据计算性能挑选合适的实现。下面这张图也是复现需要特别关注的:
分析:
这五种网络从大的层面上来讲,都具有五个组,作者对它们进行了命名:conv1,conv2_x, conv3_x, conv4_x, conv5_x;五组的输出尺寸都是一样的,分别是112x112, 56x56, 28x28, 14x14, 7x7。我们可以看到,在经过每组卷积层组之后,尺寸降低了一半。
原始输入为224x224,用7x7,stride=2卷积,得到112x112的输出,我们可以知道conv1的padding=3;conv2_x的输出和输入相同,卷积核尺寸为3x3,我们可以知道做了same padding,stride=1;conv3_x, conv4_x, conv5_x的输出均减半,没有使用池化层,所以这几组也是做了same padding,第一个block第一层的stride=2,其它层stride=1。
知道上面这些参数后,我们就可以开始搭建网络了。
2. PyTorch实现
2.1 实现BN_CONV_ReLU结构
import torch
import torch.nn as nn
import torch.nn.functional as F
from torchsummary import summary
class BN_Conv2d(nn.Module):
"""
BN_CONV, default activation is ReLU
"""
def __init__(self, in_channels: object, out_channels: object, kernel_size: object, stride: object, padding: object,
dilation=1, groups=1, bias=False, activation=nn.ReLU(inplace=True)) -> object:
super(BN_Conv2d, self).__init__()
layers = [nn.Conv2d(in_channels, out_channels, kernel_size=kernel_size, stride=stride,
padding=padding, dilation=dilation, groups=groups, bias=bias),
nn.BatchNorm2d(out_channels)]
if activation is not None:
layers.append(activation)
self.seq = nn.Sequential(*layers)
def forward(self, x):
return self.seq(x)
2.2 实现basic block的结构
class BasicBlock(nn.Module):
"""
basic building block for ResNet-18, ResNet-34
"""
message = "basic"
def __init__(self, in_channels, out_channels, strides, is_se=False):
super(BasicBlock, self)