ゼロから作るDeepLearning #10 - 畳み込みニューラルネットワーク -

※以下の企画です

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

前回に続いてゼロつくを進めていきます。  
今回は第7章「畳み込みニューラルネットワーク」の内容に入ります。  
要点をまとめつつ、ChatGPTなどの力も借りながら理解していきます。  
結構本書から離れてしまうかもですが、自分の理解のためにまとめています。

それでは頑張ります〜

# 7章 畳み込みニューラルネットワーク
## 全結合層の問題点

全結合層は、入力データの全ての要素を考慮して計算を行うため、画像データのように位置関係や局所的な特徴を持つデータに対しては非効率である。これにより以下のような問題が発生する。

- 特徴量の空間的関係が無視される
- パラメータの数が膨大になる

## 畳み込み層の仕組み
畳み込みニューラルネットワーク（CNN）は、空間的な特徴を捉えるために **畳み込み層** を使用する。  
畳み込み層は、入力データに対して **フィルタ**（カーネルとも呼ばれるらしい）を適用し、特徴マップなるものを生成する。

### 畳み込み演算
畳み込み演算は、入力データ（画像）にフィルタをスライドさせながら適用する操作である。  
具体的には、フィルタの各要素と対応する入力データの要素を掛け算し、その和を計算する。
いつも通り図無しなのでちょっと分かりづらいが、コードは以下のようになる。

```python: 畳みこみ
import numpy as np

def convolution_2d(x, kernel, stride=1, pad=0):
    H, W = x.shape
    KH, KW = kernel.shape
    OH = (H + 2 * pad - KH) // stride + 1
    OW = (W + 2 * pad - KW) // stride + 1

    # パディング処理
    x_padded = np.pad(x, [(pad, pad), (pad, pad)], mode='constant')

    # 出力の初期化
    out = np.zeros((OH, OW))

    for i in range(0, OH):
        for j in range(0, OW):
            out[i, j] = np.sum(
                x_padded[i * stride:i * stride + KH, j * stride:j * stride + KW] * kernel
            )

    return out

# 入力データとフィルタの定義
x = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
kernel = np.array([[1, 0], [0, -1]])

# 畳み込み演算の結果
out = convolution_2d(x, kernel)
print(out)
```

```terminal:実行結果
[[-4. -4.]
 [-4. -4.]]
 ```

### ハイパーパラメータ
畳み込み層には以下のハイパーパラメータが存在する。
- **フィルタサイズ**: カーネルの大きさ（例: 3×3）
- **ストライド**: フィルタをスライドさせる幅
- **パディング**: 入力データの周囲にゼロを追加することで出力サイズを調整する

## プーリング層

プーリング層は、畳み込み層で生成された特徴マップをダウンサンプリングする層である。これにより、データ量を削減し、学習を効率化する。
主な種類としては以下。

1. **Maxプーリング**: 各領域内の最大値を選択する
2. **Averageプーリング**: 各領域内の平均値を計算する

```python:プーリング
def max_pooling(x, pool_size, stride=2):
    H, W = x.shape
    PH, PW = pool_size
    OH = (H - PH) // stride + 1
    OW = (W - PW) // stride + 1

    out = np.zeros((OH, OW))
    for i in range(0, OH):
        for j in range(0, OW):
            out[i, j] = np.max(x[i * stride:i * stride + PH, j * stride:j * stride + PW])

    return out

# 入力データ
x = np.array([[1, 2, 3, 4],
              [5, 6, 7, 8],
              [9, 10, 11, 12],
              [13, 14, 15, 16]])

# プーリングの結果
out = max_pooling(x, (2, 2))
print(out)
```
```terminal:実行結果
[[ 6.  8.]
 [14. 16.]]
```

## CNNのアーキテクチャ
CNNは、以下の層を組み合わせて構築される。

1. **畳み込み層**
2. **活性化関数（ReLUなど）**
3. **プーリング層**
4. **全結合層**

今まで学んだことが集結している。
ギリギリ覚えているぞ。

### LeNetの例
LeNetは、画像認識に使用される典型的なCNNアーキテクチャ。
構造は以下の通り。
1. 畳み込み層1 → プーリング層1
2. 畳み込み層2 → プーリング層2
3. 全結合層1 → 全結合層2

図無しで申し訳ない。申し訳ない。

#### 簡易的な構造の例
```python: 簡易的な例
from keras.models import Sequential
from keras.layers import Conv2D, MaxPooling2D, Flatten, Dense

model = Sequential([
    Conv2D(6, kernel_size=(5, 5), activation='relu', input_shape=(32, 32, 1)),
    MaxPooling2D(pool_size=(2, 2)),
    Conv2D(16, kernel_size=(5, 5), activation='relu'),
    MaxPooling2D(pool_size=(2, 2)),
    Flatten(),
    Dense(120, activation='relu'),
    Dense(84, activation='relu'),
    Dense(10, activation='softmax')
])

model.summary()
```
```terminal:構築結果
Model: "sequential"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓
┃ Layer (type)                         ┃ Output Shape                ┃         Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━┩
│ conv2d (Conv2D)                      │ (None, 28, 28, 6)           │             156 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ max_pooling2d (MaxPooling2D)         │ (None, 14, 14, 6)           │               0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ conv2d_1 (Conv2D)                    │ (None, 10, 10, 16)          │           2,416 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ max_pooling2d_1 (MaxPooling2D)       │ (None, 5, 5, 16)            │               0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ flatten (Flatten)                    │ (None, 400)                 │               0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dense (Dense)                        │ (None, 120)                 │          48,120 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dense_1 (Dense)                      │ (None, 84)                  │          10,164 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dense_2 (Dense)                      │ (None, 10)                  │             850 │
└──────────────────────────────────────┴─────────────────────────────┴─────────────────┘
 Total params: 61,706 (241.04 KB)
 Trainable params: 61,706 (241.04 KB)
 Non-trainable params: 0 (0.00 B)
```

## まとめ
最後結局テストできていないのだが、なんとなく理屈は…わか…ったのか…？
ちょっと別でテスト稼働してみつつこの記事は終えてみる。
ライブラリもりもりにすればめちゃくちゃ簡単に記述できることもなんとなくわかった。

引き続き頑張ります！