1. コンピュータビジョンの発展の歴史
コンピューター ビジョンは、機械が「見る」方法を学習できるようにする学問です。具体的には、機械がカメラで撮影した写真やビデオ内のオブジェクトを識別し、オブジェクトの位置を検出し、ターゲット オブジェクトを追跡することで、シーンやストーリーを理解して説明できるようにします。写真やビデオで人間の脳の視覚システムをシミュレートします。したがって、コンピューター ビジョンはマシン ビジョンと呼ばれることが多く、その目的は画像やビデオから情報を「認識」できる人工システムを構築することです。
数十年の開発を経て、コンピュータ ビジョン テクノロジーは、交通機関 (ナンバー プレート認識、道路違反取り締まり)、セキュリティ (改札口、コミュニティ監視)、金融 (顔での支払い、窓口での自動請求書認識)、医療 (医療画像処理) の分野で使用されてきました。診断)、工業生産(製品欠陥の自動検出)などの分野で、人々の日常生活や工業生産方法に影響を与えたり、変化させたりしています。将来的には、テクノロジーが進化し続けるにつれて、より多くの製品やアプリケーションが登場し、私たちの生活にさらなる利便性と幅広い機会が生まれます。
Flying Paddle は、コンピューター ビジョン タスク用の豊富な API を提供し、基盤となる最適化と高速化を通じてこれらの API のパフォーマンスを保証します。同時に、Feipiao は、画像分類、検出、セグメンテーション、テキスト認識、ビデオ理解などの多くの分野をカバーする豊富なモデル ライブラリも提供します。ユーザーはこれらの API を直接使用してモデルを構築したり、Fei Paddle が提供するモデル ライブラリに基づいて二次的な研究開発を行ったりすることができます。
スペースの制限のため、この章では、コンピューター ビジョンの古典的なモデル (畳み込みニューラル ネットワーク) と 2 つの典型的なタスク (画像分類とターゲット検出) に焦点を当てます。主に以下の内容を扱います。
- 畳み込みニューラル ネットワーク: 畳み込みニューラル ネットワーク (CNN) は、コンピューター ビジョン テクノロジの最も古典的なモデル構造です。このチュートリアルでは主に、畳み込み、プーリング、活性化関数、バッチ正規化、ドロップアウト法などの畳み込みニューラル ネットワークの一般的なモジュールを紹介します。
- 画像分類: LeNet、AlexNet、VGG、GoogLeNet、ResNet などの画像分類アルゴリズムの古典的なモデル構造を紹介し、眼疾患スクリーニングの事例を通じてアルゴリズムの適用を示します。
- ターゲット検出: ターゲット検出のための YOLOv3 アルゴリズムを紹介し、林業の害虫と病気の検出事例を通じて YOLOv3 アルゴリズムの適用を実証します。
コンピューター ビジョンの開発は、生物学的ビジョンから始まります。生物学的視覚の起源については、学術界はまだ結論に達していません。最古の生物学的視覚は約 7 億年前にクラゲで形成されたと考える研究者もいますが、生物学的視覚は約 5 億年前のカンブリア紀に始まったと考える研究者もいます。カンブリア紀の爆発の原因は常に未解決の謎でしたが、カンブリア紀の動物が視覚能力を持っていたことは確かで、捕食者は獲物をより簡単に見つけられ、捕食者は天敵をより早く発見できました。視覚能力は、ハンターと獲物の間のゲームを激化させ、より激しい生存と進化のルールも生み出します。視覚系の形成は食物連鎖の進化を効果的に促進し、生物学的進化のプロセスを加速させました。これは生物学的発展の歴史における重要なマイルストーンです。数億年の進化を経て、現在の人間の視覚系は非常に複雑かつ強力な機能を獲得しており、人間の脳の神経細胞の数は1,000億個に達しており、これらの神経細胞はネットワークで互いに接続されています。ビジュアル ニューラル ネットワーク これにより、下の図に示すように、周囲の世界を簡単に観察できるようになります。
人間にとって猫と犬を識別するのは非常に簡単です。しかし、コンピュータの場合、たとえプログラミングに熟達した達人であっても、汎用的なプログラムを簡単に書くことは困難です(たとえば、プログラムが大きいほうを犬、小さいほうを猫だと認識しているとします。しかし、撮影角度の違いにより、そうではない可能性があります)。猫は犬よりも画像内で多くのピクセルを占めます)。では、コンピューターが人間と同じように周囲の世界を理解できるようにするにはどうすればよいでしょうか? 研究者たちは、さまざまな角度からこの問題を解決しようと試み、その結果、以下の図に示すような一連のサブタスクを開発しました。
- 画像分類: 画像内のオブジェクトのカテゴリ (ボトル、カップ、立方体など) を識別するために使用されます。
- オブジェクト検出: オブジェクト検出は、画像内の各オブジェクトのカテゴリを検出し、その位置を正確にマークするために使用されます。
- 画像セマンティック セグメンテーション (セマンティック セグメンテーション): 画像内の各ピクセルが属するカテゴリをマークするために使用され、同じカテゴリに属するピクセルは色で識別されます。
- インスタンスのセグメンテーション: 2 のターゲット検出タスクはオブジェクトの位置をマークするだけでよいのに対し、4 のインスタンス セグメンテーション タスクはオブジェクトの位置をマークするだけでなく、オブジェクトの輪郭もマークする必要があることに注意してください。オブジェクト。
初期の画像分類タスクでは、通常、最初に画像の特徴が手動で抽出され、次に機械学習アルゴリズムを使用してこれらの特徴が分類されました。分類結果は特徴抽出方法に大きく依存し、多くの場合、経験豊富な研究者のみがそれを完了できました。下の図を表示します。
そこで時代の要請に応じて、ニューラルネットワークをベースとした特徴抽出手法が登場しました。Yann LeCun は、畳み込みニューラル ネットワークを画像認識の分野に初めて適用しました。その主なロジックは、畳み込みニューラル ネットワークを使用して画像の特徴を抽出し、画像が属するカテゴリを予測することです。ネットワーク パラメーターはトレーニング データを通じて継続的に調整されます。次の図に示すように、画像の特徴を自動的に抽出し、これらの特徴を分類するネットワークが形成されます。
この方法は手書き数字認識タスクでは大きな成功を収めましたが、その後はうまく発展しませんでした。その主な理由は、一方ではデータ セットが不完全であり、単純なタスクしか処理できず、大規模なデータでは過剰適合が発生しやすいことですが、他方ではそれがハードウェアのボトルネックであることです。複雑な場合、特に計算速度が遅くなります。
現在、インターネット技術の継続的な進歩に伴い、データ量は大規模に増加しており、ますます豊富なデータセットが登場しています。さらに、ハードウェア機能の向上により、コンピュータの計算能力はますます強力になっています。研究者は常に新しいモデルとアルゴリズムをコンピューター ビジョンの分野に適用しています。これにより、ますます豊富なモデル構造とより正確な精度が生まれ、同時にコンピュータ ビジョンは、分類、検出、セグメンテーション、シーン記述、画像生成、スタイル変換などを含む、ますます多くの問題に対処するようになりました。ビデオ処理技術や 3D ビジョンを含む 2 次元画像に限定されません。
2. 畳み込みニューラルネットワーク (CNN)
畳み込みニューラル ネットワークは、現在コンピューター ビジョンで最も一般的に使用されているモデル構造です。この章では主に、畳み込みニューラル ネットワークのいくつかの基本モジュールを紹介します。
- コンボリューション
- プーリング
- ReLU活性化機能
- バッチ正規化
- ドロップアウト
復習のために、前に手書き数字認識タスクを紹介しました。これには 2 つのモデルがあります。1 つ目は、特徴抽出のための完全に接続されたネットワークです。コードは次のとおりです:
# 全连接层神经网络实现
class MNIST_FC_Model(nn.Layer):
def __init__(self):
super(MNIST_FC_Model, self).__init__()
# 定义两层全连接隐含层,输出维度是10,当前设定隐含节点数为10,可根据任务调整
self.classifier = nn.Sequential(nn.Linear(in_features=784, out_features=256),
nn.Sigmoid(),
nn.Linear(in_features=256, out_features=64),
nn.Sigmoid())
# 定义一层全连接输出层,输出维度是1
self.head = nn.Linear(in_features=64, out_features=10)
def forward(self, x):
# x.shape: [bath size, 1, 28, 28]
x = paddle.flatten(x, start_axis=1) # [bath size, 784]
x = self.classifier(x)
y = self.head(x)
return y
forward
この関数では、まず画像を 1 次元ベクトルに平坦化し、それを特徴抽出層 ( ) に入力する必要があることがわかりますclassifier
が、これにより次の 2 つの問題が発生します。
-
入力データの空間情報が失われます。空間的に隣接するピクセルは同様の RGB 値を持つことが多く、さまざまな RGB チャネル間のデータは通常密接に関連していますが、1 次元ベクトルに変換されるとこの情報は失われます。一方、画像データの形状情報には本質的なパターンが隠れている場合がありますが、これを1次元ベクトルに変換して全結合ニューラルネットワークに入力すると、これらのパターンも無視されます。
-
モデルパラメータが多すぎると、過学習が発生する可能性があります。手書き数字認識の場合、各ピクセルはすべての出力ニューロンに接続されます。画像サイズが大きくなると、入力ニューロンの数が画像サイズの二乗に応じて増加するため、モデルパラメータが多くなり過学習になりやすくなります。
上記の問題を解決するために、特徴抽出に畳み込みニューラル ネットワーク (CNN) を導入します。コードは次のとおりです。
# 多层卷积神经网络实现
class MNIST_CNN_Model(nn.Layer):
def __init__(self):
super(MNIST_CNN_Model, self).__init__()
self.classifier = nn.Sequential(
nn.Conv2D( in_channels=1, out_channels=20, kernel_size=5, stride=1, padding=2),
nn.ReLU(),
nn.MaxPool2D(kernel_size=2, stride=2),
nn.Conv2D(in_channels=20, out_channels=20, kernel_size=5, stride=1, padding=2),
nn.ReLU(),
nn.MaxPool2D(kernel_size=2, stride=2))
self.head = nn.Linear(in_features=980, out_features=args.num_classes)
def forward(self, x):
# x.shape: [10, 1, 28, 28]
x = self.classifier(x) # [bath size, 20, 7, 7]
x = x.flatten(1) # [batch size, 980]
x = self.head(x) # [batch size, num_classes]
return x
CNN ネットワークは最初に画像を平坦化する必要がなく、画像から直接特徴を抽出できることがわかりました。これにより、隣接するピクセル間の特徴パターンを抽出できるだけでなく、パラメータの数が画像サイズによって変化しないようにすることもできます。下の図は、典型的な畳み込みニューラル ネットワークの構造です。入力画像上に多層の畳み込み層とプーリング層が結合されます。ネットワークの最後には、通常、一連の全結合層が追加されます。通常、ReLU 活性化関数は、畳み込み層または完全接続層接続層の出力では、通常、過学習を防ぐためにドロップアウトがネットワークに追加されます。
もう 1 つ説明する必要がある点は、CNN では計算範囲がピクセルの空間近傍内で実行され、畳み込みカーネル パラメーターの数が全結合層のパラメーターよりもはるかに少ないことです。コンボリューション カーネル自体は入力画像のサイズとは関係なく、空間近傍内の特定の特徴パターンの抽出を表します。たとえば、一部のコンボリューション カーネルはオブジェクトのエッジの特徴を抽出し、一部のコンボリューション カーネルはオブジェクトの角の特徴を抽出し、画像上の異なる領域が同じコンボリューション カーネルを共有します。入力画像サイズが異なる場合でも、同じコンボリューションカーネルを使用して演算できます。
3. コンボリューション
このセクションでは、畳み込みアルゴリズムの原理と実装スキームを紹介し、具体的なケースを通して畳み込みを使用して画像を操作する方法を示します。主に次の内容を扱います。
- 畳み込み演算
- パディング
- ストライド
- 受容野
- 複数の入力チャンネル、複数の出力チャンネル、およびバッチ操作
- フライングパドルコンボリューションAPIの紹介
- 畳み込み演算子の応用例
3.1 畳み込み計算
コンボリューションは数学的解析における積分変換手法であり、離散形式のコンボリューションは画像処理で使用されます。ここで説明する必要があるのは、畳み込みニューラル ネットワークでは、畳み込み層の実装は実際には数学で定義された相互相関演算であり、数学的解析における畳み込みの定義とは異なります。畳み込みニューラル ネットワークのチュートリアルと一致しており、両方とも畳み込みの定義として相互相関演算を使用します。具体的な計算プロセスは次の図に示されています。
相互相関は、2 つの信号間の類似性を測定するために信号処理および画像処理で一般的に使用される演算です。数学的には、相互相関は 2 つの関数間の比較を表し、ある信号内のパターンの位置を別の信号内で見つけるためによく使用されます。
2 つの離散信号 (x) と (y) が与えられると、それらの相互相関は次の式で計算できます: ( x ⋆ y ) [ n ] = ∑ m = − ∞ ∞ x [ m ] ⋅ y [ n − m ] (x \star y)[n] = \sum_{m=-\infty}^{\infty} x[m] \cdot y[nm]( ×⋆y ) [ n ]=m = − ∞∑∞× [メートル]⋅と[ n−メートル]
ここで、( x [ m ] ) (x[m])( x [ m ])和( y [ n − m ] ) (y[nm])(および[ n−m ])はそれぞれ( x ) (x)( x )和( y ) (y)( y )異なる位置の値、( n ) (n)( n )は、結果として得られる信号のインデックスです。
相互相関の計算プロセスは、信号(x) (x)を取得するものとして理解できます。( x ) は時間内にスライドし、別の信号(y) (y)( y ) do ドット積⋅\cdot⋅そして合計∑ \sum∑、新しい信号が取得され、異なる位置での 2 つの信号の類似性が示されます。2 つの信号の形状が特定の位置で類似している場合、結果として得られる相互相関の値は大きくなります。
画像処理では、相互相関を使用して、ある画像の一致するパターンを別の画像で見つけることができます。深層学習では、畳み込み演算は実際には相互相関の一種であり、画像から特徴を抽出するために使用されます。
説明:
- コンボリューションカーネルはフィルタとも呼ばれ、コンボリューションカーネルの高さと幅をそれぞれkh k_hとする。kふそして、kw k_wkw、その場合、 kh × kw k_h \times k_wと呼ばれます。
kふ×kwコンボリューション ( 3×5 3×5など)3×5コンボリューションは、コンボリューション カーネルの高さが 3、幅が 5 であることを意味します。 - 畳み込みニューラル ネットワークでは、畳み込み演算子には、上記の畳み込み処理に加えて、バイアス項を追加する演算も含まれます。たとえば、バイアスが 1 であると仮定すると、上記の畳み込み計算の結果は次のようになります: 0 × 1 + 1 × 2 + 2 × 4 + 3 × 5 + 1 = 26 0 × 2 + 1 × 3 + 2 × 5 + 3 × 6 + 1 = 32 0 × 4 + 1 × 5 + 2 × 7 + 3 × 8 + 1 = 44 0 × 5 + 1 × 6 + 2 × 8 + 3 × 9 + 1 = 50 0 × 1 + 1 × 2 + 2 × 4 + 3×5 + 1 = 26 \\ 0 ×2 + 1 × 3 + 2 × 5 + 3×6 + 1 = 32\\ 0×4 + 1×5 + 2×7 + 3× 8 + 1=44\\ 0×5 + 1×6 + 2 × 8 + 3 × 9 + 1 =500×1+1×2+2×4+3×5+1=260×2+1×3+2×5+3×6+1=320×4+1×5+2×7+3×8+1=440×5+1×6+2×8+3×9+1=50
3.2 パディング
上の例では、入力画像サイズは3×3 3×3です。3×3、出力画像サイズは2×2 2×22×2. 1 回の畳み込みの後、画像サイズは小さくなります。畳み込み出力特徴マップのサイズは次のように計算されます (畳み込みカーネルの高さと幅はkh k_hkふそして、kw k_wkw):
H out = H − kh + 1 W out = W − kw + 1 H_{\mathrm{out}} = H - k_h + 1\\ W_{\mathrm{out}} = W - k_w + 1Hアウト=H−kふ+1Wアウト=W−kw+1
入力サイズが 4 で、コンボリューション カーネル サイズが 3 の場合、出力サイズは4 − 3 + 1 = 2 4−3+1=2 となります。4−3+1=2.入力画像とコンボリューションカーネルが他のサイズの場合に上記の計算式が成り立つかどうかは、自分で確認できます。コンボリューション カーネル サイズが 1 より大きい場合、出力特徴マップのサイズは入力画像のサイズより小さくなります。複数の畳み込みを行うと、出力イメージのサイズは減少し続けます。畳み込み後の画像サイズが小さくなるのを防ぐために、通常は次の図に示すように画像の周囲にパディングが実行されます。
図1に示すように:
- パディング サイズは 1、パディング値は 0 です。パディング後、入力画像サイズは4 × 4 から 4 × 4 に変更されます。4×4 は6×6 6×6になります6×6、3 ×3 3×3を使用3×3コンボリューション カーネル、出力画像サイズは4 × 4 4 × 44×4。
- パディングのサイズは 2、パディング値は 0 です。パディング後、入力画像サイズは4 × 4 から 4 × 4 に変更されます。4×4は8×8になります8×88×8、3 ×3 3×3を使用3×コンボリューション カーネル3、出力画像サイズは6 × 6 6 × 66×6。
画像の高さ方向の場合、最初の行の前にph 1 p_{h1}を埋めますph1 _行、最後の行の後にph 2 p_{h2}をパディングph2 _行; 画像の幅方向、列 1 の前にpw 1 p_{w1}を埋めますpw1 _列、最後の 1 列の後にpw 2 p_{w2}を入力しますpw2 _列; 塗りつぶした後の画像サイズは(H + ph 1 + ph 2) × (W + pw 1 + pw 2) (H + p_{h1} + p_{h2}) \times (W + p_{w1} + p_{w2})( H+ph1 _+ph2 _)×( W+pw1 _+pw2 _)。サイズがkh × kw k_h \times k_wkふ×kwコンボリューション カーネル操作後の出力イメージのサイズは次のようになります。
H out = H + ph 1 + ph 2 − kh + 1 W out = W + pw 1 + pw 2 − kw + 1 H_{\mathrm{out}} = H + p_{h1} + p_{h_2} - k_h + 1\\ W_{\mathrm{out}} = W + p_{w1} + p_{w_2} - k_w + 1Hアウト=H+ph1 _+ph2−kふ+1Wアウト=W+pw1 _+pw2−kw+1
畳み込み計算プロセス中、通常は高さまたは幅の両側で等しいパディングが行われます。つまり、ph 1 = ph 2 = ph p_{h1} = p_{h2} = p_hph1 _=ph2 _=pふ& pw 1 = pw 2 = pw p_{w1} = p_{w2} = p_wpw1 _=pw2 _=pwとすると、上記の計算式は次のようになります。
H out = H + 2 ph − kh + 1 W out = W + 2 pw − kw + 1 H_{\mathrm{out}} = H + 2p_h - k_h + 1\\ W_{\mathrm{out}} = W + 2p_w - k_w + 1Hアウト=H+2P _ふ−kふ+1Wアウト=W+2P _w−kw+1
コンボリューション カーネル サイズには通常、{1, 3, 5, 7} \{1, 3, 5, 7\} が使用されます。{ 1 , 3 , 5 , 7 }使用されるパディング サイズが ph = (kh − 1) / 2 の場合、このような奇数p_{h} = (k_h - 1) / 2pふ=( kふ−1 ) /2、pw = ( kw − 1 ) / 2 p_w = (k_w - 1) / 2pw=( kw−1 ) /2、画像サイズは畳み込み後も変化しません。たとえば、コンボリューション カーネル サイズが 3、パディング サイズが 1 の場合、コンボリューション後の画像サイズは変更されません。同様に、コンボリューション カーネル サイズが 5、パディング サイズが 2 の場合、画像サイズは維持されます。変更なし。
3.3 歩幅・歩幅(ストライド)
上の図では、コンボリューション カーネルは一度に 1 ピクセルずつスライドします。これは、ストライド 1 の特殊なケースです。以下の図は、ストライド 2 のコンボリューション プロセスです。コンボリューション カーネルが画像上で移動するとき、各移動のサイズは 2 ピクセルです。
幅方向と高さ方向の歩幅がそれぞれsh s_hの場合sふそして、sw s_wswの場合、出力特徴マップ サイズの計算式は次のとおりです。
H out = H + 2 ph − khsh + 1 W out = W + 2 pw − kwsw + 1 H_{\mathrm{out}} = \frac{H + 2p_h - k_h}{s_h} + 1\\ W_{\ mathrm{out}} = \frac{W + 2p_w - k_w}{s_w} + 1Hアウト=sふH+2P _ふ−kふ+1Wアウト=swW+2P _w−kw+1
入力画像サイズがH × W = 100 × 100 であるとする。 H × W = 100 × 100H×W=100×100、コンボリューションカーネルサイズkh × kw = 3 × 3 k_h \times k_w =3×3kふ×kw=3×3、ph = pw = 1 p_h = p_w = 1pふ=pw=1、ストライドはsh = sw = 2 s_h = s_w = 2sふ=sw=2の場合、出力特徴マップのサイズは次のようになります。
H out = 100 + 2 − 3 2 + 1 = 50 W out = 100 + 2 − 3 2 + 1 = 50 H_{\mathrm{out}} = \frac{100 + 2 - 3}{2} + 1 = 50\\ W_{\mathrm{out}} = \frac{100 + 2 - 3}{2} + 1 = 50Hアウト=2100+2−3+1=50Wアウト=2100+2−3+1=50
3.4 受容野
出力特徴マップ上の各点の値は、入力画像のサイズkh × kw k_h \times k_wによって決まります。kふ×kw領域の要素は乗算され、コンボリューション カーネルの各要素に加算されるため、入力画像ではkh × kw k_h \times k_wkふ×kw領域内の各要素の値の変更は、出力ポイントのピクセル値に影響します。入力画像 (Input) のこの領域を、出力特徴マップ上の対応する点の受容野と呼びます。
受容野とは一般に入力画像を指すことに注意してください。
フィールド内の各要素の値の変化を感じてください。これは、出力ポイントの値の変化に影響します。例: 3×3 3×33×3回の畳み込み3×3 3×33×以下の図に示すように、図 3に示すようになります。
そして2層を通過する場合3×3 3×33×3 回の畳み込みの後、受容野のサイズは5 × 5 5 × 55×以下の図に示すように、図5に示すようになります。
したがって、畳み込みネットワークの深さを増やすと、受容野が増加し、出力特徴マップ内のピクセルにはより多くの画像意味情報が含まれるようになります。
ネットワークが深くなるにつれて、特徴マップはますます小さくなり、受容野もますます大きくなります。上の図を例にとると、出力特徴マップ 2 の受容野は5 × 5 5 \times 5です。5×5。これは、出力特徴マップ 2 上の点が、5 × 5 5 \times 55×5ピクセルなので、特定の意味情報が含まれます。ネットワーク層の数が深くなり、特徴マップのサイズがますます小さくなるにつれて、その意味情報はますます豊富になり、高度な意味情報も備えています。
3.5 複数の入力チャンネル、複数の出力チャンネル、およびバッチ操作
先ほど紹介した畳み込み計算のプロセスは比較的単純ですが、実際のアプリケーションで扱われる問題はさらに複雑です。たとえば、カラー画像には 3 つの RGB チャネルがあり、複数の入力チャネルのシナリオを処理する必要があります。出力特徴マップも複数のチャネルを持つことが多く、ニューラル ネットワークの計算ではサンプルのバッチがまとめて計算されることが多いため、畳み込み演算子には複数入力および複数出力のチャネル データをバッチ処理する機能が必要です。以下にそれぞれのシナリオの操作方法を紹介します。
3.5.1 複数の入力チャネルのシナリオ
上の例ではコンボリューション層のデータは2次元配列ですが、実際には1枚の画像にRGBの3チャンネルが含まれることが多く、コンボリューションの出力結果を計算するためにコンボリューションカーネルの形式も変わります。入力画像のチャンネル数をC_{in}とするCで、入力データの形状はC in × H in × W in C_{in} × H_{in} \times W_{in} です。Cで×Hで×Wでの計算プロセスを次の図に示します。
- 各チャネルのコンボリューション カーネルとして 2 次元配列を設計します。コンボリューション カーネル配列の形状は、cin × kh × kw c_{in} \times k_h \times k_wです。cで×kふ×kw。
- 任意のチャネルに対してcin ∈ [ 0 , C in ) c_{in} \in [0, C_{in})cで∈[ 0 ,Cで)、それぞれサイズkh × kw k_h \times k_wkふ×kw畳み込みカーネルのサイズは、H in × W in H_{in} \times W_{in}です。Hで×Wで畳み込みは 2 次元配列に対して行われます。
- これをC_{in} に移動しますCで各チャネルの計算結果を加算すると、 H out × W out H_{out} \times W_{out}の形になります。Hあなたは_×Wあなたは_二次元配列。
3.5.2 複数出力チャネルのシナリオ
一般に、畳み込み演算の出力特徴マップにも複数のチャネルC out C_{out}が含まれます。Cあなたは_次に、C out C_{out}を設計する必要があります。Cあなたは_寸法はC in × kh × hw C_{in} \times k_h \times h_wです。Cで×kふ×hwコンボリューション カーネル。コンボリューション カーネル配列の次元はC out C_{out}です。Cあなたは_,以下に示すように。
- 任意の出力チャネルの場合、cout ∈ [ 0 , C out ) c_{out} \in [0, C_{out})cあなたは_∈[ 0 ,Cあなたは_)、それぞれ上記で説明した形状をC in × kh × kw C_{in} \times k_h \times k_wCで×kふ×kwコンボリューション カーネルは、入力画像に対してコンボリューションを実行します。
- このC を C_{out} から出してくださいCあなたは_形状はH out × W out H_{out} \times W_{out}です。Hあなたは_×Wあなたは_2 次元配列が結合されてC out × H out × W out C_{out} \times H_{out} \times W_{out} の次元が形成されます。Cあなたは_×Hあなたは_×Wあなたは_三次元配列。
注❗️: コンボリューション カーネルの出力チャネルの数は、通常、コンボリューション カーネルの数と呼ばれます。
3.5.3 バッチ操作(バッチ)
畳み込みニューラル ネットワークの計算では、通常、複数のサンプルがまとめられてバッチ操作用のミニバッチを形成します。つまり、入力データの次元は N × C in × H in × W in N \times C_{in です。 } \times H_{in} \times W_{in}N×Cで×Hで×Wで。コンボリューション演算では各画像に同じコンボリューション カーネルが使用されるため、コンボリューション カーネルの次元は、複数の出力チャネルの場合と同じであり、依然として C out × H out × W out C_{out} \times となります。 H_{アウト} \times W_{アウト}Cあなたは_×Hあなたは_×Wあなたは_、出力特徴マップの次元はN × C out × H out × W out N \times C_{out} \times H_{out} \times W_{out} です。N×Cあなたは_×Hあなたは_×Wあなたは_,以下に示すように。
3.6 PaddlePaddle コンボリューション API の概要
フライングパドル畳み込み演算子に対応する API は、paddle.nn.Conv2D
ユーザーが API を直接呼び出して計算したり、これに基づいて API を変更したりすることができます。Conv2D
名前の「2D」は、コンボリューション カーネルが 2 次元であり、主に画像データの処理に使用されることを示しています。Conv3D
同様に、ビデオ データ (一連の画像) を処理するために使用できる方法もあります。
PyTorch での畳み込みの API は次のとおりです。
torch.nn.Conv2d
ここで、次元 D は小文字です。
class paddle.nn.Conv2D (in_channels, out_channels,
kernel_size, stride=1, padding=0,
dilation=1, groups=1, padding_mode=‘zeros’,
weight_attr=None, bias_attr=None, data_format=‘NCHW’)
一般的に使用されるパラメータは次のとおりです。
in_channels
(int): 入力画像のチャンネル数。out_channels
(int): 畳み込みカーネルの数は出力特徴マップ チャネルの数と同じであり、上記のC out C_{out}に相当します。Cあなたは_。kernel_size
(int | list | tuple): 畳み込みカーネルのサイズ。畳み込みカーネルの高さと幅が両方とも 3 であることを示す 3 などの整数、または [3,2] などの 2 つの整数のリストを指定できます。 、畳み込みを示します。コアの高さは 3、幅は 2 です。stride
(int | list | tuple、オプション): ステップ サイズ (整数にすることができます。デフォルト値は 1 です。これは、垂直方向と水平方向のスライド ステップが両方とも 1 であることを意味します。または、[3,2 などの 2 つの整数のリスト] ]は、縦スライドステップが3、横スライドステップが2であることを示します。- パディング (int | list | tuple、オプション): パディング サイズ。垂直境界と水平境界のパディング サイズが両方とも 1 であることを示す 1 などの整数、または [2,1 などの 2 つの整数のリスト] ]、垂直方向の境界線のパディング サイズが 2、水平方向の境界線のパディング サイズが 1 であることを示します。
要素を使用して kernel_size に値を割り当てる場合、最初に H の後に W が続きます。
形状の概要:
- 输入数据维度 [ N , C i n , H i n , W i n ] [N, C_{in}, H_{in}, W_{in}] [ N 、Cで、Hで、Wで]
- 出力データの次元[ N 、 C out 、 H out 、 W out ] [N、 C_{out}、 H_{out}、 W_{out}][ N 、Cあなたは_、Hあなたは_、Wあなたは_]
- 重みパラメータwww (コンボリューションカーネルパラメータwww):[ out_channels , C in , filter_size_h , filter_size_w ] [\mathrm{out\_channels}, C_{in}, \mathrm{filter\_size\_h}, \mathrm{フィルター\_サイズ\_w}][ out_channels ,Cで、フィルターサイズ_h、フィルターサイズ_w ]
- バイアスパラメータbbb:[ out _channels , ] [\mathrm{out\_channels}, ][ out_channels ,]
注❗️: 入力が 1 つのグレースケール画像のみの場合でも[H in, W in] [H_{in}, W_{in}][ Hで、Wで]も、4 次元入力ベクトル[1, 1, H in, W in] [1, 1, H_{in}, W_{in}] に[ 1 、1 、Hで、Wで]。
3.7 畳み込み演算子のpaddle.nn.Conv2D
応用例
以下に、畳み込み演算子の適用例を 3 つpaddle.nn.Conv2D
図で紹介し、その計算結果を観察します。
3.7.1 ケース 1 - 単純な白黒境界検出
Conv2D
以下は、オペレーターを使用して画像境界検出を完了するタスクです。画像の左側が明るい部分、右側が暗い部分ですが、明暗の境界を検出する必要があります。
幅方向のコンボリューションカーネルパラメータを[1, 0, − 1] [1,0, −1]に設定します。[ 1 、0 、− 1 ] の場合、このコンボリューション カーネルは、幅方向に 1 ずつ離れた 2 つのピクセルの値を減算します。コンボリューション カーネルが画像上をスライドするとき、カバーするピクセルが同じ輝度領域に位置する場合、左右の間隔が 1 である 2 つのピクセルの値の差は 0 になります。コンボリューションカーネルがカバーするピクセルの一部が明るい領域にあり、一部が暗い領域にある場合にのみ、左右の間隔が 1 の 2 点のピクセル値の差が 0 になりません。このコンボリューションカーネルを画像に適用すると、出力された特徴マップ上の黒と白の分割線に相当するピクセル値のみが0ではなくなります。具体的なコードは以下の通りで、結果は以下のパターンで出力されます。
import matplotlib.pyplot as plt
import numpy as np
import paddle
import paddle.nn as nn
from paddle.nn.initializer import Assign
if __name__ == "__main__":
# 创建初始化权重参数 w
w = np.array([1, 0, -1], dtype="float32")
# 将权重矩阵调整为 卷积核 的样式 -> [C_out, c_in, k_h, k_w]
w = w.reshape(1, 1, 1, 3)
# 创建卷积算子,设置输出通道数、卷积核大小和初始化权重参数
# 创建卷积算子的时候,通过参数属性weight_attr指定参数初始化方式
conv = nn.Conv2D(in_channels=1, out_channels=1,
kernel_size=(1, 3), # k_h = 1, k_w = 3
padding=0, stride=1,
weight_attr=paddle.ParamAttr(initializer=Assign(value=w)))
# 创建输入图片,图片左边的像素点取值为1,右边的像素点取值为0
img = np.ones(shape=[50, 50], dtype="float32")
img[:, 30:] = 0.0
# 调整图片尺寸以符合Conv2D的输入要求
x = img.reshape(1, 1, 50, 50)
# 将 ndarray 转换为 tensor
x = paddle.to_tensor(x)
# 使用卷积对输入图片进行特征提取
out = conv(x)
# 将 tensor 转换为 ndarray 以方便我们画图
out = out.numpy()
# 开始画图
fig, axes = plt.subplots(1, 2, dpi=100)
axes[0].imshow(img, cmap="gray")
axes[0].set_title("origin image")
axes[1].imshow(np.squeeze(out), cmap='gray')
axes[1].set_title("convolved image")
plt.show()
3.7.2 ケース 2 - 画像内のオブジェクトのエッジ検出
上に示すのは、畳み込みネットワークを使用して画像の明るい境界と暗い境界を検出して、人工的に構築された単純な画像です。実際の画像の場合、適切なコンボリューション カーネル ( 3 × 3 3 \times 3)を使用することもできます。3×3コンボリューション カーネルの中央の値は 8 で、周囲の値は 8 - 1) オブジェクトの輪郭を検出するために操作し、出力特徴マップと元の画像の対応を観察します。次のコードを表示します。
import matplotlib.pyplot as plt
import numpy as np
import paddle
import paddle.nn as nn
from paddle.nn.initializer import Assign
from PIL import Image
if __name__ == "__main__":
# 创建初始化权重参数 w
w = np.array([[-1, -1, -1],
[-1, 8, -1],
[-1, -1, -1]], dtype="float32") / 8
# 将权重矩阵调整为 卷积核 的样式 -> [C_out, c_in, k_h, k_w]
w = w.reshape(1, 1, 3, 3)
# 由于输入通道数是3,因此需要调整卷积核的通道数,与输入图片一致
w = np.repeat(w, repeats=3, axis=1) # 沿着通道方向重复3次
# 创建卷积算子,设置输出通道数、卷积核大小和初始化权重参数
# 创建卷积算子的时候,通过参数属性weight_attr指定参数初始化方式
conv = nn.Conv2D(in_channels=3, out_channels=1,
kernel_size=(3, 3), # k_h = 1, k_w = 3
padding=0, stride=1,
weight_attr=paddle.ParamAttr(initializer=Assign(value=w)))
# 读取输入图片
img = Image.open("Tom_and_Jerry.jpg")
# 转换图片格式
x = np.array(img, dtype="float32")
# 调整图片形状以符合Conv2D的输入要求
# [H, W, C] -> [C, H, W]
x = np.transpose(x, [2, 0, 1])
# 添加Batch维度
x = x.reshape(1, 3, img.height, img.width)
# 将 ndarray 转换为 tensor
x = paddle.to_tensor(x)
# 使用卷积对输入图片进行特征提取
out = conv(x)
# 将 tensor 转换为 ndarray 以方便我们画图
out = out.numpy()
# 开始画图
fig, axes = plt.subplots(1, 2, dpi=100)
axes[0].imshow(img)
axes[0].set_title("origin image")
axes[1].imshow(np.squeeze(out), cmap="gray")
axes[1].set_title("convolved image")
plt.savefig("应用2.png", dpi=300)
plt.show()
3.7.3 ケース 3 - 画像の平均的なぼやけ
もう 1 つの一般的な畳み込みカーネル ( 5 × 5 5\times 5)5×コンボリューション カーネルの各値5は 1)。現在のピクセルは、その近傍のピクセルと平均化されます。これにより、次のコードに示すように、画像上の比較的大きなノイズのある点がより滑らかになります。
import matplotlib.pyplot as plt
import numpy as np
import paddle
import paddle.nn as nn
from paddle.nn.initializer import Assign
from PIL import Image
if __name__ == "__main__":
# 创建初始化权重参数 w
w = np.ones([1, 1, 5, 5], dtype="float32") / 25 # [C_out, c_in, k_h, k_w]
# 创建卷积算子,设置输出通道数、卷积核大小和初始化权重参数
# 创建卷积算子的时候,通过参数属性weight_attr指定参数初始化方式
conv = nn.Conv2D(in_channels=1, out_channels=1,
kernel_size=(5, 5), # k_h = 1, k_w = 3
padding=0, stride=1,
weight_attr=paddle.ParamAttr(initializer=Assign(value=w)))
# 读取输入图片
img = Image.open("Tom_and_Jerry.jpg").convert("L")
# 转换图片格式
img = np.array(img, dtype="float32")
# 调整图片形状以符合Conv2D的输入要求
# [H, W] -> [C, H, W]
x = img.reshape(1, 1, img.shape[0], img.shape[1])
# 将 ndarray 转换为 tensor
x = paddle.to_tensor(x)
# 使用卷积对输入图片进行特征提取
out = conv(x)
# 将 tensor 转换为 ndarray 以方便我们画图
out = out.numpy()
# 开始画图
fig, axes = plt.subplots(1, 2, dpi=100)
axes[0].imshow(img, cmap="gray")
axes[0].set_title("origin image")
axes[1].imshow(np.squeeze(out), cmap="gray")
axes[1].set_title("convolved image")
plt.savefig("应用3.png", dpi=300)
plt.show()
4. プーリング
プーリングは、特定の位置で隣接する出力の全体的な統計特性を使用して、その位置でのネットワークの出力を置き換えます。利点は、入力データが小さな変換を受ける場合、プーリング関数後の出力のほとんどが変更されないことです。たとえば、画像が人間の顔であるかどうかを識別する場合、顔の左側と右側に目があることを知る必要がありますが、目の正確な位置を知る必要はありません。このとき、一定の領域内のピクセルをプールするので、全体的な統計的な特徴を得るのに役立ちます。プーリング後は特徴マップが小さくなるため、完全に接続された層を後で接続すると、ニューロンの数が効果的に削減され、記憶領域が節約され、計算効率が向上します。下図のように、2×2 2×2を変換します。2×2の領域が1つのピクセルにプールされます。通常、平均プーリングと最大プーリングの 2 つの方法があります。
- 図 (a):平均プーリング。ここで使用されるサイズは2×2 2×2です2×プーリング ウィンドウが2の場合、各動きのストライドは 2 です。プーリング ウィンドウのカバレッジ エリア内のピクセルが平均されて、対応する出力特徴マップのピクセル値が取得されます。
- 図 (b):最大プーリング。プーリング ウィンドウのカバレッジ エリア内のピクセルの最大値を取得して、出力特徴マップのピクセル値を取得します。
プーリング ウィンドウが画像上でスライドすると、出力特徴マップ全体が取得されます。プーリング ウィンドウのサイズはプーリング サイズと呼ばれ、kh × kw k_h \times k_wで表されます。kふ×kw急行。畳み込みニューラル ネットワークで一般的に使用されるウィンドウ サイズは2 × 2 2 × 2です。2×2、ストライド2でプーリング。
コンボリューションカーネルと同様に、プーリングウィンドウが画像上をスライドする際の各動きの刻み幅をストライドと呼びますが、幅方向と高さ方向の動きの大きさが異なる場合は、それぞれ sh × sw s_h \times s_w が使用されます。sふ×sw急行。プールする必要がある画像を埋めることもできます。埋め込み方法は畳み込みに似ています。最初の行の前にph 1 p_{h1}が埋められていると仮定します。ph1 _行、最後の行の後にph 2 p_{h2}をパディングph2 _わかりました。最初の列の前にpw 1 p_{w1}を入力しますpw1 _列、最後の列の後にpw 2 p_{w2}を入力しますpw2 _列の場合、プーリング層の出力特徴マップ サイズは次のようになります。
H out = H + ph 1 + ph 2 − khsh + 1 W out = W + pw 1 + pw 2 − kwsw + 1 H_{out} = \frac{H + p_{h1} + p_{h2} - k_h} {s_h} + 1\\ W_{out} = \frac{W + p_{w1} + p_{w2} - k_w}{s_w} + 1Hあなたは_=sふH+ph1 _+ph2 _−kふ+1Wあなたは_=swW+pw1 _+pw2 _−kw+1
畳み込みニューラル ネットワークでは、通常2 × 2 2×2が使用されます2×サイズ2のプーリング ウィンドウ
H out = H 2 W out = W 2 H_{out} = \frac{H}{2}\\ W_{out} = \frac{W}{2}Hあなたは_=2HWあなたは_=2W
この方法でプーリングを行うと、出力特徴マップの高さと幅は半分になりますが、チャネル数は変わりません。
5. ReLU活性化機能
5.1 Sigmoid 活性化関数と ReLU 活性化関数の比較
先ほど紹介したネットワーク構造では、活性化関数としてシグモイド関数がよく使われます。ニューラル ネットワーク開発の初期には、シグモイド関数がより頻繁に使用され、現在最も一般的に使用されている活性化関数は ReLU です。これは、シグモイド関数がバックプロパゲーションの過程で勾配減衰を起こしやすいためです。この問題を理解するために、シグモイド関数の形式を詳しく見てみましょう。
シグモイド活性化関数は次のように定義されます。
y = 1 1 + e − xy = \frac{1}{1 + e^{-x}}y=1+e− ×1
ReLU 活性化関数は次のように定義されます。
y = { 0 , ( x < 0 ) x , ( x ≥ 0 ) y = \begin{cases} 0, \quad (x < 0) \\ x, \quad (x \ge 0) \end{cases}y={ 0 、( ×<0 )× 、( ×≥0 )
次のプログラムは、シグモイド関数と ReLU 関数をプロットします。
import numpy as np
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 5))
# 创建数据x
x = np.arange(-10, 10, 0.1)
# 计算Sigmoid函数
s = 1.0 / (1 + np.exp(0. - x))
# 计算ReLU函数
y = np.clip(x, a_min=0., a_max=None)
# 以下部分为画图代码
f = plt.subplot(121)
plt.plot(x, s, color='r')
currentAxis=plt.gca()
plt.text(-9.0, 0.9, r'$y=Sigmoid(x)$', fontsize=13)
currentAxis.xaxis.set_label_text('x', fontsize=15)
currentAxis.yaxis.set_label_text('y', fontsize=15)
f = plt.subplot(122)
plt.plot(x, y, color='g')
plt.text(-3.0, 9, r'$y=ReLU(x)$', fontsize=13)
currentAxis=plt.gca()
currentAxis.xaxis.set_label_text('x', fontsize=15)
currentAxis.yaxis.set_label_text('y', fontsize=15)
plt.savefig("两种激活函数对比.png")
plt.show()
5.2 勾配消失現象
ニューラル ネットワークでは、逆伝播後に勾配値がゼロ近くに減衰する現象を勾配消失現象と呼びます。
上記の関数曲線からわかるように、xxの場合、xが大きな正の数である場合××のときxが小さな負の数の場合××のときのみxの値が 0 に近い場合、シグモイド関数の導関数は比較的大きくなります。シグモイド関数の導関数を取得すると、結果は次のようになります。
dydx = − 1 ( 1 + e − x ) ⋅ d ( e − x ) dx = 1 2 + ex + e − x \begin{aligned} \frac{dy}{dx} & = -\frac{1}{ (1 + e^{-x})} \cdot \frac{d(e^{-x})}{dx}\\ & = \frac{1}{2 + e^x + e^{-x }} \end{整列}dx _やあ_=−( 1+e− ×)1⋅dx _d ( e− x )=2+eバツ+e− ×1
上の式からわかるように、シグモイド関数の導関数dydx \frac{dy}{dx}dx _やあ_最大値は1 4 \frac{1}{4}41。順伝播中、y = Sigmoid ( x ) y=\mathrm{Sigmoid}(x)y=シグモイド( x ) ; バックプロパゲーション プロセス中に、xxxの勾配はyyyの勾配は、次のようにシグモイド関数の導関数で乗算されます。
∂ L ∂ x = ∂ L ∂ y ⋅ ∂ y ∂ x \frac{\partial L}{\partial x} = \frac{\partial L}{\partial y} \cdot \frac{\partial y}{\部分的な x}∂ ×∂L _=∂y _∂L _⋅∂ ×∂y _
xxを作るxの最大勾配値はyyを超えません1 4 \frac{1}{4} yの勾配41。
ニューラルネットワークのパラメータは最初はランダムに初期化されるため、xxxの値は大小の領域にある可能性が高く、シグモイド関数の導関数が 0 に近くなり、xxxの勾配は0 に近く、たとえxxxの値は 0 に近いです。上記の分析によると、シグモイド関数の逆伝播後、xxxの勾配はyyを超えません1 4 \frac{1}{4} yの勾配41、多層ネットワークがシグモイド活性化関数を使用する場合、後続の層の勾配は非常に小さな値に減衰します。
ReLU 関数は異なりますが、x < 0 x<0の場合は異なります。バツ<0の場合ただし、x ≥ 0 x ≥ 0バツ≥ここで、 ReLU 関数の導関数である0 は1 であり、 yy はyの勾配は完全にxxに渡されますx を勾配を消失させることなく実行します。
6. バッチ正規化
バッチ正規化手法 (Batch Normalization、BatchNorm) は、2015 年に Ioffe と Szegedy によって提案され、深層学習で広く使用されています。その目的は、ニューラル ネットワークの中間層の出力を標準化して、中間層の出力を標準化することです。より安定した。
通常、ニューラル ネットワークのデータを正規化し、処理されたサンプル データセットが平均μ \muを満たすようにします。μは 0、分散σ \sigmaσの統計的分布は 1 です。これは、入力データの分布が比較的固定されている場合、アルゴリズムの安定性と収束に有利であるためです。ディープ ニューラル ネットワークの場合、パラメータが常に更新されるため、入力データが標準化されていても、後続の層が受け取る入力は依然として大幅に変化しており、通常、数値の不安定性が発生し、モデルの収束が困難になります。BatchNorm はニューラル ネットワークの中間層の出力をより安定させることができ、次の 3 つの利点があります。
- 学習を迅速に行う (より大きな学習率を使用できる)
- 初期値に対するモデルの感度を下げる
- 過学習をある程度抑える
BatchNorm の主なアイデアは、データの分布が平均 μ \ muを満たすように、トレーニング中にニューロンの値を正規化する単位としてミニバッチを使用することです。μは 0、分散σ \sigmaσは1です。具体的な計算プロセスは次のとおりです。
6.1 ステップ 1: ミニバッチ内のサンプルの平均μ B \mu_Bを計算します。メートルB
μ B ← 1 m ∑ i = 1 mx ( i ) \mu_B \leftarrow \frac{1}{m} \sum_{i=1}^mx^{(i)}メートルB←メートル1i = 1∑メートルバツ(私)
其中 x ( i ) x^{(i)} バツ( i )はミニバッチのii-私はサンプルを作ります。
たとえば、入力ミニバッチには 3 つのサンプルが含まれており、各サンプルには次の 2 つの特徴があります。
x ( 1 ) = ( 1 , 2 ) 、 x ( 2 ) = ( 3 , 6 ) 、 x ( 3 ) = ( 5 , 10 ) x^{(1)} = (1, 2)、 \quad x^ {(2)} = (3, 6)、\quad x^{(3)} = (5, 10)バツ( 1 )=( 1 、2 ) 、バツ( 2 )=( 3 、6 ) 、バツ( 3 )=( 5 、10 )
各特徴のミニバッチ内のサンプルの平均を計算します。
μ B 0 = 1 + 3 + 5 3 = 3 、μ B 1 = 2 + 6 + 10 3 = 6 \mu_{B0} = \frac{1+3+5}{3}=3、\quad \mu_ {B1} = \frac{2+6+10}{3}=6メートルB0 _=31+3+5=3 、メートルB1 _=32+6+10=6
この場合、サンプル平均は次のようになります。
μ B = ( μ B 0 , μ B 1 ) = ( 3 , 6 ) \mu_{B} = (\mu_{B0}, \mu_{B1}) = (3, 6)メートルB=( mB0 _、メートルB1 _)=( 3 、6 )
平均はフィーチャの次元に従って計算されます。
6.2 ステップ 2:ミニバッチ内のサンプルの分散σ B 2 \sigma_B^2を計算するpB2
σ B 2 ← 1 m ∑ i = 1 m ( x ( i ) − μ B ) 2 \sigma_B^2 \leftarrow \frac{1}{m} \sum_{i = 1}^m (x^{(i )} - \mu_B)^2pB2←メートル1i = 1∑メートル( ×(私)−メートルB)2
上記の計算式は、まずバッチ内のサンプルの平均μ B \mu_Bを計算します。メートルBと分散σ B 2 \sigma_B^2pB2次に、入力データを正規化し、平均 0、分散 1 の分布に調整します。
上記の入力データx (1)、x (2)、x (3) の場合、x^{(1)}、x^{(2)}、x^{(3)}バツ( 1 )、バツ( 2 )、バツ( 3 )から、各特徴に対応する分散を計算できます。
σ B 0 2 = 1 3 ⋅ [ ( 1 − 3 ) 2 + ( 3 − 3 ) 2 + ( 5 − 3 ) 2 ] = 8 3 σ B 1 2 = 1 3 ⋅ [ ( 2 − 6 ) 2 + ( 6 − 6 ) 2 + ( 10 − 6 ) 2 ] = 32 3 \sigma_{B0}^2 = \frac{1}{3} \cdot \left[(1-3)^2 + (3-3) ^2 + (5-3)^2 \right] = \frac{8}{3}\\ \sigma_{B1}^2 = \frac{1}{3} \cdot \left[ (2-6) ^2 + (6-6)^2 + (10-6)^2 \right] = \frac{32}{3}pB0_ _2=31⋅[ ( 1−3 )2+( 3−3 )2+( 5−3 )2 ]=38pB1 _2=31⋅[ ( 2−6 )2+( 6−6 )2+( 10−6 )2 ]=332
この場合、標本分散は次のようになります。
σ B 2 = ( σ B 0 2 , σ B 1 2 ) = ( 8 3 , 32 3 ) \sigma_{B}^2 = (\sigma_{B0}^2, \sigma_{B1}^2) = ( \frac{8}{3}、\frac{32}{3})pB2=( pB0_ _2、pB1 _2)=(38、332)
分散も特徴の次元に従って計算されます。
6.3 正規化された出力x ^ ( i ) \hat{x}^{(i)} を計算するバツ^(私)
x ^ ( i ) ← x ( i ) − μ B ( σ B 2 + ϵ ) \hat{x}^{(i)} \leftarrow \frac{x^{(i)} - \mu_B}{\sqrt {(\sigma_B^2 + \epsilon)}}バツ^(私)←( pB2+) _バツ(私)−メートルB
ここで、ϵ \εϵは小さな値 (たとえば1e−7
、 ) であり、その主な目的は分母が 0 にならないようにすることです。
上記の入力データx (1)、x (2)、x (3) の場合、x^{(1)}、x^{(2)}、x^{(3)}バツ( 1 )、バツ( 2 )、バツ( 3 )から、正規化された出力を計算できます。
x ^ ( 1 ) = ( 1 − 3 8 3 , 2 − 6 32 3 ) = ( − 3 2 , − 3 2 ) x ^ ( 2 ) = ( 3 − 3 8 3 , 6 − 6 32 3 ) = ( 0 , 0 ) x ^ ( 3 ) = ( 5 − 3 8 3 , 10 − 6 32 3 ) = ( 3 2 , 3 2 ) \begin{aligned} &\hat{x}^{(1)} = \ left( \frac{1 - 3}{\sqrt{\frac{8}{3}}}, \frac{2 - 6}{\sqrt{\frac{32}{3}}} \right) = \ left( -\sqrt{\frac{3}{2}}, -\sqrt{\frac{3}{2}} \right) \\ &\hat{x}^{(2)} = \left( \frac{3 - 3}{\sqrt{\frac{8}{3}}}, \frac{6 - 6}{\sqrt{\frac{32}{3}}} \right) = \left( 0,0 \right) \\ &\hat{x}^{(3)} = \left( \frac{5 - 3}{\sqrt{\frac{8}{3}}}, \frac{10 - 6}{\sqrt{\frac{32}{3}}} \right) = \left(\sqrt{\frac{3}{2}}, \sqrt{\frac{3}{2}} \右) \end{整列}バツ^( 1 )= 381−3、3322−6 =( −23、−23)バツ^( 2 )= 383−3、3326−6 =( 0 ,0 )バツ^( 3 )= 385−3、33210−6 =(23、23)
入力データx (1)、x (2)、x (3) x^{(1)}、x^{(2)}、x^{(3)} を検証できます。バツ( 1 )、バツ( 2 )、バツ( 3 )平均が 0 で分散が 1 であるという要件を満たしていますか:
import numpy as np
def calc_mean_and_var(data):
# 计算均值
mean = np.mean(data, axis=0)
# 计算方差
variance = np.var(data, axis=0)
return mean, variance
def normalization(data, mean, var):
return (data - mean) / np.sqrt(var)
if __name__ == "__main__":
# 定义输入数据
origin_data = np.array([[1, 2], [3, 6], [5, 10]])
mean, var = calc_mean_and_var(origin_data)
print("[原始数据] 均值:", mean)
print("[原始数据] 方差:", var)
# 求归一化的数据
normalization_data = normalization(origin_data, mean, var)
print("归一化后的数据:\n", normalization_data)
# 验证过归一化数据是否符合均值为0方差为1
mean_norm, var_norm = calc_mean_and_var(normalization_data)
print("[归一化] 均值:", mean_norm)
print("[归一化] 方差:", var_norm)
結果:
[原始数据] 均值: [3. 6.]
[原始数据] 方差: [ 2.66666667 10.66666667]
归一化后的数据:
[[-1.22474487 -1.22474487]
[ 0. 0. ]
[ 1.22474487 1.22474487]]
[归一化] 均值: [0. 0.]
[归一化] 方差: [1. 1.]
出力層の分布を強制的に標準化すると、特定の特徴パターンが失われる可能性があるため、正規化後、BatchNorm はデータをスケーリングして変換します。
yi ← γ x ^ i + β y_i \leftarrow \gamma \hat{x}_i + \betay私は←cバツ^私は+b
ここで、γ \ガンマc和b \betaβは学習可能なパラメータであり、初期値γ = 1、β = 0 \gamma=1、\beta=0c=1 、b=0 であり、トレーニング プロセス中に継続的に学習および調整します。
上記はBatchNormメソッドの計算ロジックであり、以下に2種類の入力データ形式の例を示します。PaddlePaddle は、入力データの 4 つの次元、2、3、4、および 5 をサポートします。ここでは、次元サイズ 2 と 4 の例を示します。
6.4 例
6.4.1 例1:入力データの形状が[N, K] [N, K]の場合[ N 、K ]時間
入力データの形状が[ N , K ] [N, K]の場合[ N 、K ] は、通常、全結合層の出力に対応します。この場合、KKについてKの各成分のNNN個のサンプル、データ、パラメーターの平均と分散は
入力 | 形 |
---|---|
xxバツ | [ N 、 C ] [ N 、 C ][ N 、C ] |
やあy | [ N 、 C ] [ N 、 C ][ N 、C ] |
平均μ B \mu_BメートルB | [C, ] [C, ][ C 、] |
分散σ B 2 \sigma_B^2pB2 | [C, ] [C, ][ C 、] |
スケーリングパラメータγ \gammac | [C, ] [C, ][ C 、] |
変換パラメータβ \betab | [C, ] [C, ][ C 、] |
サンプルコードは次のようになります。
import numpy as np
import paddle
import paddle.nn as nn
def calc_mean_and_var(data):
# 计算均值
mean = np.mean(data, axis=0)
# 计算方差
variance = np.var(data, axis=0)
return mean, variance
if __name__ == "__main__":
# 定义数据
data = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype="float32") # [N, C]
# 使用 BN 计算归一化后的输出
bn = nn.BatchNorm1D(num_features=3) # 参数为通道数
x = paddle.to_tensor(data)
y = bn(x).numpy() # [N, C]
print(f"BN 层的输出为:\n{
y}\n其shape为: {
y.shape}")
# 验证
mean, var = calc_mean_and_var(y)
print(f"BN 后的均值为: {
mean}, 其shape为: {
mean.shape}") # [C, ]
print(f"BN 后的方差为: {
var}, 其shape为: {
var.shape}") # [C]
print(f"BN 的缩放参数为: {
bn.weight}") # [C, ]
print(f"BN 的平移参数为: {
bn.bias}") # [C, ]
BN 层的输出为:
[[-1.2247438 -1.2247438 -1.2247438]
[ 0. 0. 0. ]
[ 1.2247438 1.2247438 1.2247438]]
其shape为: (3, 3)
BN 后的均值为: [0. 0. 0.], 其shape为: (3,)
BN 后的方差为: [0.99999833 0.99999833 0.99999833], 其shape为: (3,)
BN 的缩放参数为: Parameter containing: Tensor(shape=[3], dtype=float32, place=Place(gpu:0), stop_gradient=False, [1., 1., 1.])
BN 的平移参数为: Parameter containing: Tensor(shape=[3], dtype=float32, place=Place(gpu:0), stop_gradient=False, [0., 0., 0.])
6.4.1 例2: 入力データの形状が[ N , C , H , W ] [N, C, H, W]の場合[ N 、C 、H 、W ]時間
入力データの形状が[ N , C , H , W ] [N, C, H, W]の場合[ N 、C 、H 、W ] は、通常、畳み込み層の出力に対応します。この場合、それはCC次元Cを展開し、各チャネルのNNNサンプルN × H × WN × H × WN×H×Wピクセル、データ、パラメーターの平均と分散は、以下に対応します。
入力 | 形 |
---|---|
xxバツ | [ N 、 C 、 H 、 W ] [ N 、 C 、 H 、 W ][ N 、C 、H 、W ] |
やあy | [ N 、 C 、 H 、 W ] [ N 、 C 、 H 、 W ][ N 、C 、H 、W ] |
平均μ B \mu_BメートルB | [C, ] [C, ][ C 、] |
分散σ B 2 \sigma_B^2pB2 | [C, ] [C, ][ C 、] |
スケーリングパラメータγ \gammac | [C, ] [C, ][ C 、] |
変換パラメータβ \betab | [C, ] [C, ][ C 、] |
「BatchNorm で標準化された結果に対してアフィン変換を行う必要はないのですか? Numpy を使用して計算された結果が BatchNorm 演算子とどのように一致するのでしょうか?」と疑問に思う人もいるかもしれません。これは、初期値γ = 1が自動的に設定されるためです。 BatchNorm 演算子。β = 0 \gamma=1, \beta=0c=1 、b=0 (上記のコードの結果でわかるように)、このとき、アフィン変換は恒等変換と等価です。トレーニング プロセス中、これら 2 つのパラメーターは継続的に学習され、アフィン変換が有効になります。
サンプルコードを以下に示します。
import numpy as np
import paddle
import paddle.nn as nn
def calc_mean_and_var(data):
# 计算均值
mean = np.mean(data, axis=0)
# 计算方差
variance = np.var(data, axis=0)
return mean, variance
if __name__ == "__main__":
paddle.seed(100)
np.random.seed(100)
# 定义数据
data = np.random.random((10, 3, 64, 64)).astype("float32") # [N, C, H, W]
print(data.shape) # (10, 3, 64, 64)
# 使用 BN 计算归一化后的输出
bn = nn.BatchNorm2D(num_features=3) # 参数为通道数
x = paddle.to_tensor(data)
y = bn(x).numpy() # [N, C, H, W]
print(f"BN 层的输出.shape为: {
y.shape}")
print(f"BN 层的输出.shape为: {
y.shape}")
mean, var = calc_mean_and_var(y)
print(f"BN 后的均值.shape为: {
mean.shape}") # [C, ]
print(f"BN 后的方差.shape为: {
var.shape}") # [C, ]
print(f"BN 的缩放参数.shape: {
bn.weight.shape}") # [C, ]
print(f"BN 的平移参数为.shape: {
bn.bias.shape}") # [C, ]
結果:
BN 层的输出.shape为: (10, 3, 64, 64)
BN 层的输出.shape为: (10, 3, 64, 64)
BN 后的均值.shape为: (3, 64, 64)
BN 后的方差.shape为: (3, 64, 64)
BN 的缩放参数.shape: [3]
BN 的平移参数为.shape: [3]
ヒント:ここで
numpy
計算される出力はBatchNorm2D
、演算子の結果とは若干異なります。これは、BatchNorm2D
数値の安定性を確保するために、演算子が比較的小さな浮動小数点数を分母に加算するためですepsilon=1e-05
。
6.5 予測時の BatchNorm の使用
上記では、BatchNorm を使用して学習プロセス中にサンプルのバッチを正規化する方法を説明しましたが、同じ方法を使用して予測が必要なサンプルのバッチを正規化すると、予測結果に不確実性が生じます。
たとえば、サンプル A とサンプル B をサンプルのバッチとして使用して平均と分散を計算し、サンプル A、サンプル C、サンプル D をサンプルのバッチとして使用して平均と分散を計算すると、結果は次のようになります。得られるものは一般的に異なります。そうなると、サンプル A の予測結果は不確実になり、予測プロセスとしては不合理になります。解決策は、トレーニング プロセス中に多数のサンプルの平均と分散を保存し、保存された値を再計算せずに予測中に直接使用することです。実際、BatchNorm の特定の実装では、トレーニング中に平均と分散の移動平均が計算されます。PaddlePaddle では、デフォルトの計算方法は次のとおりです。
保存 _ μ B ← 保存 _ μ B × 0.9 + μ B × ( 1 − 0.9 ) 保存 _ σ B 2 ← 保存 _ σ B 2 × 0.9 + σ B 2 × ( 1 − 0.9 ) \mathrm{保存\_\ mu_B} \leftarrow \mathrm{保存\_\mu_B} \times 0.9 + \mu_B \times (1 - 0.9)\\ \mathrm{保存\_\sigma_B^2} \leftarrow \mathrm{保存\_\sigma_B^ 2} \times 0.9 + \sigma_B^2 \times (1 - 0.9)保存しました_μB←保存しました_μB×0.9+メートルB×( 1−0.9 )保存しました_ σB2←保存しました_ σB2×0.9+pB2×( 1−0.9 )
トレーニング プロセスの開始時に_ μ B \mathrm{saved}\_\mu_B が保存されました保存しました_μB和保存 _ σ B 2 \mathrm{保存\_\sigma_B^2}保存しました_ σB20 に設定すると、新しいサンプルのバッチが入力されるたびに、 μ B \mu_Bが計算されます。メートルBとσ B 2 \sigma_B^2pB2、そして、上記の式を通じて、saved _ μ B \mathrm{saved}\_\mu_Bを更新します。保存しました_μB和保存 _ σ B 2 \mathrm{保存\_\sigma_B^2}保存しました_ σB2、それらの値はトレーニング プロセス中に常に更新され、BatchNorm レイヤーのパラメーターとして保存されます。保存されたパラメータ _ μ B \mathrm{saved}\_\mu_B は予測中にロードされます。保存しました_μB和保存 _ σ B 2 \mathrm{保存\_\sigma_B^2}保存しました_ σB2、それらを使用してμ B \mu_Bを置き換えますメートルBとσ B 2 \sigma_B^2pB2。
7. ドロップアウト
ドロップアウトは、過学習を抑制するために深層学習で一般的に使用される手法であり、ニューラル ネットワークの学習プロセス中にニューロンの一部をランダムに削除することに基づいています。トレーニング中、一部のニューロンがランダムに選択され、その出力が 0 に設定されます。これらのニューロンは外部に信号を送信しません。
下図はDropoutの模式図で、左側が完成したニューラルネットワーク、右側がDropout適用後のネットワーク構造です。Dropout を適用すると、 × \timesが表示されます。×ニューロンは、後続の層に信号を送信しないようにネットワークから削除されます。学習プロセス中にどのニューロンが破棄されるかはランダムに決定されるため、モデルは特定のニューロンに過度に依存せず、過剰適合をある程度抑制できます。
Q1 : ドロップアウト操作は入力特徴マップに対して行われますか?
A1 : はい、ドロップアウト操作は入力特徴マップに対して実行されます。
Q2 : 画像がネットワークに送信された場合、Dropout はどのように画像を破棄しますか? ピクセルを破棄しますか?
A2 : はい、画像が入力としてニューラル ネットワークに入力されると、ドロップアウト操作により一部のピクセルがランダムに破棄されます。具体的には、Dropout は各順方向パス中にいくつかのピクセルを独立してランダムに選択し、それらをゼロに設定して、これらのピクセルに対応する情報を「オフ」にします。このプロセスは入力画像をブロックすることに相当し、一部のピクセル情報の損失をシミュレートします。
注意してください ❗️: ドロップアウトは通常、特徴マップに適用され、具体的には特徴マップの特定のピクセルを 0 に設定します。
シーンを予測する場合、すべてのニューロンの信号が転送されるため、トレーニング中に一部のニューロンがランダムに破棄されるため、出力データの合計サイズが小さくなるという新たな問題が発生する可能性があります。例: L 1 L_1を計算します。L1ノルムはドロップアウトを使用しない場合よりも小さくなりますが、予測中にニューロンが破棄されないため、トレーニング中と予測中にデータの分布が異なります。この問題を解決するために、PaddlePaddle は次の 2 つの方法をサポートしています。
- downscale_in_infer :トレーニング中にrrをスケールしますr は、ニューロンの一部をランダムに破棄し、その信号を逆方向に渡しません。予測する場合、すべてのニューロンの信号を逆方向に渡しますが、各ニューロンの値を (1 − r ) (1−r) で乗算します。( 1−r )。
- upscale_in_train :トレーニング中にrrをスケールしますr は、信号を逆に渡すことなくニューロンの一部をランダムに破棄しますが、保持されたニューロンの値を( 1 − r ) (1 − r)( 1−r ) ; 予測する場合、すべてのニューロンの信号は処理されずに逆方向に送信されます。
PaddlePaddle Dropout
API では、mode
パラメーターを使用してニューロンの操作方法を指定します。
paddle.nn.Dropout(p=0.5, axis=None,
mode="upscale_in_train”,
name=None)
主なパラメータは次のとおりです。
p
(float): 入力ノードが 0 になる確率、つまり破棄される確率、デフォルト値: 0.5。このパラメータの要素を破棄する確率は、すべての要素ではなく、各要素に対するものです。たとえば、行列に 12 個の数値があると仮定すると、確率 0.5 のドロップアウトには 6 つのゼロがあるとは限りません。mode
’downscale_in_infer’
(str): 破棄メソッドの実装方法は、と の2つがあり’upscale_in_train’
、デフォルトは です’upscale_in_train’
。
Dropoutはフレームワークごとにデフォルトの処理方法が異なる場合がありますので、詳細はAPIをご利用の際にご確認ください。
以下のプログラムは Dropout 後の出力データの形式を示しています。
import paddle
import paddle.nn as nn
import numpy as np
if __name__ == "__main__":
np.random.seed(100)
# 创建数据
data_1 = np.random.rand(1, 3, 2, 2).astype("float32") # [N, C, H, W]
data_2 = np.arange(1, 13).reshape([-1, 3]).astype("float32") # [N, C]
# 使用dropout作用到输入数据上
x_1 = paddle.to_tensor(data_1)
x_2 = paddle.to_tensor(data_2)
"""方式1:downgrade_in_infer模式下"""
drop_method_1 = nn.Dropout(p=0.5, mode="downscale_in_infer")
droped_train_11 = drop_method_1(x_1)
droped_train_12 = drop_method_1(x_1)
# 切换到eval模式。在动态图模式下,使用eval()切换到求值模式,该模式禁用了dropout
drop_method_1.eval()
drop_11_eval_11 = drop_method_1(x_1)
drop_12_eval_12 = drop_method_1(x_1)
"""方式2:upscale_in_train模式下"""
drop_method_2 = nn.Dropout(p=0.5, mode="upscale_in_train")
droped_train_21 = drop_method_2(x_2)
droped_train_22 = drop_method_2(x_2)
# 切换到eval模式。在动态图模式下,使用eval()切换到求值模式,该模式禁用了dropout
drop_method_2.eval()
drop_21_eval_21 = drop_method_2(x_2)
drop_22_eval_22 = drop_method_2(x_2)
# 输出
print('x1: {}, \n\n droped_train_11: \n\n {}, \n\n drop_11_eval_11: \n {}\n\n'.format(data_1, droped_train_11.numpy(), drop_11_eval_11.numpy()))
print('x1: {}, \n\n droped_train_12: \n\n {}, \n\n drop_12_eval_12: \n {}\n\n'.format(data_1, droped_train_12.numpy(), drop_12_eval_12.numpy()))
print('x2: {}, \n\n droped_train_21: \n\n {}, \n\n drop_21_eval_21: \n {}\n\n'.format(data_2, droped_train_21.numpy(), drop_21_eval_21.numpy()))
print('x2: {}, \n\n droped_train_22: \n\n {}, \n\n drop_22_eval_22: \n {}\n\n'.format(data_2, droped_train_22.numpy(), drop_22_eval_22.numpy()))
結果:
x1:
[[[[0.54340494 0.2783694 ]
[0.4245176 0.84477615]]
[[0.00471886 0.12156912]
[0.67074907 0.82585275]]
[[0.13670659 0.5750933 ]
[0.89132196 0.20920213]]]],
droped_train_11:
[[[[0.54340494 0.2783694 ]
[0.4245176 0. ]]
[[0.00471886 0. ]
[0.67074907 0. ]]
[[0.13670659 0.5750933 ]
[0. 0.20920213]]]],
drop_11_eval_11:
[[[[0.27170247 0.1391847 ]
[0.2122588 0.42238808]]
[[0.00235943 0.06078456]
[0.33537453 0.41292638]]
[[0.0683533 0.28754666]
[0.44566098 0.10460106]]]]
x1:
[[[[0.54340494 0.2783694 ]
[0.4245176 0.84477615]]
[[0.00471886 0.12156912]
[0.67074907 0.82585275]]
[[0.13670659 0.5750933 ]
[0.89132196 0.20920213]]]],
droped_train_12:
[[[[0. 0. ]
[0.4245176 0.84477615]]
[[0.00471886 0.12156912]
[0.67074907 0. ]]
[[0.13670659 0.5750933 ]
[0. 0. ]]]],
drop_12_eval_12:
[[[[0.27170247 0.1391847 ]
[0.2122588 0.42238808]]
[[0.00235943 0.06078456]
[0.33537453 0.41292638]]
[[0.0683533 0.28754666]
[0.44566098 0.10460106]]]]
x2:
[[ 1. 2. 3.]
[ 4. 5. 6.]
[ 7. 8. 9.]
[10. 11. 12.]],
droped_train_21:
[[ 0. 0. 0.]
[ 0. 10. 0.]
[14. 0. 18.]
[20. 0. 0.]],
drop_21_eval_21:
[[ 1. 2. 3.]
[ 4. 5. 6.]
[ 7. 8. 9.]
[10. 11. 12.]]
x2:
[[ 1. 2. 3.]
[ 4. 5. 6.]
[ 7. 8. 9.]
[10. 11. 12.]],
droped_train_22:
[[ 2. 0. 6.]
[ 0. 0. 12.]
[ 0. 0. 0.]
[20. 22. 0.]],
drop_22_eval_22:
[[ 1. 2. 3.]
[ 4. 5. 6.]
[ 7. 8. 9.]
[10. 11. 12.]]
上記のコードの出力から、ドロップアウト後、テンソルの一部の要素が 0 になることがわかります。これはドロップアウトによって実装された関数です。入力データの要素をランダムに 0 に設定することで、関節の適応性が排除され、弱められます。ニューロンノード間で、モデルの一般化能力を強化します。