CNN画像認識

1. 画像認識

  • 画像認識技術は情報化時代における重要な技術であり、その目的は人間に代わってコンピュータが大量の物理情報を処理できるようにすることです。コンピュータ技術の発展に伴い、人類は画像認識技術に対する理解をますます深めています。
  • 画像認識技術は、コンピューターを使用して画像を処理、分析、理解し、さまざまなパターンでターゲットやオブジェクトを識別する技術として定義されます。
  • 画像認識技術のプロセスは、情報取得、前処理、特徴抽出と選択、分類器の設計、分類決定に分かれます。

1.1 パターン認識

  • パターン認識は、人工知能と情報科学の重要な部分です。パターン認識とは、物や現象の説明、識別、分類を取得するために、物や現象を表すさまざまな形の情報を分析および処理するプロセスを指します。
  • コンピュータ画像認識技術は人間の画像認識プロセスをシミュレートします。画像認識のプロセスではパターン認識が不可欠です。
  • パターン認識はもともと人間の基本的な知能でした。しかし、コンピューターの発展と人工知能の台頭により、人間のパターン認識ではもはや生活のニーズを満たすことができなくなったため、人間はコンピューターを使って人間の精神的な仕事の一部を代替または拡張することを望んでいます。このようにして、コンピュータのパターン認識が生成されます。
  • 簡単に言うと、パターン認識はデータの分類であり、数学と密接に統合された科学であり、使用されるアイデアは主に確率と統計です。

1.2 画像認識のプロセス

画像認識技術のプロセスは次のステップに分かれています。

  1. 情報の取得:センサーを介して光や音などの情報を電気情報に変換することを指します。それは、研究対象の基本情報を取得し、それを何らかの方法で機械が認識できる情報に変換することです。
  2. 前処理: 主に、画像処理におけるノイズ除去、平滑化、変換などの操作を指し、これにより画像の重要な特徴が強調されます。画像の強化。
  3. 特徴抽出と選択: パターン認識における特徴抽出と選択の必要性を指します。特徴の抽出と選択は、画像認識プロセスにおける重要なテクノロジーの 1 つです。
  4. 分類器の設計: トレーニングを通じて認識ルールを取得することを指します。これにより、画像認識技術が高い認識率を得ることができるように、特徴分類を取得できます。分類の意思決定とは、調査対象のオブジェクトがどのカテゴリに属する​​かをより適切に識別するために、特徴空間内で識別されたオブジェクトを分類することを指します。

1.3 画像認識の応用

  1. 画像分類
  2. ウェブ検索
  3. 画像で検索
  4. スマートホーム
  5. ECショッピング:「類似機種(写真認識・スキャン認識)」
  6. アグロフォレストリー: 森林調査。
  7. ファイナンス
  8. 安全
  9. 医療
  10. 娯楽規制

2.ディープラーニング開発

2.1 なぜディープラーニングが台頭するのか

  • データスケールがディープラーニングの進歩を促進
    ここに画像の説明を挿入

ここに画像の説明を挿入

2.2 分類と検出

  • 私たちが絵に直面したとき、その絵が何であるか、風景画か人物画か、建物の描写か食べ物の描写かという最も基本的な作業が分類です。
  • 画像のカテゴリがわかったら、次のステップは検出です。たとえば、画像が顔に関するものであることがわかっているので、顔はどこにあるのか、フレームに収まるかどうかを判断します。
  • 物体の分類と検出は、セキュリティ分野での顔認識、歩行者検出、インテリジェントビデオ分析、歩行者追跡など、交通現場での物体認識、車両カウント、逆行検出、ナンバープレートの検出と認識など、多くの分野で広く使用されています。交通分野、インターネット分野におけるコンテンツベースの画像検索、フォトアルバムの自動分類など。
    ここに画像の説明を挿入

2.3 一般的な畳み込みニューラル ネットワーク

ここに画像の説明を挿入

3.VGG

VGG がクラシックである理由は、ディープ ラーニングを初めて 16 ~ 19 層に達する非常に「深い」ものにしたと同時に、非常に「小さい」コンボリューション カーネル (3X3) を使用したためです。
ここに画像の説明を挿入

3.1 VGG16

VGG が私たちに与えてくれるインスピレーションは、ネットワークをより深くし、これに基づいて過剰適合の問題に注意を払うことができるということです。
ここに画像の説明を挿入

3.2 VGG16 の構造:

  1. 元の画像は (224,224,3) にサイズ変更されます。
  2. Conv1 を 2 回 [3,3] 畳み込みネットワーク、出力フィーチャ レイヤーは 64、出力は (224,224,64)、その後 2X2 最大プーリング、出力ネットは (112,112,64) になります。
  3. Conv2 を 2 回 [3,3] 畳み込みネットワーク、出力フィーチャ レイヤーは 128、出力ネットは (112,112,128)、次に 2X2 最大プーリング、出力ネットは (56,56,128) です。
  4. Conv3 を 3 回 [3,3] 畳み込みネットワーク、出力フィーチャ レイヤーは 256、出力ネットは (56,56,256)、その後 2X2 最大プーリング、出力ネットは (28,28,256) になります。
  5. Conv3 を 3 回 [3,3] 畳み込みネットワーク、出力フィーチャ レイヤーは 256、出力ネットは (28,28,512)、その後 2X2 最大プーリング、出力ネットは (14,14,512) になります。
  6. Conv3 を 3 回 [3,3] 畳み込みネットワーク、出力フィーチャ レイヤーは 256、出力ネットは (14,14,512)、その後 2X2 最大プーリング、出力ネットは (7,7,512) になります。
  7. 畳み込みを使用して完全に接続された層をシミュレートすると、効果は同等になり、出力ネットは (1,1,4096) になります。計2回。
  8. 畳み込みを使用して完全に接続された層をシミュレートすると、効果は同等になり、出力ネットは (1,1,1000) になります。最終的な出力は、各クラスの予測です。

VGG は 3 3 コンボリューション カーネルなどの比較的小さなコンボリューション カーネルを使用しますが、Alexnet は 11 11 コンボリューション カーネルなどの比較的大きなコンボリューション カーネルを使用しており、小さなコンボリューション カーネルを使用する利点は、より詳細な情報を取得できることです。

3.3 完全な接続の代わりに畳み込み層を使用する

畳み込み層と全結合層の違い: 畳み込み層はローカルに接続されていますが、全結合層は画像のグローバル情報を使用します。想像できますか、最大部分は全世界に等しいでしょうか? これは最初に、全結合層を畳み込み層に置き換えることの実現可能性を示しています。
ここに画像の説明を挿入

例:入力に [5044] 個のニューロン ノードがあり、出力に 500 個のノードがある場合、合計 5044*500=400000 個の重みパラメータ W と 500 個のバイアス パラメータ b が必要です。

畳み込み層と全結合層は両方ともドット積演算を実行し、それらの関数は同じ形式になります。したがって、全結合層は、対応する畳み込み層に変換できます。畳み込みカーネルを入力特徴マップ (h, w) と同じサイズにするだけで済みます。これは、畳み込みに全結合層と同じ数のパラメーターを持たせることと同じです。

たとえば、VGG16 では、最初の完全接続層の入力は 7 7 512、出力は 4096 です。これは、畳み込みカーネル サイズ 7 7、ストライド 1、パディングなし、出力チャネル番号 4096 (出力は 1 1*4096) の畳み込み層、および全結合層などによって等価的に表すことができます。価格。後続の完全に接続された層は、1x1 畳み込みと等価に置き換えることができます。

つまり、全結合層を畳み込み層に変換するためのルールは、畳み込みカーネルのサイズを入力空間のサイズに設定するということです。

3.4 1*1 畳み込みの役割

特徴チャネルの次元拡張と次元削減を実現
コンボリューションカーネルの数を制御することで、チャネル数をスケーリングできます。プーリング層は高さと幅のみを変更できますが、チャネル数は変更できません。

3.5 VGG16 コード例

import torch.nn as nn

class VGG16(nn.Module):
    def __init__(self, num_classes=1000):
        super(VGG16, self).__init__()

        # VGG16 有五个卷积块
        # 下面我们定义每一个卷积块内部的卷积层结构
        # 其中,'M' 代表最大池化层

        self.features = nn.Sequential(
            # 第一个卷积块包含两个卷积层,每个都有 64 个 3x3 的卷积核
            nn.Conv2d(3, 64, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(64, 64, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),

            # 第二个卷积块包含两个卷积层,每个都有 128 个 3x3 的卷积核
            nn.Conv2d(64, 128, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(128, 128, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),

            # 第三个卷积块包含三个卷积层,每个都有 256 个 3x3 的卷积核
            nn.Conv2d(128, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(256, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(256, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),

            # 第四个卷积块包含三个卷积层,每个都有 512 个 3x3 的卷积核
            nn.Conv2d(256, 512, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),

            # 第五个卷积块也包含三个卷积层,每个都有 512 个 3x3 的卷积核
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),
        )

        # VGG16 的三个全连接层
        self.classifier = nn.Sequential(
            nn.Linear(512 * 7 * 7, 4096), # 7x7 是特征图的大小(假设输入图像是 224x224)
            nn.ReLU(True),
            nn.Dropout(),
            nn.Linear(4096, 4096),
            nn.ReLU(True),
            nn.Dropout(),
            nn.Linear(4096, num_classes),
        )

    def forward(self, x):
        x = self.features(x)
        x = x.view(x.size(0), -1) # flatten the tensor
        x = self.classifier(x)
        return x

# 实例化 VGG16 模型
model = VGG16()
print(model)

4. 残差モデル - Resnet

残留ネット (残留ネットワーク): 最初の数層の特定の層のデータ出力は、複数の層を直接スキップして、後続のデータ層の入力部分に導入されます。

残差ニューラル ユニット: 特定のニューラル ネットワークの入力が x で、期待される出力が H(x) であると仮定すると、入力 x を初期結果として出力に直接渡すと、学習する必要がある目標は F になります。 (x) = H( x) - x、これは残りのニューラル ユニットです。これは学習目標を変更することと同等であり、完全な出力 H(x) を学習するのではなく、出力と入力の差 H(x) - x、つまり残差 。

ここに画像の説明を挿入

4.1 レスネット

  • 通常の直結畳み込みニューラルネットワークとResNetの最大の違いは、ResNetには入力を後続の層に直接接続するバイパス分岐が多く存在し、後続の層が残差を直接学習できる構造であること、この構造はショートカットとも呼ばれます。接続をスキップします。
  • 従来の畳み込み層や全結合層が情報を伝送する場合、多かれ少なかれ情報の欠落などの問題が発生します。ResNet はこの問題をある程度解決し、情報の完全性を保護するために入力情報を出力に直接バイパスすることで、ネットワーク全体は入力と出力の差分の一部のみを学習するだけでよく、これにより学習目標が簡素化され、困難。
    ここに画像の説明を挿入

ResNet50 には、Conv ブロックと Identity ブロックという 2 つの基本ブロックがあります。Conv ブロックの入力次元と出力次元は異なるため、直列に接続することはできません。その機能は、ネットワークの次元、つまり入力次元と出力を変更することです。アイデンティティブロック 寸法は同じであり、直列に接続してネットワークを深くすることができます。
ここに画像の説明を挿入

ここに画像の説明を挿入

4.2 バッチ正規化

バッチ正規化:

  • すべての出力は 0 から 1 の間であることが保証されます。
  • すべての出力データは、平均が 0 に近く、標準偏差が 1 に近い正規分布になります。活性化関数の敏感な領域に収まるようにして、勾配の消失を回避し、収束を高速化します。
  • モデルの収束速度を加速し、一定の汎化能力を備えています。
  • ドロップアウトの使用を減らすことができます。
    ここに画像の説明を挿入

ここに画像の説明を挿入

4.3 ResNet50 コード例

import torch
import torch.nn as nn

class IdentityBlock(nn.Module):
    def __init__(self, in_channels, filters, kernel_size, stride):
        super(IdentityBlock, self).__init__()

        filters1, filters2, filters3 = filters

        # 主路径的第一部分
        self.conv1 = nn.Conv2d(in_channels, filters1, (1, 1))
        self.bn1 = nn.BatchNorm2d(filters1)
        self.relu1 = nn.ReLU()

        # 主路径的第二部分
        self.conv2 = nn.Conv2d(filters1, filters2, kernel_size, stride=stride, padding=1)
        self.bn2 = nn.BatchNorm2d(filters2)
        self.relu2 = nn.ReLU()

        # 主路径的第三部分
        self.conv3 = nn.Conv2d(filters2, filters3, (1, 1))
        self.bn3 = nn.BatchNorm2d(filters3)

        self.relu = nn.ReLU()

    def forward(self, x):
        identity = x

        x = self.conv1(x)
        x = self.bn1(x)
        x = self.relu1(x)

        x = self.conv2(x)
        x = self.bn2(x)
        x = self.relu2(x)

        x = self.conv3(x)
        x = self.bn3(x)

        x += identity

        x = self.relu(x)
        return x

class ConvBlock(nn.Module):
    def __init__(self, in_channels, filters, kernel_size, stride):
        super(ConvBlock, self).__init__()

        filters1, filters2, filters3 = filters

        # 主路径的第一部分
        self.conv1 = nn.Conv2d(in_channels, filters1, (1, 1), stride=stride)
        self.bn1 = nn.BatchNorm2d(filters1)
        self.relu1 = nn.ReLU()

        # 主路径的第二部分
        self.conv2 = nn.Conv2d(filters1, filters2, kernel_size, padding=1)
        self.bn2 = nn.BatchNorm2d(filters2)
        self.relu2 = nn.ReLU()

        # 主路径的第三部分
        self.conv3 = nn.Conv2d(filters2, filters3, (1, 1))
        self.bn3 = nn.BatchNorm2d(filters3)

        # 快捷路径
        self.shortcut_conv = nn.Conv2d(in_channels, filters3, (1, 1), stride=stride)
        self.shortcut_bn = nn.BatchNorm2d(filters3)

        self.relu = nn.ReLU()

    def forward(self, x):
        identity = self.shortcut_conv(x)
        identity = self.shortcut_bn(identity)

        x = self.conv1(x)
        x = self.bn1(x)
        x = self.relu1(x)

        x = self.conv2(x)
        x = self.bn2(x)
        x = self.relu2(x)

        x = self.conv3(x)
        x = self.bn3(x)

        x += identity
        x = self.relu(x)
        return x

class ResNet50(nn.Module):
    def __init__(self, num_classes=1000):
        super(ResNet50, self).__init__()

        self.pad = nn.ZeroPad2d((3, 3, 3, 3))
        self.conv1 = nn.Conv2d(3, 64, (7, 7), stride=(2, 2))
        self.bn1 = nn.BatchNorm2d(64)
        self.relu = nn.ReLU()
        self.maxpool = nn.MaxPool2d((3, 3), stride=(2, 2))

        # 第一阶段
        self.stage2a = ConvBlock(64, [64, 64, 256], 3, stride=(1, 1))
        self.stage2b = IdentityBlock(256, [64, 64, 256], 3, stride=(1, 1))
        self.stage2c = IdentityBlock(256, [64, 64, 256], 3, stride=(1, 1))

        # 第二阶段
        self.stage3a = ConvBlock(256, [128, 128, 512], 3, stride=(2, 2))
        self.stage3b = IdentityBlock(512, [128, 128, 512], 3, stride=(1, 1))
        # ... 继续其余的阶段

        self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
        self.fc = nn.Linear(2048, num_classes)

    def forward(self, x):
        x = self.pad(x)
        x = self.conv1(x)
        x = self.bn1(x)
        x = self.relu(x)
        x = self.maxpool(x)

        # 第一阶段
        x = self.stage2a(x)
        x = self.stage2b(x)
        x = self.stage2c(x)

        # 第二阶段
        x = self.stage3a(x)
        x = self.stage3b(x)
        # ... 继续其余的阶段

        x = self.avgpool(x)
        x = torch.flatten(x, 1)
        x = self.fc(x)

        return x

model = ResNet50()
print(model)

5. 移行の学習と開始

5.1 畳み込みニューラル ネットワークの移行学習 - 微調整

  • 実際には、データセットが十分に大きくないため、ネットワークを最初からトレーニングする人はほとんどいません。一般的な方法は、事前にトレーニングされたネットワーク (ImageNet でトレーニングされた 1000 カテゴリのネットワークなど) を使用して微調整 (微調整とも呼ばれます) するか、特徴抽出器として使用することです。
  • 簡単に言うと、転移学習とは、あるデータ セットでトレーニングされた畳み込みニューラル ネットワーク モデルを、簡単な調整によって別のデータ セットにすばやく移動することです。
  • モデルの層の数とモデルの複雑さが増加するにつれて、モデルのエラー率も減少します。しかし、複雑な畳み込みニューラル ネットワークをトレーニングするには、大量のラベル付け情報が必要で、数日から数週間もかかります。データのラベル付けとトレーニング時間の問題を解決するには、転移学習を使用できます。

転移学習シナリオには一般的な 2 つのタイプがあります。

  1. 畳み込みネットワークは特徴抽出器として使用されます。ImageNet で事前トレーニングされたネットワークを使用し、最後に完全に接続された層を削除し、残りを特徴抽出器として使用します (たとえば、AlexNet は最終分類器の前の 4096 次元の特徴ベクトルです)。このようにして抽出された特徴をCNNコードと呼びます。このような機能を取得したら、線形分類器 (Liner SVM、Softmax など) を使用して画像を分類できます。
  2. 畳み込みネットワークの微調整。ネットワークの入力層 (データ) を置き換え、新しいデータでトレーニングを続行します。[微調整] では、レイヤーのすべてまたは一部を微調整することを選択できます。通常、前のレイヤーは画像の一般的な特徴 (エッジ検出、色検出など) を抽出します。これらは多くのタスクに役立ちます。後者のレイヤーは特定のカテゴリに関連する特徴を抽出するため、多くの場合、微調整には微調整後のレイヤーのみが必要になります。
  • 一部の論文では、トレーニングされた開始モデル内のすべての畳み込み層のパラメーターが保持され、最後の完全に接続された層のみが置き換えられることが示されています。最後に完全に接続された層の前のネットワークはボトルネック層と呼ばれます。
  • 原理: 学習済みの開始モデルでは、ボトルネック層の出力が単層の全結合層を通過するため、ニューラル ネットワークは 1000 種類の画像を非常によく区別できます。そのため、ニューラル ネットワークが出力するノード ベクトルは、ボトルネック レイヤーは、あらゆる画像のより表現力豊かな特徴ベクトルとして使用できます。したがって、新しいデータセットでは、トレーニングされたニューラル ネットワークを直接使用して画像の特徴を抽出でき、抽出された特徴ベクトルを入力として使用して、新しい単層の完全に接続されたニューラル ネットワークをトレーニングして、新しいデータセットに対処します。分類の問題。
  • 一般に、データ量が十分な場合、転移学習の効果は完全な再学習ほど良くありません。ただし、転移学習に必要なトレーニング時間とトレーニング サンプルは、完全なモデルをトレーニングする場合よりもはるかに小さくなります。
  • インセプションモデルに関しては、実際には Alexnet とはまったく異なる構造を持つ畳み込みニューラル ネットワークです。Alexnet モデルでは、異なる畳み込み層が直列に接続されていますが、インセプション モデルの開始構造では、異なる畳み込み層が並列に結合されています。

5.2 始まり

インセプション ネットワークは、CNN 開発の歴史における重要なマイルストーンです。Inception が登場する前、最も一般的な CNN は、パフォーマンスの向上を期待して、畳み込み層をどんどん積み重ねてネットワークをさらに深くしていました。しかし、次のような問題があります。

  1. 画像内の突起部分のサイズは大きく異なります。
  2. 情報の位置が大きく異なるため、畳み込み演算に適切な畳み込みカーネル サイズを選択するのは困難です。よりグローバルな情報分布を持つ画像はより大きなコンボリューション カーネルを好み、比較的局所的な情報分布を持つ画像はより小さなコンボリューション カーネルを好みます。
  3. 非常に深いネットワークでは、過剰適合が発生しやすくなります。勾配更新をネットワーク全体に送信することは困難です。
  4. より大きな畳み込み層を単純に積み重ねると、計算コストが非常に高くなります。

5.3 インセプションモジュール

解決策:
同じレベルで複数のサイズのフィルターを実行してはどうでしょうか? ネットワークは基本的に「深く」なるのではなく、わずかに「広く」なるでしょう。そこで、著者は Inception モジュールを設計しました。

インセプション モジュール: 異なるサイズ (1x1、3x3、5x5) の 3 つのフィルターを使用して入力に対して畳み込み演算を実行し、さらに最大プーリングを実行します。すべてのサブレイヤーの出力は最終的に連結され、次のインセプション モジュールに送信されます。

一方で、ネットワークの幅が広がり、他方では、ネットワークの規模への適応性が高まります。

ここに画像の説明を挿入

次元削減のためのインセプション モジュール:
前述したように、ディープ ニューラル ネットワークは大量のコンピューティング リソースを必要とします。計算能力のコストを削減するために、著者は 3x3 および 5x5 畳み込み層の前に 1x1 畳み込み層を追加して、入力チャネルの数を制限します。追加の畳み込み演算を追加するのは直観に反しているように思えるかもしれませんが、1x1 畳み込みは 5x5 畳み込みよりもはるかに安価であり、入力チャネル数の削減は計算コストの削減にも役立ちます。
ここに画像の説明を挿入

5.4 InceptionV1 – Googlenet

  1. GoogLeNet は Inception モジュラー (9) 構造、合計 22 層を採用しています。
  2. 勾配の消失を回避するために、ネットワークはさらに 2 つの補助ソフトマックスを追加して勾配を前進させます。
    ここに画像の説明を挿入

5.5 インセプションV2

Inception V2 では、入力時に BatchNormalization が追加されます。

  • すべての出力は 0 から 1 の間であることが保証されます。
  • すべての出力データは、平均が 0 に近く、標準偏差が 1 に近い正規分布になります。活性化関数の敏感な領域に収まるようにして、勾配の消失を回避し、収束を高速化します。
  • モデルの収束速度を加速し、一定の汎化能力を備えています。
  • ドロップアウトの使用を減らすことができます。

v2

  • 著者は、単一の 5x5 畳み込み層の代わりに、2 つの連続する 3x3 畳み込み層 (stride=1) で構成される小規模ネットワークを使用できることを提案しています。これが InceptionV2 構造です。
  • 5x5 コンボリューション カーネル パラメーターは、3x3 コンボリューション カーネルの 25/9=2.78 倍です。
    ここに画像の説明を挿入

ここに画像の説明を挿入

さらに、著者は、n*n 畳み込みカーネル サイズを 1×n と n×1 の 2 つの畳み込みに分解します。
ここに画像の説明を挿入

最初の 3 つの原則は、3 つの異なるタイプのインセプション モジュールを構築するために使用されます。
ここに画像の説明を挿入

5.6 InceptionV3 - ネットワーク構造図

InceptionV3 は、以前の Inception v2 で説明したすべてのアップグレードを統合し、7x7 コンボリューションも使用します。
ここに画像の説明を挿入

5.7 インセプション V3

Inception V3 のデザイン アイデアとトリック:

  1. 小さな畳み込みに分解することは非常に効果的です。これにより、パラメータの量が減り、過学習が減り、ネットワークの非線形表現能力が向上します。
  2. 入力から出力まで、畳み込みネットワークは画像のサイズを徐々に小さくし、出力チャネルの数を徐々に増やす必要があります。つまり、空間を構造化し、空間情報を高レベルの抽象的な特徴情報に変換します。
  3. インセプション モジュールは複数のブランチを使用して、さまざまな抽象化レベルの高レベルの機能を抽出します。これは非常に効果的であり、ネットワークの表現能力を豊かにすることができます。

5.8 インセプション V4

ここに画像の説明を挿入

  1. 左側の図は、基本的な Inception v2/v3 モジュールです。5x5 畳み込みの代わりに 2 つの 3x3 畳み込みを使用し、平均プーリングを使用して、このモジュールは主に 35x35 のサイズの特徴マップを処理します。
  2. 中国の画像モジュールは、nxn 畳み込みの代わりに 1xn および nx1 畳み込みを使用し、平均プーリングも使用します。このモジュールは主に 17x17 のサイズの特徴マップを処理します。
  3. 右の図は、3x3 コンボリューションを 1x3 コンボリューションと 3x1 コンボリューションに置き換えます。

一般に、Inception v4 の基本的な Inception モジュールは Inception v2/v3 の構造を踏襲していますが、構造はより簡潔で統一されているように見え、より多くの Inception モジュールが使用され、実験効果がより優れています。
ここに画像の説明を挿入

5.9 インセプション構造の概要

インセプション モデルの利点:

  1. コスト効率に優れ、少ない計算量で特徴変換や非線形変換のレイヤーを追加できる1x1コンボリューションカーネルを採用しています。
  2. バッチ正規化が提案されており、特定の手段を通じてニューロンの各層の入力値分布が、平均 0、分散 1 の正規分布に引き上げられるため、活性化関数の敏感な領域に収まります。 、勾配の消失を回避し、収束を高速化します。
  3. 4 つのブランチを組み合わせた構造である Inception モジュールを紹介します。

5.10 畳み込みニューラルネットワーク転移学習

  • 現在、エンジニアリングで最も一般的に使用されている構造は、vgg、resnet、および inception です。設計者は通常、元のモデルを直接適用してデータを 1 回トレーニングし、その後、微調整とモデル削減の効果がより高いモデルを選択します。
  • エンジニアリングで使用されるモデルは、高速でありながら高精度である必要があります。
  • モデル削減の一般的に使用される方法は、畳み込みの数を減らし、resnet のモジュールの数を減らすことです。

5.11 InceptionV3 の例

#-------------------------------------------------------------#
#   InceptionV3的网络部分
#-------------------------------------------------------------#
from __future__ import print_function
from __future__ import absolute_import

import warnings
import numpy as np

from keras.models import Model
from keras import layers
from keras.layers import Activation,Dense,Input,BatchNormalization,Conv2D,MaxPooling2D,AveragePooling2D
from keras.layers import GlobalAveragePooling2D,GlobalMaxPooling2D
from keras.engine.topology import get_source_inputs
from keras.utils.layer_utils import convert_all_kernels_in_model
from keras.utils.data_utils import get_file
from keras import backend as K
from keras.applications.imagenet_utils import decode_predictions
from keras.preprocessing import image


def conv2d_bn(x,
              filters,
              num_row,
              num_col,
              strides=(1, 1),
              padding='same',
              name=None):
    if name is not None:
        bn_name = name + '_bn'
        conv_name = name + '_conv'
    else:
        bn_name = None
        conv_name = None
    x = Conv2D(
        filters, (num_row, num_col),
        strides=strides,
        padding=padding,
        use_bias=False,
        name=conv_name)(x)
    x = BatchNormalization(scale=False, name=bn_name)(x)
    x = Activation('relu', name=name)(x)
    return x


def InceptionV3(input_shape=[299,299,3],
                classes=1000):


    img_input = Input(shape=input_shape)

    x = conv2d_bn(img_input, 32, 3, 3, strides=(2, 2), padding='valid')
    x = conv2d_bn(x, 32, 3, 3, padding='valid')
    x = conv2d_bn(x, 64, 3, 3)
    x = MaxPooling2D((3, 3), strides=(2, 2))(x)

    x = conv2d_bn(x, 80, 1, 1, padding='valid')
    x = conv2d_bn(x, 192, 3, 3, padding='valid')
    x = MaxPooling2D((3, 3), strides=(2, 2))(x)

    #--------------------------------#
    #   Block1 35x35
    #--------------------------------#
    # Block1 part1
    # 35 x 35 x 192 -> 35 x 35 x 256
    branch1x1 = conv2d_bn(x, 64, 1, 1)

    branch5x5 = conv2d_bn(x, 48, 1, 1)
    branch5x5 = conv2d_bn(branch5x5, 64, 5, 5)

    branch3x3dbl = conv2d_bn(x, 64, 1, 1)
    branch3x3dbl = conv2d_bn(branch3x3dbl, 96, 3, 3)
    branch3x3dbl = conv2d_bn(branch3x3dbl, 96, 3, 3)

    branch_pool = AveragePooling2D((3, 3), strides=(1, 1), padding='same')(x)
    branch_pool = conv2d_bn(branch_pool, 32, 1, 1)
    
    # 64+64+96+32 = 256  nhwc-0123
    x = layers.concatenate(
        [branch1x1, branch5x5, branch3x3dbl, branch_pool],
        axis=3,
        name='mixed0')

    # Block1 part2
    # 35 x 35 x 256 -> 35 x 35 x 288
    branch1x1 = conv2d_bn(x, 64, 1, 1)

    branch5x5 = conv2d_bn(x, 48, 1, 1)
    branch5x5 = conv2d_bn(branch5x5, 64, 5, 5)

    branch3x3dbl = conv2d_bn(x, 64, 1, 1)
    branch3x3dbl = conv2d_bn(branch3x3dbl, 96, 3, 3)
    branch3x3dbl = conv2d_bn(branch3x3dbl, 96, 3, 3)

    branch_pool = AveragePooling2D((3, 3), strides=(1, 1), padding='same')(x)
    branch_pool = conv2d_bn(branch_pool, 64, 1, 1)
    
    # 64+64+96+64 = 288 
    x = layers.concatenate(
        [branch1x1, branch5x5, branch3x3dbl, branch_pool],
        axis=3,
        name='mixed1')

    # Block1 part3
    # 35 x 35 x 288 -> 35 x 35 x 288
    branch1x1 = conv2d_bn(x, 64, 1, 1)

    branch5x5 = conv2d_bn(x, 48, 1, 1)
    branch5x5 = conv2d_bn(branch5x5, 64, 5, 5)

    branch3x3dbl = conv2d_bn(x, 64, 1, 1)
    branch3x3dbl = conv2d_bn(branch3x3dbl, 96, 3, 3)
    branch3x3dbl = conv2d_bn(branch3x3dbl, 96, 3, 3)

    branch_pool = AveragePooling2D((3, 3), strides=(1, 1), padding='same')(x)
    branch_pool = conv2d_bn(branch_pool, 64, 1, 1)
    
    # 64+64+96+64 = 288 
    x = layers.concatenate(
        [branch1x1, branch5x5, branch3x3dbl, branch_pool],
        axis=3,
        name='mixed2')

    #--------------------------------#
    #   Block2 17x17
    #--------------------------------#
    # Block2 part1
    # 35 x 35 x 288 -> 17 x 17 x 768
    branch3x3 = conv2d_bn(x, 384, 3, 3, strides=(2, 2), padding='valid')

    branch3x3dbl = conv2d_bn(x, 64, 1, 1)
    branch3x3dbl = conv2d_bn(branch3x3dbl, 96, 3, 3)
    branch3x3dbl = conv2d_bn(
        branch3x3dbl, 96, 3, 3, strides=(2, 2), padding='valid')

    branch_pool = MaxPooling2D((3, 3), strides=(2, 2))(x)
    x = layers.concatenate(
        [branch3x3, branch3x3dbl, branch_pool], axis=3, name='mixed3')

    # Block2 part2
    # 17 x 17 x 768 -> 17 x 17 x 768
    branch1x1 = conv2d_bn(x, 192, 1, 1)

    branch7x7 = conv2d_bn(x, 128, 1, 1)
    branch7x7 = conv2d_bn(branch7x7, 128, 1, 7)
    branch7x7 = conv2d_bn(branch7x7, 192, 7, 1)

    branch7x7dbl = conv2d_bn(x, 128, 1, 1)
    branch7x7dbl = conv2d_bn(branch7x7dbl, 128, 7, 1)
    branch7x7dbl = conv2d_bn(branch7x7dbl, 128, 1, 7)
    branch7x7dbl = conv2d_bn(branch7x7dbl, 128, 7, 1)
    branch7x7dbl = conv2d_bn(branch7x7dbl, 192, 1, 7)

    branch_pool = AveragePooling2D((3, 3), strides=(1, 1), padding='same')(x)
    branch_pool = conv2d_bn(branch_pool, 192, 1, 1)
    x = layers.concatenate(
        [branch1x1, branch7x7, branch7x7dbl, branch_pool],
        axis=3,
        name='mixed4')

    # Block2 part3 and part4
    # 17 x 17 x 768 -> 17 x 17 x 768 -> 17 x 17 x 768
    for i in range(2):
        branch1x1 = conv2d_bn(x, 192, 1, 1)

        branch7x7 = conv2d_bn(x, 160, 1, 1)
        branch7x7 = conv2d_bn(branch7x7, 160, 1, 7)
        branch7x7 = conv2d_bn(branch7x7, 192, 7, 1)

        branch7x7dbl = conv2d_bn(x, 160, 1, 1)
        branch7x7dbl = conv2d_bn(branch7x7dbl, 160, 7, 1)
        branch7x7dbl = conv2d_bn(branch7x7dbl, 160, 1, 7)
        branch7x7dbl = conv2d_bn(branch7x7dbl, 160, 7, 1)
        branch7x7dbl = conv2d_bn(branch7x7dbl, 192, 1, 7)

        branch_pool = AveragePooling2D(
            (3, 3), strides=(1, 1), padding='same')(x)
        branch_pool = conv2d_bn(branch_pool, 192, 1, 1)
        x = layers.concatenate(
            [branch1x1, branch7x7, branch7x7dbl, branch_pool],
            axis=3,
            name='mixed' + str(5 + i))

    # Block2 part5
    # 17 x 17 x 768 -> 17 x 17 x 768
    branch1x1 = conv2d_bn(x, 192, 1, 1)

    branch7x7 = conv2d_bn(x, 192, 1, 1)
    branch7x7 = conv2d_bn(branch7x7, 192, 1, 7)
    branch7x7 = conv2d_bn(branch7x7, 192, 7, 1)

    branch7x7dbl = conv2d_bn(x, 192, 1, 1)
    branch7x7dbl = conv2d_bn(branch7x7dbl, 192, 7, 1)
    branch7x7dbl = conv2d_bn(branch7x7dbl, 192, 1, 7)
    branch7x7dbl = conv2d_bn(branch7x7dbl, 192, 7, 1)
    branch7x7dbl = conv2d_bn(branch7x7dbl, 192, 1, 7)

    branch_pool = AveragePooling2D((3, 3), strides=(1, 1), padding='same')(x)
    branch_pool = conv2d_bn(branch_pool, 192, 1, 1)
    x = layers.concatenate(
        [branch1x1, branch7x7, branch7x7dbl, branch_pool],
        axis=3,
        name='mixed7')

    #--------------------------------#
    #   Block3 8x8
    #--------------------------------#
    # Block3 part1
    # 17 x 17 x 768 -> 8 x 8 x 1280
    branch3x3 = conv2d_bn(x, 192, 1, 1)
    branch3x3 = conv2d_bn(branch3x3, 320, 3, 3,
                          strides=(2, 2), padding='valid')

    branch7x7x3 = conv2d_bn(x, 192, 1, 1)
    branch7x7x3 = conv2d_bn(branch7x7x3, 192, 1, 7)
    branch7x7x3 = conv2d_bn(branch7x7x3, 192, 7, 1)
    branch7x7x3 = conv2d_bn(
        branch7x7x3, 192, 3, 3, strides=(2, 2), padding='valid')

    branch_pool = MaxPooling2D((3, 3), strides=(2, 2))(x)
    x = layers.concatenate(
        [branch3x3, branch7x7x3, branch_pool], axis=3, name='mixed8')

    # Block3 part2 part3
    # 8 x 8 x 1280 -> 8 x 8 x 2048 -> 8 x 8 x 2048
    for i in range(2):
        branch1x1 = conv2d_bn(x, 320, 1, 1)

        branch3x3 = conv2d_bn(x, 384, 1, 1)
        branch3x3_1 = conv2d_bn(branch3x3, 384, 1, 3)
        branch3x3_2 = conv2d_bn(branch3x3, 384, 3, 1)
        branch3x3 = layers.concatenate(
            [branch3x3_1, branch3x3_2], axis=3, name='mixed9_' + str(i))

        branch3x3dbl = conv2d_bn(x, 448, 1, 1)
        branch3x3dbl = conv2d_bn(branch3x3dbl, 384, 3, 3)
        branch3x3dbl_1 = conv2d_bn(branch3x3dbl, 384, 1, 3)
        branch3x3dbl_2 = conv2d_bn(branch3x3dbl, 384, 3, 1)
        branch3x3dbl = layers.concatenate(
            [branch3x3dbl_1, branch3x3dbl_2], axis=3)

        branch_pool = AveragePooling2D(
            (3, 3), strides=(1, 1), padding='same')(x)
        branch_pool = conv2d_bn(branch_pool, 192, 1, 1)
        x = layers.concatenate(
            [branch1x1, branch3x3, branch3x3dbl, branch_pool],
            axis=3,
            name='mixed' + str(9 + i))
    # 平均池化后全连接。
    x = GlobalAveragePooling2D(name='avg_pool')(x)
    x = Dense(classes, activation='softmax', name='predictions')(x)


    inputs = img_input

    model = Model(inputs, x, name='inception_v3')

    return model

def preprocess_input(x):
    x /= 255.
    x -= 0.5
    x *= 2.
    return x

if __name__ == '__main__':
    model = InceptionV3()

    model.load_weights("inception_v3_weights_tf_dim_ordering_tf_kernels.h5")
    
    img_path = 'elephant.jpg'
    img = image.load_img(img_path, target_size=(299, 299))
    x = image.img_to_array(img)
    x = np.expand_dims(x, axis=0)

    x = preprocess_input(x)

    preds = model.predict(x)
    print('Predicted:', decode_predictions(preds))


6. 軽量モデル - Mobilenet

MobileNet モデルは、Google が携帯電話などの組み込みデバイス向けに提案した軽量のディープ ニューラル ネットワークであり、その中心となるアイデアは深さ方向に分離可能な畳み込みです。

6.1 モバイルネット - 深さ方向の分離可能な畳み込み

ここに画像の説明を挿入

6.2 モバイルネット – 深さ方向の分離可能な畳み込み

畳み込みポイントの場合:
16 個の入力チャネルと 32 個の出力チャネルを持つ 3×3 畳み込み層があると仮定します。具体的には、3×3 サイズの 32 個のコンボリューション カーネルが 16 チャネルの各データをトラバースし、最終的に必要な 32 個の出力チャネルを取得でき、必要なパラメータは 16×32×3×3 = 4608 になります。

深さ分離可能なコンボリューションを適用し、3×3 サイズの 16 個のコンボリューション カーネルを使用して 16 チャネルのデータをそれぞれ走査し、16 個の特徴マップを取得します。次に、1×1 サイズの 32 個のコンボリューション カーネルを使用して 16 個の特徴マップ、必要なパラメーターを走査します。 16×3×3+16×32×1×1=656となります。

深さ方向の分離可能な畳み込みによりモデルのパラメーターを削減できることがわかります。
ここに画像の説明を挿入

6.3 モバイルネット

ここに画像の説明を挿入

ここに画像の説明を挿入

6.4 MobileNet コード例

#-------------------------------------------------------------#
#   MobileNet的网络部分
#-------------------------------------------------------------#
import warnings
import numpy as np

from keras.preprocessing import image

from keras.models import Model
from keras.layers import DepthwiseConv2D,Input,Activation,Dropout,Reshape,BatchNormalization,GlobalAveragePooling2D,GlobalMaxPooling2D,Conv2D
from keras.applications.imagenet_utils import decode_predictions
from keras import backend as K


def MobileNet(input_shape=[224,224,3],
              depth_multiplier=1,
              dropout=1e-3,
              classes=1000):


    img_input = Input(shape=input_shape)

    # 224,224,3 -> 112,112,32
    x = _conv_block(img_input, 32, strides=(2, 2))

    # 112,112,32 -> 112,112,64
    x = _depthwise_conv_block(x, 64, depth_multiplier, block_id=1)

    # 112,112,64 -> 56,56,128
    x = _depthwise_conv_block(x, 128, depth_multiplier,
                              strides=(2, 2), block_id=2)
    # 56,56,128 -> 56,56,128
    x = _depthwise_conv_block(x, 128, depth_multiplier, block_id=3)

    # 56,56,128 -> 28,28,256
    x = _depthwise_conv_block(x, 256, depth_multiplier,
                              strides=(2, 2), block_id=4)
    
    # 28,28,256 -> 28,28,256
    x = _depthwise_conv_block(x, 256, depth_multiplier, block_id=5)

    # 28,28,256 -> 14,14,512
    x = _depthwise_conv_block(x, 512, depth_multiplier,
                              strides=(2, 2), block_id=6)
    
    # 14,14,512 -> 14,14,512
    x = _depthwise_conv_block(x, 512, depth_multiplier, block_id=7)
    x = _depthwise_conv_block(x, 512, depth_multiplier, block_id=8)
    x = _depthwise_conv_block(x, 512, depth_multiplier, block_id=9)
    x = _depthwise_conv_block(x, 512, depth_multiplier, block_id=10)
    x = _depthwise_conv_block(x, 512, depth_multiplier, block_id=11)

    # 14,14,512 -> 7,7,1024
    x = _depthwise_conv_block(x, 1024, depth_multiplier,
                              strides=(2, 2), block_id=12)
    x = _depthwise_conv_block(x, 1024, depth_multiplier, block_id=13)

    # 7,7,1024 -> 1,1,1024
    x = GlobalAveragePooling2D()(x)
    x = Reshape((1, 1, 1024), name='reshape_1')(x)
    x = Dropout(dropout, name='dropout')(x)
    x = Conv2D(classes, (1, 1),padding='same', name='conv_preds')(x)
    x = Activation('softmax', name='act_softmax')(x)
    x = Reshape((classes,), name='reshape_2')(x)

    inputs = img_input

    model = Model(inputs, x, name='mobilenet_1_0_224_tf')
    model_name = 'mobilenet_1_0_224_tf.h5'
    model.load_weights(model_name)

    return model

def _conv_block(inputs, filters, kernel=(3, 3), strides=(1, 1)):
    x = Conv2D(filters, kernel,
               padding='same',
               use_bias=False,
               strides=strides,
               name='conv1')(inputs)
    x = BatchNormalization(name='conv1_bn')(x)
    return Activation(relu6, name='conv1_relu')(x)


def _depthwise_conv_block(inputs, pointwise_conv_filters,
                          depth_multiplier=1, strides=(1, 1), block_id=1):

    x = DepthwiseConv2D((3, 3),
                        padding='same',
                        depth_multiplier=depth_multiplier,
                        strides=strides,
                        use_bias=False,
                        name='conv_dw_%d' % block_id)(inputs)

    x = BatchNormalization(name='conv_dw_%d_bn' % block_id)(x)
    x = Activation(relu6, name='conv_dw_%d_relu' % block_id)(x)

    x = Conv2D(pointwise_conv_filters, (1, 1),
               padding='same',
               use_bias=False,
               strides=(1, 1),
               name='conv_pw_%d' % block_id)(x)
    x = BatchNormalization(name='conv_pw_%d_bn' % block_id)(x)
    return Activation(relu6, name='conv_pw_%d_relu' % block_id)(x)

def relu6(x):
    return K.relu(x, max_value=6)

def preprocess_input(x):
    x /= 255.
    x -= 0.5
    x *= 2.
    return x

if __name__ == '__main__':
    model = MobileNet(input_shape=(224, 224, 3))

    img_path = 'elephant.jpg'
    img = image.load_img(img_path, target_size=(224, 224))
    x = image.img_to_array(img)
    x = np.expand_dims(x, axis=0)
    x = preprocess_input(x)
    print('Input image shape:', x.shape)

    preds = model.predict(x)
    print(np.argmax(preds))
    print('Predicted:', decode_predictions(preds,1))  # 只显示top1


7. 畳み込みニューラルネットワークの設計スキル

問題の背景:

  • ニューラル ネットワークをトレーニングする能力に習熟するのは、それほど簡単ではありません。これまでの機械学習の考え方と同様、細部が違いを生みます。ただし、ニューラル ネットワークのトレーニングには、さらに詳細に対処する必要があります。データとハードウェアの制限は何ですか? どのようなネットワークから始めるべきでしょうか? 畳み込み層は何層構築する必要がありますか? インセンティブ関数を設定するにはどうすればよいですか?
  • 学習率は、ニューラル ネットワーク トレーニングを調整するための最も重要なハイパーパラメーターであり、最適化が最も難しいパラメーターの 1 つでもあります。小さすぎると解決策にたどり着けない可能性があり、大きすぎると最適な解決策を見逃してしまう可能性があります。適応学習率方式を使用する場合、コンピューティングのニーズを満たすためにハードウェア リソースに多額の費用を費やす必要があることを意味します。
  • 設計の選択とハイパーパラメータの設定は、CNN のトレーニングとパフォーマンスに大きな影響を与えますが、深層学習の分野への新規参入者にとって、設計アーキテクチャの直感の開発にはリソースの不足と分散が必要になる場合があります。

ここに画像の説明を挿入

1) アーキテクチャはアプリケーションに従う
Google Brain や Deep Mind などの想像力豊かな研究機関が発明したピカピカの新しいモデルに魅了されるかもしれませんが、その多くはニーズにとって不可能か非実用的です。おそらく、特定のアプリケーションに最も適したモデルを使用する必要があります。VGG など、非常にシンプルですが強力なモデルです。
ここに画像の説明を挿入

2) パスの急増
毎年、ImageNet Challenge の優勝者は、前年の優勝者よりもより深いネットワークを使用します。AlexNet から Inception、Resnets に至るまで、「ネットワーク内のパスの数は指数関数的に増加する」傾向があります。

3) シンプルさを追求する
大きいほど良いというわけではありません。

4) 対称性を高める
建築でも生物学でも、対称性は品質と職人技の表れと考えられています。

5) ピラミッドの形状
表現力と冗長または無駄な情報の削減との間で常にトレードオフを行っています。CNN は通常、活性化関数をダウンサンプリングし、入力層から最終層までの接続チャネルを増やします。

6) オーバートレーニング
もう 1 つのトレードオフは、トレーニングの精度と一般化です。ドロップアウトやドロップパスなどの正則化手法を使用して汎化能力を向上させることは、ニューラル ネットワークの重要な利点です。実際のユースケースよりも難しい問題でネットワークをトレーニングして、汎化パフォーマンスを向上させます。

7) 問題の空間をカバーする
トレーニング データを拡張し、汎化能力を向上させるために、ノイズを追加し、トレーニング セットのサイズを人為的に増加します。例には、ランダムな回転、トリミング、およびいくつかの画像強調操作が含まれます。

8) 段階的な機能構造
アーキテクチャが成功すると、各層の「仕事」が単純化されます。非常に深いニューラル ネットワークでは、各層は入力を段階的に変更するだけです。ResNets では、各層の出力は入力と同様になる場合があります。したがって、実際には、ResNet では短いスキップ長を使用してください。

9) 層の入力を正規化する
正規化は、計算層の作業を容易にするショートカットであり、実際にはトレーニングの精度を向上させることができます。正規化により、すべての層の入力サンプルが同等の立場に置かれ (単位変換と同様)、バックプロパゲーションをより効率的にトレーニングできるようになります。

10) 細かく調整された事前トレーニング済みネットワークを使用する (微調整)
機械学習会社 Diffbot の CEO であるマイク・タン氏は、「視覚データが ImageNet に似ている場合、事前トレーニング済みネットワークを使用すると、学習に役立ちます」と述べています。もっと早く。" 低レベル CNN は、ラインやエッジなどの一般的なパターンをほとんど検出できるため、多くの場合再利用できます。たとえば、分類レイヤーを独自の設計のレイヤーに置き換え、特定のデータを使用して最後のいくつかのレイヤーをトレーニングします。

11) 循環学習率の使用
学習率の実験には多大な時間がかかり、エラーが発生する可能性があります。適応学習率は計算コストが高くなる可能性がありますが、循環学習率はそうではありません。循環学習率を使用する場合、一連の最大境界と最小境界を設定し、その範囲内で変化させることができます。

おすすめ

転載: blog.csdn.net/m0_63260018/article/details/132265213