Резюме использования механизма внимания в CNN

искусственный интеллект

содержание

Резюме

1. Механизм внимания канала и механизм пространственного внимания

2. SE-Net: сети сжатия и возбуждения

Реализация модуля SE

Еще одна реализация SE

3. Облегченный модуль ECANet (супер-улучшенное внимание канала)

4. Координация внимания


Резюме

Основная идея механизма внимания в компьютерном зрении состоит в том, чтобы позволить системе научиться обращать внимание — иметь возможность игнорировать нерелевантную информацию и фокусироваться на важной информации.

Механизм внимания делится по области внимания:

пространственная область
домен канала
домен слоя
смешанный домен
Временная область (временная область): существует еще одна область особого внимания, реализуемая сильным вниманием, временная область (временная область), но поскольку сильное внимание реализуется с помощью обучения с подкреплением, обучение отличается.

1. Механизм внимания канала и механизм пространственного внимания

Модуль внимания сверточных блоков (CBAM) представляет собой модуль механизма внимания сверточных блоков. Это модуль механизма внимания, сочетающий пространственное и канальное внимание. По сравнению с механизмом внимания сенета, который обращает внимание только на канал (канал), он позволяет добиться лучших результатов.

Внимание канала: передайте входную карту характеристик через глобальное максимальное объединение и глобальное среднее объединение на основе ширины и высоты соответственно, а затем пройдите через MLP соответственно. Функции вывода MLP добавляются на основе элементов, а затем выполняется операция активации сигмовидной формы для создания окончательной карты функций внимания канала. Выполните поэлементное умножение карты признаков внимания канала и входной карты признаков, чтобы сгенерировать входные признаки, требуемые модулем Пространственное внимание.

Пространственное внимание: карта объектов, выводимая модулем внимания канала, используется в качестве входной карты объектов этого модуля. Сначала выполните глобальный максимальный пул на основе канала и глобальный средний пул, а затем выполните операцию concat на основе канала. Затем после операции свертки размерность уменьшается до 1 канала. Затем создайте функцию пространственного внимания через сигмоид. Наконец, функция умножается на входную функцию модуля, чтобы получить окончательную сгенерированную функцию.

код показывает, как показано ниже:

import torch.nn as nn
import math
try:
    from torch.hub import load_state_dict_from_url
except ImportError:
    from torch.utils.model_zoo import load_url as load_state_dict_from_url
import torch
#通道注意力机制
class ChannelAttention(nn.Module):
    def __init__(self, in_planes, ratio=16):
        super(ChannelAttention, self).__init__()
        self.avg_pool = nn.AdaptiveAvgPool2d(1)
        self.max_pool = nn.AdaptiveMaxPool2d(1)

        self.fc1   = nn.Conv2d(in_planes, in_planes // 16, 1, bias=False)
        self.relu1 = nn.ReLU()
        self.fc2   = nn.Conv2d(in_planes // 16, in_planes, 1, bias=False)

        self.sigmoid = nn.Sigmoid()

    def forward(self, x):
        avg_out = self.fc2(self.relu1(self.fc1(self.avg_pool(x))))
        max_out = self.fc2(self.relu1(self.fc1(self.max_pool(x))))
        out = avg_out + max_out
        return self.sigmoid(out)


#空间注意力机制
class SpatialAttention(nn.Module):
    def __init__(self, kernel_size=7):
        super(SpatialAttention, self).__init__()

        assert kernel_size in (3, 7), 'kernel size must be 3 or 7'
        padding = 3 if kernel_size == 7 else 1

        self.conv1 = nn.Conv2d(2, 1, kernel_size, padding=padding, bias=False)
        self.sigmoid = nn.Sigmoid()

    def forward(self, x):
        avg_out = torch.mean(x, dim=1, keepdim=True)
        max_out, _ = torch.max(x, dim=1, keepdim=True)
        x = torch.cat([avg_out, max_out], dim=1)
        x = self.conv1(x)
        return self.sigmoid(x)

На примере добавления механизма внимания в сеть Resnet

class ResNet(nn.Module):

    def __init__(self, block, layers, num_classes=1000, zero_init_residual=False,
                 groups=1, width_per_group=64, replace_stride_with_dilation=None,
                 norm_layer=None):
        super(ResNet, self).__init__()
        if norm_layer is None:
            norm_layer = nn.BatchNorm2d
        self._norm_layer = norm_layer

        self.inplanes = 64
        self.dilation = 1
        if replace_stride_with_dilation is None:
            # each element in the tuple indicates if we should replace
            # the 2x2 stride with a dilated convolution instead
            replace_stride_with_dilation = [False, False, False]
        if len(replace_stride_with_dilation) != 3:
            raise ValueError("replace_stride_with_dilation should be None "
                             "or a 3-element tuple, got {}".format(replace_stride_with_dilation))
        self.groups = groups
        self.base_width = width_per_group
        self.conv1 = nn.Conv2d(3, self.inplanes, kernel_size=7, stride=2, padding=3,
                               bias=False)
        self.bn1 = norm_layer(self.inplanes)
        self.relu = nn.ReLU(inplace=True)

        # 网络的第一层加入注意力机制
        self.ca = ChannelAttention(self.inplanes)
        self.sa = SpatialAttention()

        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,
                                       dilate=replace_stride_with_dilation[0])
        self.layer3 = self._make_layer(block, 256, layers[2], stride=2,
                                       dilate=replace_stride_with_dilation[1])
        self.layer4 = self._make_layer(block, 512, layers[3], stride=2,
                                       dilate=replace_stride_with_dilation[2])
        # 网络的卷积层的最后一层加入注意力机制
        self.ca1 = ChannelAttention(self.inplanes)
        self.sa1 = SpatialAttention()

        self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
        self.fc = nn.Linear(512 * block.expansion, num_classes)

        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
            elif isinstance(m, (nn.BatchNorm2d, nn.GroupNorm)):
                nn.init.constant_(m.weight, 1)
                nn.init.constant_(m.bias, 0)

        # Zero-initialize the last BN in each residual branch,
        # so that the residual branch starts with zeros, and each residual block behaves like an identity.
        # This improves the model by 0.2~0.3% according to https://arxiv.org/abs/1706.02677
        if zero_init_residual:
            for m in self.modules():
                if isinstance(m, Bottleneck):
                    nn.init.constant_(m.bn3.weight, 0)
                elif isinstance(m, BasicBlock):
                    nn.init.constant_(m.bn2.weight, 0)

    def _make_layer(self, block, planes, blocks, stride=1, dilate=False):
        norm_layer = self._norm_layer
        downsample = None
        previous_dilation = self.dilation
        if dilate:
            self.dilation *= stride
            stride = 1
        if stride != 1 or self.inplanes != planes * block.expansion:
            downsample = nn.Sequential(
                conv1x1(self.inplanes, planes * block.expansion, stride),
                norm_layer(planes * block.expansion),
            )

        layers = []
        layers.append(block(self.inplanes, planes, stride, downsample, self.groups,
                            self.base_width, previous_dilation, norm_layer))
        self.inplanes = planes * block.expansion
        for _ in range(1, blocks):
            layers.append(block(self.inplanes, planes, groups=self.groups,
                                base_width=self.base_width, dilation=self.dilation,
                                norm_layer=norm_layer))

        return nn.Sequential(*layers)

    def forward(self, x):
        x = self.conv1(x)
        x = self.bn1(x)
        x = self.relu(x)

        x = self.ca(x) * x
        x = self.sa(x) * x

        x = self.maxpool(x)

        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)

        x = self.ca1(x) * x
        x = self.sa1(x) * x


        x = self.avgpool(x)
        x = x.reshape(x.size(0), -1)
        x = self.fc(x)

        return x

Примечание: Поскольку сетевую структуру ResNet нельзя изменить, CBAM нельзя добавить в блок.Поскольку сетевая структура изменилась, параметры предварительного обучения использовать нельзя. Добавление последнего слоя свертки и первого слоя свертки не меняет сеть и может использовать предварительно обученные параметры.

Добавить местоположение:

 

 # 网络的第一层加入注意力机制
 self.ca = ChannelAttention(self.inplanes)
 self.sa = SpatialAttention()

и

# 网络的卷积层的最后一层加入注意力机制
self.ca1 = ChannelAttention(self.inplanes)
self.sa1 = SpatialAttention()

forWord часть кода

x = self.ca(x) * x
x = self.sa(x) * x

x = self.maxpool(x)

x = self.layer1(x)
x = self.layer2(x)
x = self.layer3(x)
x = self.layer4(x)

x = self.ca1(x) * x
x = self.sa1(x) * x

2, SE-Net: Squeeze-and-Excitation Networks


Ссылка на бумагу:АР Вест V.org/ABS/1709.01…
Кодовый адрес: https://github.com/hujie-frank/SENet
Адрес кода PyTorch:GitHub.com/miracle Я вижу f/…

SE-Net выиграла задачу классификации последнего конкурса ImageNet 2017. Основной принцип заключается в том, что для каждого выходного канала прогнозируется постоянный вес, и каждый канал взвешивается. Структура выглядит следующим образом:

На первом этапе количество H*W в каждом канале глобально усредняется для получения скаляра, который называется Squeeze, а затем два FC получают значение веса между 01, и каждый элемент каждого исходного HxW умножается на соответствующий Вес канала получает новая карта характеристик, которая называется Excitation. Любая исходная структура сети может быть использована для повторной калибровки признаков с помощью этого метода сжатия-возбуждения, как показано ниже.

   

Конкретной реализацией является Global Average Pooling-FC-ReLU-FC-Sigmoid, FC первого уровня понизит канал, а затем второй уровень FC поднимет канал, чтобы получить веса C, которые совпадают с числом каналов.Вес используется для взвешивания соответствующего канала. R на приведенном выше рисунке представляет собой коэффициент уменьшения.В ходе эксперимента было определено, что выбрано 16, что может обеспечить лучшую производительность, а объем вычислений относительно невелик.Основная идея SENet состоит в том, чтобы использовать сеть для изучения веса функции в соответствии с потерями, чтобы эффективная карта функций имела большой вес, а карта недействительных или малых эффектов имела небольшой вес для обучения модели. добиться лучших результатов..

Реализация модуля SE

Вот реализация версии PyTorch (ссылкаsenet.pytorch):

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)
        y = self.fc(y).view(b, c, 1, 1)
        return x * y.expand_as(x)

Чтобы использовать модуль SE в сети Resnet, вам нужно всего лишь добавить модуль SE в остаточную единицу (применяется в части остаточного обучения):

class SEBottleneck(nn.Module):
        expansion = 4

        def __init__(self, inplanes, planes, stride=1, downsample=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

Еще одна реализация SE

Этот метод использует свертки вместо полных соединений.

class SEBlock(nn.Module):



    def __init__(self, input_channels, internal_neurons):

        super(SEBlock, self).__init__()

        self.down = nn.Conv2d(in_channels=input_channels, out_channels=internal_neurons, kernel_size=1, stride=1,

                              bias=True, padding_mode='same')

        self.up = nn.Conv2d(in_channels=internal_neurons, out_channels=input_channels, kernel_size=1, stride=1,

                            bias=True, padding_mode='same')



    def forward(self, inputs):

        x = F.avg_pool2d(inputs, kernel_size=inputs.size(3))

        x = self.down(x)

        x = F.leaky_relu(x)

        x = self.up(x)

        x = F.sigmoid(x)

        x = x.repeat(1, 1, inputs.size(2), inputs.size(3))

        return inputs * x

3. Облегченный модуль ECANet (супер-улучшенное внимание канала)

Ссылка на бумагу:АР Вест V.org/ABS/1910.03…

Кодовый адрес:GitHub.com/bangbone w U/EC…

Перевод диссертации:Ван Хао.blog.CSDN.net/article/decent…

ECANet в основном вносит некоторые улучшения в модуль SENet и предлагаетСтратегия локального межканального взаимодействия без уменьшения размерности (модуль ECA) и метод адаптивного выбора размера ядер одномерной свертки, чтобы улучшить производительность.

Реализация ECANet:

class eca_layer(nn.Module):
    """Constructs a ECA module.
    Args:
        channel: Number of channels of the input feature map
        k_size: Adaptive selection of kernel size
    """
    def __init__(self, channel, k_size=3):
        super(eca_layer, self).__init__()
        self.avg_pool = nn.AdaptiveAvgPool2d(1)
        self.conv = nn.Conv1d(1, 1, kernel_size=k_size, padding=(k_size - 1) // 2, bias=False) 
        self.sigmoid = nn.Sigmoid()
 
    def forward(self, x):
        # x: input features with shape [b, c, h, w]
        b, c, h, w = x.size()
 
        # feature descriptor on the global spatial information
        y = self.avg_pool(x)
 
        # Two different branches of ECA module
        y = self.conv(y.squeeze(-1).transpose(-1, -2)).transpose(-1, -2).unsqueeze(-1)
 
        # Multi-scale information fusion
        y = self.sigmoid(y)
 
        return x * y.expand_as(x)

Вызов ECANet в модели

channelNum=64
class CRBlock(nn.Module):
    def __init__(self):
        super(CRBlock, self).__init__()
        self.convban = nn.Sequential(OrderedDict([
            ("conv3x3_bn", ConvBN(channelNum, channelNum, 3)),
        ]))
        self.path1 = Encoder_conv(channelNum, 2)
        self.path2 = nn.Sequential(OrderedDict([
            ('conv1x5', ConvBN(channelNum, channelNum, [1, 3])),
            ('conv5x1', ConvBN(channelNum, channelNum, 3)),
            ('ac', ACBlock(channelNum, channelNum, kernel_size=3)),
            ('eca', eca_layer(channelNum, 3)),
            # ('ac', ACBlock(channelNum, channelNum, kernel_size=3)),
        ]))
        self.path2 = nn.Sequential(OrderedDict([
            ('conv1x5', ConvBN(channelNum, channelNum, [1, 5])),
            ('conv5x1', ConvBN(channelNum, channelNum, [5, 1])),
            ("conv9x1_bn", ConvBN(channelNum, channelNum, 1)),
            ('eca', eca_layer(channelNum, 3)),
        ]))
        self.encoder_conv = Encoder_conv(channelNum * 4)
        self.encoder_conv1 = ConvBN(channelNum * 4, channelNum, 1)
        self.identity = nn.Identity()
        self.relu = Mish()
        self.ca1 = eca_layer(channelNum * 4, 3)
        # self.ca2 = eca_layer(channelNum*4, 1)

    def forward(self, x):
        identity = self.identity(x)
        x = self.convban(x)
        out1 = self.path1(x)
        out2 = self.path2(x)
        out3 = self.path2(x)
        out = torch.cat((out1, out2, out3, x), dim=1)
        out = self.relu(out)
        out = self.encoder_conv(out)
        out = self.ca1(out)
        out = self.encoder_conv1(out)
        out = self.relu(out + identity)
        return out

4. Координация внимания

бумага:АР Вест V.org/ABS/2103.02…

Ссылка на код:GitHub.com/Andrew-Q I Coin…

Координация внимания кодирует отношения каналов и долгосрочные зависимости с помощью точной информации о местоположении Конкретные операции делятся наВстраивание информации о координатахиКоординация AttentionGeneration2 шага.

Структура сети следующая:

图片

Видеть:Tickets.WeChat.QQ.com/Yes/In Q OO_IR FQ…

Pytorch реализация Coordinate Attention.

import torch
from torch import nn


class CA_Block(nn.Module):
    def __init__(self, channel, h, w, reduction=16):
        super(CA_Block, self).__init__()

        self.h = h
        self.w = w

        self.avg_pool_x = nn.AdaptiveAvgPool2d((h, 1))
        self.avg_pool_y = nn.AdaptiveAvgPool2d((1, w))

        self.conv_1x1 = nn.Conv2d(in_channels=channel, out_channels=channel//reduction, kernel_size=1, stride=1, bias=False)

        self.relu = nn.ReLU()
        self.bn = nn.BatchNorm2d(channel//reduction)

        self.F_h = nn.Conv2d(in_channels=channel//reduction, out_channels=channel, kernel_size=1, stride=1, bias=False)
        self.F_w = nn.Conv2d(in_channels=channel//reduction, out_channels=channel, kernel_size=1, stride=1, bias=False)

        self.sigmoid_h = nn.Sigmoid()
        self.sigmoid_w = nn.Sigmoid()

    def forward(self, x):

        x_h = self.avg_pool_x(x).permute(0, 1, 3, 2)
        x_w = self.avg_pool_y(x)

        x_cat_conv_relu = self.relu(self.conv_1x1(torch.cat((x_h, x_w), 3)))

        x_cat_conv_split_h, x_cat_conv_split_w = x_cat_conv_relu.split([self.h, self.w], 3)

        s_h = self.sigmoid_h(self.F_h(x_cat_conv_split_h.permute(0, 1, 3, 2)))
        s_w = self.sigmoid_w(self.F_w(x_cat_conv_split_w))

        out = x * s_h.expand_as(x) * s_w.expand_as(x)

        return out


if __name__ == '__main__':
    x = torch.randn(1, 16, 128, 64)    # b, c, h, w
    ca_model = CA_Block(channel=16, h=128, w=64)
    y = ca_model(x)
    print(y.shape)

 

 

Справочная статья:

Колонка Calling.com/afraid/99261200?…

blog.CSDN.net/Производство Bo8205/…