[セマンティックセグメンテーション] DeepLab v3+ (DeepLab v3 Plus、Backbone、Xception、MobileNet v2、Encoder、Decoder、ASPP、マルチスケール フュージョン、拡張コンボリューション)

セマンティック画像セグメンテーションのための Atrous 分離可能畳み込みを備えたエンコーダ/デコーダ

DeepLab v3+ モデルは、その効果が非常に優れているため、セマンティック セグメンテーションの新たなピーク (2018 年) であると考えられています。このペーパーでは主にモデルのアーキテクチャに焦点を当て、特徴を抽出するためにエンコーダの解像度を任意に制御する機能と、拡張畳み込みによる精度と時間消費のバランスを取る機能を紹介します。

これは2018年にCVPRに掲載された記事です。DeepLab V3 と比較すると、①Encoder-Decoder アーキテクチャ、②改良された ASPP モジュール、③より強力なマルチスケール情報融合、④バックボーンとしての Xception、⑤任意のサイズの入力のサポートの 5 つの変更点があります。したがって、DeepLab v3 以降は、アーキテクチャ、モジュールの改善、機能の点で DeepLab v3 と比べて改善されており、セマンティック セグメンテーション タスクのパフォーマンスがさらに向上しています。

  1. エンコーダ デコーダ アーキテクチャ: DeepLab v3+ では、DeepLab v3 の ASPP (Atrous Spatial Pyramid Pooling) モジュールと特定のデコーダ モジュールを組み合わせた新しいエンコーダ デコーダ アーキテクチャが導入されています。このようなアーキテクチャは、グローバル情報とローカル情報を効果的に統合できるため、セマンティック セグメンテーションの精度が向上します。

  2. 改良された ASPP モジュール: ASPP は DeepLab v3+ で改良されました。ASPP の目標は、さまざまなスケールでコンテキスト情報をキャプチャして、画像内のオブジェクトをよりよく理解することです。DeepLab v3+ は、より大きな畳み込み比を使用し、モデルがマルチスケール情報をより適切に処理できるようにします。

  3. より強力なマルチスケール情報融合: セマンティック セグメンテーションのパフォーマンスをさらに向上させるために、DeepLab v3+ は、さまざまなレベルでネットワークに損失関数を追加できるディープ監視と呼ばれるテクノロジーを使用し、低レベルの機能も参加できるようにします。これにより、マルチスケールの情報をより適切に統合することができます。

  4. バックボーンとしての Xception : DeepLab v3+ は、バックボーンとして Xception (より効率的な畳み込みニューラル ネットワーク アーキテクチャ) を使用しており、DeepLab v3 で使用されている ResNet と比較して、Xception はパラメータが少なく、計算効率が高くなります。

  5. あらゆるサイズの入力をサポート: DeepLab v3+ は、平均プーリング戦略を使用してさまざまなサイズの入力画像を処理するため、固定入力サイズに制限されることなく、セマンティック セグメンテーション タスクであらゆるサイズの画像を受け入れることができます。

Xception は、2016 年に François Chollet によって提案された畳み込みニューラル ネットワーク アーキテクチャです。これは Google Inception シリーズ ネットワークをさらに発展させたもので、深さ方向の分離可能な畳み込みを使用してパラメータの数と計算の複雑さを削減し、ImageNet 画像分類タスクで優れたパフォーマンスを達成しています。Xceptionの正式名称は「Extreme Inception」で、名前の「X」はInceptionシリーズをベースにした極限の改良を意味する「Extreme」から取られている。

Xception の主な機能は、Inception モジュールの従来の標準畳み込みを深さ分離可能な畳み込みに置き換えることです。深さ方向の分離可能なコンボリューションは、標準のコンボリューションを 2 つのステップに分割します: 最初に、各入力チャンネルで独立したコンボリューションを実行する深さ方向のコンボリューション (DW Conv)、次にポイントワイズ コンボリューション (Point-wise Convolution)。 、1 × 1 1\times 1を使用します。1×1 つのコンボリューション カーネルはチャネル間の線形結合を実行します。この構造により、計算量とパラメータが削減され、高いパフォーマンスを維持しながら計算効率が向上します。

Xception は、コンピュータ ビジョン タスク、特に画像分類タスクである程度の成功を収めています。その設計思想は、その後のいくつかのネットワーク アーキテクチャにも影響を与えました。

DeepLab v3+ の核となるアイデア

DeepLab v3+ の中心となるアイデアは、新しいエンコーダ/デコーダ アーキテクチャを導入することでセマンティック画像セグメンテーションのパフォーマンスを向上させることです。従来の DeepLab シリーズ モデルは、Atrous Convolution (拡張畳み込み) や ASPP (Atrous Spatial Pyramid Pooling) などのテクノロジーを使用して、画像のコンテキスト情報をキャプチャし、画像内のオブジェクトをよりよく理解します。ただし、これらのモデルは、マルチスケール情報やエッジ部分を扱う場合に特定の制限がある場合があります

これらの問題を解決するために、DeepLab v3+ では、次の点で構成されるエンコーダー デコーダー構造が導入されています。

  1. エンコーダ: 深さ方向の分離可能な畳み込みを使用する Xception ネットワークが、エンコーダのバックボーン ネットワークとして使用されます。Xception は、従来の ResNet よりもパラメーターが少なく、計算効率が高い効率的な畳み込みニューラル ネットワーク アーキテクチャです。

  2. ASPP : Encoder の最後に、Atrous Spatial Pyramid Pooling (ASPP) モジュールが導入されます。ASPP は、画像の意味情報をよりよく理解するために、さまざまなスケールでコンテキスト情報をキャプチャできます。

  3. デコーダー: DeepLab v3+ は、アップサンプリング操作を通じてエンコーダーによって抽出された特徴マップを元の入力画像のサイズに復元する特定のデコーダー モジュールを使用します。この目的は、モデルがセマンティック セグメンテーションをより適切に実行できるように、グローバル情報とローカル情報を統合することです。

  4. 深い監視: DeepLab v3+ にも深い監視テクノロジーが導入されています。さまざまなレベルのデコーダに損失関数を追加して、低レベルの機能も監視に参加できるようにします。これは、マルチスケール情報をより適切に統合し、モデルのパフォーマンスを向上させるのに役立ちます。

  5. あらゆるサイズの入力をサポート: DeepLab v3+ は、平均プーリング戦略を使用してさまざまなサイズの入力画像を処理するため、固定入力サイズに制限されることなく、セマンティック セグメンテーション タスクであらゆるサイズの画像を受け入れることができます。

要約すると、DeepLab v3+ の中心となるアイデアは、エンコーダ デコーダ構造やその他の改良を通じて、画像のコンテキスト情報とマルチスケール情報をより適切にキャプチャし、それによってセマンティック画像セグメンテーション タスクのパフォーマンスを向上させることです。

抽象的な

DCNN は、セマンティック セグメンテーション タスクで ASPP モジュールまたはエンコーダ-デコーダ (エンコード-デコード) 構造をよく使用します。前者は、複数のスケールおよび複数の有効受容野での畳み込みまたはプーリング操作を使用して、マルチスケールのコンテキスト情報をエンコードします。一方、後者は、空間情報を段階的に回復することにより、より明確なオブジェクトの境界を捕捉します。このホワイトペーパーでは、両方のアプローチの利点を組み合わせることを提案します。具体的には、私たちが提案するモデル DeepLab v3+ は、セグメンテーション結果、特にオブジェクト境界のセグメンテーション結果を改善するために、DeepLab v3 に基づくシンプルで効果的なデコーダー モジュール (Decoder) を追加します。私たちは Xception モデルをさらに調査し、深さ方向に分離可能な畳み込みを ASPP およびデコーダ モジュールに適用して、より高速で強力なエンコーダ/デコーダ ネットワークを実現しました。PASCAL VOC 2012 および Cityscapes データ セットで提案されたモデルの有効性を検証し、テスト セットのパフォーマンスはそれぞれ89.0% および 89.0\%に達しました。89.0%および82.1% 82.1\%後処理なしで82.1%私たちの論文には、リンク先の Tensorflow で公開されている提案モデルのリファレンス実装も付属しています:公式コード (TensorFlow バージョン)

2. DeepLab v3+ 実装のアイデア

2.1 バックボーン(基幹ネットワーク)

DeepLab v3+ では、バックボーン特徴抽出ネットワークとして Xception シリーズを使用していますが、Bubbliying はXception と MobileNet v2 という 2 つのバックボーン ネットワークを提供しています。Xception はトレーニングコストが比較的高いため、この記事では Backbone として MobileNet v2 を使用します。

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

  • DeepLab v3+ では、エンコーダー部分に多数の拡張畳み込み (ASPP モジュール内) が導入されています。これにより、モデルは情報を失うことなく受容野を増やすことができるため、各畳み込み出力にはより広範囲の情報が含まれます。
  • 元の論文で使用されているバックボーンは Xception です。

MobileNet v2 は、2018 年に Google が提案した効率的な畳み込みニューラル ネットワーク アーキテクチャであり、コンピューティング リソースが限られたデバイス上での画像分類および関連するビジョン タスクのために使用されます。これは MobileNet シリーズの第 2 世代であり、MobileNet v1 を改良および拡張したものです。

MobileNet v2 の中心となるアイデアは、一連の革新的な設計を通じてモデルのパフォーマンスとコンピューティング効率を向上させることです。主な改善点は次のとおりです。

  1. 線形ボトルネック構造: MobileNet v2 では、深いネットワークを構築するために線形ボトルネック構造が導入されています。この構造には、次元削減のための 1x1 畳み込み、次に一連の Depthwise Separable Convolution (深さ分離可能畳み込み) 層が含まれ、最後に1 × 1 1\times 1を使用します。1×1回の畳み込みで次元を増加します。この構造により、パラメータと計算の量が削減されながら、ネットワークの深さが増加します。

  2. 逆残差構造: MobileNet v2 では、ディープ ネットワークにおける勾配消失問題を解決するために逆残差構造が導入されています。この構造により、特定の層で接続をスキップできるようになり、勾配がネットワークを通じてより適切に伝播できるようになり、より深いネットワークのトレーニングに役立ちます。

  3. 線形アクティベーション関数: MobileNet v2 は、従来の非線形アクティベーション関数 (ReLU など) の代わりに線形アクティベーション関数を使用します。これにより、勾配伝播中の情報損失を回避し、より深いネットワークのトレーニングに役立ちます。

  4. より広いネットワーク: MobileNet v2 は、モデルの表現力と精度を高めるために、より広いネットワーク (つまり、より多くのチャネル) を使用します。

  5. SE (スクイーズアンドエキサイテーション) モジュール: MobileNet v2 では、重要な機能に対するネットワークの注目を強化するために SE モジュールが導入されています。SE モジュールは、チャネル間の重要度を適応的に調整することでモデルのパフォーマンスを向上させます。

MobileNet v2 は、ImageNet 画像分類タスクで優れたパフォーマンスを達成し、計算効率が高いため、リソースに制約のあるモバイル デバイスや組み込みシステムへの展開に適しています。MobileNet v2 は、その優れたパフォーマンスと効率的なコンピューティング特性により、モバイル コンピューター ビジョン タスクにとって重要な選択肢となっています。

MobileNet v2 の詳細については、ブログ「MobileNet シリーズ (v1 ~ v3) 理論解説」を参照してください。

2.1.1 反転された残差モジュール

MobileNet v2 PyTorch の公式実装コードを見てみましょう。

class ConvBNReLU(nn.Sequential):
    def __init__(self, in_channel, out_channel, kernel_size=3, stride=1, groups=1):
        padding = (kernel_size - 1) // 2
        super(ConvBNReLU, self).__init__(
            nn.Conv2d(in_channel, out_channel, kernel_size, stride, padding, groups=groups, bias=False),
            nn.BatchNorm2d(out_channel),
            nn.ReLU6(inplace=True)
        )


class InvertedResidual(nn.Module):
    def __init__(self, in_channel, out_channel, stride, expand_ratio):
        super(InvertedResidual, self).__init__()
        hidden_channel = in_channel * expand_ratio
        self.use_shortcut = stride == 1 and in_channel == out_channel

        layers = []
        if expand_ratio != 1:
            # 1x1 pointwise conv
            layers.append(ConvBNReLU(in_channel, hidden_channel, kernel_size=1))
        layers.extend([
            # 3x3 depthwise conv
            ConvBNReLU(hidden_channel, hidden_channel, stride=stride, groups=hidden_channel),
            # 1x1 pointwise conv(linear)
            nn.Conv2d(hidden_channel, out_channel, kernel_size=1, bias=False),
            nn.BatchNorm2d(out_channel),
        ])

        self.conv = nn.Sequential(*layers)

    def forward(self, x):
        if self.use_shortcut:
            return x + self.conv(x)
        else:
            return self.conv(x)

逆残差構造は 2 つの部分に分割できます。

  1. メインパス/特徴抽出部分(左): 最初に使用1 × 1 1\times 11×1回の畳み込みで次元を増やし、3 × 3 3\times 33×特徴抽出には3深さの分離可能な畳み込みを使用し、1 × 1 1\times 11×1畳み込みの次元削減
  2. 残差部分/勾配戻り部分(右): 入力と出力が直結されています

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

逆残差構造の模式図

DeepLab v3+ では、通常 5 回のダウンサンプリングは実行されないことに注意してください (ダウンサンプリング率が大きいほど、セグメンテーション モデルの効果は悪くなります)。3 回のダウンサンプリングはオプションです (2 3 = 8 2^3 = 8 )23=8倍のダウンサンプリング) と 4 倍のダウンサンプリング (2 4 = 16 2^4 = 16)24=16回のダウンサンプリング)、この記事で使用されている 4 回のダウンサンプリングは 16 回のダウンサンプリングです。

MobileNet v2 (Backbone) の特徴抽出が完了すると、入力画像の高さと幅を 2 ​​回圧縮 (4 回のダウンサンプリング) した結果、2 つの有効な特徴層が得られます (低レベルの特徴、低レベルの特徴)セマンティクス).フィーチャ マップ)、有効なフィーチャ レイヤーは、入力イメージの高さと幅を 4 回圧縮した結果です (16 倍のダウンサンプリング)。

2.1.2 DeepLab v3+ での MobileNet v2 バックボーン コードの実装

PyTorch によって正式に実装された MobileNet v2 モデルは簡単に入手できますが、前に述べたように、DeepLab v3+ はすべての Backbone を使用するわけではなく、ブランチ (浅い特徴マップを抽出する) もあるため、MobileNet v2 用の Backbone を使用する必要があります。変更されました。どのように変更するかが問題ですが、将来的に MobileNet v2 の元のバックボーンを使用する可能性があるため、対応するソース コードを変更しないのが最善の状況であり、読み込まれた MobileNet v2 を変更できます。

変更の根拠は、PyTorch ではモデルが辞書であることです。

ファイル名: deeplabv3_plus.py.

from nets.mobilenetv2 import mobilenetv2


class MobileNetV2(nn.Module):
    def __init__(self, downsample_factor=8, pretrained=True):
        super(MobileNetV2, self).__init__()
		
		# 导入 partial
        from functools import partial
		
		# 定义好模型
        model = mobilenetv2(pretrained)
        
        # 去除逆残差结构后面的1×1卷积
        self.features = model.features[:-1]

        self.total_idx = len(self.features)  # Block的数量
        self.down_idx = [2, 4, 7, 14]  # 每个进行下采样逆残差结构在模型中所处的idx


        if downsample_factor == 8:
            """
                如果下采样倍数为8,则先对倒数两个需要下采样的Block进行参数修改,使其stride=1、dilate=2
                如果下采样倍数为16,则先对最后一个Block进行参数修改,使其stride=1、dilate=2
            """
            # 修改倒数两个Block
            for i in range(self.down_idx[-2], self.down_idx[-1]):
                self.features[i].apply(
                    partial(self._nostride_dilate, dilate=2))  # 修改stride=1, dilate=2
            # 修改剩下的所有block,使其都使用膨胀卷积
            for i in range(self.down_idx[-1], self.total_idx):
                self.features[i].apply(
                    partial(self._nostride_dilate, dilate=4))
                
        elif downsample_factor == 16:
            for i in range(self.down_idx[-1], self.total_idx):
                self.features[i].apply(
                    partial(self._nostride_dilate, dilate=2)
                )

    def _nostride_dilate(self, m, dilate):
        """修改返回的block,使其stride=1

        Args:
            m (str): 模块的名称
            dilate (int): 膨胀系数
        """
        classname = m.__class__.__name__  # 获取模块名称
        if classname.find('Conv') != -1:  # 如果有卷积层
            if m.stride == (2, 2):  # 如果卷积层的步长为2
                m.stride = (1, 1)  # 修改步长为1
                if m.kernel_size == (3, 3):  # 修改对应的膨胀系数和padding
                    m.dilation = (dilate//2, dilate//2)
                    m.padding = (dilate//2, dilate//2)
            else:  # 如果卷积层步长本来就是1
                if m.kernel_size == (3, 3):  # 修改对应的膨胀系数和padding
                    m.dilation = (dilate, dilate)
                    m.padding = (dilate, dilate)

    def forward(self, x):
        """前向推理

        Args:
            x (tensor): 输入特征图

        Returns:
            (tensor, tensor): 输出特征图(两个)
        """
        low_level_features = self.features[:4](x)  # 浅层的特征图
        x = self.features[4:](low_level_features)  # 经过完整Backbone的特征图
        return low_level_features, x

partialここで、この Python 組み込みメソッドについて説明する必要があります。

from functools import partial

# 原始函数
def add(x, y):
	return x + y

# 使用 partial 部分应用 add 函数的第一个参数为 5
add_5 = partial(add, 5)

# 调用新的函数 add_5 只需要提供第二个参数
result = add_5(10)  # 实际调用 add(5, 10)

print(result)  # 输出: 15

上記の例では、partial関数はadd関数の最初のパラメータを 5 に固定し、新しい関数を返しますadd_5を呼び出すとadd_5(10)、実際には を呼び出すのと同じになりadd(5, 10)、結果は 15 になります。

関数を使用するとparital、場合によってはコードがより簡潔で読みやすくなり、関数の再利用がより便利になります。

2.2 特徴抽出構造の強化 —— ASPP + Concat

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

DeepLab v3+ では、強化された特徴抽出ネットワークは 2 つの部分に分割できます。

  1. エンコーダーでは、異なる展開係数を持つ拡張畳み込みを使用して、4 回圧縮されたフィーチャ レイヤーからフィーチャを抽出し、チャネルに沿ってスタックしてマージし、1 × 1 1\times 1 を実行します1×1畳み込み圧縮機能。
  2. デコーダーでは、 2 回圧縮された低レベル フィーチャ レイヤーに1 × 1 1\times 1 を使用します。1×1回の畳み込みでチャネル数を調整し、次にアップサンプリングの最初の部分で取得した特徴マップを使用してチャネル ディメンションを実行しconcat、最後に 2 回の通常の畳み込みを実行します。

2.2.1 ASPP コードの実装

ファイル名: deeplabv3_plus.py.

class ASPP(nn.Module):
    def __init__(self, dim_in, dim_out, rate=1, bn_mom=0.1):
        super(ASPP, self).__init__()
        self.branch1 = nn.Sequential(  # 1×1 普通卷积
            nn.Conv2d(dim_in, dim_out, 1, 1, padding=0,
                      dilation=rate, bias=True),
            nn.BatchNorm2d(dim_out, momentum=bn_mom),
            nn.ReLU(inplace=True),
        )
        self.branch2 = nn.Sequential(  # 3×3膨胀卷积(r=6)
            nn.Conv2d(dim_in, dim_out, 3, 1, padding=6 *
                      rate, dilation=6*rate, bias=True),
            nn.BatchNorm2d(dim_out, momentum=bn_mom),
            nn.ReLU(inplace=True),
        )
        self.branch3 = nn.Sequential(  # 3×3膨胀卷积(r=12)
            nn.Conv2d(dim_in, dim_out, 3, 1, padding=12 *
                      rate, dilation=12*rate, bias=True),
            nn.BatchNorm2d(dim_out, momentum=bn_mom),
            nn.ReLU(inplace=True),
        )
        self.branch4 = nn.Sequential(  # 3×3膨胀卷积(r=18)
            nn.Conv2d(dim_in, dim_out, 3, 1, padding=18 *
                      rate, dilation=18*rate, bias=True),
            nn.BatchNorm2d(dim_out, momentum=bn_mom),
            nn.ReLU(inplace=True),
        )
        
        """
            在论文中,这里应该是池化层,但这里定义为普通卷积层,
            但莫慌,在forward函数中先进行池化再进行卷积(相当于增加了一个后置卷积)
        """
        self.branch5_conv = nn.Conv2d(dim_in, dim_out, 1, 1, 0, bias=True)
        self.branch5_bn = nn.BatchNorm2d(dim_out, momentum=bn_mom)
        self.branch5_relu = nn.ReLU(inplace=True)

        self.conv_cat = nn.Sequential(  # concat之后需要用到的1×1卷积
            nn.Conv2d(dim_out*5, dim_out, 1, 1, padding=0, bias=True),
            nn.BatchNorm2d(dim_out, momentum=bn_mom),
            nn.ReLU(inplace=True),
        )

    def forward(self, x):
        [b, c, row, col] = x.size()  # [BS, C, H, W]
        
        # 先进行前4个分支
        conv1x1 = self.branch1(x)
        conv3x3_1 = self.branch2(x)
        conv3x3_2 = self.branch3(x)
        conv3x3_3 = self.branch4(x)
        
        # 第五个分支:全局平均池化+卷积
        global_feature = torch.mean(input=x, dim=2, keepdim=True)  # 沿着H进行mean
        global_feature = torch.mean(input=global_feature, dim=3, keepdim=True)  # 再沿着W进行mean
        
        # 经典汉堡包卷积结构
        global_feature = self.branch5_conv(global_feature)
        global_feature = self.branch5_bn(global_feature)
        global_feature = self.branch5_relu(global_feature)
        
        # 双线性插值使其回复到输入特征图的shape
        global_feature = F.interpolate(
            input=global_feature, size=(row, col), scale_factor=None, mode='bilinear', align_corners=True)

        # 沿通道方向将五个分支的内容堆叠起来
        feature_cat = torch.cat(
            [conv1x1, conv3x3_1, conv3x3_2, conv3x3_3, global_feature], dim=1)
        
        # 最后经过1×1卷积调整通道数
        result = self.conv_cat(feature_cat)
        return result

ここでresult得られるのは、図の緑色の特徴マップです。

2.2.2 Decoder の強化された特徴抽出構造

ファイル名: deeplabv3_plus.py.

class DeepLab(nn.Module):
    def __init__(self, num_classes, backbone="mobilenet", pretrained=True, downsample_factor=16):
        super(DeepLab, self).__init__()
        if backbone == "xception":
            """
            获得两个特征层
                1. 浅层特征    [128,128,256]
                2. 主干部分    [30,30,2048]
            """
            self.backbone = xception(
                downsample_factor=downsample_factor, pretrained=pretrained)
            in_channels = 2048
            low_level_channels = 256
        elif backbone == "mobilenet":
            """
            获得两个特征层
                1. 浅层特征    [128,128,24
                2. 主干部分    [30,30,320]
            """
            self.backbone = MobileNetV2(
                downsample_factor=downsample_factor, pretrained=pretrained)
            in_channels = 320
            low_level_channels = 24
        else:
            raise ValueError(
                'Unsupported backbone - `{}`, Use mobilenet, xception.'.format(backbone))

        # ASPP特征提取模块:利用不同膨胀率的膨胀卷积进行特征提取
        self.aspp = ASPP(dim_in=in_channels, dim_out=256,
                         rate=16//downsample_factor)

        # 浅层特征图
        self.shortcut_conv = nn.Sequential(
            nn.Conv2d(low_level_channels, 48, 1),
            nn.BatchNorm2d(48),
            nn.ReLU(inplace=True)
        )

        self.cat_conv = nn.Sequential(
            nn.Conv2d(48+256, 256, 3, stride=1, padding=1),
            nn.BatchNorm2d(256),
            nn.ReLU(inplace=True),
            nn.Dropout(0.5),

            nn.Conv2d(256, 256, 3, stride=1, padding=1),
            nn.BatchNorm2d(256),
            nn.ReLU(inplace=True),

            nn.Dropout(0.1),
        )
		
		# 最后的1×1卷积,目的是调整输出特征图的通道数,调整为 num_classes
        self.cls_conv = nn.Conv2d(256, num_classes, 1, stride=1)

    def forward(self, x):
        H, W = x.size(2), x.size(3)
        
        low_level_features, x = self.backbone(x)  # 浅层特征-进行卷积处理
        x = self.aspp(x)  # 主干部分-利用ASPP结构进行加强特征提取
        
        # 先利用1×1卷积对浅层特征图进行通道数的调整
        low_level_features = self.shortcut_conv(low_level_features)

        # 先对深层特征图进行上采样
        x = F.interpolate(x, size=(low_level_features.size(
            2), low_level_features.size(3)), mode='bilinear', align_corners=True)
        # 再进行堆叠
        x = self.cat_conv(torch.cat((x, low_level_features), dim=1))

        # 最后使用3×3卷积进行特征提取
        x = self.cls_conv(x)
        
        # 上采样得到和原图一样大小的特征图
        x = F.interpolate(x, size=(H, W), mode='bilinear', align_corners=True)
        return x

2.3 予測構造の取得

2.1と2.2で得られた結果を用いて入力画像の特徴を求めることができますが、今回はその特徴を利用して予測結果を得る必要があります。

特徴を使用して予測結果を取得するプロセスは、次の 2 つのステップに分割できます。

  1. 1 × 1 1\times 1を使用する1×チャネル調整用の1 つの畳み込み、 に調整されますnum_classes
  2. この関数を使用してアップサンプリングを実行し、最終的な出力レイヤーが入力画像と同じになるようinterpolateにします。[H, W]

そのコード実装は 2.2.2 で提供されています。

2.4 損失関数

DeepLab v3+ で使用される損失関数は、次の 2 つの部分で構成されます。

  1. クロスエントロピー損失
  2. ダイスロス

2.4.1 相互エントロピー損失

クロス エントロピー ロスは、セマンティック セグメンテーション プラットフォームがソフトマックスを使用してピクセルを分類するときに使用される一般的なクロス エントロピー ロスです。

クロス エントロピー ロスは、2 つの確率分布間の差を測定するために使用されます。深層学習では、通常、複数分類タスク、特にピクセルレベルの分類画像分類、その他のタスクに使用されます。

分類問題があり、モデルの出力が確率分布y ^ \hat{y}であると仮定します。y^、各カテゴリの予測確率を表し、真のラベルはyyですyは、サンプルの真のカテゴリを表します。クロスエントロピー損失関数の式は次のとおりです。

クロスエントロピー損失 = − ∑ i = 1 N ∑ j = 1 C yij log ⁡ ( y ^ ij ) \text{クロスエントロピー損失} = -\sum_{i=1}^{N} \sum_{j=1} ^{C} y_{ij} \log(\hat{y}_{ij})クロスエントロピー損失=i = 1Nj = 1Cyイジログ( _y^イジ)

で、

  • NNN はサンプル数を表します。
  • CCC はカテゴリの数を表します。
  • yij y_{ij}yイジサンプルⅡですiの真のラベルif サンプルii私はカテゴリーjjに属しますjは 1、それ以外の場合は 0。
  • y ^ ij \hat{y}_{ij}y^イジモデルiiによって予測されたサンプルです。私はカテゴリーjjに属しますjの確率。

クロスエントロピー損失関数の計算プロセスは、各サンプルの予測確率と真のラベルのクロスエントロピーを計算し、すべてのサンプルのクロスエントロピーを合計し、負の値を取得します。この損失関数の目標は、モデルの予測と真のラベルの差を最小限に抑え、モデルがトレーニング データによりよく適合し、目に見えないデータの汎化を向上できるようにすることです。

2.4.2 Dice Loss (Dice 係数損失関数)

Dice Loss は、セマンティック セグメンテーションの評価指標を Loss として使用します。Dice 係数は、設定された類似性測定関数です。通常、2 つのサンプルの類似性を計算するために使用されます。値の範囲は [0, 1] [0,1 ] です[ 0 ,1 ]

ダイス損失を計算するときは、まずダイス係数を計算し、次にダイス係数を 1 から減算してダイス損失を取得する必要があります。

  1. ダイス係数を計算します

サイコロ = 2 ∣ X ∩ Y ∣ ∣ X ∣ + ∣ Y ∣ \text{サイコロ} = \frac{2|X \cap Y|}{|X| + |Y|}サイコロ=X +Y 2∣X _Y

その中で、XXX は予測結果のバイナリ マスク (またはセグメンテーション領域) を表し、YYY は、真の結果のバイナリ マスクを表します。

  1. サイコロの損失を計算する:

サイコロの損失 = 1 − サイコロ \text{サイコロの損失} = 1 - \text{サイコロ}ダイスロス=1サイコロ

Dice Loss の値の範囲は[0, 1] [0, 1]です。[ 0 ,1 ] , 0 に近づくほど、予測結果と実際の結果の類似性が高くなり、損失が小さくなります。最適化の目標は、モデルがトレーニング データによりよく適合し、精度を向上させることができるように、ダイス損失を最小限に抑えることです。目に見えないデータの理解 データの一般化能力。

3. 予測プロセス

3.1 予測の概要

トレーニング結果の予測には 2 つのファイルが必要です。

  1. deeplab.py
  2. predict.py

まずdeeplab.pyこれを変更する必要がありmodel_pathnum_classes次の 2 つのパラメーターを変更する必要があります。

  • model_pathlogs\:フォルダー内のトレーニングされた重みファイルを指します。
  • num_classes: 検出カテゴリの数を指します (+1 (背景) が必要)

変更が完了したら、predict.pyテストのために実行できます。実行後、検出するイメージのパスを入力します。

CUDA_VISIBLE_DEVICE=2,3 python predict

# 模型加载完毕后需要输入要预测图片的路径

3.2 前処理(前処理)

DeepLab v3+ には、予測に主に 2 つの部分が含まれています。

  1. 予測の前処理
  2. 予測の後処理

次に、まず前処理部分について説明します。


ファイル名: deeplab.py.

def detect_image(self, image, count=False, name_classes=None):
    """图片推理

    Args:
        image (_type_): 输入图片
        count (bool, optional): _description_. Defaults to False.
        name_classes (_type_, optional): 类别墅. Defaults to None.

    Returns:
        _type_: 模型预测结果(单通道图)
    """
    # 在这里将图像转换成RGB图像,防止灰度图在预测时报错。(代码仅仅支持RGB图像的预测,所有其它类型的图像都会转化成RGB)
    image = cvtColor(image)

    # 对输入图像进行一个备份,后面用于绘图
    old_img = copy.deepcopy(image)
    orininal_h = np.array(image).shape[0]
    orininal_w = np.array(image).shape[1]

    # 给图像增加灰条,实现不失真的resize(也可以直接resize进行识别)
    # 裁剪后的图片, 缩放后的图像宽度, 缩放后的图像高度
    image_data, nw, nh = resize_image(image, (self.input_shape[1], self.input_shape[0]))

    # 添加上batch_size维度
    image_data = np.expand_dims(np.transpose(preprocess_input(
        np.array(image_data, np.float32)), (2, 0, 1)), 0)

上記は前処理プロセスであり、主な点は次のとおりです。歪みなくサイズ変更、コードは次のように実装されます。

def resize_image(image, size):
    """将给定的图像进行调整大小并居中放置在新的画布上

    Args:
        image (_type_): 输入图片
        size (_type_): 目标大小

    Returns:
        (elem1, elem2, elem3): (裁剪后的图片, 缩放后的图像宽度, 缩放后的图像高度)
    """
    iw, ih  = image.size  # 获取图片大小
    w, h    = size  # 获取目标大小

    scale   = min(w/iw, h/ih)  # 计算了原始图像与目标大小之间的缩放比例。
    nw      = int(iw*scale)  # 计算了缩放后的图像宽度
    nh      = int(ih*scale)  # 计算了缩放后的图像高度

    # 使用 Pillow 的 resize() 方法将图像调整为缩放后的大小。
    # 使用 BICUBIC 插值方法进行图像的重采样,以获得更平滑的结果。
    image   = image.resize((nw,nh), Image.BICUBIC)
    
    # 创建了一个新的画布,用于放置调整后的图像。
    # 画布大小与目标大小相同,并且以灰色 (128, 128, 128) 作为默认背景色。
    new_image = Image.new('RGB', size, (128,128,128))
    
    # 将调整后的图像粘贴到新的画布上。图像会居中放置在画布上
    new_image.paste(image, ((w-nw)//2, (h-nh)//2))

    return new_image, nw, nh

3.3 後処理

    with torch.no_grad():
        """
            在推理(inference)过程中关闭梯度计算,以节省内存并提高推理速度
        """
        images = torch.from_numpy(image_data)
        if self.cuda:
            images = images.cuda()


        # 图片传入网络进行预测
        pr = self.net(images)[0]

        # 取出每一个像素点的种类
        pr = F.softmax(pr.permute(1, 2, 0), dim=-1).cpu().numpy()

        # 将灰条部分截取掉
        pr = pr[int((self.input_shape[0] - nh) // 2): int((self.input_shape[0] - nh) // 2 + nh),
                int((self.input_shape[1] - nw) // 2): int((self.input_shape[1] - nw) // 2 + nw)]

        # 进行图片的resize(普通的resize)
        pr = cv2.resize(pr, (orininal_w, orininal_h),
                        interpolation=cv2.INTER_LINEAR)

        # 取出每一个像素点的种类
        pr = pr.argmax(axis=-1)


    # 计数
    if count:
        classes_nums = np.zeros([self.num_classes])
        total_points_num = orininal_h * orininal_w
        print('-' * 63)
        print("|%25s | %15s | %15s|" % ("Key", "Value", "Ratio"))
        print('-' * 63)
        for i in range(self.num_classes):
            num = np.sum(pr == i)
            ratio = num / total_points_num * 100
            if num > 0:
                print("|%25s | %15s | %14.2f%%|" %
                      (str(name_classes[i]), str(num), ratio))
                print('-' * 63)
            classes_nums[i] = num
        print("classes_nums:", classes_nums)

    if self.mix_type == 0:
        # seg_img = np.zeros((np.shape(pr)[0], np.shape(pr)[1], 3))
        # for c in range(self.num_classes):
        #     seg_img[:, :, 0] += ((pr[:, :] == c ) * self.colors[c][0]).astype('uint8')
        #     seg_img[:, :, 1] += ((pr[:, :] == c ) * self.colors[c][1]).astype('uint8')
        #     seg_img[:, :, 2] += ((pr[:, :] == c ) * self.colors[c][2]).astype('uint8')
        seg_img = np.reshape(np.array(self.colors, np.uint8)[
                             np.reshape(pr, [-1])], [orininal_h, orininal_w, -1])

        # 将新图片转换成Image的形式
        image = Image.fromarray(np.uint8(seg_img))

        # 将新图与原图及进行混合
        image = Image.blend(old_img, image, 0.7)

    elif self.mix_type == 1:
        # seg_img = np.zeros((np.shape(pr)[0], np.shape(pr)[1], 3))
        # for c in range(self.num_classes):
        #     seg_img[:, :, 0] += ((pr[:, :] == c ) * self.colors[c][0]).astype('uint8')
        #     seg_img[:, :, 1] += ((pr[:, :] == c ) * self.colors[c][1]).astype('uint8')
        #     seg_img[:, :, 2] += ((pr[:, :] == c ) * self.colors[c][2]).astype('uint8')
        seg_img = np.reshape(np.array(self.colors, np.uint8)[
                             np.reshape(pr, [-1])], [orininal_h, orininal_w, -1])

        # 将新图片转换成Image的形式
        image = Image.fromarray(np.uint8(seg_img))

    elif self.mix_type == 2:
        seg_img = (np.expand_dims(pr != 0, -1) *
                   np.array(old_img, np.float32)).astype('uint8')

        # 将新图片转换成Image的形式
        image = Image.fromarray(np.uint8(seg_img))

    return image

4. トレーニングパート

4.1 トレーニング ファイル

私たちが使用するトレーニング ファイルは PASCAL VOC 形式です。セマンティック セグメンテーション モデルのトレーニング用のファイルは、次の 2 つの部分に分かれています。

  1. 原画
  2. ラベル

以下に示すように。

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

元の画像は通常の RGB 画像で、ラベルはグレースケール画像または 8 ビット カラー画像です ( 2 8 = 256 2^8 = 25628=256なので、ピクセル範囲は[0, 255] [0, 255][ 0 ,255 ])。元の画像の形状は[height, width, 3]、ラベルの形状は です[height, width]ラベルの場合、各ピクセルの内容は数値であり、0, 1, 2, 3, 4, 5, ..., ピクセルが属するカテゴリを表します。

セマンティック セグメンテーションの仕事は、元の画像の各ピクセルを分類することであるため、予測結果の各ピクセルが各カテゴリに属する​​確率をラベルと比較することで、ネットワークをトレーニングできます。

さらに、注意する必要があります❗️: ラベル ファイルはグレースケール画像ですが、P モードで開いたので、グレースケール値が異なる部分に色が付けられています。この画像を普通に開くと、効果は次のとおりです。

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

はい、人間の目ではピクセル値によってさまざまなカテゴリを区別するのが難しいため (飛行機のピクセル値は 1、人のピクセル値は 15)、通常は P モードを使用して実際のラベルを開きます。もちろん、コンピューターの場合は、ピクセル値のサイズに基づいてさまざまなカテゴリに分類できます。

4.2 データセットの準備

この記事では、モデルのトレーニングに PASCAL VOC 形式を使用します。トレーニングの前に独自のデータ セットを作成する必要があります。独自のデータ セットがない場合は、公式のデータ セットをダウンロードできます。

PASCAL VOC 2012 データセットのダウンロード アドレス: Visual Object Classes Challenge 2012 (VOC2012)

PASCAL VOC を解凍すると、ディレクトリ構造は次のようになります。

`-- VOCdevkit
    `-- VOC2012
        |-- Annotations
        |-- ImageSets
        |   |-- Action
        |   |-- Layout
        |   |-- Main
        |   `-- Segmentation
        |-- JPEGImages
        |-- SegmentationClass
        `-- SegmentationObject

トレーニング前に、フォルダーの下のフォルダーに画像ファイルを配置しますトレーニング前に、ラベル ファイルをフォルダー の下のフォルダー配置しますVOCdevkit/VOC2012/JPEGImages
VOCdevkit/VOC2012/SegmentationClass

4.3 独自のデータセットを標準化する

道具:labelme

pip install labelme
labelme  # 打开labelme
  1. Open Dir: マークしたいファイルが存在するフォルダーを開きます。
  2. Create Polygons: ポリゴンのラベル付けを開始します
  3. マークした後、次の画像をマークし続けることができます (ボタンを使用してD次の画像を入力し、 ボタンを使用して前の画像を入力します);途中でファイルのパスをA保存する必要があります。パスを確認できます。.json

自動保存をオンにすることをお勧めしますFile -> Save Automatically

ラベル付きデータにより、.json次の内容のファイルが生成されます。

{
    
    
  "version": "5.2.1",  # Labelme 的版本号
  "flags": {
    
    },  # 用于存储一些标注时的标记或标志信息
  "shapes": [  # 一个数组,包含图像中标注的物体的形状信息。每个元素表示一个物体的标注
    {
    
    
      "label": "airplane",  # 标注物体的类别名称
      "points": [  # 这是一个数组,包含构成标注物体边界的点坐标。每个点坐标表示一个点的 x 和 y 坐标值
        [
          198.94308943089433,
          287.6422764227642
        ],
        ...
        ...
        [
          331.4634146341464,
          343.739837398374
        ]
      ],
      "group_id": null,  # 用于分组的标识符,通常在多个形状之间进行分组时使用
      "description": "",  # 对标注物体的描述信息
      "shape_type": "polygon",  # 标注物体形状的类型。在这个示例中,使用了 "polygon",表示标注的物体是由多边形组成的
      "flags": {
    
    }  # 用于存储关于标注物体的其他标记或标志信息
    }
  ],
  "imagePath": "..\\Airplane_01.jpg",  # 原始图像的文件路径
  "imageData": "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDA.....  # 图像的数据,通常以 Base64 编码的形式包含在 JSON"imageHeight": 720,  # 原始图像的高度,以像素为单位
  "imageWidth": 1280  # 这是原始图像的宽度,以像素为单位。
}

.jsonファイルに手動で注釈を付けた後、ファイルを変換する必要があります。PASCAL VOC の Ground Truth は Json ファイルではなくグレースケール画像であるためです。Json をグレースケール画像に変換するコードは次のとおりです。

import base64
import json
import os
import os.path as osp

import numpy as np
import PIL.Image
from labelme import utils


if __name__ == '__main__':
    jpgs_path = "datasets/JPEGImages"
    pngs_path = "datasets/SegmentationClass"
    
    # 需要注意的是要有一个背景类别
    classes = ["_background_", "aeroplane", "bicycle", "bird", "boat", "bottle", "bus", "car", "cat", "chair", "cow",
               "diningtable", "dog", "horse", "motorbike", "person", "pottedplant", "sheep", "sofa", "train", "tvmonitor"]

    count = os.listdir("./datasets/before/")
    for i in range(0, len(count)):
        path = os.path.join("./datasets/before", count[i])

        if os.path.isfile(path) and path.endswith('json'):
            data = json.load(open(path))

            if data['imageData']:
                imageData = data['imageData']
            else:
                imagePath = os.path.join(
                    os.path.dirname(path), data['imagePath'])
                with open(imagePath, 'rb') as f:
                    imageData = f.read()
                    imageData = base64.b64encode(imageData).decode('utf-8')

            img = utils.img_b64_to_arr(imageData)
            label_name_to_value = {
    
    '_background_': 0}
            for shape in data['shapes']:
                label_name = shape['label']
                if label_name in label_name_to_value:
                    label_value = label_name_to_value[label_name]
                else:
                    label_value = len(label_name_to_value)
                    label_name_to_value[label_name] = label_value

            # label_values must be dense
            label_values, label_names = [], []
            for ln, lv in sorted(label_name_to_value.items(), key=lambda x: x[1]):
                label_values.append(lv)
                label_names.append(ln)
            assert label_values == list(range(len(label_values)))

            lbl, _ = utils.shapes_to_label(img.shape, data['shapes'], label_name_to_value)
            lbl = lbl.astype(np.uint8)

            new = np.zeros_like(lbl, dtype=np.uint8)
            for name in label_names:
                index_json = label_names.index(name)
                index_all = classes.index(name)
                new += np.uint8(index_all) * np.uint8(lbl == index_json)

            utils.lblsave(osp.join(pngs_path, count[i].split(".")[0] + '.png'), new)

            print('Saved ' + count[i].split(".")[0] +
                  '.jpg and ' + count[i].split(".")[0] + '.png')
  • 元の画像のサフィックスは.jpg
  • ラベルの接尾辞は、.png

結果のデモンストレーション:

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

4.4 データセットの処理 (独自のデータのトレーニング)

データ セットの配置が完了したら、トレーニング用のデータとルート ディレクトリ内のtrain.txtデータを取得するために、次のステップでデータ セットを処理する必要がありますval.txtvoc_annotation.py

PASCAL VOC のオリジナル データ セットをトレーニングする場合は、次のスクリプトを実行する必要はありません (データ セットには と が付属していますtrain.txt) val.txt

import os
import random

import numpy as np
from PIL import Image
from tqdm import tqdm

"""
想要增加测试集修改trainval_percent 
    修改train_percent用于改变验证集的比例 9:1
  
注意:当前该库将测试集当作验证集使用,不单独划分测试集
"""
trainval_percent = 1
train_percent = 0.9

# 指向VOC数据集所在的文件夹(默认指向根目录下的VOC数据集)
VOCdevkit_path = 'VOCdevkit'

if __name__ == "__main__":
    random.seed(0)
    print("Generate txt in ImageSets.")
    segfilepath = os.path.join(VOCdevkit_path, 'VOC2012/SegmentationClass')
    saveBasePath = os.path.join(VOCdevkit_path, 'VOC2012/ImageSets/Segmentation')

    temp_seg = os.listdir(segfilepath)
    total_seg = []
    for seg in temp_seg:
        if seg.endswith(".png"):
            total_seg.append(seg)

    num = len(total_seg)
    list = range(num)
    tv = int(num*trainval_percent)
    tr = int(tv*train_percent)
    trainval = random.sample(list, tv)
    train = random.sample(trainval, tr)

    print("train and val size", tv)
    print("traub suze", tr)
    ftrainval = open(os.path.join(saveBasePath, 'trainval.txt'), 'w')
    ftest = open(os.path.join(saveBasePath, 'test.txt'), 'w')
    ftrain = open(os.path.join(saveBasePath, 'train.txt'), 'w')
    fval = open(os.path.join(saveBasePath, 'val.txt'), 'w')

    for i in list:
        name = total_seg[i][:-4]+'\n'
        if i in trainval:
            ftrainval.write(name)
            if i in train:
                ftrain.write(name)
            else:
                fval.write(name)
        else:
            ftest.write(name)

    ftrainval.close()
    ftrain.close()
    fval.close()
    ftest.close()
    print("Generate txt in ImageSets done.")

    print("Check datasets format, this may take a while.")
    print("检查数据集格式是否符合要求,这可能需要一段时间。")
    classes_nums = np.zeros([256], np.int)
    for i in tqdm(list):
        name = total_seg[i]
        png_file_name = os.path.join(segfilepath, name)
        if not os.path.exists(png_file_name):
            raise ValueError("未检测到标签图片%s,请查看具体路径下文件是否存在以及后缀是否为png。" % (png_file_name))

        png = np.array(Image.open(png_file_name), np.uint8)
        if len(np.shape(png)) > 2:
            print("标签图片%s的shape为%s,不属于灰度图或者八位彩图,请仔细检查数据集格式。" % (name, str(np.shape(png))))
            print("标签图片需要为灰度图或者八位彩图,标签的每个像素点的值就是这个像素点所属的种类。" % (name, str(np.shape(png))))

        classes_nums += np.bincount(np.reshape(png, [-1]), minlength=256)

    print("打印像素点的值与数量。")
    print('-' * 37)
    print("| %15s | %15s |" % ("Key", "Value"))
    print('-' * 37)
    for i in range(256):
        if classes_nums[i] > 0:
            print("| %15s | %15s |" % (str(i), str(classes_nums[i])))
            print('-' * 37)

    if classes_nums[255] > 0 and classes_nums[0] > 0 and np.sum(classes_nums[1:255]) == 0:
        print("检测到标签中像素点的值仅包含0与255,数据格式有误。")
        print("二分类问题需要将标签修改为背景的像素点值为0,目标的像素点值为1。")
    elif classes_nums[0] > 0 and np.sum(classes_nums[1:]) == 0:
        print("检测到标签中仅仅包含背景像素点,数据格式有误,请仔细检查数据集格式。")

    print("JPEGImages中的图片应当为.jpg文件、SegmentationClass中的图片应当为.png文件。")

4.5 ネットワークトレーニングを開始する

データセット フォルダーに とtrain.txtがあることを確認してval.txt、トレーニングを開始できます。

注意事項❗️:

  • num_classes検出カテゴリを指すために使用されるポイントの数 + 1 (背景カテゴリを含む)
  • 例: PASCAL VOC データ セット カテゴリが 20 の場合、num_classes=21
  • 他のデータセットにも同じことが当てはまります

それ以外の場合はtrain.py、「フォルダー」で次を選択します。

  • backbone: Xception または MobileNet v2 をバックボーンとして使用します
  • model_path:トレーニング前のウェイト位置(バックボーンモデルに対応)
  • downsample_factor:ダウンサンプリング係数(任意値:8または16)
    • ダウンサンプリング係数が大きいほど、モデルのトレーニングが速くなり、理論的な効果は悪くなります。

その後、トレーニングを開始できます。

4.6 トレーニング結果の予測

トレーニング結果の予測には 2 つのファイルを使用する必要があります。

  1. deeplab.py
  2. predict.py

本文には 3.1 との明らかな違いはありません。

4.7 トレーニングパラメータ分析

トレーニングは 2 つの段階に分かれています。

  1. フリーズ フェーズ: フリーズ フェーズの目的は、トレーニング中にモデルのバックボーンを修正することです (バックボーンはパラメータを更新しなくなりました)、つまり特徴抽出ネットワーク部分をトレーニングし、最上位の分類器 (通常は全結合層または畳み込み層) 部分のみをトレーニングします。フリーズ フェーズは通常、特にマシンのパフォーマンスが制限されている場合、ビデオ メモリが小さい場合、またはグラフィック カードのパフォーマンスが低い場合に、初期トレーニングに使用されます。Freeze_Epochフリーズフェーズでは、に等しいように設定できUnFreeze_Epoch、この時点ではフリーズトレーニングのみが実行されます。

  2. アンフリーズ フェーズ: アンフリーズ フェーズは、モデルのバックボーンがアンフリーズされるとき、つまりすべてのパラメータを更新できるときに、フリーズ フェーズの後に実行されます。凍結解除フェーズの目的は、特定のタスクのニーズに合わせてモデル全体を微調整することです解凍フェーズでは、UnFreeze_Epochトレーニング ラウンド (または反復) の数を制御するために適切な設定を行うことができます。

要約すると、フリーズ フェーズは最初に分類器部分をトレーニングするために使用され、フリーズ解除フェーズはモデル全体を微調整するために使用されます。Freeze_Epochや などのパラメータはUnFreeze_Epoch、特定の問題や実験条件に応じて設定できます。マシンのパフォーマンス、ビデオ メモリ、トレーニング データのサイズに応じて、これらのハイパーパラメータを調整して、限られたリソースでより良いトレーニング結果を達成できます。


  • Init_Epoch: モデルによって現在開始されているトレーニング エポック。その値は次の値より大きくなる可能性があります。Freeze_Epoch
    • たとえば、次のように設定します。Init_Epoch = 60、Freeze_Epoch = 50、UnFreeze_Epoch = 100
    • その後、トレーニングが開始されると、モデルはフリーズ段階をスキップし、epoch=60から直接開始し、対応する学習率を調整します。
    • 主なアプリケーション シナリオ: ブレークポイント後にトレーニングを再開
  • Freeze_Epoch:モデルはトレーニングのためにフリーズされていますFreeze_Epoch(そのFreeze_Train=False時点)
  • Freeze_batch_size:モデルはトレーニングのためにフリーズされていますbatch_size(そのFreeze_Train=False時点)

4.8 トレーニングパラメータの推奨事項

4.8.1 モデル全体の事前トレーニング済みの重みからトレーニングを開始する

アダムオプティマイザー:

# 冻结训练
Init_Epoch = 0,Freeze_Epoch = 50,UnFreeze_Epoch = 100,Freeze_Train = True,
optimizer_type = 'adam',Init_lr = 5e-4,weight_decay = 0

# 不冻结训练
Init_Epoch = 0,UnFreeze_Epoch = 100,Freeze_Train = False,
optimizer_type = 'adam',Init_lr = 5e-4,weight_decay = 0

SGD オプティマイザー:

# 冻结训练
Init_Epoch = 0,Freeze_Epoch = 50,UnFreeze_Epoch = 100,Freeze_Train = True,
optimizer_type = 'sgd',Init_lr = 7e-3,weight_decay = 1e-4

# 不冻结训练
Init_Epoch = 0,UnFreeze_Epoch = 100,Freeze_Train = False,
optimizer_type = 'sgd',Init_lr = 7e-3,weight_decay = 1e-4

ここで:の間で調整UnFreeze_Epochできます100-300

4.8.2 バックボーンネットワークの事前トレーニング済みの重みからトレーニングを開始する

アダムオプティマイザー:

# 冻结训练
Init_Epoch = 0,Freeze_Epoch = 50,UnFreeze_Epoch = 100,Freeze_Train = True,
optimizer_type = 'adam',Init_lr = 5e-4,weight_decay = 0

# 不冻结训练
Init_Epoch = 0,UnFreeze_Epoch = 100,Freeze_Train = False,
optimizer_type = 'adam',Init_lr = 5e-4,weight_decay = 0

SGD オプティマイザー:

# 冻结训练
Init_Epoch = 0,Freeze_Epoch = 50,UnFreeze_Epoch = 120,Freeze_Train = True,
optimizer_type = 'sgd',Init_lr = 7e-3,weight_decay = 1e-4

# 不冻结训练
Init_Epoch = 0,UnFreeze_Epoch = 120,Freeze_Train = False,
optimizer_type = 'sgd',Init_lr = 7e-3,weight_decay = 1e-4

で:

  • トレーニングはバックボーン ネットワークの事前トレーニング済みの重みから開始されるため、バックボーンの重みは必ずしもセマンティック セグメンテーションに適しているわけではなく、局所的な最適解から飛び出すにはさらに多くのトレーニングが必要です。
  • UnFreeze_Epochの間で調整できます120-300
  • Adam収束はよりもSGD高速です。したがって、UnFreeze_Epoch理論的にはこれより小さくすることもできますが、それでもさらに大きい値を推奨しますEpoch

4.8.3 バッチサイズの設定

グラフィックス カードが受け入れられる範囲内では、大きいほど優れています。ビデオ メモリの不足はデータ セットのサイズとは関係ありません。ビデオ メモリが不足している場合は、batch_size を小さく調整してください。

注意❗️:

  • BatchNormレイヤの影響を受け、batch_size最小値は です2。 になることはできません1

通常は の1 ~ 2 倍Freeze_batch_sizeが推奨されます。Unfreeze_batch_size学習率の自動調整に関係するため、設定ギャップが大きすぎることはお勧めできません。

5. 訓練結果

5.1 トレーニングの概要

===>background: Iou-93.08; Recall (equal to the PA)-97.11; Precision-95.74
===>aeroplane:  Iou-84.57; Recall (equal to the PA)-91.72; Precision-91.56
===>bicycle:    Iou-42.19; Recall (equal to the PA)-86.07; Precision-45.28
===>bird:       Iou-81.81; Recall (equal to the PA)-92.48; Precision-87.64
===>boat:       Iou-61.61; Recall (equal to the PA)-76.12; Precision-76.37
===>bottle:     Iou-71.61; Recall (equal to the PA)-88.54; Precision-78.93
===>bus:        Iou-93.45; Recall (equal to the PA)-95.97; Precision-97.27
===>car:        Iou-84.7; Recall (equal to the PA)-90.26; Precision-93.22
===>cat:        Iou-87.14; Recall (equal to the PA)-92.56; Precision-93.71
===>chair:      Iou-33.68; Recall (equal to the PA)-53.53; Precision-47.6
===>cow:        Iou-80.36; Recall (equal to the PA)-86.62; Precision-91.75
===>diningtable:        Iou-50.32; Recall (equal to the PA)-54.12; Precision-87.77
===>dog:        Iou-79.77; Recall (equal to the PA)-90.29; Precision-87.25
===>horse:      Iou-79.56; Recall (equal to the PA)-87.99; Precision-89.25
===>motorbike:  Iou-80.65; Recall (equal to the PA)-89.82; Precision-88.75
===>person:     Iou-80.07; Recall (equal to the PA)-86.52; Precision-91.49
===>pottedplant:        Iou-57.46; Recall (equal to the PA)-70.36; Precision-75.8
===>sheep:      Iou-80.42; Recall (equal to the PA)-89.93; Precision-88.37
===>sofa:       Iou-43.68; Recall (equal to the PA)-49.21; Precision-79.53
===>train:      Iou-84.46; Recall (equal to the PA)-89.14; Precision-94.14
===>tvmonitor:  Iou-67.93; Recall (equal to the PA)-74.5; Precision-88.52
===> mIoU: 72.31; mPA: 82.52; Accuracy: 93.53
Get miou done.
Save mIoU out to miou_out/mIoU.png
Save mPA out to miou_out/mPA.png
Save Recall out to miou_out/Recall.png
Save Precision out to miou_out/Precision.png
Save confusion_matrix out to miou_out/confusion_matrix.csv

5.2 平均 IoU (平均交差および和集合比)

平均 IoU (mIoU) は、セマンティック セグメンテーション タスクで最も一般的に使用される指標の 1 つです。これは、すべてのカテゴリの Intersection over Union (IoU) の平均です。カテゴリごとに、IoU は、予測領域と実際の領域の交差面積をそれらの結合面積で割ったものを表します。mIoU は、すべてのカテゴリにおけるモデルのセグメンテーション精度を測定します。値の範囲は[0, 1] [0, 1]です。[ 0 ,1 ]、1に近づくほど、モデルのパフォーマンスが向上します。

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

5.3 mPA (平均ピクセル精度)

mPA (平均ピクセル精度) は、ピクセルレベルの精度の平均です。ピクセルレベルの精度は、モデルが各ピクセルをどれだけ正確に予測するかを示す尺度です。これは、ピクセルの総数に対する正しく分類されたピクセルの数の比率です。mPA は、データセット全体にわたるモデルのピクセル分類精度を測定します。

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

5.4m精度(平均精度)

  • 精度: これは、バイナリ分類問題で一般的に使用される指標の 1 つであり、肯定的なカテゴリのサンプルを予測する際のモデルの精度を測定するために使用されます。セマンティック セグメンテーションでは、各カテゴリはバイナリ分類問題として考えることができ、そのカテゴリの精度は、正しく予測されたピクセル数を肯定的なカテゴリとして予測されたピクセル数で割ることによって計算されます。
  • mPrecision (平均精度) : すべてのカテゴリの精度率の平均であり、すべてのカテゴリでのモデルの予測精度を測定するために使用されます。

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

5.5 mRecall (平均再現率)

  • Recall : これは、バイナリ分類問題で一般的に使用される指標の 1 つであり、肯定的なカテゴリのサンプルを識別するモデルの能力を測定するために使用されます。セマンティック セグメンテーションでは、各カテゴリをバイナリ分類問題として考えることができ、そのカテゴリの再現率は、正しく予測されたピクセル数を真陽性カテゴリ内のピクセル数で割ることによって計算されます。
  • mRecall (平均再現率) : すべてのカテゴリの再現率の平均であり、すべてのカテゴリの陽性カテゴリ サンプルのモデルの全体的な認識能力を測定するために使用されます。

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

知識の源

  1. https://www.bilibili.com/video/BV173411q7xF
  2. https://blog.csdn.net/weixin_44791964/article/details/120113686

おすすめ

転載: blog.csdn.net/weixin_44878336/article/details/132018570