目的
ニューラルネットワークをスクラッチで実装したことがなかったので、こちらの記事を参考に実装しました。この記事は、その備忘録かつ内部的な処理フローの理解を目的としています。具体的なコードや前提などを詳しく知りたい方は、元の記事を御覧ください。
今回は、MNISTという手書き数字の画像が入ったデータセットを用いて、
画像に書かれている数字が0~9のどれなのか分類するニューラルネットワークを実装しています。ここでは コードを追うのではなく、全体の処理の流れ にフォーカスして解説します。
全体の処理の流れ
- データ準備
- モデル構築
- 学習(順伝播 → 逆伝播 → 重み更新)
- 正解率の評価
1. データ準備
まずは MNIST データセットをダウンロードし、入力画像を 28×28 → 784 次元ベクトル、ラベルを one-hot ベクトルに変換します。
from tensorflow.keras.datasets import mnist
import numpy as np
def get_mnist_data():
(x_train, y_train), (x_test, y_test) = mnist.load_data()
x_all = np.concatenate([x_train, x_test], axis=0)
x_all = x_all.reshape(-1, 784).astype(np.float32) / 255.0
y_all = np.concatenate([y_train, y_test], axis=0)
y_onehot = np.zeros((y_all.shape[0], 10), dtype=np.float32)
for i, label in enumerate(y_all):
y_onehot[i, label] = 1.0
return x_all.tolist(), y_onehot.tolist()
2. モデル構築
NN
クラスでネットワーク全体を定義し、add_layer()
で入力層 → 隠れ層 → 出力層を積み重ねます。各層(Layer
)は複数の Perceptron
ノードを内包します。
model = NN()
model.add_layer(Layer(784, 20)) # 入力層 → 隠れ層
model.add_layer(Layer(20, 20)) # 隠れ層
model.add_layer(Layer(20, 10)) # 出力層 (10クラス)
3. 学習
学習は、1エポックあたり次の 3 ステップを繰り返します。
(a) 順伝播(Forward Propagation)
各層を順番に通して予測を計算します。
# NN.forward の実装
def forward(self, in_data, train_flg):
out = in_data
for layer in self.layers:
out = layer(out, train_flg)
return out
各 Perceptron
ノードでは:
z = sum(w * x for w, x in zip(self.w_list, in_data)) + self.b
y = sigmoid(z)
# (train_flg=True のときは in_data, z, y を保存)
(b) 誤差逆伝播(Backpropagation)
出力層と中間層で δ の計算式が異なります。再帰的に次の層の δ を利用して計算します。
出力層:
# Optimizer.d_calc の一部
if l_idx == len(self.layers) - 1:
# δ = (出力 - 正解) * sigmoid'(z)
delta = (node.after_act - t_data_list[j]) * sigmoid_d(node.before_act)
中間層:
# Optimizer.d_calc の一部
else:
# δ = sigmoid'(z) * sum(δ_next * w)
delta = sigmoid_d(node.before_act) * sum(
self.d_calc(l_idx+1, k, t_data_list) * next_node.w_list[j]
for k, next_node in enumerate(self.layers[l_idx+1].node_list)
)
(c) 重み更新(Gradient Descent)
計算した δ を用いて、各重みとバイアスを勾配降下法で更新します。
# Optimizer.back_prop の一部
for w_idx, w in enumerate(node.w_list):
node.w_list[w_idx] = w - self.lr * delta * node.in_data[w_idx]
# バイアス更新
node.b -= self.lr * delta
4. 正解率の評価
テストデータを入力し、出力確率分布の最大値インデックスと正解ラベルのインデックスを比較して正解率を算出します。
def calc_acc(self, test_x, test_y):
ok = 0
for x, y in zip(test_x, test_y):
out = self.forward(x, False)
pred = out.index(max(out))
true = y.index(1)
if pred == true:
ok += 1
return ok / len(test_x)
まとめ
- データ準備 → モデル構築 → 学習(Forward → Backward → Update) → 評価 のループで動く
- ここではコードでなく、各ステップの処理フローを重視して解説しました
- 詳細な実装や数式の導出は 参考記事 をご参照ください