ResNet网络结构学习

ResNet

背景

在resnet提出之前,认为卷积层和池化层堆叠越多,提取到的图片特征信息就越全,学习效果越好。但是随着网络的加深,容易出现梯度消失和梯度爆炸等问题。

梯度消失:每一层的梯度小于1,反向传播时网络越深,远离输出层的梯度越趋于0。

梯度爆炸:每一层的梯度大于1,反向传播时网络越深,远离输出层的梯度越大。

退化问题:随着层数增加,预测效果反而更差。

方法

batch normalization

关于梯度消失和梯度爆炸的问题,可以通过Batch Normalization(批标准化)来缓解。

一般被添加于卷积、全连接层输出后,激活函数之前 or 全连接、卷积的输入前。

对全连接层作用在特征维度

对卷积层作用在通道维度

固定小批量里边的均值和方差,将该层的特征值分布重新拉回标准正态分布(均值为0,方差为1),使其落入激活函数对于输入较为敏感的区间。

可以缓解梯度消失,加快网络收敛。

residual 残差结构

让神经网络的某些层跳过下一层神经元的连接,隔层相连,从而可以加深网络,并且不会使网络的效果变差。

$$
f(x)=x+g(x)
$$

 

x是输入,g(x)是新加入层的作用结果。当g(x)无法获得更好的信息时,x可以跳过g(x),至少可以保证下一层有x

在ResNet中,残差块有两种结构

左边称为BasicBlock

右边称为Bottleneck,相较于Basic,多了1x1卷积层,用于残差块内的降维升维,减小参数量。

 

在ResNet中,残差块的shortcut捷径也有两种结构

左边的残差块中,g(x)不会改变通道数,因而shortcut中没有结构。

右边的残差块中,g(x)会改变通道数或者需要进行下采样,因而shortcut中有一个1x1的卷积层,用于改变x的通道数,并通过stride进行下采样操作,使其与g(x)通道匹配。

ResNet网络结构

在resnet18和resnet34中,使用的残差块是BasicBlock。而在resnet50及以上中,使用的残差块是Bottleneck。但无论是那种残差块,其下采样操作都是相似的,即从第二个layer开始,每个layer的第一个残差块进行一次下采样操作。

以ResNet18 和 ResNet50 为例

 

在进入残差块之前,均采用了7x7卷积,bn,relu,maxpooling层。其中7x7卷积层和maxpooling层都进行了下采样。

进入残差layer时,第一层的残差块没有进行下采样,然后接下来每层的第一个残差块进行了下采样,同时将通道数提升一倍。

在出残差layer后,也都是通过avgpool,得到通道数x1x1特征,进而送入全连接层。

 

对于BasicBlock组成的layer而言。通道数自上而下都是非递减的。

而BottleneckBlock会遇到 输入通道数大于输出通道数的情况。这时,1x1卷积层的作用得以体现。通过第一个1x1卷积进行2倍降维,然后第二个1x1卷积进行4倍升维。

代码实现

class BasicBlock(nn.Module):
    expansion = 1  #通道升降维倍数
    def __init__(self, in_channels, channels, stride=1, downsample=None):
        super().__init__()
​
        self.conv1 = nn.Conv2d(in_channels, channels, kernel_size=3, 
                                stride=stride, padding=1)               # 第一个3x3卷积层,通过stride进行下采样
        self.bn1 = nn.BatchNorm2d(channels)
​
        self.conv2 = nn.Conv2d(channels, channels, kernel_size=3,       # 第二个3x3卷积层,不进行下采样
                                stride=1, padding=1)
        self.bn2 = nn.BatchNorm2d(channels)
        
        self.downsample = downsample                                    # shortcut中的1x1卷积层
        self.stride = stride
        
        self.relu = nn.ReLU(inplace=True)
​
    def forward(self, x):
        residual = x
​
        out = self.bn1(self.conv1(x))
        out = self.relu(out)
        out = self.bn2(self.conv2(out))
​
        if self.downsample is not None:
            residual = self.downsample(x)                               # 1x1卷积层用于下采样和通道融合
​
        out += residual
        return self.relu(out)
class Bottleneck(nn.Module):
    expansion = 4  # 通道升降维倍数
​
    def __init__(self, in_channels, channels, stride=1, downsample=None):
        super().__init__()
        self.conv1 = nn.Conv2d(in_channels, channels,                   #第一个是1x1卷积
                                kernel_size=1, bias=False)
        self.bn1 = nn.BatchNorm2d(channels)                                 
​
        self.conv2 = nn.Conv2d(channels, channels, kernel_size=3,       #第二个是3x3卷积,通过stride进行下采样
                                stride=stride, padding=1, bias=False)
        self.bn2 = nn.BatchNorm2d(channels)
​
        self.conv3 = nn.Conv2d(channels, channels * self.expansion,     #第三个是1x1卷积,升维
                                kernel_size=1, bias=False)
        self.bn3 = nn.BatchNorm2d(self.expansion * channels)
​
        self.downsample = downsample
        self.stride = stride
    
    def forward(self, x):
        residual = x
        out = F.relu(self.bn1(self.conv1(x)))
        out = F.relu(self.bn2(self.conv2(out)))
        out = self.bn3(self.conv3(out))
​
        if self.downsample:
            residual = self.downsample(x)             
        out += residual
​
        return F.relu(out)
class ResNet(nn.Module):
    def __init__(self, block, layers, num_classes=1000):
        self.in_channels = 64
        super().__init__()
        
        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)
        
        self.layer1 = self._make_layer(block, 64, layers[0])                #第一个残差层不进行下采样
        self.layer2 = self._make_layer(block, 128, layers[1], stride=2)
        self.layer3 = self._make_layer(block, 256, layers[2], stride=2)
        self.layer4 = self._make_layer(block, 512, layers[3], stride=2)
        
        self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
        self.flatten = nn.Flatten()
        self.fc = nn.Linear(512 * block.expansion, num_classes)
​
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
                m.weight.data.normal_(0, math.sqrt(2. / n))
            elif isinstance(m, nn.BatchNorm2d):
                m.weight.data.fill_(1)
                m.bias.data.zero_()
​
    def _make_layer(self, block, channels, blocks, stride=1):   
       '''
       :param block 残差块选择,BisicBlock or Bottleneck
       :param channels 输出通道维数
       :param blocks 残差块的个数
       :param stride 卷积层的stride参数,=1不下采样,=2下采样
       :return nn.Sequential
       '''
        downsample = None
        # 前一种操作需要下采样,后一种操作需要融合通道
        if stride != 1 or self.in_channels != channels * block.expansion:
            downsample = nn.Sequential(
                nn.Conv2d(self.in_channels, channels*block.expansion, kernel_size=1, 
                        stride=stride, bias=False),
                nn.BatchNorm2d(channels*block.expansion)
            )
        layers = []
        layers.append(block(self.in_channels, channels, stride, downsample))    #第一个残差块
         
        #后续残差块,需要改变in_channels,使其对应上一个残差块的out_channels
        self.in_channels = channels * block.expansion                                
        for i in range(1, blocks):             
            layers.append(block(self.in_channels, channels))
        
        return nn.Sequential(*layers)
​
    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 = self.flatten(x)
        x = self.fc(x)
​
        return x
 
posted @ 2022-05-01 22:28  Brisling  阅读(757)  评论(0编辑  收藏  举报