[Colección de mecanismos de atención] Estructura de la red de atención del canal de atención del canal, serie de interpretación del código fuente 1

Estructura de la red Channel Attention, serie uno de interpretación del código fuente

SE-Net, SK-Net y CBAM

1 DIC

Enlace texto original: Texto original SENet
Enlace código fuente: Código fuente SENet

Squeeze-and-Excitation Networks (SENet) es una nueva estructura de reconocimiento de imágenes anunciada por la empresa de vehículos autónomos Momenta en 2017. Modela la correlación entre canales de características y fortalece características importantes para mejorar la precisión. Esta estructura es la campeona de la competencia ILSVR 2017. El autor menciona en el texto original que SENet ha logrado una tasa de error top5 de 2.251%, que es un 25% inferior al primer lugar en 2016. También fue algo muy exitoso en ese año. .

1.1 Bloques de compresión y excitación

inserte la descripción de la imagen aquí
El módulo SE Block se compone principalmente de una operación Squeeze y una operación de excitación: la operación Squeeze es responsable de la agrupación global de la dimensión espacial (como 7 x 7 --> 1 x 1); la operación de excitación aprende las dependencias del canal después puesta en común, y realiza el empoderamiento ponderado del canal. La estructura de la red en la imagen de arriba en realidad resume muy bien el tema de SENet A continuación, lo explicaré en detalle desde dos aspectos: Squeeze y Excitation.

1.1.1 Squeeze: incrustación de información global

La parte inicial de la estructura de red Ftr: X->U es la estructura de convolución clásica en el pasado, y la parte posterior a U es la parte innovadora de SENet: use la agrupación promedio global para comprimir U en las dos dimensiones de H y W, y combine una La característica espacial completa en el canal se codifica como una característica global, lo que da como resultado una salida intermedia de 1x1xC. Para decirlo de manera más simple, aquí se está usando un mapa de funciones de verificación de agrupación bidimensional para la reducción de la dimensionalidad, desde las tres dimensiones originales de H, W y C a una dimensión de C, de modo que la subsiguiente potenciación del canal sea factible. , y su fórmula se muestra en la siguiente figura:
inserte la descripción de la imagen aquí

1.1.2 Excitación: recalibración adaptativa

Para conocer mejor la información de características obtenida por la operación Squeeze, el autor utiliza la operación Excitation para obtener las dependencias entre canales. Para lograr este objetivo, el autor analiza que la función debe cumplir con dos criterios: (1)debe ser flexible(en particular, ser capaz de aprender interacciones no lineales entre canales); (2)Debe ser capaz de aprender una relación no mutuamente excluyente.(porque queremos asegurarnos de que se permita enfatizar múltiples canales). Por lo tanto, el autor usa dos capas FC completamente conectadas para aprender las dependencias entre canales, y finalmente usa la función sigmoidea para normalizar los pesos (limitar el valor de peso de cada canal a 0-1 y limitar la suma de peso a 1), el fórmula es la siguiente:
inserte la descripción de la imagen aquí

1.1.3 Toma una castaña: Módulo SE-ResNet

inserte la descripción de la imagen aquí
La imagen de arriba es la estructura de red de SE-ResNet. Para la etapa Residual, SE-Block realizará una reducción de dimensionalidad a través de una agrupación global (la reducción de dimensionalidad puede no estar estandarizada) para obtener las características de la dimensión del canal C, y luego pasará por dos capas de FC. La primera capa de FC continuará reduciendo la dimensión de C, principalmente a través del hiperparámetro r (r se refiere a la relación de compresión, el autor probó el desempeño de r bajo varios valores y finalmente concluyó que cuando r=16, el desempeño general y La cantidad de cálculo es la más equilibrada); después de la activación, la segunda capa de FC mapea el canal comprimido de vuelta a la dimensión original, y finalmente usa la función Sigmoid para asignar diferentes pesos a cada canal.
Scale representa la operación de multiplicar el peso por la característica a ponderar, después de la operación Scale, el peso en la dimensión del canal se suma perfectamente a la característica.

1.2 Implementación del código

1.2.1 Módulo SE

La implementación de SE se muestra en el siguiente código y he realizado comentarios detallados para cada paso. Si no comprende la fórmula anterior, la operación de la función correspondiente aquí puede ayudarlo a comprender la fórmula.

class SELayer(nn.Module):
    def __init__(self, channel, reduction=16):
        super(SELayer, self).__init__()
        self.avg_pool = nn.AdaptiveAvgPool2d(1)# Squeeze操作的定义
        self.fc = nn.Sequential(# Excitation操作的定义
            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()# 得到H和W的维度,在这两个维度上进行全局池化
        y = self.avg_pool(x).view(b, c)# Squeeze操作的实现
        y = self.fc(y).view(b, c, 1, 1)# Excitation操作的实现
        # 将y扩展到x相同大小的维度后进行赋权
        return x * y.expand_as(x)

1.2.2 SE-ResNet

El siguiente código muestra la operación antes de agregar SENet al enlace residual de Resnet. De hecho, SENet se puede agregar en un bloque poco profundo (como antes de conv1) o en una capa profunda (después de bn2). La ubicación para agregar debe determinarse de acuerdo con su propia tareas. Si su red presta más atención a las características superficiales, como las características de textura, entonces se puede agregar a la capa superficial; por el contrario, si su red presta más atención a las características profundas, como las características de contorno y las características estructurales, debería ser agregado a la capa profunda Análisis de problemas específicos.

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

2 SKNet

Enlace al texto original: Texto original de SKNet
Enlace al código fuente: Código fuente de SKNet

El artículo de CVPR2019 Selective Kernel Networks , este artículo también rinde homenaje a la idea de SENet. SENet propuso Sequeeze and Excitation block, y SKNet propuso Selective Kernel Convolution. Ambos pueden integrarse fácilmente en la estructura de red actual, como ResNet, Inception, ShuffleNet, para lograr una mejora de la precisión.

2.1 Convolución de Kernel Selectiva

El artículo se centra enLos campos receptivos de diferentes tamaños tienen diferentes efectos en objetivos de diferentes escalas., y ¿qué método debemos adoptar para que la red pueda utilizar automáticamente el campo receptivo efectivo para la clasificación? Para solucionar este problema, el autor propone unaMecanismo de selección dinámica para kernel de convolución., este mecanismo permite que cada neurona ajuste adaptativamente el tamaño de su campo receptivo (núcleo de convolución) de acuerdo con la información de entrada multiescala.
inserte la descripción de la imagen aquí
La figura anterior es el módulo de convolución del núcleo seleccionado.La red incluye principalmente tres operaciones: dividir, fusionar y seleccionar. Split genera diferentes mapas de características a través de múltiples kernels de diferentes tamaños. El modelo en la figura anterior solo diseña dos kernels de convolución de diferentes tamaños. De hecho, se pueden diseñar múltiples kernels de convolución de múltiples ramas; la operación Fuse combina y agrega información de múltiples rutas para obtener una representación global y completa de los pesos de selección; la operación de selección agrega mapas de características de núcleos de diferentes tamaños según los pesos de selección.

2.1.1 Dividir

Use diferentes kernels de convolución para la entrada X para generar diferentes salidas de características. La figura anterior muestra la operación de convolución usando los kernels de convolución 3x3 y 5x5. Para mejorar la eficiencia del cálculo, la operación de convolución 5x5 usa la tasa de huecos 2. El kernel de convolución se implementa mediante convolución de agujeros de 3x3 y utiliza convolución de grupo, convolución separable en profundidad, BatchNorm y ReLU.

2.1.2 Espoleta

Fusión de información de las múltiples salidas de características obtenidas, es decir, la operación de suma en pytorch, para obtener un nuevo mapa de características U, que es la fórmula (1) en la figura a continuación; luego use la misma operación de Squeeze para generar la información de esta dimensión del canal, a saber, la fórmula (2) en la figura; finalmente, use la capa FC de capa totalmente conectada de 1 capa para aprender las dependencias entre canales, y finalmente use ReLU y BatchNorm para la normalización, que es la fórmula (3) en la figura de abajo. Las fórmulas correspondientes son las siguientes:
inserte la descripción de la imagen aquí

2.1.3 Seleccionar

En la dimensión del canal, se pondera el mapa de características final obtenido por múltiples ramales y se utiliza la función sigmoidea. Finalmente, se agregan los mapas de características ponderadas de todas las sucursales para obtener el resultado final.

2.2 Implementación del código

Combinado con las explicaciones anteriores, el código es realmente muy claro. He comentado las definiciones y operaciones específicas. Puede consultar los comentarios para comprenderlo.

class SKConv(nn.Module):
    def __init__(self, features, WH, M, G, r, stride=1 ,L=32):
        super(SKConv, self).__init__()
        d = max(int(features/r), L)
        self.M = M
        self.features = features
        self.convs = nn.ModuleList([])
        # 生成M个分支,将其添加到convs中,每个分支采用不同的卷积核和不同规模的padding,保证最终得到的特征图大小一致
        for i in range(M):
            self.convs.append(nn.Sequential(
                nn.Conv2d(features, features, kernel_size=3+i*2, stride=stride, padding=1+i, groups=G),
                nn.BatchNorm2d(features),
                nn.ReLU(inplace=False)
            ))
        # 学习通道间依赖的全连接层
        self.fc = nn.Linear(features, d)
        self.fcs = nn.ModuleList([])
        for i in range(M):
            self.fcs.append(
                nn.Linear(d, features)
            )
        self.softmax = nn.Softmax(dim=1)
        
    def forward(self, x):
        for i, conv in enumerate(self.convs):
            fea = conv(x).unsqueeze_(dim=1)
            if i == 0:
                feas = fea
            else:
                feas = torch.cat([feas, fea], dim=1)
        fea_U = torch.sum(feas, dim=1)# 将多个分支得到的特征图进行融合
        fea_s = fea_U.mean(-1).mean(-1)# 在channel这个维度进行特征抽取
        fea_z = self.fc(fea_s)# 学习通道间的依赖关系
        # 赋权操作,由于是对多维数组赋权,所以看起来比SENet麻烦一些
        for i, fc in enumerate(self.fcs):
            vector = fc(fea_z).unsqueeze_(dim=1)
            if i == 0:
                attention_vectors = vector
            else:
                attention_vectors = torch.cat([attention_vectors, vector], dim=1)
        attention_vectors = self.softmax(attention_vectors)
        attention_vectors = attention_vectors.unsqueeze(-1).unsqueeze(-1)
        fea_v = (feas * attention_vectors).sum(dim=1)
        return fea_v

3 CBAM

Enlace del texto original: Texto original de CBAM
Enlace del código fuente: Código fuente de CBAM

CBAM (Módulo de atención de bloque convolucional) es un mecanismo de atención de canal liviano, y también es un mecanismo de atención visual ampliamente utilizado. Fue propuesto en ECCV en 2018. El artículo utiliza la atención de canal y la atención espacial al mismo tiempo, y descubrió que es mejor conectar las dos atenciones en serie.

3.1 Módulo de atención de bloque convolucional

La siguiente figura es el diagrama de estructura de red de CBAM.
inserte la descripción de la imagen aquí

Puede verse que CBAM contiene dos submódulos independientes, Módulo de atención de canal (CAM) y Módulo de atención espacial (Módulo de atención parcial, SAM) , que realizan la ponderación espacial y de canal respectivamente. Esto no solo ahorra parámetros y potencia informática, sino que también garantiza que se pueda integrar en la arquitectura de red existente como un módulo plug-and-play.

3.1.1 Módulo de atención de canales

inserte la descripción de la imagen aquí
La idea básica del mecanismo de atención de canales es la misma que SENet, pero la operación específica es ligeramente diferente a SENet, y marqué las diferentes partes en rojo. Primero , el mapa de características de entrada F (H×W×C) se somete a la agrupación máxima global (MaxPool) y a la agrupación promedio global (AvgPool) en función de las dos dimensiones de H y W, respectivamente , para obtener dos 1×1×C mapa de características; luego , los dos mapas de características se envían a una red neuronal de dos capas (MLP) de peso compartido para aprender las dependencias entre los canales, y la reducción de la dimensionalidad se logra a través de la relación de compresión r entre las dos capas neuronales. Finalmente , las características generadas por MLP se suman en función de los elementos y luego se activan mediante sigmoid para generar el peso final del canal, a saber, M_c. Su fórmula es la siguiente:

inserte la descripción de la imagen aquí

3.1.2 Módulo de atención espacial

Originalmente, este tema analiza principalmente el mecanismo de atención del canal, y hablaré sobre el seguimiento de CBAM cuando piense en el mecanismo de atención espacial en el próximo artículo, pero según lo que dijo mi hermana Yi, el algoritmo se ha llevado a mi boca, así que solo tomo un trozo resuelto.

inserte la descripción de la imagen aquí
El mecanismo de atención espacial toma el mapa de características F' producido por el módulo de atención del canal como el mapa de características de entrada de este módulo. En primer lugar , las operaciones de agrupación máxima (MaxPool) y agrupación promedio (AvgPool) se realizan en función de la dimensión del canal para obtener dos mapas de características de alto × ancho × 1; luego, los dos mapas de características se empalman en función de la dimensión del canal , es decir, la operación de concatenación ; Luego , use el kernel de convolución 7×7 (el autor ha verificado que el efecto 7x7 es mejor que otros kernels de convolución de dimensión a través de experimentos) para la reducción de la dimensionalidad del canal, y la reducción de la dimensionalidad es un solo canal mapa de características, es decir, H × W × 1; finalmente , después de que sigmoid aprende la relación de dependencia entre los elementos espaciales, se genera el peso de la dimensión espacial, a saber, M_s. Su fórmula es la siguiente:

inserte la descripción de la imagen aquí

3.2 Implementación del código

3.2.1 CA&SA

Para conocer la definición de red específica y la implementación de la operación, consulte los comentarios de mi código.

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)# 定义全局最大池化
        # 定义CBAM中的通道依赖关系学习层,注意这里是使用1x1的卷积实现的,而不是全连接层
        self.fc = nn.Sequential(nn.Conv2d(in_planes, in_planes // 16, 1, bias=False),
                               nn.ReLU(),
                               nn.Conv2d(in_planes // 16, in_planes, 1, bias=False))
        self.sigmoid = nn.Sigmoid()

    def forward(self, x):
        avg_out = self.fc(self.avg_pool(x))# 实现全局平均池化
        max_out = self.fc(self.max_pool(x))# 实现全局最大池化
        out = avg_out + max_out# 两种信息融合
        # 最后利用sigmoid进行赋权
        return self.sigmoid(out)

class SpatialAttention(nn.Module):
    def __init__(self, kernel_size=7):
        super(SpatialAttention, self).__init__()
        # 定义7*7的空间依赖关系学习层
        self.conv1 = nn.Conv2d(2, 1, kernel_size, padding=kernel_size//2, bias=False)
        self.sigmoid = nn.Sigmoid()

    def forward(self, x):
        avg_out = torch.mean(x, dim=1, keepdim=True)# 实现channel维度的平均池化
        max_out, _ = torch.max(x, dim=1, keepdim=True)# 实现channel维度的最大池化
        x = torch.cat([avg_out, max_out], dim=1)# 拼接上述两种操作的到的两个特征图
        x = self.conv1(x)# 学习空间上的依赖关系
        # 对空间元素进行赋权
        return self.sigmoid(x)

3.2.2 CBAM_ResNet

Debido a limitaciones de espacio, aquí solo se muestra la adición de CA&SA de BasicBlock

class BasicBlock(nn.Module):
    expansion = 1

    def __init__(self, inplanes, planes, stride=1, downsample=None):
        super(BasicBlock, self).__init__()
        self.conv1 = conv3x3(inplanes, planes, stride)
        self.bn1 = nn.BatchNorm2d(planes)
        self.relu = nn.ReLU(inplace=True)
        self.conv2 = conv3x3(planes, planes)
        self.bn2 = nn.BatchNorm2d(planes)
		# 定义ca和sa,注意CA与channel num有关,需要指定这个超参!!!
        self.ca = ChannelAttention(planes)
        self.sa = SpatialAttention()
        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.ca(out) * out# 对channel赋权
        out = self.sa(out) * out# 对spatial赋权
        if self.downsample is not None:
            residual = self.downsample(x)
        out += residual
        out = self.relu(out)

        return out
La serie termina aquí, ¡cuanto más te guste, más rápida será la actualización!

Supongo que te gusta

Origin blog.csdn.net/weixin_43427721/article/details/124652525
Recomendado
Clasificación