行列積によるニューラルネットワークの順伝播
\(y = Wx + b\) を手書きし、異なる形状の行列を入力して、次元がどのように変化・対応するかを観察します。
このケースを学ぶと、以下が理解できるようになります:ニューラルネットワークのトレーニング中に shape mismatch エラーが発生するのは、本質的には行列積の次元が一致していないことが原因です。
身近な例から
宅配便の仕分けセンター
コンベアから運ばれてきた荷物(入力)が、仕分け機によって異なるトラック(隠れ層)に振り分けられ、最終的に各エリア(出力)へ配送されます。
各仕分け機は1つの「ニューロン」です。すべての荷物を受け取り、ルールに従ってどの荷物が自分の経路を通るかを決定します。仕分け機のルールこそが「重み」です。ある方向の荷物は多く分け、ある方向の荷物は少なく分けます。
仕分けセンター全体の仕組みは、次のとおりです:行列積 + 活性化関数 + バイアス。
直感的な理解
1層のニューラルネットワーク = 3つのステップを、1行のコードで実行:
\( z = X @ W + b \)
各部分には明確な役割があります:
(batch, in)
(in, out)
(out,)
(batch, out)
入力特徴数(in_features)は重み行列の行数と等しくなければなりません。出力特徴数(out_features)は重み行列の列数と等しくなります。batch_size はプロセス全体を通して変わりません。
数学的定義
単層の順伝播
\[ z_j = \sum_{i=1}^{n} x_i \cdot W_{ij} + b_j \]各出力 \(z_j\) は、入力ベクトルと重み行列の第 \(j\) 列とのドット積に、バイアスを加えたものです。
2層ネットワークの直列接続
\[ \begin{aligned} h &= \text{ReLU}(X W_1 + b_1) \quad & (m \times 4) \cdot (4 \times 6) &= (m \times 6) \\ o &= \text{Sigmoid}(h W_2 + b_2) \quad & (m \times 6) \cdot (6 \times 2) &= (m \times 2) \end{aligned} \]注意:第1層の出力特徴数(6)は、第2層の重み行列の行数(6)と等しくなければなりません。これが「層と層の間の次元のつなぎ目」です。
Pythonで実際にやってみる
汎用の dense_layer 関数を使って、単一サンプル・バッチ処理・2層ネットワークの3つのシナリオをデモンストレーションします。
実例
def dense_layer(X, W, b, activation=None):
"""
1層全結合ネットワークの順伝播
X: 入力行列 (batch_size, in_features)
W: 重み行列 (in_features, out_features)
b: バイアスベクトル (out_features,)
"""
z = X @ W + b # 行列積 + ブロードキャスト加算
if activation == "relu":
return np.maximum(0, z) # ReLU: 負の値を0にする
if activation == "sigmoid":
return 1 / (1 + np.exp(-z)) # Sigmoid: (0,1)に圧縮
return z
print("=" * 55)
print("サンプル シナリオ 1:単一サンプル、4次元 -> 3次元")
print("=" * 55)
X = np.array([[1.0, 2.0, 0.5, -1.0]]) # (1, 4)
W = np.random.randn(4, 3) * 0.5 # (4, 3)
b = np.zeros(3) # (3,)
y = dense_layer(X, W, b, activation="relu")
print("X.shape:", X.shape, " W.shape:", W.shape,
" -> y.shape:", y.shape)
print("\n" + "=" * 55)
print("サンプル シナリオ 2:8サンプルのバッチ、同じ重み")
print("=" * 55)
X_batch = np.random.randn(8, 4) # (8, 4)
y_batch = dense_layer(X_batch, W, b, activation="relu")
print("X_batch.shape:", X_batch.shape,
" -> y_batch.shape:", y_batch.shape)
print("バッチサイズ8のまま、特徴次元は 4 -> 3")
print("\n" + "=" * 55)
print("サンプル シナリオ 3:2層ネットワークを重ねる")
print("=" * 55)
# 第1層:4次元 -> 6次元 + ReLU
W1 = np.random.randn(4, 6) * 0.5
b1 = np.zeros(6)
# 第2層:6次元 -> 2次元 + Sigmoid
W2 = np.random.randn(6, 2) * 0.5
b2 = np.zeros(2)
h = dense_layer(X_batch, W1, b1, activation="relu")
out = dense_layer(h, W2, b2, activation="sigmoid")
print(f"入力層: {X_batch.shape}")
print(f"隠れ層: {h.shape} (4->6, W1=(4,6))")
print(f"出力層: {out.shape} (6->2, W2=(6,2))")
# 検証:次元が一致しないとどうなるか?
print("\n" + "=" * 55)
print("サンプル シナリオ 4:次元が一致しないとエラーになる")
print("=" * 55)
try:
W_wrong = np.random.randn(5, 3) * 0.5 # 行数が合わない
dense_layer(X_batch, W_wrong, b)
except ValueError as e:
print("エラーの原因:X_batch には4列ありますが、"
"W_wrong には5行ある -> 乗算できません")
print(f" 正しい方法:W の行数 ({W_wrong.shape
f" は X の列数 ({X_batch.shape)
======================================================= サンプル 场景 1:单样本,4 维 -> 3 维 ======================================================= X.shape: (1, 4) W.shape: (4, 3) -> y.shape: (1, 3) ======================================================= サンプル 场景 2:8 个样本批量,同权重 ======================================================= X_batch.shape: (8, 4) -> y_batch.shape: (8, 3) 批大小 8 不变,特征维度 4 -> 3 ======================================================= サンプル 场景 3:堆叠两层网络 ======================================================= 输入层: (8, 4) 隐藏层: (8, 6) (4->6, W1=(4,6)) 输出层: (8, 2) (6->2, W2=(6,2)) ======================================================= サンプル 场景 4:维度不匹配会报错 ======================================================= 报错原因:X_batch 有 4 列,W_wrong 有 5 行 -> 无法相乘 正确做法:W 的行数 (5) 应等于 X 的列数 (4)
AIでの応用シーン
| AI シナリオ | 次元変化の例 | 説明 |
|---|---|---|
| 全結合層 | (32, 784) @ (784, 128) = (32, 128) | MNIST分類:28x28=784 ピクセル → 128次元特徴 |
| Transformer の QKV | (B, L, D) @ (D, D) = (B, L, D) | 自己注意:入力と出力のトークン次元は変わらない |
| Embedding ルックアップテーブル | (B, L) → (B, L, D) | 単語IDシーケンス → 単語ベクトルシーケンス |
| 分類ヘッド | (B, 768) @ (768, 10) = (B, 10) | BERT の最終層の分類出力 |