pytorch での正規化: BatchNorm、LayerNorm、および GroupNorm

1 正規化の概要

ディープ ニューラル ネットワークのトレーニングは困難な作業です。長年にわたり、研究者たちは学習プロセスを高速化し、安定させるためのさまざまな方法を提案してきました。正規化は、この点で非常に効果的であることが証明されている手法です。

1.1 正規化が必要な理由

データの正規化操作はデータ処理の基本的な作業です。実際の問題によっては、取得したサンプル データが多次元である、つまり、サンプルが複数の特徴によって表され、異なるデータ サンプルの特徴のスケールが異なる場合があります。データ分析の結果に影響を与える可能性があります。この問題を解決するには、データの正規化が必要です。元のデータのデータ正規化後、各特徴は同じオーダーの大きさになるため、包括的な比較評価に適しています。

たとえば、ここでは 2 つの特徴を持つ単純なニューラル ネットワーク モデルを構築します。特徴は、年齢:0~65歳、給与:0~10,000の2つです。これらの特徴をモデルに供給し、勾配を計算します。

入力のサイズが異なると、重みの更新も異なり、最小値に向けた最適化ステップも不均一になります。これにより、損失関数の形状も不均衡になります。この場合、オーバーシュートを避けるために学習率を低くする必要があり、これは学習プロセスが遅くなることを意味します。

したがって、私たちの解決策は、入力を正規化し、平均を減算し (センタリングし)、標準偏差で割ることによって特徴を縮小することです。

「漂白」とも呼ばれるこのプロセスは、すべての値の平均と単位分散が 0 になるように処理します。これにより、より高速な収束とより安定したトレーニングが実現します。

1.2 正規化の役割

ディープ ラーニングでは、データの正規化は、トレーニング プロセスとニューラル ネットワーク モデルのパフォーマンスを最適化するために使用される重要な前処理ステップです。正規化テクノロジは、勾配の消失と勾配の爆発の問題の解決に役立ち、モデルの収束を高速化し、モデルの堅牢性と一般化能力を向上させます。詳細は以下のとおりです。

  • 勾配の消失と爆発の問題: 勾配の消失と爆発は、ディープ ニューラル ネットワークでよく見られる問題です。データ正規化によりこれらの問題が軽減され、勾配が妥当な範囲内で伝播できるようになり、モデルのトレーニング効果の向上に役立ちます。

  • 一貫性のない特徴スケール: 深層学習モデルは特徴のスケールに非常に敏感です。異なるフィーチャのスケール範囲が異なる場合、一部のフィーチャがモデル トレーニング プロセスを支配し、他のフィーチャの影響が無視される可能性があります。データ正規化により、さまざまなフィーチャのスケールを同じ範囲に統一できるため、モデルがすべてのフィーチャをバランスの取れた方法で処理し、スケールの不一致によって生じるバイアスを回避できるようになります。

  • モデルの収束速度: データの正規化により、モデルの収束速度が向上します。データがより狭い範囲に正規化されると、モデルは適切なパラメーター値をより速く見つけ、トレーニング中の発振や不安定性を軽減できます。これにより、トレーニング時間が節約され、モデルの効率が向上します。

  • 堅牢性と一般化機能:​​ データの正規化により、モデルはさまざまなデータ分布やノイズ状況にさらに適応できます。正規化によりモデルの堅牢性が向上し、入力データの変更や摂動に対するモデルの耐性が高まります。同時に、正規化はモデルの汎化能力の向上にも役立ち、目に見えないデータに対するモデルのパフォーマンスが向上します。

1.3 正規化の手順

正規化では、特定の次元でデータを正規化することで、平均値と単位分散がゼロになるように入力データの分布を調整します。入力は通常、次の手順で正規化されます。

  • 指定された入力データについて、指定された次元での平均と分散を計算します。

  • 計算された平均と分散を使用して入力データを標準化し、ゼロ意味にして単位分散を与えます。

  • スケールとパンの操作は標準化されたデータに対して実行され、学習可能なパラメータを通じて調整が行われ、データを表現するモデルの能力が復元されます。

さらに、正規化では、スケーリングおよび変換操作を通じて学習可能なパラメーター、つまりスケーリング パラメーター (スケール) と変換パラメーター (シフト) が導入されます。これらのパラメーターは、正規化されたデータに線形変換を実行して、モデルの表現力を復元するために使用されます。

具体的には、各特徴次元で、正規化されたデータが \hat{x} であると仮定すると、最終出力は次の式で計算されます: y=\ガンマ \hat{x}+\beta。このうち、 そして は最終出力、 \ガンマ はスケーリング パラメータ (scale)、 \ベータ は変換パラメータ (shift) です。 )。これら 2 つのパラメーターは学習可能であり、バックプロパゲーションや確率的勾配降下法などの最適化アルゴリズムを通じて更新できます。トレーニング プロセス中に、モデルは勾配降下法を通じてこれらのパラメーターを調整し、モデルがさまざまなデータ分布を適応的にスケーリングおよび変換できるようにします。このようにして、モデルは実際の状況に応じて各特徴の重要性と偏りを自由に調整できるため、さまざまなデータ分布によりよく適応できます。

2 pytorchでの正規化

BatchNorm、LayerNorm、および GroupNorm はすべて、深層学習で一般的に使用される正規化手法です。これらは、入力を平均 0 と分散 1 の分布に正規化することで、勾配の消失や爆発を防ぎ、モデルの汎化能力を向上させます。

2.1 バッチノルム

一般に CNN では、畳み込み層の後に BatchNorm 層が続き、勾配の消失と爆発を軽減し、モデルの安定性を向上させます。

PyTorch では、torch.nn.BatchNorm1dtorch.nn.BatchNorm2dtorch.nn.BatchNorm3d などのバッチ正規化レイヤーを使用してバッチ正規化を実装できます。変換。

コード例:

import torch
import torch.nn as nn
import numpy as np

feature_array = np.array([[[[1, 0],  [0, 2]],
                           [[3, 4],  [1, 2]],
                           [[-2, 9], [7, 5]],
                           [[2, 3],  [4, 2]]],

                          [[[1, 2],  [-1, 0]],
                            [[1, 2], [3, 5]],
                            [[4, 7], [-6, 4]],
                            [[1, 4], [1, 5]]]], dtype=np.float32)

feature_tensor = torch.tensor(feature_array.copy(), dtype=torch.float32)
bn_out = nn.BatchNorm2d(num_features=4, eps=1e-5)(feature_tensor)
print(bn_out)

for i in range(feature_array.shape[1]):
    channel = feature_array[:, i, :, :]
    mean = feature_array[:, i, :, :].mean()
    var = feature_array[:, i, :, :].var()
    print(mean)
    print(var)

    feature_array[:, i, :, :] = (feature_array[:, i, :, :] - mean) / np.sqrt(var + 1e-5)
print(feature_array)

実行結果は次のとおりです。

tensor([[[[ 0.3780, -0.6299],
          [-0.6299,  1.3859]],

         [[ 0.2847,  1.0441],
          [-1.2339, -0.4746]],

         [[-1.1660,  1.1660],
          [ 0.7420,  0.3180]],

         [[-0.5388,  0.1796],
          [ 0.8980, -0.5388]]],


        [[[ 0.3780,  1.3859],
          [-1.6378, -0.6299]],

         [[-1.2339, -0.4746],
          [ 0.2847,  1.8034]],

         [[ 0.1060,  0.7420],
          [-2.0140,  0.1060]],

         [[-1.2572,  0.8980],
          [-1.2572,  1.6164]]]], grad_fn=<NativeBatchNormBackward0>)
0.625
0.984375
2.625
1.734375
3.5
22.25
2.75
1.9375
[[[[ 0.37796253 -0.6299376 ]
   [-0.6299376   1.3858627 ]]

  [[ 0.28474656  1.0440707 ]
   [-1.2339017  -0.4745776 ]]

  [[-1.1659975   1.1659975 ]
   [ 0.7419984   0.3179993 ]]

  [[-0.53881454  0.17960484]
   [ 0.8980242  -0.53881454]]]


 [[[ 0.37796253  1.3858627 ]
   [-1.6378376  -0.6299376 ]]

  [[-1.2339017  -0.4745776 ]
   [ 0.28474656  1.8033949 ]]

  [[ 0.10599977  0.7419984 ]
   [-2.0139956   0.10599977]]

  [[-1.2572339   0.8980242 ]
   [-1.2572339   1.6164436 ]]]]

2.2 レイヤーノルム

LayerNorm は Transformer ブロックで使用されます。一般的な入力サイズは (batch_size, token_num, dim) で、正規化は最後の次元で行われます: nn.LayerNorm(dim)

バッチ正規化とは異なり、レイヤー正規化は単一サンプルの特徴次元に対して計算され、その目的は、単一レイヤー内の各サンプルの特徴次元を正規化して特徴間の独立性を強化し、より安定した特徴表現を提供することです。次のような利点があります。

  • 単一サンプルの処理に適しています: バッチ正規化と比較して、レイヤー正規化の計算は単一レイヤー内の単一サンプルの特徴次元に基づいており、サンプルの小さなバッチの統計情報には依存しません。これにより、層の正規化は、リカレント ニューラル ネットワーク (RNN) など、単一のサンプルが処理される状況に適しており、各タイム ステップの入力を個別のサンプルとして表示できます。

  • 動的計算グラフやシーケンスデータに最適: 層正規化の計算は小さなバッチサンプルの統計情報に依存しないため、動的計算グラフやシーケンスデータの計算により適しています。レイヤー正規化により、可変長シーケンス データを処理するとき、または動的計算グラフを使用するときに、より優れたパフォーマンスと結果が得られます。

さらに、Transformer モデルでのレイヤー正規化 (Layer Normalization) の使用は、主にその独立した特徴次元正規化プロパティによるものです。Transformer モデルの中核は、入力シーケンス内のすべての特徴を検出するセルフ アテンション メカニズムです。それぞれの位置に注意してください。各位置のフィーチャの次元は独立していると見なせるため、各位置のレイヤー正規化により、より安定したフィーチャ表現が提供され、フィーチャ間の結合が軽減され、モデルが位置間でより適切に学習できるようになります。 。さらに、フィーチャ間の内部共変量伝達が減少するため、ディープ ニューラル ネットワークで一般的な勾配消失と勾配爆発の問題が軽減され、モデルのトレーニング効果と収束速度が向上します。

レイヤー正規化 (レイヤー正規化) は通常、アクティベーション関数の前に適用されます。レイヤー正規化の後にアクティベーション関数を適用すると、アクティベーション関数の入力が正規化された範囲内に維持され、アクティベーション関数の入力が大きすぎたり小さすぎたりすることを回避できます。このアプローチは、バッチ正規化が適用される順序と似ています。

例: 従来の RNN では、通常、入力シーケンスは線形変換され、次に活性化関数 (tanh や ReLU など) が非線形変換に適用されます。ただし、このような操作では、勾配の消失または爆発の問題が発生する可能性があり、異なるタイム ステップでの入力間に大きな変化が生じる可能性があります。上記の問題は、活性化関数の前に層の正規化を適用することで解決できます。

サンプルコード:

import torch
import torch.nn as nn
import numpy as np

feature_array = np.array([[[[1, 0],  [0, 2]],
                           [[3, 4],  [1, 2]],
                           [[2, 3],  [4, 2]]],

                          [[[1, 2],  [-1, 0]],
                            [[1, 2], [3, 5]],
                            [[1, 4], [1, 5]]]], dtype=np.float32)


feature_array = feature_array.reshape((2, 3, -1)).transpose(0, 2, 1)
feature_tensor = torch.tensor(feature_array.copy(), dtype=torch.float32)

ln_out = nn.LayerNorm(normalized_shape=3)(feature_tensor)
print(ln_out)

b, token_num, dim = feature_array.shape
feature_array = feature_array.reshape((-1, dim))
for i in range(b*token_num):
    mean = feature_array[i, :].mean()
    var = feature_array[i, :].var()
    print(mean)
    print(var)

    feature_array[i, :] = (feature_array[i, :] - mean) / np.sqrt(var + 1e-5)
print(feature_array.reshape(b, token_num, dim))

コードを実行すると、次のように表示されます。

tensor([[[-1.2247,  1.2247,  0.0000],
         [-1.3728,  0.9806,  0.3922],
         [-0.9806, -0.3922,  1.3728],
         [ 0.0000,  0.0000,  0.0000]],

        [[ 0.0000,  0.0000,  0.0000],
         [-0.7071, -0.7071,  1.4142],
         [-1.2247,  1.2247,  0.0000],
         [-1.4142,  0.7071,  0.7071]]], grad_fn=<NativeLayerNormBackward0>)
2.0
0.6666667
2.3333333
2.888889
1.6666666
2.888889
2.0
0.0
1.0
0.0
2.6666667
0.88888884
1.0
2.6666667
3.3333333
5.555556
[[[-1.2247357   1.2247357   0.        ]
  [-1.3728105   0.980579    0.3922316 ]
  [-0.98057896 -0.39223155  1.3728106 ]
  [ 0.          0.          0.        ]]

 [[ 0.          0.          0.        ]
  [-0.70710295 -0.70710295  1.4142056 ]
  [-1.2247427   1.2247427   0.        ]
  [-1.4142123   0.7071062   0.7071062 ]]]

2.3 グループノルム

バッチサイズが大きすぎたり小さすぎたりする場合は、BN の使用は適さないため、代わりに GN を使用してください。

(1) バッチ サイズが大きすぎる場合、BN はすべてのデータを同じ平均と分散に正規化します。これにより、トレーニング中にモデルが非常に不安定になり、収束が困難になる可能性があります。

(2) バッチサイズが小さすぎる場合、BN はデータの統計情報を効果的に学習できない可能性があります。

たとえば、Deformable DETR では GroupNorm が使用されます。

サンプルコード:

import torch
import torch.nn as nn
import numpy as np

feature_array = np.array([[[[1, 0],  [0, 2]],
                           [[3, 4],  [1, 2]],
                           [[-2, 9], [7, 5]],
                           [[2, 3],  [4, 2]]],

                          [[[1, 2],  [-1, 0]],
                            [[1, 2], [3, 5]],
                            [[4, 7], [-6, 4]],
                            [[1, 4], [1, 5]]]], dtype=np.float32)

feature_tensor = torch.tensor(feature_array.copy(), dtype=torch.float32)
gn_out = nn.GroupNorm(num_groups=2, num_channels=4)(feature_tensor)
print(gn_out)

feature_array = feature_array.reshape((2, 2, 2, 2, 2)).reshape((4, 2, 2, 2))

for i in range(feature_array.shape[0]):
    channel = feature_array[i, :, :, :]
    mean = feature_array[i, :, :, :].mean()
    var = feature_array[i, :, :, :].var()
    print(mean)
    print(var)

    feature_array[i, :, :, :] = (feature_array[i, :, :, :] - mean) / np.sqrt(var + 1e-5)
feature_array = feature_array.reshape((2, 2, 2, 2, 2)).reshape((2, 4, 2, 2))
print(feature_array)

実行結果は次のとおりです。

tensor([[[[-0.4746, -1.2339],
          [-1.2339,  0.2847]],

         [[ 1.0441,  1.8034],
          [-0.4746,  0.2847]],

         [[-1.8240,  1.6654],
          [ 1.0310,  0.3965]],

         [[-0.5551, -0.2379],
          [ 0.0793, -0.5551]]],


        [[[-0.3618,  0.2171],
          [-1.5195, -0.9406]],

         [[-0.3618,  0.2171],
          [ 0.7959,  1.9536]],

         [[ 0.4045,  1.2136],
          [-2.2923,  0.4045]],

         [[-0.4045,  0.4045],
          [-0.4045,  0.6742]]]], grad_fn=<NativeGroupNormBackward0>)
1.625
1.734375
3.75
9.9375
1.625
2.984375
2.5
13.75
[[[[-0.4745776  -1.2339017 ]
   [-1.2339017   0.28474656]]

  [[ 1.0440707   1.8033949 ]
   [-0.4745776   0.28474656]]

  [[-1.8240178   1.6654075 ]
   [ 1.0309665   0.3965256 ]]

  [[-0.55513585 -0.23791535]
   [ 0.07930512 -0.55513585]]]


 [[[-0.3617867   0.21707201]
   [-1.5195041  -0.9406454 ]]

  [[-0.3617867   0.21707201]
   [ 0.79593074  1.9536481 ]]

  [[ 0.40451977  1.2135593 ]
   [-2.2922788   0.40451977]]

  [[-0.40451977  0.40451977]
   [-0.40451977  0.67419964]]]]

おすすめ

転載: blog.csdn.net/lsb2002/article/details/134916391