Méthodes de Convolution Légère pour l'Optimisation des Réseaux de Neurones

Les techniques de convolution légère sont cruciales pour concevoir des modèles de deep learning efficaces sur des appareils à ressources limitées. Cet article détaille des approches telles que la convolution par groupe, la convolution séparable en profondeur, les blocs de résiduels inversés, les fonctions d'activation améliorées, le mélange de canaux et l'analyse de la relation entre FLOPs et vitesse d'exécution.

Convolution par Groupe

La convolution par groupe réduit les paramètres en divisant les canaux d'entrée en sous-groupes traités indépendamment. Pour une entrée de dimension \(D_{in} \times H \times W\) et une sortie \(D_{out}\), avec G groupes, le nombre de paramètres passe de \(K^2 D_{in} D_{out}\) à \(K^2 D_{in} D_{out} / G\). Lorsque G correspond à \(D_{in} = D_{out}\), cela équivaut à une convolution en profondeur (depthwise convolution). Si la taille du noyau est égale aux dimensions spatiales, on obtient une convolution en profondeur globale (GDC), utilisée pour le pooling pondéré.

Convolution Séparable en Profondeur

Cette convolution se compose d'une convolution en profondeur suivie d'une convolution poncteulle, offrant une réduction significative des calculs. Le code suivant illustre une implémentation modifiée en PyTorch avec des noms de variables alternatifs :

from torch import nn

class SeparableConvLite(nn.Module):
    def __init__(self, in_channels, out_channels, kernel_sz=1, stride_val=1, pad=0, onnx_compat=False):
        super().__init__()
        activation = nn.ReLU if onnx_compat else nn.ReLU6
        self.conv_block = nn.Sequential(
            nn.Conv2d(in_channels=in_channels, out_channels=in_channels, kernel_size=kernel_sz,
                      groups=in_channels, stride=stride_val, padding=pad),
            nn.BatchNorm2d(in_channels),
            activation(),
            nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=1),
        )

    def forward(self, feat_in):
        return self.conv_block(feat_in)

Le ratio de paramètres par rapport à une convolution standard est \(\frac{1}{D_{out}} + \frac{1}{K^2}\), permettant des gains en efficacité.

Blocs de Résiduels Inversés et Goulots Linéaires

Introduit dans MobileNetV2, ce design résout les problèmes de perte d'information lors de l'application de ReLU à basses dimensions. En étendant les canaux via une convolution ponctuelle avant la convolution en profondeur, et en utilisant une activation linéaire en sortie, les caractéristiques sont préservées. L'implémentation PyTorch ci-dessous utilise des termes modifiés :

class InvResidualModule(nn.Module):
    def __init__(self, input_dim, output_dim, stride_mode, expansion_ratio):
        super(InvResidualModule, self).__init__()
        self.stride_mode = stride_mode
        assert stride_mode in [1, 2]

        expanded_dim = int(round(input_dim * expansion_ratio))
        self.use_skip = self.stride_mode == 1 and input_dim == output_dim

        op_list = []
        if expansion_ratio != 1:
            # pointwise convolution
            op_list.append(ConvBNReLU(input_dim, expanded_dim, kernel_size=1))
        op_list.extend([
            # depthwise convolution
            ConvBNReLU(expanded_dim, expanded_dim, stride=stride_mode, groups=expanded_dim),
            # linear pointwise convolution
            nn.Conv2d(expanded_dim, output_dim, 1, 1, 0, bias=False),
            nn.BatchNorm2d(output_dim),
        ])
        self.ops = nn.Sequential(*op_list)

    def forward(self, data_in):
        if self.use_skip:
            return data_in + self.ops(data_in)
        else:
            return self.ops(data_in)

Fonctions d'Activation

Pour minimiser la charge de calcul, des approximations comme h-sigmoid et h-swish sont conçues à l'aide de ReLU6 : h-sigmoid = ReLU6(x+3)/6 et h-swish = x * (ReLU6(x+3)/6). Ces fonctions sont souvent appliquées dans les couches profondes pour améliorer l'efficacité.

Mélange de Canaux

Le mélange de cenaux, proposé dans ShuffleNet, facilite la communication entre les groupes de convolution sans surcoût important. En réorganisant les dimensions des caractéristiques, il assure l'échange d'informations entre les sous-groupes. Voici une version modifiée du bloc en PyTorch :

class ChannelMixer(nn.Module):
    def __init__(self, group_count):
        super(ChannelMixer, self).__init__()
        self.group_count = group_count

    def forward(self, feature_map):
        batch_sz, ch, h, w = feature_map.size()
        grp = self.group_count
        feature_map = feature_map.view(batch_sz, grp, ch // grp, h, w).permute(0, 2, 1, 3, 4).contiguous().view(batch_sz, ch, h, w)
        return feature_map

Dans ShuffleNet, les unités de base intègrent des convolutions par groupe et des mélanges de canaux pour une performance optimisée.

FLOPs et Vitesse

Les FLOPs (opérations en virgule flottatne) ne sont pas directement proportionnels à la vitesse d'exécution due à des facteurs comme les coûts d'accès mémoire, le parallélisme et les optimisations matérielles. Les principes de ShuffleNetV2 recommandent de maintenir des largeurs de canal uniformes, éviter les groupes excessifs, réduire les fragments de réseau et prendre en compte les opérations élémentaires. Ces guidances conduisent à des architectures telles que les blocs avec split de canaux et structures hybrides pour une efficacité accrue.

Étiquettes: Convolution MobileNet ShuffleNet PyTorch ReLU6

Publié le 5 juin à 22h40