Python環境を起動中... 初回のみ数秒かかります
Lesson 2 / 12

NumPyで順伝播を組む

このレッスンで学ぶこと

  • 全結合層の計算 z = X·W + b を NumPy の行列積で書ける
  • 活性化関数(ReLU・シグモイド)を実装し、形を変えないことを確認できる
  • 多層パーセプトロン(2層)の順伝播を最後までたどれる
  • ソフトマックスで出力を確率に変換できる

1. 順伝播(forward)とは

順伝播は、入力データをネットワークの層に順番に通して出力(予測)を得る1回の流れです。 理論では「線形変換 → 活性化 → 線形変換 → …」と習ったはず。これをNumPyのコードに落とすのがこのレッスンです。

PyTorch では model(x) の一行で順伝播が走りますが、中で何が起きているかを一度自分の手で書いておくと、 PyTorch のコードを読むときに「ここで行列積、ここで活性化」と迷わず追えるようになります。この感覚が、後半でPyTorchのコードを読み解く土台になります。

理論の確認: 全結合層(線形層)は z = xW + bW は重み行列、b はバイアス。 活性化関数は非線形性を加える役割でした。ここではその計算そのものを NumPy で確かめます。

2. 全結合層を「行列積」で書く

ミニバッチ(複数サンプルをまとめた入力)X の形を (N, D_in) とします(N=サンプル数、D_in=入力特徴数)。 重み W(D_in, D_out)、バイアス b(D_out,) とすると、出力は z = X @ W + b で形は (N, D_out) になります。 @ は行列積(NumPyの matmul)です。

sample_1.py
Ctrl+Enter
出力

💡 shape の読み方: 行列積 (4, 3) @ (3, 2) は、内側の3が一致していれば計算でき、結果は外側を取って (4, 2)。 バイアス b は形 (2,) ですが、ブロードキャストで各サンプル(各行)に同じ値が足されます。

⚠ よくある shape ミス: W(2, 3) の向きで作ると X @ W内側が一致せずエラーになります((4,3)@(2,3) は不可)。 「入力の特徴数W最初の次元」と覚えましょう。

3. 活性化関数(ReLU・シグモイド)

線形変換だけを重ねても表現力は上がりません。間に非線形な活性化関数を挟みます。 代表的な ReLU とシグモイドを NumPy で実装します。どちらも要素ごとの計算なので、形(shape)は変わりません

sample_2.py
Ctrl+Enter
出力
理論の確認: ReLU は max(0, x)、シグモイドは 1/(1+e^-x)。 PyTorch では torch.relu()nn.ReLU()torch.sigmoid() が同じ計算をしてくれます(L05で登場)。 中身は今書いたとおりです。

4. 多層パーセプトロンの順伝播 + ソフトマックス

いよいよ2層のネットワークを順伝播させます。入力 → 全結合(1層目) → ReLU → 全結合(2層目) → ソフトマックスの流れです。 最後のソフトマックスは、出力(ロジット)を合計1の確率に変換します(多クラス分類の定番)。

sample_3.py
Ctrl+Enter
出力

💡 ソフトマックスの axis=1 入力が (N, クラス数) のとき、クラス方向(axis=1)で合計1にします。 axis を間違えて縦方向(サンプル方向)で正規化してしまうと、確率の意味が変わってしまうので注意しましょう。

💡 keepdims=True をかみ砕くと: これは「合計や最大を取ってつぶれた軸を“長さ1”として残す」オプションです。 そのあとの割り算(ブロードキャスト)を成立させるために必要になります。形(shape)で具体的に見てみましょう。

e.shape                       → (4, 2)   # 各行に2クラスの値
e.sum(axis=1)                 → (4,)    # 列がつぶれて 1次元 になる
e.sum(axis=1, keepdims=True)  → (4, 1)  # 列を「長さ1」で残す

この差が、割り算でそのまま効いてきます。

e / e.sum(axis=1, keepdims=True)   # (4,2) ÷ (4,1) → OK
    # → (4,1) の1列が「2クラスぶんに自動コピー」され、
    #    各行がその行の合計で割られて、行ごとに合計1になる

e / e.sum(axis=1)                  # (4,2) ÷ (4,) → ValueError!
    # → 右端の軸(クラス数2)と長さ4が合わず、割り算できない

イメージは「各行を、その行の合計で割って確率にする」。 そのためには割る相手を (4, 1) という「縦に並んだ1列」の形にしておく必要があり、それを作るのが keepdims=True です。 実際に上のセルから keepdims=True を消すと、エラーになるのを確かめられます。

⚠ オーバーフロー対策: softmaxx - x.max(...) と最大値を引くのは、exp が巨大な値になってあふれる(inf になる)のを防ぐためです。 数学的な結果は変わりません。PyTorch の softmax も内部で同じ工夫をしています。

5. 練習問題

コードの空欄を埋めて「▶ 実行」しましょう。「↺ リセット」で元に戻せます。

問題 1

全結合層の出力 shape を合わせる

入力 X(8, 6) のとき、出力が (8, 4) になるように重み W とバイアス b の形を決め、z = X @ W + b を計算してください。

exercise_1.py
出力
ヒントを見る(答え+解説)
import numpy as np
np.random.seed(1)

X = np.random.randn(8, 6)   # (N=8, D_in=6)

# W と b の形を決めて、z = X @ W + b を計算してください(zの形は (8, 4) になるはず)
W = np.random.randn(6, 4)   # (D_in=6, D_out=4)
b = np.zeros(4)             # (D_out=4,)
z = X @ W + b

print("z.shape:", z.shape)  # (8, 4) になればOK

入力の特徴数 6W の最初の次元、欲しい出力数 4 が2番目の次元。 バイアスは出力数ぶんの 4。これで (8,6)@(6,4)=(8,4) になります。

問題 2

ReLU を通す

配列 z に ReLU を適用して、負の値が0になることを確かめてください。

exercise_2.py
出力
ヒントを見る(答え+解説)
import numpy as np

z = np.array([[-2.0, 0.5], [1.5, -0.3]])

# ReLU を適用してください(負を0に、正はそのまま)
a = np.maximum(0, z)

print("a =\n", a)
# 期待: [[0.  0.5]
#        [1.5 0. ]]

np.maximum(0, z)z の各要素と0を比べて大きい方を取るので、負の値だけが0になります。形は変わりません。

問題 3

ソフトマックスで確率にする

ロジット logits(形 (2, 3))を、各行で合計1の確率に変換してください(axis=1 に注意)。

exercise_3.py
出力
ヒントを見る(答え+解説)
import numpy as np

logits = np.array([[1.0, 2.0, 3.0],
                   [1.0, 1.0, 1.0]])

# 各行で合計1になるソフトマックスを計算してください
e = np.exp(logits - logits.max(axis=1, keepdims=True))
prob = e / e.sum(axis=1, keepdims=True)

print("prob =\n", np.round(prob, 3))
print("各行の合計:", np.round(prob.sum(axis=1), 3))  # [1. 1.]

2行目は全部同じ値なので、確率は [0.333, 0.333, 0.333] と均等になります。 keepdims=True を外すと形が崩れてブロードキャストに失敗するので注意。

6. まとめ

このレッスンのポイント

  • 全結合層は z = X @ W + b。形は (N, D_in) → (N, D_out)
  • 行列積は内側の次元が一致すれば計算でき、結果は外側の形になる
  • バイアスはブロードキャストで各サンプルに加算される
  • 活性化(ReLU・シグモイド)は要素ごとの計算で形を変えない
  • ソフトマックスは axis=1各行を合計1の確率にする(最大値を引いてオーバーフロー対策)
  • 順伝播は 線形 → 活性化 → 線形 → ソフトマックス の流れ。PyTorch の model(x) はこれを自動でやっている

最後に、2層の順伝播を関数にまとめて自由に試してみましょう。

free_practice.py
出力

完了するとコース一覧に進捗が記録されます