La position actuelle:Accueil du site>[Note de thèse] lsnet: Extremely Light Weight siame Network for Change Detection in Remote Sensing Image

[Note de thèse] lsnet: Extremely Light Weight siame Network for Change Detection in Remote Sensing Image

2022-05-15 07:20:32M0 61899108

 Documents

Titre de la thèse:LSNET: EXTREMELY LIGHT-WEIGHT SIAMESE NETWORK FOR CHANGE DETECTIONOF REMOTE SENSING IMAGE

Livraison:CVPR 2022

Adresse de la thèse:https://arxiv.org/abs/2201.09156

Adresse du projet:https://github.com/qaz670756/LSNet

L'idée de la thèse est simple , Deux modifications majeures ont été apportées. , Premièrement, la légèreté du réseau de base ,AdoptionCGB Le module construit un double réseau de base léger ; D'autre part, l'amélioration de la fusion pyramidale des caractéristiques ,IndenseFPNBasé sur l'amélioration, Supprimer les connexions redondantes , Augmenter le chemin de fusion du bas vers le haut . La raison pour laquelle les paramètres du modèle et le calcul sont considérablement réduits est la légèreté du réseau de base. , La Convolution séparable en profondeur est utilisée au lieu de la convolution ordinaire .

Résultats expérimentaux 

Paramètres officiels de formation : 

{
  "patch_size": 256,
  "augmentation": true,
  "num_gpus": 1,
  "num_workers": 8,
  "num_channel": 3,
  "EF": false,
  "epochs": 101,
  "batch_size": 12,
  "learning_rate": 1e-3,
  "model_name": "denseFPN",
  "loss_function": "contra_hybrid",
  "dataset_dir": "data/Real/subset/",
  "weight_dir": "./outputs/",
  "log_dir": "./log/"
}

 

Abstract

Les réseaux jumeaux deviennent progressivement des images de télédétection (remote sensing image,RSI) Mainstream of Change Detection . Mais avec la structure 、 La complexité des modules et des processus de formation , Les modèles sont plus complexes , Difficile à appliquer dans la pratique .

Cet article, Proposer une méthode pour RSI Réseau jumeau ultra - léger pour la détection des changements (Light-Weight Siamese Network,LSNet), Remplacement de la convolution standard par la convolution des cavités de convolution séparables en profondeur , Et supprimer les connexions denses redondantes , Seuls les flux de caractéristiques valides sont conservés lors de la fusion de caractéristiques jumelles , Les paramètres et les calculs sont considérablement réduits .InCDDSur l'ensemble de données, Comparé au premier modèle ,LSNet Les paramètres et les calculs ont été réduits respectivement de 90.35%Et91.34%, La précision n'a diminué que 1.5%.

Introduction

TraditionnelRSI La méthode de détection des changements dépend des caractéristiques artificielles et du temps nécessaire avant et après le traitement. , Difficulté à distinguer les changements sémantiques des bruits de fond .

Les paires d'images peuvent être entrées directement dans le réseau de convolution jumelle ,Aucun prétraitement n'est nécessaire, L'apprentissage supervisé de bout en bout peut séparer la région de changement sémantique de la région invariante .

  • Dans cet article, nous présentons un réseau jumeau Léger LSNet,Très efficace,Comme le montre la figure1. L'épine dorsale du réseau utilise un module de démarrage contextuel (Context Guide Block,CGB)Construire, Le module est basé sur la convolution des cavités séparables en profondeur et l'agrégation globale des caractéristiques. .Utilisation comparativeResNet-50 En tant que tronc ,LSNet La quantité de paramètres et la quantité de calcul de la colonne vertébrale sont les mêmes que celles de la colonne vertébrale originale. 3.97%Et32.56%.
  • Proposer un réseau pyramidal de caractéristiques différentielles (diffFPN) Pour l'extraction progressive des caractéristiques et la récupération de la résolution ( Éliminer les connexions redondantes tout en maintenant le flux caractéristique ), Enfin, la région d'image modifiée est séparée de la région d'image constante .

Method

LSNet: Y compris une double dorsale (LightSiamese Backbone) Et un réseau pyramidal de caractéristiques différentielles (diffFPN). L'épine dorsale utilise un module de démarrage contextuel (CGB)Construire,diffFPN Pour une fusion efficace de paires de caractéristiques jumelles .

Light-Siamese backbone

ImagesT1EtT2 Tronc de réseau jumeau avec poids partagé , L'épine dorsale est constituée de 4 Composé de couches composites (De haut en bas, Les couches composites sont composées respectivement de 3/3/8/12- Oui.CGBComposition du module),ChaqueCGB équivalent à deux niveaux ,Il y a donc4 Sortie des caractéristiques du Groupe ,Total52Couche.

Éléments de base(Context Guide Block)CGBComme le montre la figure2Sur la droite.EntréeX Après une expansion parallèle (Gonflement)Convolution, Pour obtenir une gamme différente (Sentir la nature sauvage) Informations contextuelles locales pour . La Convolution d'expansion est calculée de façon séparable en profondeur , C'est - à - dire que tous les canaux sont groupés , La Convolution ne fonctionne que dans un seul groupe .(Convolution séparable en profondeur, Peut réduire considérablement la quantité de calcul , Mais il y a une limite de vitesse , Le goulot d'étranglement est la largeur de bande d'accès )

Interaction des canaux et extraction globale de l'information . 

Differential feature pyramid network

 SUNNet Une méthode de fusion pyramidale des caractéristiques avec des connexions denses est proposée ,Fig.3(a)Comme indiqué,

 Ce genre dedenseFPNPrésence structurelle2Questions:

  • Connexions redondantes.(T_1,0、T_2,0 Les caractéristiques isosuperficielles sont entrées à plusieurs reprises dans d_1,0、d_2,0、d_3,0Moyenne,Inefficacité)
  • Flux de caractéristiques irrationnels .(denseFPNMoyenne,Couche de sortied_0,0Etd_1,0 Contient des caractéristiques incomplètes du tronc )

Et donc,,Présentation du documentdiffFPPNStructure,  Supprimer les connexions redondantes , Ajout d'un chemin de fusion de bas en haut , Faire en sorte que les trois couches de sortie contiennent des caractéristiques complètes du réseau de base .

Experiment and Results

Dataset and evaluation metrics

Ensemble de données:CDD

Indicateurs communs: precision、recall、F1-score、overall accuracy

Indicateurs quantitatifs:F1-G、F1-F ( Quantifier les paramètres unitaires et calculer les paires de quantités séparément F1 Effets des scores ),F1-Eff( Évaluer l'efficacité globale du modèle ) 

Accuracy and efficiency comparison 

Comparaison des paramètres et des calculs entre deux modules (CDDEnsemble de données), Comme le montre le tableau ,

  • Comparé àResNet-50,LightSiamese-52 La quantité de paramètres et la quantité de calcul de la colonne vertébrale sont les mêmes que celles de la colonne vertébrale originale. 1/25Et1/3.
  • denseFPN Il y a un flux caractéristique irrationnel dans la structure de ,diffFPN Pour ne soulever que 0.0709M Lorsque la quantité de paramètre est ,Réduction du calcul1.0884GFLOPs, Réduction de plus de la moitié .

  Plusieurs façons de CDD Comparaison des performances dans l'ensemble de données ,LSNet Tous les indices de performance de la méthode sont également ok, Top 3 .

  Comparaison de l'efficacité de plusieurs méthodes ,Utilisation visiblediffFPN La méthode de F1-PEtF1-G.

Tableau combiné2Et des graphiques3,AvecSNUNetComparé à,LSNet Les paramètres et les calculs ont été réduits respectivement de 90.35%Et91.34%,Précision réduite seulement1.5%.

LSNetRésultats visuels pour. Les résultats sont relativement précis , Mais les détails des bords doivent être affinés .(e)Comme vous pouvez le voir, Le bord de la zone de changement est plus élevé que la probabilité interne , Il montre que le réseau utilise la structure de la région comme caractéristique d'identification , Améliore sa robustesse aux changements de couleur et de texture . 

Conclusion

Pour détecter efficacement RSIChangement, Un réseau de jumelage léger est proposé , Le réseau a un module de démarrage contextuel (CGB) Le tronc jumeau léger construit (LightSiamese Backbone) Module de fusion de paires de caractéristiques (diffFPN).Dans lesCCDLes résultats sur l'ensemble de données montrent que, Par rapport à d'autres approches dominantes , La méthode obtient des résultats compétitifs avec des paramètres et des calculs limités , Son efficacité a été démontrée . 

Code de base 

Context Guide Block

class ContextGuidedBlock(nn.Module):
    """Context Guided Block for CGNet.

    This class consists of four components: local feature extractor,
    surrounding feature extractor, joint feature extractor and global
    context extractor.

    Args:
        in_channels (int): Number of input feature channels.
        out_channels (int): Number of output feature channels.
        dilation (int): Dilation rate for surrounding context extractor.
            Default: 2.
        reduction (int): Reduction for global context extractor. Default: 16.
        skip_connect (bool): Add input to output or not. Default: True.
        downsample (bool): Downsample the input to 1/2 or not. Default: False.
        conv_cfg (dict): Config dict for convolution layer.
            Default: None, which means using conv2d.
        norm_cfg (dict): Config dict for normalization layer.
            Default: dict(type='BN', requires_grad=True).
        act_cfg (dict): Config dict for activation layer.
            Default: dict(type='PReLU').
        with_cp (bool): Use checkpoint or not. Using checkpoint will save some
            memory while slowing down the training speed. Default: False.
    """

    def __init__(self,
                 in_channels,
                 out_channels,
                 dilation=2,
                 reduction=16,
                 skip_connect=True,
                 downsample=False,
                 conv_cfg=None,
                 norm_cfg=dict(type='BN', requires_grad=True),
                 act_cfg=dict(type='PReLU'),
                 with_cp=False):
        super(ContextGuidedBlock, self).__init__()
        self.with_cp = with_cp
        self.downsample = downsample

        # channels = out_channels if downsample else out_channels // 2
        channels = out_channels // 2
        if 'type' in act_cfg and act_cfg['type'] == 'PReLU':
            act_cfg['num_parameters'] = channels
        kernel_size = 3 if downsample else 1
        stride = 2 if downsample else 1
        padding = (kernel_size - 1) // 2
        # self.channel_shuffle = ChannelShuffle(2 if in_channels==in_channels//2*2 else in_channels)
        self.conv1x1 = nn.Sequential(
            nn.Conv2d(in_channels, channels, kernel_size=kernel_size, stride=stride, padding=padding),
            build_norm_layer(channels),
            nn.PReLU(num_parameters=channels)
        )

        self.f_loc = nn.Conv2d(channels, channels, kernel_size=3,
                               padding=1, groups=channels, bias=False)

        self.f_sur = nn.Conv2d(channels, channels, kernel_size=3, padding=dilation,
                               dilation=dilation, groups=channels, bias=False)

        self.bn = build_norm_layer(2 * channels)
        self.activate = nn.PReLU(2 * channels)

        # original bottleneck in CGNet: A light weight context guided network for segmantic segmentation
        # is removed for saving computation amount
        # if downsample:
        #     self.bottleneck = build_conv_layer(
        #         conv_cfg,
        #         2 * channels,
        #         out_channels,
        #         kernel_size=1,
        #         bias=False)

        self.skip_connect = skip_connect and not downsample
        self.f_glo = GlobalContextExtractor(out_channels, reduction, with_cp)
        # self.f_glo = CoordAtt(out_channels,out_channels,groups=reduction)

    def forward(self, x):

        def _inner_forward(x):
            # x = self.channel_shuffle(x)
            out = self.conv1x1(x)
            loc = self.f_loc(out)
            sur = self.f_sur(out)

            joi_feat = torch.cat([loc, sur], 1)  # the joint feature
            joi_feat = self.bn(joi_feat)
            joi_feat = self.activate(joi_feat)
            if self.downsample:
                pass
                # joi_feat = self.bottleneck(joi_feat)  # channel = out_channels
            # f_glo is employed to refine the joint feature
            out = self.f_glo(joi_feat)

            if self.skip_connect:
                return x + out
            else:
                return out

        return _inner_forward(x)


def cgblock(in_ch, out_ch, dilation=2, reduction=8, skip_connect=False):
    return nn.Sequential(
        ContextGuidedBlock(in_ch, out_ch,
                           dilation=dilation,
                           reduction=reduction,
                           downsample=False,
                           skip_connect=skip_connect))

light_siamese_backbone

class light_siamese_backbone(nn.Module):
    def __init__(self, in_ch=None, num_blocks=None, cur_channels=None,
                 filters=None, dilations=None, reductions=None):
        super(light_siamese_backbone, self).__init__()
        norm_cfg = {'type': 'BN', 'eps': 0.001, 'requires_grad': True}
        act_cfg = {'type': 'PReLU', 'num_parameters': 32}
        self.inject_2x = InputInjection(1)  # down-sample for Input, factor=2
        self.inject_4x = InputInjection(2)  # down-sample for Input, factor=4
        # stage 0
        self.stem = nn.ModuleList()
        for i in range(num_blocks[0]):
            self.stem.append(
                ContextGuidedBlock(
                    cur_channels[0], filters[0],
                    dilations[0], reductions[0],
                    skip_connect=(i != 0),
                    downsample=False,
                    norm_cfg=norm_cfg,
                    act_cfg=act_cfg)  # CG block
            )
            cur_channels[0] = filters[0]

        cur_channels[0] += in_ch
        self.norm_prelu_0 = nn.Sequential(
            build_norm_layer(cur_channels[0]),
            nn.PReLU(cur_channels[0]))

        # stage 1
        self.level1 = nn.ModuleList()
        for i in range(num_blocks[1]):
            self.level1.append(
                ContextGuidedBlock(
                    cur_channels[0] if i == 0 else filters[1],
                    filters[1], dilations[1], reductions[1],
                    downsample=(i == 0),
                    norm_cfg=norm_cfg,
                    act_cfg=act_cfg))  # CG block

        cur_channels[1] = 2 * filters[1] + in_ch
        self.norm_prelu_1 = nn.Sequential(
            build_norm_layer(cur_channels[1]),
            nn.PReLU(cur_channels[1]))

        # stage 2
        self.level2 = nn.ModuleList()
        for i in range(num_blocks[2]):
            self.level2.append(
                ContextGuidedBlock(
                    cur_channels[1] if i == 0 else filters[2],
                    filters[2], dilations[2], reductions[2],
                    downsample=(i == 0),
                    norm_cfg=norm_cfg,
                    act_cfg=act_cfg))  # CG block

        cur_channels[2] = 2 * filters[2]
        self.norm_prelu_2 = nn.Sequential(
            build_norm_layer(cur_channels[2]),
            nn.PReLU(cur_channels[2]))

        # stage 3
        self.level3 = nn.ModuleList()
        for i in range(num_blocks[3]):
            self.level3.append(
                ContextGuidedBlock(
                    cur_channels[2] if i == 0 else filters[3],
                    filters[3], dilations[3], reductions[3],
                    downsample=(i == 0),
                    norm_cfg=norm_cfg,
                    act_cfg=act_cfg))  # CG block

        cur_channels[3] = 2 * filters[3]
        self.norm_prelu_3 = nn.Sequential(
            build_norm_layer(cur_channels[3]),
            nn.PReLU(cur_channels[3]))

    def forward(self, x):
        # x = torch.cat([xA, xB], dim=0)
        # stage 0
        inp_2x = x  # self.inject_2x(x)
        inp_4x = self.inject_2x(x)
        for layer in self.stem:
            x = layer(x)
        x = self.norm_prelu_0(torch.cat([x, inp_2x], 1))
        x0_0A, x0_0B = x[:x.shape[0] // 2, :, :, :], x[x.shape[0] // 2:, :, :, :]

        # stage 1
        for i, layer in enumerate(self.level1):
            x = layer(x)
            if i == 0:
                down1 = x
        x = self.norm_prelu_1(torch.cat([x, down1, inp_4x], 1))
        x1_0A, x1_0B = x[:x.shape[0] // 2, :, :, :], x[x.shape[0] // 2:, :, :, :]

        # stage 2
        for i, layer in enumerate(self.level2):
            x = layer(x)
            if i == 0:
                down1 = x
        x = self.norm_prelu_2(torch.cat([x, down1], 1))
        x2_0A, x2_0B = x[:x.shape[0] // 2, :, :, :], x[x.shape[0] // 2:, :, :, :]

        # stage 3
        for i, layer in enumerate(self.level3):
            x = layer(x)
            if i == 0:
                down1 = x
        x = self.norm_prelu_3(torch.cat([x, down1], 1))
        x3_0A, x3_0B = x[:x.shape[0] // 2, :, :, :], x[x.shape[0] // 2:, :, :, :]

        return [x0_0A, x0_0B, x1_0A, x1_0B, x2_0A, x2_0B, x3_0A, x3_0B]


class InputInjection(nn.Module):
    """Downsampling module for CGNet."""

    def __init__(self, num_downsampling):
        super(InputInjection, self).__init__()
        self.pool = nn.ModuleList()
        for i in range(num_downsampling):
            self.pool.append(nn.AvgPool2d(3, stride=2, padding=1))

    def forward(self, x):
        for pool in self.pool:
            x = pool(x)
        return x

def build_norm_layer(ch):
    layer = nn.BatchNorm2d(ch, eps=0.01)
    for param in layer.parameters():
        param.requires_grad = True
    return layer

diffFPN

class diffFPN(nn.Module):
    def __init__(self, cur_channels=None, mid_ch=None,
                 dilations=None, reductions=None,
                 bilinear=True):
        super(diffFPN, self).__init__()
        # lateral convs for unifing channels
        self.lateral_convs = nn.ModuleList()
        for i in range(4):
            self.lateral_convs.append(
                cgblock(cur_channels[i] * 2, mid_ch * 2 ** i, dilations[i], reductions[i])
            )
        # top_down_convs
        self.top_down_convs = nn.ModuleList()
        for i in range(3, 0, -1):
            self.top_down_convs.append(
                cgblock(mid_ch * 2 ** i, mid_ch * 2 ** (i - 1), dilation=dilations[i], reduction=reductions[i])
            )

        # diff convs
        self.diff_convs = nn.ModuleList()
        for i in range(3):
            self.diff_convs.append(
                cgblock(mid_ch * (3 * 2 ** i), mid_ch * 2 ** i, dilations[i], reductions[i])
            )
        for i in range(2):
            self.diff_convs.append(
                cgblock(mid_ch * (3 * 2 ** i), mid_ch * 2 ** i, dilations[i], reductions[i])
            )
        self.diff_convs.append(
            cgblock(mid_ch * 3, mid_ch * 2,
                    dilation=dilations[0], reduction=reductions[0])
        )
        self.up2x = up(32, bilinear)

    def forward(self, output):
        tmp = [self.lateral_convs[i](torch.cat([output[i * 2], output[i * 2 + 1]], dim=1))
               for i in range(4)]

        # top_down_path
        for i in range(3, 0, -1):
            tmp[i - 1] += self.up2x(self.top_down_convs[3 - i](tmp[i]))

        # x0_1
        tmp = [self.diff_convs[i](torch.cat([tmp[i], self.up2x(tmp[i + 1])], dim=1)) for i in [0, 1, 2]]
        x0_1 = tmp[0]
        # x0_2
        tmp = [self.diff_convs[i](torch.cat([tmp[i - 3], self.up2x(tmp[i - 2])], dim=1)) for i in [3, 4]]
        x0_2 = tmp[0]
        # x0_3
        x0_3 = self.diff_convs[5](torch.cat([tmp[0], self.up2x(tmp[1])], dim=1))

        return x0_1, x0_2, x0_3

LSNet_diffFPN

class LSNet_diffFPN(nn.Module):
    # SNUNet-CD with ECAM
    def __init__(self, in_ch=3, mid_ch=32, out_ch=2, bilinear=True):
        super(LSNet_diffFPN, self).__init__()
        torch.nn.Module.dump_patches = True

        n1 = 32  # the initial number of channels of feature map
        filters = (n1, n1 * 2, n1 * 4, n1 * 8, n1 * 16)
        num_blocks = (3, 3, 8, 12)
        dilations = (1, 2, 4, 8)
        reductions = (4, 8, 16, 32)
        cur_channels = [0, 0, 0, 0]
        cur_channels[0] = in_ch

        self.backbone = light_siamese_backbone(in_ch=in_ch, num_blocks=num_blocks,
                                               cur_channels=cur_channels,
                                               filters=filters, dilations=dilations,
                                               reductions=reductions)

        self.head = cam_head(mid_ch=mid_ch,out_ch=out_ch)

        self.FPN = diffFPN(cur_channels=cur_channels, mid_ch=mid_ch,
                           dilations=dilations, reductions=reductions, bilinear=bilinear)


        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)

    def forward(self, x, debug=False):

        output = self.backbone(x)

        x0_1, x0_2, x0_3 = self.FPN(output)

        out = self.head(x0_1, x0_2, x0_3)

        if debug:
            print_flops_params(self.backbone, [x], 'backbone')
            print_flops_params(self.FPN, [output], 'diffFPN')
            print_flops_params(self.head, [x0_1, x0_2, x0_3], 'head')

        return (x0_1, x0_2, x0_3, x0_3, out,)

Mentions de copyright
Auteur de cet article [M0 61899108],Réimpression s’il vous plaît apporter le lien vers l’original, merci
https://fra.chowdera.com/2022/135/202205142322539306.html

Recommandé au hasard