Modelo ResNeXt: implementación de pytorch

Paper Portal: Transformaciones residuales agregadas para redes neuronales profundasArtículo
anterior: Modelo ResNet: implementación de pytorch

Mejoras de ResNeXt:

Basado en la red ResNet, se mejora la estructura Residual. La principal mejora es dividir las características de entrada en grupos C, realizar cálculos de rutas de conversión C para obtener salidas C, y la salida final se agrega mediante salidas C (más el atajo x). El método de implementación es reemplazar la segunda convolución en la estructura Residual con Group Convolution (convolución de grupo).

Estructura residual mejorada:

estructura residual
Tenga en cuenta que las siguientes tres estructuras son matemáticamente equivalentes, solo necesita reemplazar la segunda Conv con Group Conv.

Estructura equivalente
Para la estructura Residual utilizada por ResNet18 y 34, no tiene mucho sentido mejorar la estructura de múltiples caminos, que es matemáticamente equivalente a aumentar el número de núcleos de convolución (número de canales de salida).
Estructura equivalente

Convolución de grupo:

La convolución estándar se calcula para todos los canales del mapa de características de entrada, es decir, la longitud de cada kernel de convolución es consistente con la cantidad de canales del mapa de características.
La convolución de grupo se refiere a agrupar los mapas de características de entrada en la dimensión del canal, realizar una convolución estándar en cada grupo de mapas de características y luego empalmar los resultados de cada grupo de cálculos para obtener el resultado final.
Se puede encontrar que la convolución de grupo tiene un número menor de parámetros que la convolución estándar.
Tenga en cuenta que cuando el número de grupos de convolución de grupo es igual al número de canales del mapa de características de entrada, la convolución de grupo es Convolución separable en profundidad (Depthwise Separable Convolution); cuando el número de grupos de convolución de grupo es igual a 1, el convolución de grupo El producto es la convolución estándar.

La estructura de ResNeXt50:

La estructura general es consistente con ResNet 50. En la estructura Residual, el número de canales intermedios convolucionales se duplica. La segunda convolución usa Group Convolution, y C representa el número de grupos.
Estructura ResNeXt

import torch
import torch.nn as nn


class BasicBlock(nn.Module):  # 定义残差块,resnet18、resnet34使用此残差块
    expansion = 1  # 残差操作维度变化倍数

    def __init__(self, in_channel, out_channel, stride=1, downsample=None):  # 初始化方法
        super(BasicBlock, self).__init__()  # 继承初始化方法

        self.conv1 = nn.Conv2d(in_channels=in_channel, out_channels=out_channel, kernel_size=3, stride=stride,
                               padding=1, bias=False)  # conv操作
        self.bn1 = nn.BatchNorm2d(num_features=out_channel)  # bn操作
        self.conv2 = nn.Conv2d(in_channels=out_channel, out_channels=out_channel, kernel_size=3, stride=1, padding=1,
                               bias=False)  # conv操作
        self.bn2 = nn.BatchNorm2d(num_features=out_channel)  # bn操作
        self.relu = nn.ReLU(inplace=True)  # relu激活函数
        self.downsample = downsample  # 是否下采样

    def forward(self, x):  # 前传函数
        identity = x  # 原始x
        if self.downsample:  # 如果下采样
            identity = self.downsample(x)  # 残差边存在conv操作,x-->x'

        x = self.conv1(x)  # conv操作
        x = self.bn1(x)  # bn操作
        x = self.relu(x)  # relu激活函数
        x = self.conv2(x)  # conv操作
        x = self.bn2(x)  # bn操作

        x += identity  # F(x)+x/x'
        x = self.relu(x)  # relu激活函数

        return x


class Bottleneck(nn.Module):  # 定义残差块,renet50、resnet101、resnet152使用此残差块
    expansion = 4  # 残差操作维度变化倍数

    def __init__(self, in_channel, out_channel, stride=1, downsample=None, groups=1, base_width=64):  # 初始化方法
        super(Bottleneck, self).__init__()  # 继承初始化方法
        width = int(in_channel * (base_width / 64.0)) * groups  # F(x)第二个卷积的通道数
        self.conv1 = nn.Conv2d(in_channels=in_channel, out_channels=width, kernel_size=1, stride=1,
                               bias=False)  # conv操作
        self.bn1 = nn.BatchNorm2d(num_features=width)  # bn操作
        self.conv2 = nn.Conv2d(in_channels=width, out_channels=width, groups=groups, kernel_size=3, stride=stride,
                               padding=1, bias=False)  # conv操作,若为ResNeXt网络,则这里为group conv操作
        self.bn2 = nn.BatchNorm2d(num_features=width)  # bn操作
        self.conv3 = nn.Conv2d(in_channels=width, out_channels=out_channel * self.expansion, kernel_size=1, stride=1,
                               bias=False)  # conv操作
        self.bn3 = nn.BatchNorm2d(num_features=out_channel * self.expansion)  # bn操作

        self.relu = nn.ReLU(inplace=True)  # relu激活函数
        self.downsample = downsample  # 是否下采样

    def forward(self, x):  # 前传函数
        identity = x  # 原始x
        if self.downsample:  # 如果下采样
            identity = self.downsample(x)  # 残差边存在conv操作,x-->x'

        x = self.conv1(x)  # conv操作
        x = self.bn1(x)  # bn操作
        x = self.relu(x)  # relu激活函数

        x = self.conv2(x)  # conv操作
        x = self.bn2(x)  # bn操作
        x = self.relu(x)  # relu激活函数

        x = self.conv3(x)  # conv操作
        x = self.bn3(x)  # bn操作

        x += identity  # F(x)+x/x'
        x = self.relu(x)  # relu激活函数

        return x


class ResNet(nn.Module):  # 定义resnet模型
    def __init__(self, block, layers, num_classes, in_channel=3, channel=64, groups=1, base_width=64):  # 初始化方法
        super(ResNet, self).__init__()  # 继承初始化方法
        self.in_channel = in_channel  # 输入通道数,应为图片通道数
        self.channel = channel  # 第一次conv输出通道数
        self.groups = groups  # 分组卷积组数,ResNet默认为1,即不采用组卷积,ResNeXt不为1,采用组卷积
        self.base_width = base_width  # 每组通道数(第一个Block)

        self.conv1 = nn.Conv2d(in_channels=in_channel, out_channels=channel, kernel_size=7, stride=2, padding=3,
                               bias=False)  # conv操作
        self.bn1 = nn.BatchNorm2d(self.channel)  # bn操作

        self.relu = nn.ReLU(inplace=True)  # relu激活函数
        self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)  # maxpool操作

        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(output_size=(1, 1))  # avgpool操作
        self.fc = nn.Linear(512 * block.expansion, num_classes)  # linear映射

        for m in self.modules():  # 遍历模型结构
            if isinstance(m, nn.Conv2d):  # 如果当前结构是卷积操作
                nn.init.kaiming_normal_(m.weight, mode="fan_out", nonlinearity="relu")  # 使用kaiming初始化方法

    def _make_layer(self, block, channel, blocks, stride=1):  # 定义函数,用于生成模型结构
        downsample = None  # 默认不对原始x进行操作

        if stride != 1 or self.channel != channel * block.expansion:  # 如果卷积步长不为1或卷积前后通道数不一致,则需要对原始x进行操作
            downsample = nn.Sequential(
                nn.Conv2d(in_channels=self.channel, out_channels=channel * block.expansion, kernel_size=1,  # conv操作
                          stride=stride, bias=False),
                nn.BatchNorm2d(num_features=channel * block.expansion)  # bn操作
            )
        layers = []  # 列表用于存放模型结构

        layers.append(block(self.channel, channel, downsample=downsample, stride=stride, groups=self.groups,
                            base_width=self.base_width))  # 模型追加block结构
        self.channel = channel * block.expansion  # 通道数转换为卷积后输出通道数
        for _ in range(1, blocks):  # 进行blocks次循环
            layers.append(block(self.channel, channel, groups=self.groups, base_width=self.base_width))  # 模型追加block结构
        return nn.Sequential(*layers)  # 返回模型结构

    def forward(self, x):  # 前传函数
        x = self.conv1(x)  # conv操作
        x = self.bn1(x)  # bn操作
        x = self.relu(x)  # relu激活函数
        x = self.maxpool(x)  # maxpool激活函数

        x = self.layer1(x)  # 第一块残差集合
        x = self.layer2(x)  # 第二块残差集合
        x = self.layer3(x)  # 第三块残差集合
        x = self.layer4(x)  # 第四块残差集合

        x = self.avgpool(x)  # avgpool操作
        x = torch.flatten(x, 1)  # 将多维特征映射成一维特征向量
        x = self.fc(x)  # linear映射

        return x


def resnet18(num_classes=2):  # 定义函数,生成resnet18模型
    return ResNet(BasicBlock, [2, 2, 2, 2], num_classes=num_classes)  # resnet18使用BasicBlock基础残差块,四块残差集合使用的残差块数量为2,2,2,2


def resnet34(num_classes=2):  # 定义函数,生成resnet34模型
    return ResNet(BasicBlock, [3, 4, 6, 3], num_classes=num_classes)  # resnet18使用BasicBlock基础残差块,四块残差集合使用的残差块数量为3,4,6,3


def resnet50(num_classes=2):  # 定义函数,生成resnet50模型
    return ResNet(Bottleneck, [3, 4, 6, 3], num_classes=num_classes)  # resnet18使用Bottleneck基础残差块,四块残差集合使用的残差块数量为3,4,6,3


def resnet101(num_classes=2):  # 定义函数,生成resnet101模型
    return ResNet(Bottleneck, [3, 4, 23, 3],
                  num_classes=num_classes)  # resnet18使用Bottleneck基础残差块,四块残差集合使用的残差块数量为3, 4, 23, 3


def resnet152(num_classes=2):  # 定义函数,生成resnet152模型
    return ResNet(Bottleneck, [3, 8, 36, 3],
                  num_classes=num_classes)  # resnet18使用Bottleneck基础残差块,四块残差集合使用的残差块数量为3, 8, 36, 3


def resnext50_32x4d(num_classes=2):  # 定义函数,生成resnext50模型
    return ResNet(Bottleneck, [3, 4, 6, 3],  # resnext50使用Bottleneck基础残差块,四块残差集合使用的残差块数量为3, 4, 6, 3
                  num_classes=num_classes,
                  groups=32,  # 组卷积组数为32
                  base_width=4)  # 每组的通道数为4(第一个Block)


def resnext101_32x8d(num_classes=2):  # 定义函数,生成resnext101模型
    return ResNet(Bottleneck, [3, 4, 23, 3],  # resnext101使用Bottleneck基础残差块,四块残差集合使用的残差块数量为3, 4, 23, 3
                  num_classes=num_classes,
                  groups=32,  # 组卷积组数为32
                  base_width=8)  # 每组的通道数为8(第一个Block)

Supongo que te gusta

Origin blog.csdn.net/Peach_____/article/details/128808810
Recomendado
Clasificación