CNNのプーリングの関数をRで作ってみた

#CNN(Convolutional Neural Network)のプーリング
　CNNは日本語でいうと畳み込みニューラルネットワークといい、画像認識や音声認識でよく使われるそうだ。画像データを解析していくことになるが、プーリングは簡単にいうと画像をぼかす処理のことをいう。ぼかすことで後の処理をしやすくしようという意図があるらしい。
　ここら辺はまだまだ勉強不足です＞＜

　しかし、どんなことをしているのかを視覚的に見たいし、自分でも作ってみたかったのでRでプーリングの関数を作りました。

##画像データの取り込み
　画像データはMNISTの手書き数字データです。有名ですね。データは数字の書かれているマスを縦・横それぞれ28等分した計784変量から成るデータで、それぞれのセルには0から255の数字が書かれています。よって、グレースケールで描くと数字が出力されます。本投稿では、この中の４という数字の一つを使います。

　まず、MNISTからデータを取ってきます。それを.csvにしてRで読み込みimage()関数で描画します。Rのコードは次のようになります。

```R:画像出力
#データの読み込み
X <- read.csv("WriteHandData.csv")

#ラベル4だけ取り出してX_4に格納
X_4 <- X[X$label=="4",-c(1)]

#X_4を行列化
#画像データを行列化しているので画像データ行列と呼ぶ（あまり聞かない）
X_4 <- as.matrix(X_4)

#横反転して数字が綺麗に出力されるように数値を入れ替えてN_4に格納
#ラベル４のデータの上から４番目を１つ抽出
num_4 <- as.numeric(as.vector(X_4[4,]))/255
nn_4 <- matrix(num_4,28,28)
N_4 <- matrix(0,28,28)
for(i in 1:dim(nn_4)[1]){
  for(j in 1:dim(nn_4)[2]){
    N_4[i,j] <- nn_4[i,dim(nn_4)[2]-j+1]  
  }
}

#image()関数で出力
image(N_4,xlab = "",ylab = "",xaxt = "n",yaxt = "n",
      col=gray((255:0)/255),axes = FALSE,zlim=c(0,1))

#グリッド線を描く
grid(28, 28, lwd = 0.5, col = "red",lty = 1)
```
すると下の図のような数字が出力されます。赤い線で28×28等分しています。各セルごとに白色から黒色がついていることが確認できます。
![writehand4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1625514/abfbf0f5-7106-e458-1373-c41f5afc1d5c.png)

##プーリング
　では、この画像をプーリングしていきます。プーリングについての説明は、[畳み込みニューラルネットワーク チートシート](https://stanford.edu/~shervine/l/ja/teaching/cs-230/cheatsheet-convolutional-neural-networks)がわかりやすいと思います。最大プーリングと平均プーリングの関数をそれぞれ作ります。まあ、これらの違いはmaxかmeanかだけの違いなのでコードはほとんど同じです。

###最大プーリング
　まずは、最大プーリングのコードです。

```R:最大プーリング
#引数matに画像データ行列、tateとyokoでプールサイズ(縦,横)
#(縦,横) = (2,2)をデフォルト値にしている
max_pooling <- function(mat, tate=2, yoko=2){
  P <- matrix(0,(dim(mat)[1]-yoko+1),(dim(mat)[2]-tate+1))
  for(i in 1:(dim(P)[1])){
    for(j in 1:(dim(P)[2])){
      #プールされた領域の中の最大値をとる
      P[i,j] <- max(mat[(i:(i+yoko-1)),(j:(j+tate-1))])
    }
  }
  return(P)
}
```

　出力は(3×3)プーリングした後の画像データ行列が返ってきます。これをまたimage()で出力する。

```R:最大プーリングの出力
#出力結果の画像データ行列をmax_P4に格納
max_P4 <- max_pooling(N_4, tate = 3, yoko = 3)
image(max_P4, xlab = "", ylab = "", xaxt = "n", yaxt = "n",
      col=gray((255:0)/255), axes = FALSE, zlim=c(0,1))
grid(26, 26, lwd = 0.5, col = "red",lty = 1)
```
　最大プーリング後の画像は次のようになる。ここで、プーリングすると元の画像サイズ(w,h)から(w - tate + 1, h - yoko + 1)になるので注意する。今の場合だと、元の画像サイズが(28, 28)でプールサイズ(tate, yoko) = (3, 3)なので(28 - 3 +1, 28 - 3 + 1)で(26, 26)になる。

![max-pooling.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1625514/bd133009-1c12-8b45-9664-336b9ff19e2c.png)

　最大プーリングすると、４の特徴がはっきりと出てきました！！

###平均プーリング
　次に平均プーリングです。最大プーリングとほとんど同じなので、画像出力まで一緒にコードを書くと次のようになります。

```R:平均プーリングとその出力
ave_pooling <- function(mat, tate=2, yoko=2){
  P <- matrix(0,(dim(mat)[1]-tate+1),(dim(mat)[1]-yoko+1))
  for(i in 1:(dim(P)[1])){
    for(j in 1:(dim(P)[2])){
      #プールされた領域の中の最大値をとる
      P[i,j] <- mean(mat[(i:(i+tate-1)),(j:(j+yoko-1))])
    }
  }
  return(P)
}

#出力結果の画像データ行列をave_P4に格納
ave_P4 <- ave_pooling(N_4, tate = 3, yoko = 3)
image(ave_P4, xlab = "", ylab = "", xaxt = "n", yaxt = "n",
      col=gray((255:0)/255), axes = FALSE, zlim=c(0,1))
grid(26, 26, lwd = 0.5, col = "red",lty = 1)
```

　平均プーリング後の画像データは次のようになる!元の画像データよりも少しぼやけている様子がわかる。
![ave-pooling.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1625514/28f71820-d5f9-48fb-090e-619ea94b5d1d.png)

#まとめ
　元の画像と最大プーリング、平均プーリング後の画像を並べると、次のようになる。最大プーリングは元の画像より、特徴がはっきりとしており、平均プーリングはぼやけている様子がわかった。よって、最大プーリングは特徴抽出が得意で、平均プーリングは何か個人情報などぼかした方が良い画像に使われるのだろう(推測です+o+)
　何はともあれ自分で関数を作り比較できたことはよかったと思います。
![all-pooling.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1625514/ace085d1-f911-b761-adb8-6913694ef1d7.png)


##付録
　プールサイズを先ほどは正方形でしたが長方形でしてみる。やり方は簡単で、先ほど作った関数のtateとyokoを変えれば良い。
まずは(tate, yoko) = (7, 2)でプーリングしたもの。
![all-pooling-tate.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1625514/eb20577c-a6c2-5614-1b7f-cfcf02ac2664.png)


　次に(tate, yoko) = (2, 7)でプーリングしたもの。
![all-pooling-yoko.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1625514/18400e30-9805-aebf-4fd9-636391ece724.png)

　プールサイズが縦長の長方形だと数字を縦長に、横長の長方形だと数字を横長にプーリングしていることがわかった。平均プーリングだと、ぼやけ具合は縦長長方形でプーリングすると縦に重なりが多いと濃く、横長長方形でプーリングすると横に重なりが多いと濃くなっている。事前情報である程度特徴がわかっていれば、縦長か横長か指定することで特徴を抽出しやすくなるかもしれないなあと思いました。


#参考
>MNISTデータはこちらから取得しています。
>>http://yann.lecun.com/exdb/mnist/