なぜCNNはバッチ正規化、自然言語処理はレイヤー正規化なのか?違いと理由を初心者向けに解説
こんにちは。ゆうせいです。
ディープラーニングを学び始めると、「正規化層」という部品が必ず登場します。画像認識のCNNではバッチ正規化(Batch Normalization)が、TransformerやBERT、GPTなどの自然言語処理モデルではレイヤー正規化(Layer Normalization)が使われています。
どちらも「値のばらつきをそろえる」という同じ目的の部品なのに、なぜ分野によって使い分けられているのでしょうか。
今回は、データの形と「どの方向で平均をとるか」に注目して、この疑問を順番に解き明かします。G検定やE資格でもよく問われるテーマなので、試験対策としても役立ててください。
結論:平均をとる「方向」が、データの性質に合っているかどうか
先に結論をお伝えします。
- バッチ正規化は、ミニバッチ内の複数のデータをまたいで平均と分散を計算します
- レイヤー正規化は、1つのデータ(自然言語処理では1つのトークン)の中だけで平均と分散を計算します
CNNが扱う画像は、サイズがそろっていて、バッチサイズも大きく取りやすいため、「データをまたいだ統計」が安定します。
一方、自然言語処理が扱う文章は、長さがバラバラで、バッチサイズも小さくなりがちです。さらに推論時は1文ずつ処理することも多いため、「データをまたいだ統計」に頼るバッチ正規化は不安定になります。そこで、他のデータに依存しないレイヤー正規化が選ばれています。
ここから、この結論の中身を一つずつ確認していきます。
そもそも正規化とは何か
正規化の目的
ニューラルネットワークは層を重ねるほど、途中の層に流れる値の大きさや偏りが変わりやすくなります。ある層の出力が極端に大きくなったり、平均が大きくずれたりすると、次の層は毎回違う条件で学習しなければならず、学習が遅く不安定になります。
そこで、層の途中で値を「平均0、分散1」にそろえる処理を入れます。これが正規化層です。
身近な例でいえば、100点満点のテストと10点満点の小テストの点数を、そのまま足し合わせると比べにくくなります。偏差値のように基準をそろえると比較しやすくなる、という考え方に近いものです。
なお、バッチ正規化を提案した論文では「内部共変量シフト(学習中に各層への入力分布が変わってしまう現象)を抑える」ことが目的として説明されました。ただし、その後の研究では「損失関数の地形がなめらかになり、勾配が安定することが主な効果ではないか」という指摘もあり、効果の理由は現在も議論が続いています。
正規化の基本式
バッチ正規化もレイヤー正規化も、計算式の形は同じです。
各記号の意味は次のとおりです。
- x:正規化する前の値
- μ(ミュー):平均
- σ²(シグマの2乗):分散
- ε(イプシロン):0で割ることを防ぐための非常に小さな値(例:0.00001)
- γ(ガンマ):スケールを調整する学習パラメータ
- β(ベータ):位置をずらす学習パラメータ
最後に γ と β を掛けたり足したりするのは、「平均0、分散1が常に最適とは限らない」からです。モデル自身が学習によって、ちょうどよい大きさと位置に戻せるようにしています。
数値で確認してみよう
3つの値 2, 4, 6 を正規化してみます(ε は無視します)。
平均:
分散:
標準偏差は約1.633なので、正規化後の値は次のようになります。
平均が0、ばらつきが1にそろいました。
バッチ正規化とレイヤー正規化の違いは、この計算に使う「値の集め方」だけです。ここが今回の記事の中心になります。
バッチ正規化の仕組み
CNNのデータの形
CNNの中間層を流れるデータは、次の4つの軸を持つテンソル(多次元配列)です。
- N:バッチサイズ(一度に処理する画像の枚数)
- C:チャンネル数(フィルタの数。特徴の種類)
- H:高さ
- W:幅
形状は (N, C, H, W) と表されます。
どの方向で平均をとるか
バッチ正規化は、チャンネルごとに、N・H・W の3方向すべての値を集めて平均と分散を計算します。
チャンネル c の平均は次の式で表されます。
分散も同様です。
学校のテストに例えると、「数学」という1科目について、学年全員の点数を集めて平均点を出すイメージです。科目(チャンネル)ごとに、全員(バッチ)をまたいで基準をそろえます。
学習時と推論時で動きが変わる
バッチ正規化には、学習時と推論時で計算方法が変わるという特徴があります。
- 学習時:そのミニバッチの平均と分散を使う
- 推論時:学習中に記録しておいた平均と分散の移動平均(running mean、running var)を使う
推論時には画像が1枚だけ来ることもあるため、その場で「バッチの平均」を計算できません。そこで、学習中に集めた統計を代わりに使います。PyTorchでは、model.train() と model.eval() の切り替えによってこの動作が変わります。この点は、後ほど自然言語処理での問題点を考えるときに重要になります。
練習問題1
形状が (N, C, H, W) = (16, 64, 28, 28) のテンソルにバッチ正規化を適用します。1つの平均値を計算するために集められる値の個数として正しいものはどれでしょうか。
- 64個(チャンネルの数)
- 784個(28×28の画素数)
- 12,544個(16×28×28)
- 802,816個(全要素の数)
解答:3
解説:バッチ正規化はチャンネルごとに N・H・W の方向で平均をとるため、16×28×28 = 12,544個の値から1つの平均を計算します。
- 1は、チャンネルの数そのものであり、平均の計算に使う値の個数ではありません
- 2は、1枚の画像の1チャンネル分だけです。バッチ方向(16枚分)が含まれていません
- 4は、チャンネルもまとめてしまった場合の個数です。バッチ正規化はチャンネルを区別して計算します
なぜCNNではバッチ正規化が相性が良いのか
理由1:チャンネルは「同じ特徴」を表している
CNNでは、1つのチャンネルは1つのフィルタの出力です。たとえば「縦の線を検出するフィルタ」の出力なら、画像のどの位置でも、どの画像でも「縦の線らしさ」という同じ意味を持ちます。
意味が同じ値どうしを集めて平均をとるので、統計量として筋が通っています。
理由2:統計量に使える値が非常に多い
画像は縦と横に多くの画素を持つため、バッチサイズがそれほど大きくなくても、平均の計算に使える値が大量にあります。
たとえば、バッチサイズ32、特徴マップが56×56の場合、1チャンネルあたりの値の個数は次のとおりです。
32 × 56 × 56 = 100,352個
10万個を超える値から平均を出すため、ミニバッチが変わっても平均や分散が大きくぶれにくくなります。
理由3:画像のサイズがそろっている
画像認識では、入力画像を同じサイズにそろえてから学習するのが一般的です。全データが同じ形をしているため、「意味のない値」が統計に混ざりません。
理由4:学習が速く安定する
バッチ正規化を入れると、大きな学習率を使っても学習が発散しにくくなります。また、ミニバッチごとに統計が少しずつ揺れることが、ノイズを加えるのと似た正則化の効果を生むことも知られています。ResNetをはじめとする代表的なCNNで、畳み込み層の直後にバッチ正規化を置く構成が広く使われてきました。
練習問題2
CNNでバッチ正規化の統計量が安定しやすい理由として、最も適切なものはどれでしょうか。
- 1チャンネルあたり、画素数×バッチ数の大量の値から統計を計算できるから
- 画像データはもともと平均0、分散1になるように撮影されているから
- CNNは推論時にも、必ず大きなバッチで画像をまとめて処理するから
- 畳み込み層は学習パラメータを持たないため、出力が変化しないから
解答:1
解説:画像は H×W の画素を持つため、バッチ方向と合わせて非常に多くの値から統計を計算できます。値の数が多いほど、平均や分散は安定します。
- 2は誤りです。画像の画素値は通常0〜255などの範囲で、平均0、分散1にはなっていません
- 3は誤りです。推論時は1枚ずつ処理することも多く、そのためにバッチ正規化は移動平均を使います
- 4は誤りです。畳み込み層はフィルタの重みという学習パラメータを持ち、学習とともに出力も変化します
なぜ自然言語処理ではバッチ正規化が困るのか
自然言語処理のデータの形
自然言語処理の中間層を流れるデータは、次の3つの軸を持ちます。
- N:バッチサイズ(一度に処理する文の数)
- T:系列長(1文あたりのトークン数)
- D:埋め込み次元(1トークンを表すベクトルの長さ。例:768)
形状は (N, T, D) です。ここにバッチ正規化を適用すると、主に次の4つの問題が起きます。
問題1:文の長さがバラバラで、パディングが混ざる
文章は1文ごとに長さが違います。バッチとしてまとめて処理するには、短い文の後ろを「パディング」と呼ばれる穴埋め用の値(多くは0)で埋めて、長さをそろえます。
例として、次の2文を同じバッチで処理する場合を考えます。
- 文A:「私 は 猫 が 好き です」(6トークン)
- 文B:「こんにちは」(1トークン)+パディング5個
バッチ方向に平均をとると、文Bの5個のパディングまで統計に含まれてしまいます。パディングは意味を持たない値なので、平均と分散が実際の単語の分布からずれてしまいます。マスク処理で除外することもできますが、実装が複雑になります。
問題2:バッチサイズが小さくなりやすい
TransformerやBERT、GPTのような大規模モデルは、パラメータ数が多く、1文あたりの計算量も大きいため、GPUのメモリに載せられるバッチサイズが小さくなりがちです。複数のGPUに分散すると、1台あたりのバッチがさらに小さくなります。
バッチサイズが2や4程度だと、ミニバッチごとの平均と分散が大きく揺れ、正規化そのものが不安定になります。
問題3:位置ごとの統計が意味を持ちにくい
RNNでバッチ正規化を使おうとすると、時刻(何番目のトークンか)ごとに統計を持つ必要が出てきます。しかし、長い文はごく一部なので、50番目や100番目の位置の統計は少ないデータからしか計算できません。学習時に見たことのない長さの文が推論時に来ると、対応する統計そのものがありません。
問題4:推論時は1文ずつ、1トークンずつ処理する
チャットボットや翻訳では、利用者から届いた1文を処理し、さらにGPTのような生成モデルでは1トークンずつ順番に出力を作ります。この状況ではバッチ方向の統計を計算できないため、バッチ正規化は学習時の移動平均に頼るしかありません。
学習時と推論時で計算方法が違うと、両者の差が予測のずれにつながることがあります。画像に比べて文章はデータの分布がばらつきやすく、この差の影響を受けやすくなります。
練習問題3
自然言語処理でバッチ正規化を使いにくい理由として、誤っているものはどれでしょうか。
- 文の長さをそろえるパディングが、バッチ方向の統計に混ざってしまうから
- 大規模モデルではメモリの制約で、バッチサイズが小さくなりやすいから
- 推論時には1文ずつ処理することが多く、バッチの統計を計算できないから
- 単語の埋め込みベクトルは整数値なので、平均と分散を計算できないから
解答:4
解説:4は誤りです。単語の埋め込みベクトルは実数値の並びであり、平均と分散を問題なく計算できます。単語IDは整数ですが、埋め込み層を通った後は実数のベクトルになります。
- 1は正しい記述です。パディングは意味のない値なので、統計をゆがめます
- 2は正しい記述です。小さいバッチでは平均と分散が大きく揺れます
- 3は正しい記述です。推論時にバッチ統計が使えないため、学習時との差が生まれます
レイヤー正規化の仕組み
どの方向で平均をとるか
レイヤー正規化は、1つのトークンが持つ D 次元のベクトルの中だけで平均と分散を計算します。
テストの例えでいえば、「ある生徒1人の全科目の点数」を集めて、その生徒の中での平均点を基準にするイメージです。他の生徒(他のデータ)の点数は一切使いません。
数値で確認してみよう
あるトークンのベクトルが、D = 4 の [1, 2, 3, 6] だったとします。
平均:
分散:
標準偏差は約1.871なので、正規化後のベクトルは次のようになります。
[-1.069, -0.535, 0, 1.604]
この計算には、同じバッチにある他の文も、同じ文の他のトークンも登場しません。
自然言語処理と相性が良い理由
計算に使うのが自分自身のベクトルだけなので、バッチ正規化で起きた問題がまとめて解消されます。
- パディングの影響を受けない:各トークンを個別に正規化するため、他のトークンのパディングが統計に混ざりません
- バッチサイズに依存しない:バッチサイズが1でも同じ計算ができます
- 文の長さに依存しない:位置ごとの統計を持たないため、どんな長さの文でも扱えます
- 学習時と推論時で計算が同じ:移動平均を使わないため、model.train() と model.eval() で動作が変わりません
このため、Transformerでは各サブ層(自己注意機構やフィードフォワード層)の前後にレイヤー正規化が置かれています。初期のTransformerはサブ層の後ろに置く Post-LN でしたが、層を深くしても学習が安定しやすい Pre-LN(サブ層の前に置く方式)が、GPT-2以降の多くのモデルで採用されています。
発展:RMSNorm
近年の大規模言語モデルでは、レイヤー正規化を簡略化した RMSNorm を使うものも増えています。RMSNormは平均を引く処理を省き、二乗平均平方根(RMS)だけで割ります。
平均の計算を省ける分だけ計算が軽くなり、性能はレイヤー正規化と同程度とされています。考え方は「1つのトークンの中だけで値の大きさをそろえる」という点でレイヤー正規化と共通です。
練習問題4
形状が (N, T, D) = (8, 20, 512) のテンソルにレイヤー正規化を適用します。正しい説明はどれでしょうか。
- 20個の位置ごとに、8文分の値を集めて平均を計算する
- 8×20 = 160個のトークンそれぞれで、512個の値から平均を計算する
- 512個の次元ごとに、8×20個の値を集めて平均を計算する
- テンソル全体の81,920個の値から、平均を1つだけ計算する
解答:2
解説:レイヤー正規化は、1トークンが持つ D = 512 個の値の中で平均と分散を計算します。トークンは 8×20 = 160 個あるため、平均と分散も160組計算されます。
- 1は、位置ごとにバッチ方向の統計をとる方法で、RNNにバッチ正規化を適用した場合の考え方です
- 3は、次元ごとにバッチと系列をまたいで統計をとる方法で、バッチ正規化に近い計算です
- 4は、全体を1つの統計でまとめる方法で、レイヤー正規化ではありません
バッチ正規化とレイヤー正規化の比較
ここまでの内容を表にまとめます。
| 比較項目 | バッチ正規化 | レイヤー正規化 |
|---|---|---|
| 平均をとる方向 | バッチをまたぐ(CNNでは N, H, W) | 1データの中だけ(NLPでは D) |
| 統計の単位 | チャンネルごと | トークンごと |
| バッチサイズへの依存 | 大きい(小さいと不安定) | なし(1でも動く) |
| 可変長データ | 苦手(パディングが混ざる) | 得意 |
| 学習時と推論時 | 計算が変わる(移動平均を使う) | 計算が同じ |
| 正則化の効果 | ある(バッチごとの揺れ) | ほとんどない |
| 主な利用例 | ResNetなどのCNN | Transformer、BERT、GPT |
PyTorchで動きを確認する
実際にコードで、平均をとる方向の違いを確認してみます。PyTorchがインストールされた環境で実行できます。
import torch
import torch.nn as nn
torch.manual_seed(0)
# ---- CNN:バッチ正規化 ----
# 形状 (N, C, H, W) = (8枚, 3チャンネル, 32, 32)
images = torch.randn(8, 3, 32, 32) * 5 + 10
batch_norm = nn.BatchNorm2d(num_features=3) # チャンネル数を指定
batch_norm.train() # 学習モード:バッチの統計を使う
bn_output = batch_norm(images)
print("BatchNorm 入力の形状:", images.shape)
print("BatchNorm 出力の形状:", bn_output.shape)
# N, H, W 方向(dim=0, 2, 3)でまとめると、チャンネルごとに平均0・標準偏差1になる
print("チャンネルごとの平均:", bn_output.mean(dim=(0, 2, 3)))
print("チャンネルごとの標準偏差:", bn_output.std(dim=(0, 2, 3), unbiased=False))
# ---- 自然言語処理:レイヤー正規化 ----
# 形状 (N, T, D) = (2文, 5トークン, 16次元)
tokens = torch.randn(2, 5, 16) * 3 + 2
layer_norm = nn.LayerNorm(normalized_shape=16) # 最後の次元 D を指定
ln_output = layer_norm(tokens)
print("LayerNorm 入力の形状:", tokens.shape)
print("LayerNorm 出力の形状:", ln_output.shape)
# D 方向(dim=-1)でまとめると、トークンごとに平均0・標準偏差1になる
print("トークンごとの平均:\n", ln_output.mean(dim=-1))
print("トークンごとの標準偏差:\n", ln_output.std(dim=-1, unbiased=False))
実行すると、バッチ正規化では3チャンネル分の平均がほぼ0、標準偏差がほぼ1になります。レイヤー正規化では、2×5 = 10個のトークンそれぞれで平均がほぼ0、標準偏差がほぼ1になります。平均は 0.0000 に近い非常に小さな値(例:1e-08程度)で表示されますが、計算誤差によるものです。
コードを読むときのポイントは次のとおりです。
- 入力と出力の形状は、どちらの正規化でも変わりません。値の大きさだけが調整されます
- nn.BatchNorm2d にはチャンネル数を、nn.LayerNorm には正規化する最後の次元の大きさを渡します
- 学習パラメータの γ と β は、バッチ正規化ではチャンネル数分(3個ずつ)、レイヤー正規化では D 個分(16個ずつ)用意されます
初心者がつまずきやすい点
- nn.BatchNorm1d に (N, T, D) の形をそのまま渡すと、2番目の軸 T をチャンネルとみなしてしまいます。nn.BatchNorm1d は (N, C, L) の形を前提にしているため、自然言語処理のテンソルに使う場合は軸の入れ替えが必要です
- バッチ正規化を含むモデルで model.eval() を呼び忘れると、推論時にもバッチの統計が使われ、入力の組み合わせによって予測結果が変わってしまいます
- レイヤー正規化は model.train() と model.eval() で計算が変わりませんが、Dropoutなど他の層のために切り替えは必要です
練習問題5
PyTorchで、学習済みのCNN(バッチ正規化を含む)を使って画像を1枚ずつ推論したところ、同じ画像でも一緒に入力する画像によって結果が変わりました。最も考えられる原因はどれでしょうか。
- nn.LayerNorm の normalized_shape を、チャンネル数にしていたから
- 推論のときに、画像の縦と横のサイズを入れ替えて入力していたから
- model.eval() を呼ばず、学習モードのまま推論していたから
- 畳み込み層のフィルタの数を、学習時より少なく変更していたから
解答:3
解説:学習モードのままだと、バッチ正規化は推論時にもその場のバッチの平均と分散を使います。そのため、一緒に入力した画像によって統計が変わり、同じ画像でも結果が変わります。model.eval() を呼ぶと、学習中に記録した移動平均が使われ、結果が安定します。
- 1は、このモデルがレイヤー正規化を含んでいないため当てはまりません
- 2は、正方形の画像であれば結果は変わらず、一緒に入力する画像による違いも説明できません
- 4は、学習済みの重みと形が合わなくなるため、読み込みの時点でエラーになります
注意点:「CNNならバッチ正規化」と決まっているわけではない
ここまで「CNNはバッチ正規化、自然言語処理はレイヤー正規化」と説明してきましたが、この使い分けは絶対的な決まりではありません。データの性質や計算環境に合わせて選ぶものです。
- 物体検出やセグメンテーションでは、高解像度の画像を扱うため、バッチサイズが1〜2程度になることがあります。この場合は、チャンネルをいくつかのグループに分けて1データの中で正規化するグループ正規化(Group Normalization)がよく使われます
- 画像をパッチに分けてTransformerで処理するVision Transformer(ViT)では、画像を扱うモデルでもレイヤー正規化が使われます
- ConvNeXtのように、CNNでありながらTransformerの設計を取り入れ、レイヤー正規化を採用したモデルもあります
判断の基準は、「その正規化の計算方向が、データの形とバッチサイズの条件に合っているか」です。
まとめ
今回の内容を振り返ります。
- 正規化層は、層の途中の値を平均0、分散1にそろえ、学習を速く安定させる部品です
- バッチ正規化は、バッチをまたいでチャンネルごとに統計を計算します
- CNNは画像サイズがそろい、1チャンネルあたりの値も多いため、バッチ正規化の統計が安定します
- 自然言語処理は、文の長さがバラバラで、バッチサイズが小さく、推論は1文ずつになりやすいため、バッチ正規化が不安定になります
- レイヤー正規化は1トークンの中だけで統計を計算するため、バッチサイズや文の長さに影響されず、学習時と推論時の計算も同じです
理解度を確認するために、次の問いに自分の言葉で答えてみてください。
- バッチ正規化とレイヤー正規化は、どちらの方向で平均をとりますか
- バッチサイズが1のとき、バッチ正規化の学習ではどのような問題が起きますか
- 画像を扱うモデルでもレイヤー正規化が使われるのは、どのような場合ですか
次の学習ステップとしては、グループ正規化とインスタンス正規化を加えた「4種類の正規化の計算方向の比較」に進むことをおすすめします。そのうえで、Transformerの構造(自己注意機構、残差接続、Pre-LNとPost-LNの違い)を学ぶと、レイヤー正規化がモデルのどこで、なぜ使われているのかがより具体的に理解できます。
投稿者プロフィール


