经典注意力网络: SeNet及pytorch实践

一、论文介绍

1.1 简介

    卷积神经网络建立在卷积运算的基础上,通过在局部接受域内融合空间信息和通道信息来提取信息特征。在SeNet工作中,其专注于通道关系,并提出了一种新的架构单元,我们称之为“挤压-激励”(SE)块,该单元通过明确建模通道之间的相互依赖性,自适应地重新校准通道智能特征响应。挤压,模块可以看作是全连接层的通道压缩,激励模块可以看作是全连接的通道放缩。

     现在一些SeNet的一些改进取消了全连接层的使用,改用一维卷积进行替代,从运算速度与计算效益上确实大有提高。

1.2 模型图

1.3 方法介绍

先看输入特征数据

其中u_c代表卷积运算后输出C{}'通道的特征数据, X代表C通道的输入数据,v_c代表C{}'个输入通道为C的卷积核。

     由于输出是通过所有通道的求和产生的,因此通道依赖关系隐式嵌入在v_c中,但这些依赖关系与过滤器捕获的空间相关性纠缠在一起。全局注意力可以对整张特征图的每个像素点的关系进行建模,那么同样特征图通道之间也存在联系。SeNet通过显式建模通道相互依赖性来实现这一目标,以便在将卷积核响应馈送到下一个变换之前,分两个步骤(挤压和激励)重新校准滤卷积核响应。

压缩与激励模块

    首先经过特征图的全局平均池化将特征图的空间维度进行压缩,H*W->1

    从该公式中可看出主要的部分就是两个全连接层,W_1进行通道的压缩,W_2进行通道的扩充,\delta代表激活函数ReLU,σ代表的是sigmoid函数,以下是sigmoid函数的图:

     sigmoid的值域在[0-1]之间,你可以将之前的操作理解为:特征值较大的其概率值也比较大,也就是其相对比较重要。

     

     sigmoid得出的概率值后又与原来的的特征值进行相乘,这一步的目的就是为了引导模型的训练。难道卷积就不能计算出哪里的特征值比较重要?当然可以,不过挤压与激励模块能引导模型的训练方向,不单单局限于特征图的局部信息,更加兼顾通道上重要信息。

二、代码

 SeNet相对来说还是比较简单的,通过代码可以很好的理解,废话不多说上代码。

2.1 SEBlock

from torch import nn


class SELayer(nn.Module):
    def __init__(self, channel, reduction=16):
        super(SELayer, self).__init__()
        self.avg_pool = nn.AdaptiveAvgPool2d(1)
        self.fc = nn.Sequential(
            nn.Linear(channel, channel // reduction, bias=False),
            nn.ReLU(inplace=True),
            nn.Linear(channel // reduction, channel, bias=False),
            nn.Sigmoid()
        )

    def forward(self, x):
        b, c, _, _ = x.size()
        y = self.avg_pool(x).view(b, c)  # 先pooling获取通道维度,也就是把H,w=>1
        y = self.fc(y).view(b, c, 1, 1)  # 全连接层对通道系数进行调整
        # tensor_1.expand_as(tensor_2) :把tensor_1扩展成和tensor_2一样的形状
        return x * y.expand_as(x)  # 原值乘以对应系数

改代码与上面讲的一样,先全局池化,再经过全连接的压缩与扩充,主要的是Sigmoid()函数取概率值,最后与原特征值相乘即可。

2.2 SE_ResNet

import torch
import torch.nn as nn
from torch.hub import load_state_dict_from_url
from torchvision.models import ResNet


# 论文核心 SE Block, 这里称为 SE layer
class SELayer(nn.Module):
    def __init__(self, channel, reduction=16):
        super(SELayer, self).__init__()
        self.avg_pool = nn.AdaptiveAvgPool2d(1)  # 全局平均池化,输入BCHW -> 输出 B*C*1*1
        self.fc = nn.Sequential(
            nn.Linear(channel, channel // reduction, bias=False),  # 可以看到channel得被reduction整除,否则可能出问题
            nn.ReLU(inplace=True),
            nn.Linear(channel // reduction, channel, bias=False),
            nn.Sigmoid()
        )

    def forward(self, x):
        b, c, _, _ = x.size()
        y = self.avg_pool(x).view(b, c)  # 得到B*C*1*1,然后转成B*C,才能送入到FC层中。
        y = self.fc(y).view(b, c, 1, 1)  # 得到B*C的向量,C个值就表示C个通道的权重。把B*C变为B*C*1*1是为了与四维的x运算。
        return x * y.expand_as(x)  # 先把B*C*1*1变成B*C*H*W大小,其中每个通道上的H*W个值都相等。*表示对应位置相乘。


def conv3x3(in_planes, out_planes, stride=1):
    return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride, padding=1, bias=False)


class SEBasicBlock(nn.Module):
    expansion = 1

    def __init__(self, inplanes, planes, stride=1, downsample=None, groups=1,
                 base_width=64, dilation=1, norm_layer=None,
                 *, reduction=16):
        # 参数列表里的 * 星号,标志着位置参数的就此终结,之后的那些参数,都只能以关键字形式来指定。
        super(SEBasicBlock, self).__init__()
        self.conv1 = conv3x3(inplanes, planes, stride)
        self.bn1 = nn.BatchNorm2d(planes)
        self.relu = nn.ReLU(inplace=True)
        self.conv2 = conv3x3(planes, planes, 1)
        self.bn2 = nn.BatchNorm2d(planes)
        self.se = SELayer(planes, reduction)
        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)
        out = self.se(out)

        if self.downsample is not None:
            residual = self.downsample(x)

        out += residual
        out = self.relu(out)

        return out


class SEBottleneck(nn.Module):
    expansion = 4

    def __init__(self, inplanes, planes, stride=1, downsample=None, groups=1,
                 base_width=64, dilation=1, norm_layer=None,
                 *, reduction=16):
        # 参数列表里的 * 星号,标志着位置参数的就此终结,之后的那些参数,都只能以关键字形式来指定。
        super(SEBottleneck, self).__init__()
        self.conv1 = nn.Conv2d(inplanes, 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, planes * 4, kernel_size=1, bias=False)
        self.bn3 = nn.BatchNorm2d(planes * 4)
        self.relu = nn.ReLU(inplace=True)
        self.se = SELayer(planes * 4, reduction)
        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)
        out = self.relu(out)

        out = self.conv3(out)
        out = self.bn3(out)
        out = self.se(out)

        if self.downsample is not None:
            residual = self.downsample(x)

        out += residual
        out = self.relu(out)

        return out


def se_resnet18(num_classes=1_000):
    model = ResNet(SEBasicBlock, [2, 2, 2, 2], num_classes=num_classes)
    model.avgpool = nn.AdaptiveAvgPool2d(1)
    return model


def se_resnet34(num_classes=1_000):
    model = ResNet(SEBasicBlock, [3, 4, 6, 3], num_classes=num_classes)
    model.avgpool = nn.AdaptiveAvgPool2d(1)
    return model


def se_resnet50(num_classes=1_000, pretrained=False):
    model = ResNet(SEBottleneck, [3, 4, 6, 3], num_classes=num_classes)
    model.avgpool = nn.AdaptiveAvgPool2d(1)
    if pretrained:
        model.load_state_dict(load_state_dict_from_url(
            "https://github.com/moskomule/senet.pytorch/releases/download/archive/seresnet50-60a8950a85b2b.pkl"))
    return model


def se_resnet101(num_classes=1_000):
    model = ResNet(SEBottleneck, [3, 4, 23, 3], num_classes=num_classes)
    model.avgpool = nn.AdaptiveAvgPool2d(1)
    return model


def se_resnet152(num_classes=1_000):
    model = ResNet(SEBottleneck, [3, 8, 36, 3], num_classes=num_classes)
    model.avgpool = nn.AdaptiveAvgPool2d(1)
    return model


if __name__ == "__main__":
    inputs = torch.randn(2, 3, 224, 224)
    model = se_resnet50(pretrained=False)
    outputs = model(inputs)
    print(outputs.size())

Se_Resnet的版本都封装好了,需要的自己训练测试下结果,以上是我的介绍,如有问题请指正。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值