DenseNet (高密度に接続された畳み込みネットワーク)

序文

DenseNet は、高密度に接続された畳み込みネットワーク (高密度畳み込みネットワーク) を指します。その利点としては、主に、勾配消失の効果的な軽減、より効率的な特徴転送、より少ない計算、より少ないパラメータ量、および ResNet よりも優れたパフォーマンスが挙げられます。その欠点は主にメモリ使用量が大きいことです。

1.DenseNetネットワーク

DenseNet ネットワークは Resnet や GoogleNet に似ており、これらはすべてディープ ネットワークにおける勾配消失の問題を解決するために設計されたネットワークです。

  • Resnet は深さ方向から開始し、フロント層とバック層の間に「短絡接続」または「ショートカット」を確立することで、より深い CNN ネットワークをトレーニングできます。
  • GoogleNet は幅方向から開始し、インセプションを使用します (さまざまなサイズのコンボリューション カーネルを使用してさまざまなスケールの知覚を実現し、最後に融合して画像のより良い表現を取得します)。
  • DenseNet は特徴から始まり、前のすべての層と後続の層の間の密な接続を通じてトレーニング プロセスですべての特徴を最大限に活用することで、より良い結果を達成し、パラメーターを削減します。

2.デザインコンセプト

Resnet と DenseNet の違いを詳細に比較するために、Resnet ネットワークと DenseNet ネットワークをそれぞれ紹介します。

2.1 レスネット

](https://img-blog.csdnimg.cn/6a2acedac29f4b44a95dc8e7d6e96ab4.png

ResNet ネットワークの短絡接続メカニズム (+ は要素レベルの加算演算を表します)

Resnet ネットワークでは、入力部分のコンテンツは、残留接続を通じて畳み込みコンテンツと結合されます。層の数が深すぎるために畳み込み層が機能しない場合でも、最終結果には影響しません。どちらの畳み込み層が機能する場合でも、畳み込み層が呼び出されるため、層が深すぎる問題が回避されます。これも残留構造によってもたらされる利点です。

2.2 高密度ネット

ここに画像の説明を挿入します

DenseNet ネットワークの高密度接続メカニズム (c はチャネル レベルの接続操作を表します)

DenseNet では、機能を最大限に活用するために、より根本的な高密度接続メカニズムが設計されています。簡単に言うと、各層の特徴が保存され、後続の各畳み込み演算で使用されます。したがって、各層のネットワーク構造は、それまでの情報(畳み込み演算の前後の情報も含む)をすべて利用します。

注: Resnet ネットワーク構造では、ネットワークの残りの接続は + 操作です。つまり、2 つのデータ サイズは同じであり、取得されたデータも前のデータと同じサイズになります。DenseNet ネットワークでは、これはマージ操作です。つまり、チャネル次元で一緒に接続されます (ここでの各レイヤーの特徴マップ サイズは同じです)。

2.3 高密度接続の実装

まず DenseBlock に内部構造を実装します。これは BN+ReLU+1x1 Conv+BN+ReLU+3x3 Conv 構造で、最後にトレーニング プロセス用のドロップアウト層を追加します。

class _DenseLayer(nn.Sequential):
    """Basic unit of DenseBlock (using bottleneck layer) """
    def __init__(self, num_input_features, growth_rate, bn_size, drop_rate):
        super(_DenseLayer, self).__init__()
        self.add_module("norm1", nn.BatchNorm2d(num_input_features))
        self.add_module("relu1", nn.ReLU(inplace=True))
        self.add_module("conv1", nn.Conv2d(num_input_features, bn_size*growth_rate,
                                           kernel_size=1, stride=1, bias=False))
        self.add_module("norm2", nn.BatchNorm2d(bn_size*growth_rate))
        self.add_module("relu2", nn.ReLU(inplace=True))
        self.add_module("conv2", nn.Conv2d(bn_size*growth_rate, growth_rate,
                                           kernel_size=3, stride=1, padding=1, bias=False))
        self.drop_rate = drop_rate

    def forward(self, x):
        new_features = super(_DenseLayer, self).forward(x)
        if self.drop_rate > 0:
            new_features = F.dropout(new_features, p=self.drop_rate, training=self.training)
        #通过torch.cat操作将前面的数据合并在一起 
        #类似如:第一次操作:[x, features] 
        #      第二次操作:[x, features_1] 其中,此时的x为[x, features] 
        return torch.cat([x, new_features], 1)

3. DenseNetの実装

DenseNetのネットワーク構造を図に示します。ネットワークは主に、Dense Block と Transition Layer の 2 つの部分で構成されていることがわかります。
DenseNet は、合計 3 つの画像分類データセット (CIFAR、SVHN、および ImageNet) でテストされています。最初の 2 つのデータ セットの入力画像サイズは 32 × 32 です。最初の DenseBlock に入る前に、使用される DenseNet は最初に 3x3 コンボリューション (stride=1) を実行します。コンボリューション カーネルの数は 16 です (DenseNet -BC の場合は2k)。DenseNet には合計 3 つの DenseBlock が含まれており、各モジュールの機能マップ サイズはそれぞれ 32 × 32、16 × 16、8 × 8 であり、各 DenseBlock のレイヤー数は同じです。最後の DenseBlock の後にはグローバル AvgPooling レイヤーが続き、それがソフトマックス分類器に供給されます。DenseNet では、特徴マップのサイズが変更されないように、すべての 3x3 畳み込みで padding=1 が使用されることに注意してください。基本的な DenseNet では、次の 3 つのネットワーク構成が使用されます: { L = 40 , k = 12 }、{ L = 100 , k = 12 }、{ L = 40 , k = 24 }。DenseNet-BC 構造では、次の 3 つのネットワーク構成が使用されます: { L = 100 , k = 12 } 、 { L = 250 , k = 24 } 、 { L = 190 , k = 40 } 。ここでの L は、ネットワーク層の総数 (ネットワークの深さ) を指します。通常、トレーニング パラメータを持つ層のみがカウントされ、プーリングなどのパラメータのない層は統計に含まれません。また、BN 層にはパラメータが含まれていますが、これは個別にカウントされませんが、それが接続されている畳み込み層に含めることができます。通常の L = 40、k = 12 のネットワークの場合、最初の畳み込み層、2 つの遷移畳み込み層、最後の線形層を除くと、合計 36 層が残り、これらが 3 つの DenseBlock に均等に分割されます。 DenseBlock には 12 のレイヤーが含まれています。他のネットワーク構成でも、各 DenseBlock に含まれるレイヤーの数を計算できます。

ここに画像の説明を挿入します

ImageNet データセットで使用される DenseNet 構造

3.1 密ブロックの実装

後続の層の入力が非常に大きくなるため、DenseBlock 内でボトルネック層を使用して計算量を減らすことができます。主に、図に示すように、元の構造に 1x1 Conv を追加することによって計算量を削減します。つまり、BN+ReLU+ 1x1 Conv+BN+ReLU+3x3 Conv 。DenseNet-B 構造と呼ばれます。このうち 1x1 Conv は 4 k 個の特徴マップを取得し、特徴量を減らして計算効率を向上させる役割を果たします。
ここに画像の説明を挿入します

DenseBlock モジュールは、内部で密接続メソッドを使用します (入力フィーチャの数は直線的に増加します)。

class _DenseBlock(nn.Sequential):
    """DenseBlock"""
    def __init__(self, num_layers, num_input_features, bn_size, growth_rate, drop_rate):
        super(_DenseBlock, self).__init__()
        for i in range(num_layers):
            layer = _DenseLayer(num_input_features+i*growth_rate, growth_rate, bn_size,
                                drop_rate)
            self.add_module("denselayer%d" % (i+1,), layer)

3.2 遷移層の実装

Transition レイヤーの場合、主に 2 つの隣接する DenseBlock を接続し、特徴マップのサイズを削減します。Transition 層には 1x1 コンボリューションと 2x2 AvgPooling が含まれており、その構造は BN+ReLU+1x1 Conv+2x2 AvgPooling です。さらに、トランジション層はモデルを圧縮する役割を果たすことができます。DenseBlock を Transition に接続して得られる特徴マップのチャネル数を m とすると、Transition 層は (畳み込み層を通じて) θ m 個の特徴を生成できます。ここで、θ ∈ (0, 1] は圧縮係数 (圧縮率) です) θ = 1 の場合 の場合、Transition 層通過後も特徴量は変化しない、つまり圧縮なし 圧縮係数が 1 未満の場合、この構造は DenseNet-C と呼ばれ、θ = 0.5ボトルネック層を使用し、圧縮係数が 1 未満の DenseBlock 構造の場合、Transition 組み合わせ構造は DenseNet-BC と呼ばれ、主に
畳み込み層とプーリング層からなる Transition 層:

class _Transition(nn.Sequential):
    """Transition layer between two adjacent DenseBlock"""
    def __init__(self, num_input_feature, num_output_features):
        super(_Transition, self).__init__()
        self.add_module("norm", nn.BatchNorm2d(num_input_feature))
        self.add_module("relu", nn.ReLU(inplace=True))
        self.add_module("conv", nn.Conv2d(num_input_feature, num_output_features,
                                          kernel_size=1, stride=1, bias=False))
        self.add_module("pool", nn.AvgPool2d(2, stride=2))

3.3 DenseNet ネットワーク

class DenseNet(nn.Module):
    "DenseNet-BC model"
    def __init__(self, growth_rate=32, block_config=(6, 12, 24, 16), num_init_features=64,
                 bn_size=4, compression_rate=0.5, drop_rate=0, num_classes=1000):
        """
        :param growth_rate: (int) number of filters used in DenseLayer, `k` in the paper
        :param block_config: (list of 4 ints) number of layers in each DenseBlock
        :param num_init_features: (int) number of filters in the first Conv2d
        :param bn_size: (int) the factor using in the bottleneck layer
        :param compression_rate: (float) the compression rate used in Transition Layer
        :param drop_rate: (float) the drop rate after each DenseLayer
        :param num_classes: (int) number of classes for classification
        """
        super(DenseNet, self).__init__()
        # first Conv2d
        self.features = nn.Sequential(OrderedDict([
            ("conv0", nn.Conv2d(3, num_init_features, kernel_size=7, stride=2, padding=3, bias=False)),
            ("norm0", nn.BatchNorm2d(num_init_features)),
            ("relu0", nn.ReLU(inplace=True)),
            ("pool0", nn.MaxPool2d(3, stride=2, padding=1))
        ]))

        # DenseBlock
        num_features = num_init_features
        for i, num_layers in enumerate(block_config):
            block = _DenseBlock(num_layers, num_features, bn_size, growth_rate, drop_rate)
            self.features.add_module("denseblock%d" % (i + 1), block)
            num_features += num_layers*growth_rate
            if i != len(block_config) - 1:
                transition = _Transition(num_features, int(num_features*compression_rate))
                self.features.add_module("transition%d" % (i + 1), transition)
                num_features = int(num_features * compression_rate)

        # final bn+ReLU
        self.features.add_module("norm5", nn.BatchNorm2d(num_features))
        self.features.add_module("relu5", nn.ReLU(inplace=True))

        # classification layer
        self.classifier = nn.Linear(num_features, num_classes)

        # params initialization
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight)
            elif isinstance(m, nn.BatchNorm2d):
                nn.init.constant_(m.bias, 0)
                nn.init.constant_(m.weight, 1)
            elif isinstance(m, nn.Linear):
                nn.init.constant_(m.bias, 0)

    def forward(self, x):
        features = self.features(x)
        out = F.avg_pool2d(features, 7, stride=1).view(features.size(0), -1)
        out = self.classifier(out)
        return out

3.4 DenseNet-121 ネットワーク

ここでは DenseNet-121 ネットワークが実装されており、事前学習するかどうかを設定することで Pytorch モデル内の DenseNet-121 モデルを読み込むことができます。

def densenet121(pretrained=False, **kwargs):
    """DenseNet121"""
    model = DenseNet(num_init_features=64, growth_rate=32, block_config=(6, 12, 24, 16),
                     **kwargs)

    if pretrained:
        # '.'s are no longer allowed in module names, but pervious _DenseLayer
        # has keys 'norm.1', 'relu.1', 'conv.1', 'norm.2', 'relu.2', 'conv.2'.
        # They are also in the checkpoints in model_urls. This pattern is used
        # to find such keys.
        pattern = re.compile(
            r'^(.*denselayer\d+\.(?:norm|relu|conv))\.((?:[12])\.(?:weight|bias|running_mean|running_var))$')
        state_dict = model_zoo.load_url(model_urls['densenet121'])
        for key in list(state_dict.keys()):
            res = pattern.match(key)
            if res:
                new_key = res.group(1) + res.group(2)
                state_dict[new_key] = state_dict[key]
                del state_dict[key]
        model.load_state_dict(state_dict)
    return model

4. テスト

最後に、簡単なコードを使用してモデルをテストします。
注: 入力は 224,224 ピクチャである必要があります。そうでない場合は、モデル パラメータを調整する必要があります。

model = densenet121()
x = torch.rand(size=(1,3,224,224))
print(model(x).shape)

おすすめ

転載: blog.csdn.net/qq_36758270/article/details/129999813