【グラフニューラルネットワーク】ViG(Vision GNN)ネットワークコード実装

論文の解釈:

[グラフニューラルネットワーク] ビジュアルグラフニューラルネットワーク ViG (Vision GNN) -- 論文読解https://blog.csdn.net/weixin_37878740/article/details/130124772?spm=1001.2014.3001.5501コードアドレス:

ViG https://github.com/huawei-noah/Efficient-AI-Backbones/tree/master/vig_pytorch

1. ネットワーク構造

ViG は、等方性アーキテクチャ(ViT と同様) およびピラミッドアーキテクチャ(ResNet と同様)        にスタックできます。この記事では主にピラミッド構造 PyramidViG-B を例として分析します。関連するコードは、git のpyramid.py フォルダーと gcn_lib フォルダーにある 3 つのファイルです。

         上図のように仕様の異なるViGブロックを積み重ねることで4段のピラミッドネットワークを構築できます。移植後は、Faster RCNN のバックボーン ネットワークとして Resnet50 を置き換えることができます (ただし、直接移植の効果は理想的ではありません)。

        ネットワーク定義コード:

def pvig_b_224_gelu(num_classes =1000,pretrained=False, **kwargs):
    class OptInit:
        # 参数列表
        def __init__(self, num_classes=1000, drop_path_rate=0.0, **kwargs):
            self.k = 9 # 邻居节点数,默认为9
            self.conv = 'mr' # 图卷积层类型,可选 {edge, mr}
            self.act = 'gelu' # 激活层类型,可选 {relu, prelu, leakyrelu, gelu, hswish}
            self.norm = 'batch' # 归一化方式,可选 {batch, instance}
            self.bias = True # 卷积层是否使用偏置
            self.dropout = 0.0 # dropout率
            self.use_dilation = True # 是否使用扩张knn
            self.epsilon = 0.2 # gcn的随机采样率
            self.use_stochastic = False # gcn的随机性
            self.drop_path = drop_path_rate
            self.blocks = [2,2,18,2] # 各层的block个数
            self.channels = [128, 256, 512, 1024] # 各层的通道数
            self.n_classes = num_classes # 分类器输出通道数
            self.emb_dims = 1024 # 嵌入尺寸

    opt = OptInit(**kwargs)
    model = DeepGCN(opt)    #构造gcn
    model.default_cfg = default_cfgs['vig_b_224_gelu']    #注入参数
    return model
#  网络参数计算代码
class DeepGCN(torch.nn.Module):
    def __init__(self, opt):
        super(DeepGCN, self).__init__()
        # ...
        #  参数赋值省略
        # ...

        blocks = opt.blocks            # 获取各层block个数列表[2,2,18,2]
        self.n_blocks = sum(blocks)    # 获取block层数总数
        channels = opt.channels        # 获取输出通道数(用于分类器赋值)
        reduce_ratios = [4, 2, 1, 1]   # 下采样率
        #  获取FFN的随机深度衰减规律
        dpr = [x.item() for x in torch.linspace(0, drop_path, self.n_blocks)]
        # 获取各层knn的数量
        num_knn = [int(x.item()) for x in torch.linspace(k, k, self.n_blocks)]
        max_dilation = 49 // max(num_knn)    #最大相关数目
        HW = 224 // 4 * 224 // 4

 2. ViGモジュール

実際のネットワーク構築では、ViG ブロックをスタックして使用します。ViG ブロックは、 GCN モジュールと FFN モジュール      で構成されます。コードループを使用して ViG ブロックをスタックして構築します。

# 构造骨干网络
self.backbone = nn.ModuleList([])
        idx = 0
        for i in range(len(blocks)):
            if i > 0:
                #  如果不是第一层需要额外在层间添加下采样
                self.backbone.append(Downsample(channels[i-1], channels[i]))
                HW = HW // 4
            for j in range(blocks[i]):
                self.backbone += [
                    # 构造GCN
                    Seq(Grapher(channels[i], num_knn[idx], min(idx // 4 + 1, max_dilation), conv, act, norm,
                                    bias, stochastic, epsilon, reduce_ratios[i], n=HW, drop_path=dpr[idx],
                                    relative_pos=True),
                    # 构造FFN
                          FFN(channels[i], channels[i] * 4, act=act, drop_path=dpr[idx])
                         )]
                idx += 1
        self.backbone = Seq(*self.backbone)
        # 构造分类器
        self.prediction = Seq(nn.Conv2d(channels[-1], 1024, 1, bias=True),
                              nn.BatchNorm2d(1024),
                              act_layer(act),
                              nn.Dropout(opt.dropout),
                              nn.Conv2d(1024, opt.n_classes, 1, bias=True))
        self.model_init()

        ネットワークの順方向伝達関数では、グラフ ネットワークに入る前に画像がステム(つまり、ViT でのパッチ操作) と位置エンコード(位置に対応する行列)を受けていることがわかります。

    def forward(self, inputs):
        x = self.stem(inputs) + self.pos_embed    #patch分割和位置嵌入
        B, C, H, W = x.shape
        for i in range(len(self.backbone)):
            x = self.backbone[i](x)

        x = F.adaptive_avg_pool2d(x, 1)
        return self.prediction(x).squeeze(-1).squeeze(-1)

       ステム操作と位置の埋め込みは次のとおりです。

self.stem = Stem(out_dim=channels[0], act=act)
#返回整数部分
self.pos_embed = nn.Parameter(torch.zeros(1, channels[0], 224//4, 224//4))

        1.グラファーモジュール

                まずは Grapher の順方向伝達関数を見てみましょう

def forward(self, x):
        _tmp = x
        x = self.fc1(x)
        B, C, H, W = x.shape
        relative_pos = self._get_relative_pos(self.relative_pos, H, W)
        x = self.graph_conv(x, relative_pos)
        x = self.fc2(x)
        x = self.drop_path(x) + _tmp
        return x

                各 Grapher モジュールの基本的な処理フローは次のとおりであることがわかります。

                ①全結合層fc1

# 由一个1x1Conv和一个BatchNorm组成
self.fc1 = nn.Sequential(
            nn.Conv2d(in_channels, in_channels, 1, stride=1, padding=0),
            nn.BatchNorm2d(in_channels),
        )

                ②_get_relative_pos (.)関数により関連位置を更新します

                実際、コードの観点から見ると、ダウンサンプリングによるサイズの変化に合わせる(サイズを調整する)ために使用されます。

    def _get_relative_pos(self, relative_pos, H, W):
        if relative_pos is None or H * W == self.n:
            return relative_pos
        else:
            N = H * W
            N_reduced = N // (self.r * self.r)
            return F.interpolate(relative_pos.unsqueeze(0), size=(N, N_reduced), mode="bicubic").squeeze(0)

ブロックが初期化されるとき、初期値はget_2d_relative_pos_embed(.)関数                 によって与えられます(有効になっていない場合は、直接 None に設定されます)。

# 获取位置嵌入
relative_pos_tensor = torch.from_numpy(np.float32(
        get_2d_relative_pos_embed(in_channels,int(n**0.5)))).unsqueeze(0).unsqueeze(1)
# 进行双线性插值
relative_pos_tensor = F.interpolate(relative_pos_tensor, size=(n, n//(r*r)), 
        mode='bicubic', align_corners=False)
# 转换为nn参数
self.relative_pos = nn.Parameter(-relative_pos_tensor.squeeze(1), requires_grad=False)

                        get_2d_relative_pos_embed(.)位置埋め込み関数。gcn_lib/pos_embed.pyにあります。この機能は、グリッドを構築し、埋め込まれた位置情報を取得することです(cls_token を含む)。

                ③グラフ畳み込み(graph_conv)

self.graph_conv = DyGraphConv2d(in_channels, in_channels * 2, kernel_size,
                     dilation, conv, act, norm, bias, stochastic, epsilon, r)

                graph_conv に移動して、そのフォワード パス関数を確認します。

def forward(self, x, relative_pos=None):
    B, C, H, W = x.shape
    y = None
    if self.r > 1:    #  此参数为下采样率,金字塔池化情况下默认开启(始终大于1)
        y = F.avg_pool2d(x, self.r, self.r)
        y = y.reshape(B, C, -1, 1).contiguous()            
    x = x.reshape(B, C, -1, 1).contiguous()

    # 获取邻居节点的聚合信息(基于knn)
    edge_index = self.dilated_knn_graph(x, y, relative_pos)
    # 图卷积
    x = super(DyGraphConv2d, self).forward(x, edge_index, y)
    # 将tensor变形为四维并输出
    return x.reshape(B, -1, H, W).contiguous()

                その中で、 self.dirated_knn_graph は、gcn_lib/torch_edge.pyの DenseDiratedKnnGraph であり、ほとんどのグラフ ネットワーク アルゴリズムと同様に、torch.topk(.) は隣接行列の疎性のために使用されます。同時に、part_pairwise_ distance 関数を使用して、フィーチャから x_square_part、x_inner、および x_square の 3 つの値を抽出します。

                ④全結合層fc2

        self.fc2 = nn.Sequential(
            nn.Conv2d(in_channels * 2, in_channels, 1, stride=1, padding=0),
            nn.BatchNorm2d(in_channels),
        )

                これは、入力チャンネルが 2 倍になることを除いて、前の完全に接続されたレイヤーと同じです。

                ⑤DropPathランダム削除

self.drop_path = DropPath(drop_path) if drop_path > 0. else nn.Identity()

                過学習を防ぐために使用され、ネットワークも残差と同様の構造を持っています

x = self.drop_path(x) + _tmp

        2.FNNモジュール

                FNN モジュールは多層パーセプトロンであり、2 層の完全接続によって実装され、残差構造も備えています。

shortcut = x
x = self.fc1(x)
x = self.act(x)
x = self.fc2(x)
x = self.drop_path(x) + shortcut
return x
        self.fc1 = nn.Sequential(
            nn.Conv2d(in_features, hidden_features, 1, stride=1, padding=0),
            nn.BatchNorm2d(hidden_features),
        )
        self.act = act_layer(act)
        self.fc2 = nn.Sequential(
            nn.Conv2d(hidden_features, out_features, 1, stride=1, padding=0),
            nn.BatchNorm2d(out_features),
        )
        self.drop_path = DropPath(drop_path) if drop_path > 0. else nn.Identity()

                ここでのアクティベーション層の動作は、デフォルトでreluアクティベーション関数になります。

3. ネットワークの移行

        ピラミッド構造によるマルチスケール機能により、Swin Transformerと同様に特徴抽出用のバックボーンネットワークとして利用できるViGを、オリジナルのResNet50を置き換えるバックボーンネットワークとしてFaster RCNNに移植しました。予測予測ヘッドと平均プーリングadaptive_avg_pool2dを削除した後、224x224x3入力から7x7x1024特徴を取得できます。

    def forward(self, inputs):
        x = self.stem(inputs) + self.pos_embed
        B, C, H, W = x.shape
        for i in range(len(self.backbone)):
            x = self.backbone[i](x)

        # x = F.adaptive_avg_pool2d(x, 1)
        return x

        テストの結果、ViG はデータセット上で 70% 以上の mAP を取得できましたが、その効果は resnet50 や mobilenetv3 より劣っており、具体的な理由は不明です。

おすすめ

転載: blog.csdn.net/weixin_37878740/article/details/130642630
おすすめ