前回は、シグモイド関数とRelu関数について、その特性やその微分について紹介しました。
【仕組み解説】全結合層をゼロから実装しよう:活性化関数-Reluとシグモイド関数-(NN #7)
頻繁に利用されるReLUとシグモイド関数の具体的な数式と役割を比較解説。逆伝播に向けた実装準備を行います。
今回は、今までの内容をついに実装してみようと思います。
ゼロから作る全結合層
ゼロから作る全結合層シリーズでは、初心者でも理解しやすいように、 「パーセプトロンの仕組み」から「全結合層の実装」までを...
この記事シリーズでは、ディープラーニングに入るまでの道筋をその根本的な設計思想からディープラーニングの肝であり、基礎の最小単位である全結合層について、ゼロから作って理解していきます。
順方向伝播(プロパゲーション)
今まで扱ってきたような、入力から出力までの計算の流れを、順方向への伝達(伝播)なので、順方向伝播あるいは、単に順伝播と言います。
順方向伝播に必要な部品
今回はわかりやすく、2つのクラスを作ります。
Affineクラス: \(Y = X \cdot W + B\) を計算する層。Reluクラス: \(y = \max(0, x)\) を計算する層。
(プログラミングについては、書いていればそのうちかけるようになるので、”習うより慣れよ”ということで、できるだけコメントを残していますが詳しくは説明しません。わからなければ、GPT,Geminiなどを駆使して理解してください。(プログラミングの練習なら自分でゲームを作ってみるといいかもしれません))
アフィンレイヤー
順伝播をするだけなら、xなどを覚えておく必要はないですが、後々逆伝播に使うので、値を保持できるようにしています。
アフィンレイヤークラス(順方向だけ)
import torch
class Affine:
def __init__(self, W, b):
# 初期化:重みとバイアスを受け取る
self.W = W
self.b = b
# 逆伝播(Backward)の時に使う変数の入れ物
self.x = None
self.dW = None # 重みの傾き
self.db = None # バイアスの傾き
def forward(self, x):
# 順伝播: Y = XW + b
# あとで使うので x を覚えておく
self.x = x
# torch.matmul は行列積です
out = torch.matmul(x, self.W) + self.b
return out特にみてほしいところは forward関数です。中身を見れば、前々回に紹介した y=の式の計算ですね。
\[Y = X \cdot W + B \]
\[\begin{pmatrix} y_{11} & y_{12} & y_{13} \\ y_{21} & y_{22} & y_{23} \\ y_{31} & y_{32} & y_{33} \end{pmatrix} = \begin{pmatrix} x_{11} & x_{12} & x_{13} & x_{14} \\ x_{21} & x_{22} & x_{23} & x_{24} \\ x_{31} & x_{32} & x_{33} & x_{34} \end{pmatrix} \cdot \begin{pmatrix} w_{11} & w_{12} & w_{13} \\ w_{21} & w_{22} & w_{23} \\ w_{31} & w_{32} & w_{33} \\ w_{41} & w_{42} & w_{43} \end{pmatrix} + \begin{pmatrix} b_{1} & b_{2} & b_{3} \end{pmatrix}\]
foward関数
def forward(self, x):
# 順伝播: Y = XW + b
# あとで使うので x を覚えておく
self.x = x
# torch.matmul は行列積です
out = torch.matmul(x, self.W) + self.b
return outReluクラス
順伝播をするだけなら、活性化関数にはReluを使うことにします。これは簡単ですね、0未満なら 0を返し、0以上だったらその値をそのまま返せばいいので、処理としては、0以下のものを0に置き換えればいいです。
Relu関数
class Relu:
def __init__(self):
# 順伝播の入力を覚えておくためのマスク(True/Falseの配列)
self.mask = None
def forward(self, x):
# 順伝播: 0以下の場所を False にするマスクを作る
self.mask = (x <= 0)
out = x.clone() # 入力をコピー
out[self.mask] = 0 # マスクがTrue(つまり0以下)の場所を0にする
return out順方向だけ全結合層を実装してみる
アフィンクラスとReluクラスができたので、これで順方向だけ全結合層(layer1,layer2)を作って、試してみます。
準備として、ランダムに入力、重み、バイアスを与えて、レイヤー1(Affine)とレイヤー2(Relu)に与えて、順方向伝播(forward)してみます。
実験コード
# --- 実験スタート ---
# 1. データの準備 (適当な乱数)
torch.manual_seed(0) # 結果を毎回同じにするためシード値
# N=バッチサイズ(2件), D=入力次元(3), H=隠れ層のニューロン数(4)
X = torch.randn(2, 3)
W = torch.randn(3, 4)
b = torch.randn(4)
print(f"入力 X の形: {X.shape}")
print(f"重み W の形: {W.shape}")
print("-" * 20)
# 2. レイヤーの作成
layer1 = Affine(W, b)
layer2 = Relu()
# 3. 順伝播 (Forward)
print("【順伝播 (Forward) スタート】")
# Affineを通す
out_affine = layer1.forward(X)
print(f"Affine後の形: {out_affine.shape}")
print(f"Affine後の値:\n{out_affine}")
# ReLUを通す
out_relu = layer2.forward(out_affine)
print(f"ReLU後の形 : {out_relu.shape}")
print(f"出力結果:\n{out_relu}")
# ↑ マイナスの値が0になっているか確認してみてください
print("-" * 20)結果
上のテストコードを実行した結果です。
入力 X の形: torch.Size([2, 3])
重み W の形: torch.Size([3, 4])
--------------------
【順伝播 (Forward) スタート】
Affine後の形: torch.Size([2, 4])
Affine後の値:
tensor([[ 2.2385, -0.5385, 0.7473, -0.3009],
[ 1.4825, -1.4018, 1.6827, -0.4882]])
ReLU後の形 : torch.Size([2, 4])
出力結果:
tensor([[2.2385, 0.0000, 0.7473, 0.0000],
[1.4825, 0.0000, 1.6827, 0.0000]])結果を見ると、バッチサイズ2、入力特徴量3 の入力を受け取り、出力特徴量4の重みを掛け合わせたので、(2,4)のサイズの出力が得られおり、Relu関数でマイナスの値が全部0になっていることが確認できます。
まとめと次回:逆伝播とは?
今回の実装で、全結合層の順方向側は完成しました。NNの学習では、この順方向伝播の結果をもとに、重みを更新して学習を行います。この学習では、値を出力側から入力側に順次渡していくような動作をするので逆方向伝播、逆伝播と言います。
次回は、どのような仕組みで学習がなされるのか、誤差の捉え方から、どのようなイメージで学習していくのかを理解していこうと思います。
【仕組み解説】全結合層をゼロから実装しよう:NNの学習の仕組み-誤差と損失、逆伝播-(NN #9)
NNが賢くなる仕組みを解説。損失関数、誤差の計算、そして学習の核となる逆伝播(Backward)の理論を理解します。
ゼロから作る全結合層
ゼロから作る全結合層シリーズでは、初心者でも理解しやすいように、 「パーセプトロンの仕組み」から「全結合層の実装」までを...
ここまで読んでいただきありがとうございます。
では、次の記事で。 lumenHero