ゼロから作るDeepLearning #7 -一通りやってみる-

※以下の企画です

https://qiita.com/tatataichi/private/56936a028e625e62af8e

前回に続いてゼロつくやっていきます。
今回も4章 -ニューラルネットワークの学習-の続きです。終わる気がしないです。でも楽しいです。
今回はここまで学んだ内容をまとめて、二層のNNを作成するっぽいです。
※淡々と完成品をながめる内容になっています、ごめんなさい。

それでは頑張っていきます〜
# 4章 ニューラルネットワークの学習
### 二層ニューラルネットワーク
今までの総集編的に、二層ニューラルネットワークをつくってみる。

1. **入力層**: 入力データを受け取る
2. **隠れ層**: 重みとバイアスを用いて非線形変換を行う
3. **出力層**: 最終的な予測結果を出力する

このネットワークでは、活性化関数としてシグモイド関数を使用し、出力層ではソフトマックス関数を利用して確率分布を計算する。覚えてるかな。

また、学習プロセスでは以下の概念を用いる。

- **ミニバッチ**: 学習データを小さなバッチ（部分集合）に分割し、それぞれで学習を行う
- **エポック**: 学習データ全体を1回使い切ることを1エポックと呼ぶ

### Pythonコードでの実装

以下に、二層ニューラルネットワークのPython実装を示す。

```python:二層NN
class TwoLayerNet:
    def __init__(self, input_size, hidden_size, output_size, weight_init_std=0.01):
        # パラメータの初期化
        self.params = {}
        self.params['W1'] = weight_init_std * np.random.randn(input_size, hidden_size)
        self.params['b1'] = np.zeros(hidden_size)
        self.params['W2'] = weight_init_std * np.random.randn(hidden_size, output_size)
        self.params['b2'] = np.zeros(output_size)

    def predict(self, x):
        W1, b1 = self.params['W1'], self.params['b1']
        W2, b2 = self.params['W2'], self.params['b2']

        # 順伝播計算
        a1 = np.dot(x, W1) + b1
        z1 = sigmoid(a1)
        a2 = np.dot(z1, W2) + b2
        y = softmax(a2)
        return y

    def loss(self, x, t):
        y = self.predict(x)
        return cross_entropy_error(y, t)

    def accuracy(self, x, t):
        y = self.predict(x)
        y = np.argmax(y, axis=1)
        if t.ndim != 1: t = np.argmax(t, axis=1)
        return np.sum(y == t) / float(x.shape[0])

    def numerical_gradient(self, x, t):
        loss_W = lambda W: self.loss(x, t)

        grads = {}
        grads['W1'] = numerical_gradient(loss_W, self.params['W1'])
        grads['b1'] = numerical_gradient(loss_W, self.params['b1'])
        grads['W2'] = numerical_gradient(loss_W, self.params['W2'])
        grads['b2'] = numerical_gradient(loss_W, self.params['b2'])

        return grads

# 学習ループ

x_train = np.random.rand(100, 2)  # ダミーデータ
t_train = np.random.randint(0, 2, (100, 2))  # ダミーラベル

iters_num = 10000  # 繰り返し回数
train_size = x_train.shape[0]
batch_size = 10  # ミニバッチのサイズ
learning_rate = 0.1

network = TwoLayerNet(input_size=2, hidden_size=3, output_size=2)

for i in range(iters_num):
    # ミニバッチ取得
    batch_mask = np.random.choice(train_size, batch_size)
    x_batch = x_train[batch_mask]
    t_batch = t_train[batch_mask]

    # 勾配計算
    grad = network.numerical_gradient(x_batch, t_batch)

    # パラメータ更新
    for key in ('W1', 'b1', 'W2', 'b2'):
        network.params[key] -= learning_rate * grad[key]

    # 損失計算（経過表示用）
    if i % 1000 == 0:
        loss = network.loss(x_batch, t_batch)
        print(f"Iteration {i}: Loss = {loss}")
```
```terminal:実行結果
Iteration 0: Loss = 0.4159152144822502
Iteration 1000: Loss = 1.0246051813826795
Iteration 2000: Loss = 0.7584882918919034
Iteration 3000: Loss = 0.3438290883535614
Iteration 4000: Loss = 0.9000166826573157
Iteration 5000: Loss = 0.8239559733051298
Iteration 6000: Loss = 0.7598275395762528
Iteration 7000: Loss = 0.9725876956517123
Iteration 8000: Loss = 0.7598053819300377
Iteration 9000: Loss = 0.6955747666012347
```

### アウトプットのポイント

1. **ミニバッチ学習**:
    - 全データを一度に使用せず、データを小さなバッチに分割して学習
    - メモリ効率が向上し、収束が早くなるらしい

2. **エポックとイテレーション**:
    - 1エポックは全データを使い切る学習サイクル
    - 1エポック内でデータを分割してミニバッチごとに学習を進める

3. **勾配降下法の適用**:
    - 数値微分で得られた勾配を基に、パラメータを更新

実行結果を見てみると、ちゃんとミニバッチ学習の度に損失が小さくなっていることがわかる。
今までとの大きな違いは、大量のデータを分割して繰り返しているという点。
これが今後やっていくNNでの学習のアルゴリズムになる。

## まとめ
ミニバッチとエポックの概念を取り入れた二層ニューラルネットワークが、実用的な深層学習モデル構築の基礎となるらしい。
この仕組みを理解することで、より大規模なデータセットや複雑なネットワーク構造にも応用可能とのことなので、しっかり覚えておく。
それでは次回も頑張ります〜