深度卷积神经网络中残差的学习和应用

一.残差定义

什么是残差:残差是目标特征输入特征之间的差异,我的理解就是输出与输入的关系。

残差网络ResNet学习的主体呢是残差函数,这个最早是由微软研究院提出的,它是将输入直接传递到输出,用输入直接影响输出,而不是输入通过中间的种种学习后生成的完整映射后得到输出。

二.残差的核心原理

深度卷积学习中,当网络的层数增加时,容易出现梯度消失/爆炸的问题。因为当这个层数过深的时候,也就是Epoch过大的时候,他这个反向传播时,梯度会逐层相乘,导致浅层权重更新困难。一旦出现了梯度消失,趋于零了,或者梯度爆炸,1000以上,这个连乘就会导致极大的偏离。假如有100层,每层的梯度系数为0.5,那么0.5的100次方也是一个趋近于零的数,同理如果每层的梯度系数为2,那么2的100次方就会非常大,模型就会无法收敛。这就会导致深层的特征没办法有效地提取,因为传不出来。所以当我们这个深度学习的层数多时,我们就要来使用这个残差网络或者说残差网络的变体。

所以残差的核心,就是为了防止深层网络的梯度消失和网络的退化,以下是残差的数学表达式。

假设某一层的理想映射为 H(x),残差块通过学习残差函数 F(x) = H(x) - x ,实际上这就是实际输出减去输入了。将输出表达为:

H(x) = F(x,\left \{ M_{i} \right \}) + x

其中x为输入特征,\left \{ M_{i} \right \}为残差块的权重参数,F(x,\left \{ M_{i} \right \}) 为残差函数,通常由多层卷积组成。

残差的关键,就是使用了残差路径,和主路径共同组成了一个残差块。

主路径由多层网络构成,负责学习残差函数。残差路径直接将输入传递到后续的层,保证信息能够无损传递。

所以在反向传播的过程中,梯度通过这两条路径回传,即使像之前所说的0.5的100次方使主路径趋近于零,残差路径的梯度仍能有效传递,避免了梯度消失。

残差路径,如果输入的维度和输出的维度匹配时,直接传递输入x,即恒等映射。

def residual_block(x):
    shortcut = x  # 残差路径:无操作
    x = Conv2D(64, (3,3))(x)
    x = ReLU()(x)
    x = Conv2D(64, (3,3))(x)
    return Add()([x, shortcut])  # H(x) = F(x) + x

如果输入输出的维度不一样,残差路径需要通过卷积来调整维度。

def residual_block(x):
    shortcut = Conv2D(128, (1,1))(x)  # 残差路径:1x1卷积调整维度
    x = Conv2D(128, (3,3))(x)
    x = ReLU()(x)
    x = Conv2D(128, (3,3))(x)
    return Add()([x, shortcut])

三.残差块的结构

残差块共有两种常见结构。

3.1Basic Block

适用于浅层网络。解决网络退化问题,提升训练稳定性。包含两个3*3的卷积层,残差路径直接输入,即恒等捷径。

import torch.nn as nn

class BasicBlock(nn.Module):
    def __init__(self, in_channels, out_channels, stride=1):
        super().__init__()
        self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1, bias=False)
        self.bn1 = nn.BatchNorm2d(out_channels)
        self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=1, padding=1, bias=False)
        self.bn2 = nn.BatchNorm2d(out_channels)
        
        # 处理维度不匹配的恒等捷径(如步长大于1或通道数不同)
        self.shortcut = nn.Sequential()
        if stride !=1 or in_channels != out_channels:
            self.shortcut = nn.Sequential(
                nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=stride, bias=False),
                nn.BatchNorm2d(out_channels)
            )

    def forward(self, x):
        shortcut = self.shortcut(x)
        x = nn.ReLU()(self.bn1(self.conv1(x)))
        x = self.bn2(self.conv2(x))
        x += shortcut
        return nn.ReLU()(x)

3.2Bottleneck Block 

适用于深层网络,通过降维,卷积,升维来减少计算量。

class BottleneckBlock(nn.Module):
    def __init__(self, in_channels, out_channels, stride=1, expansion=4):
        super().__init__()
        mid_channels = out_channels // expansion  # 通道数下降为1/4(如256→64)
        self.conv1 = nn.Conv2d(in_channels, mid_channels, kernel_size=1, stride=1, bias=False)
        self.bn1 = nn.BatchNorm2d(mid_channels)
        self.conv2 = nn.Conv2d(mid_channels, mid_channels, kernel_size=3, stride=stride, padding=1, bias=False)
        self.bn2 = nn.BatchNorm2d(mid_channels)
        self.conv3 = nn.Conv2d(mid_channels, out_channels, kernel_size=1, stride=1, bias=False)
        self.bn3 = nn.BatchNorm2d(out_channels)
        
        self.shortcut = nn.Sequential()
        if stride !=1 or in_channels != out_channels:
            self.shortcut = nn.Sequential(
                nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=stride, bias=False),
                nn.BatchNorm2d(out_channels)
            )

    def forward(self, x):
        shortcut = self.shortcut(x)
        x = nn.ReLU()(self.bn1(self.conv1(x)))
        x = nn.ReLU()(self.bn2(self.conv2(x)))
        x = self.bn3(self.conv3(x))
        x += shortcut
        return nn.ReLU()(x)

当网络层数小于34层时一般用第一种,大于34层时用第二种 ,并且第二种能显著降低计算量。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值