いまさら畳み込みを可視化してみた

# はじめに
研究室の指導の中で畳み込み層 (convolution) について説明する機会があったのですが，その際に作った可視化のためのコードをそのままにしておくのが勿体無かったので，$n$番煎じではありますが簡単な解説を添えて投稿しようと思います．

環境は以下の通り．

```bash: libs_version
$ pip list | grep numpy
>> numpy                     1.26.4
$ pip list | grep seaborn
>> seaborn                   0.13.2
```

::: note
本記事で提示するコードはJupyter Labで実行しましたが，一応それに依存しない記法に書き換えています．ただし，動作確認はしていないので，エラーが発生した場合はご自身で対処されて下さい．
:::

# 畳み込みとは
入力データ（画像など）とカーネルを作用させることで，入力データの特徴を抽出する処理のことであり，カーネルに似た特徴を持つ部分が強調されます．
畳み込みの解説が本記事のメインテーマでは無いので，より詳細を知りたい方は以下を参照して下さい．（個人的に，最も分かり易かったものです．）

【よく分かる解説】

https://youtu.be/xzzTYL90M8s?si=oqdoHmZ7GAnOrPC7


# 画像の準備
まずは，簡単な画像を準備します．MNISTなどから引っ張ってくるのも良いですが，1枚しか使わないので手書きします．

```python: set_img
img = np.array([
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 1, 1, 1, 1, 1, 0, 0, 0],
    [0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 1, 1, 1, 1, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
    [0, 0, 1, 0, 0, 0, 1, 0, 0, 0],
    [0, 0, 0, 1, 1, 1, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
], dtype=float)

sns.heatmap(img, square=True, cmap='Blues')
```

得られる画像はこんな感じです．

![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/693436/801f4231-9307-02e0-90d5-4c396b66fb1f.png)


# Kernel の準備
次に，カーネルを準備します．比較のために，3つ用意します．

```python: set_kernel1
kernel1 = np.array([
    [0, 1, 0],
    [0, 1, 0],
    [0, 1, 0]
], dtype=float)

sns.heatmap(kernel1, square=True, cmap='Blues')
```

![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/693436/b7e39e3d-255e-8b74-5ada-d4ae65ce765f.png)

```python: set_kernel2
kernel2 = np.array([
    [0, 0, 0],
    [1, 1, 1],
    [0, 0, 0]
], dtype=float)

sns.heatmap(kernel2, square=True, cmap='Blues')
```

![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/693436/6f0f09ae-4087-7da9-dc79-0f195d43793f.png)

```python: set_kernel3
kernel3 = np.array([
    [1, 0, 0],
    [0, 1, 0],
    [0, 0, 1]
], dtype=float)

sns.heatmap(kernel3, square=True, cmap='Blues')
```

![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/693436/7a1e90a5-ad03-0ff0-90da-50f34a4cbd29.png)


# 畳み込み演算
畳み込み演算をおこなう関数を定義します．やっていることは非常に単純で，カーネルの適用範囲を1つずつズラしながら要素積を取り，その結果の要素の和を順番に格納します．コメントも付しているのでそれらを参考にされて下さい．

```python: def_convolution
# 畳み込みをおこなう関数
def convolution(img, kernel):
    kernel_size = len(kernel)
    conv_img = []

    # stride = 1 のときの操作
    for row in range(len(img) - kernel_size + 1):
        tmp = []
        for col in range(len(img[0]) - kernel_size + 1):
            # 入力データとカーネルの要素積
            dot = np.sum(img[row: row+kernel_size, col: col+kernel_size]*kernel)

            # 要素積を正規化（無くても問題ないです）
            tmp.append(dot/kernel_size/kernel_size)
        conv_img.append(tmp)
    
    return np.array(conv_img)
```

:::note
上記の実装は，深層学習ライブラリで提供されているConvolutionとは微妙に異なる点もありますが，CNNをこれから学ぶ初学者向けの説明のため，その点はご了承下さい．
:::


# 処理結果
先に定義したそれぞれのカーネルを適用した結果を示します．

```python: kernel1_result
conv_img1 = convolution(img, kernel1)
sns.heatmap(conv_img1, square=True, cmap='Blues')
```

![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/693436/0579ae88-8cec-65ae-7cdd-c5b379e620ed.png)

```python: kernel2_result
conv_img2 = convolution(img, kernel2)
sns.heatmap(conv_img2, square=True, cmap='Blues')
```

![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/693436/87c8f9a7-5265-d1c9-fec6-574cf7554969.png)

```python: kernel3_result
conv_img3 = convolution(img, kernel3)
sns.heatmap(conv_img3, square=True, cmap='Blues')
```

![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/693436/26dd49c1-c8a4-e243-dbb2-969a54c1b3a7.png)


上の結果を見比べると，確かにカーネルの特徴と似た部分が強調されていることがわかります．


# 最後に
非常に簡単な記事でしたが，気まぐれに作成したコードをある程度有効活用できたのではないでしょうか．本記事のコードは，大したものではありませんが，その利用に特に制限は設けません．ただ，何かしらの問題が発生しても補償できないので，その点は悪しからず．