実装で学ぶ深層学習2(画像分類編)

#はじめに
深層学習１の続きでmnistの手書き文字認識をします。
深層学習の基本的な構造は前回の記事を参照ください。
#実装
##データダウンロードと可視化
今回はmnistと呼ばれる機械学習用に公開されているデータセットをダウンロードしてモデルの訓練とテストを行います。
実際に自分で持っている画像にラベルをつけて読み込んでも構いません。
まずダウンロードとそのダウンロードしたデータの可視化を行いましょう。

```python
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
a = np.arange(100)
sns.heatmap(a.reshape((10,10)))
```
![heat1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/625181/795adc47-111f-ec42-5f82-3f74a6927616.png)

heatmapを作成することで配列を簡単に可視化を行うことができた。
それではmnistから手書き文字認識用のデータをダウンロードして可視化する。

```python
from tensorflow.keras.datasets import mnist
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
```

これにてtrain_imagesの中に訓練用の画像(60000,28,28)が手に入った。
これは要するに60000枚の28pixelx28pixelの濃淡画像を意味する。
出力の仕方によっては白黒画像である。早速中の画像を見てみる。

```python
sns.heatmap(train_images[1210])
print(train_labels[1210])
```
![5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/625181/67d5b827-739f-3414-178f-856d6f023483.png)


これで5のような画像とラベルの内部に5が入っていることがわかる。

それではこれらの生のデータから学習するデータを作る。
今回のような多クラス分類では,入力はそのままでいいが、出力がラベルの数字というようにしてしまうと精度がかなり落ちてしまう、というのも7か9で悩んで8を出力するような事態になりかねない。よって出力はラベルの数だけ出力し、出力するのは入力がそのラベルである確率である。よって以下のような前処理を行う。

```python
train_x = train_images.reshape(60000,28,28,1)
train_y = np.zeros((60000,10))
test_x = test_images.reshape((10000,28,28,1))
test_y = np.zeros((10000,10))
for i in range(60000):
  train_y[i][train_labels[i]]=1.0
for i in range(10000):
  test_y[i][test_labels[i]]=1.0
```

これで入出力の形式は整った。ちなみにtest_imagesには10000枚のデータが入っている。

##モデルの形成

```python
from keras import layers
from keras import models
from keras import optimizers
model = models.Sequential()
model.add(layers.Conv2D(16,(3,3),padding="same",activation="relu",input_shape = (28,28,1)))
model.add(layers.MaxPooling2D((2,2)))
model.add(layers.Conv2D(32,(3,3),padding="same",activation="relu"))
model.add(layers.MaxPooling2D((2,2)))
model.add(layers.Flatten())
model.add(layers.Dense(128,activation = "relu"))
model.add(layers.Dense(128,activation = "relu"))
model.add(layers.Dense(10,activation = "softmax"))
model.compile(loss = "categorical_crossentropy",optimizer="adam",metrics=["accuracy"])
```

前回使っていない層が登場した。layers.Conv2Dである。
###畳み込み層
画像処理の分野で画像を加工する時に周りの画素値の平均に自身の画素値を変えることでぼかしの効果を得ることができる。他にも全てのpixelに対しその近傍のpixelによって値を更新するような処理はたくさんある。その作業を行うをカーネルを用いると簡単に扱える。
https://deepage.net/deep_learning/2016/11/07/convolutional_neural_network.html
このサイトがわかりやすく畳み込み層について解説しているが、ここでも簡単に述べると、大きな画像に対して特殊なスポイトを用いて画像を隣の紙にコピーするようなもので、スポイトは色を吸う時にまわりのいくつかの色も一緒にすってしまう。そしてそのすったものの重みつきの和を隣の紙にうつす(この時はまわりの色に影響をもたらさない)。
そのスポイトの重みを表すのがカーネルである。そうすると一番端が色をすいづらいので画像の周りに0で縁取ってからこの作業を行うことをzero paddingと呼び、コード内のpadding = "same"は画像サイズを変えないようにzeroで縁取ってね、という意味である。ここでスポイトの数を増やせばいくらでも画像に別の効果をもたらした画像が得られることがわかる。
Conv2Dのはじめの引数は画像を何枚に増やすかに相当する。
次の引数はkernelのサイズである。

###pooling
maxpoolingと書かれたものがあると思う。これはpoolingの一種で画像を小するための方法である。2x2pixelを1pixelに見立てて、2x2の中の最大値をとる方法で画像を小さくしている。これによって画像という巨大次元の入力を扱いやすくしていっている。

###softmax
今回初登場の活性化関数だが、これは多クラス分類において、出力すべきはおのおののラベルの確率なので最後の10次元ベクトルの和は1であるべきである。これをうまいことやってくれるのがsoftmaxである。

###categorical_crossentropy
これは損失を出力の差分で行うのではなく0~1の範囲での学習に適した交差エントロピーを使うというもの。1を0.9と判断する時の損失に比べ1を0.1と判断した時に与えられる損失がただの差分を利用したものよりも大きくこういった分類問題には適している。

##訓練

```python
history = model.fit(train_x,train_y,steps_per_epoch=10,epochs = 10)
```

これで訓練ができていることがわかる。
何のしなくともlogが出てきて訓練データないでは学習の終わりで95%以上まで精度が上がっていることがわかる。

ちなみにmodel.fitの返り値はhistoryに格納しているが、これを使って学習の様子をplotすることができる。
たとえば正答率の推移を見たければ

```python
plt.plot(history.history['accuracy'])
```
![accrate.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/625181/59a64c31-d78e-6b98-cb68-d59cd25b0c8d.png)

これで学習経過の可視化ができた。kerasのdocumentを読んでコードを弄りながら理解しよう。

##検証
訓練データでどれだけ成果を出しても訓練データの外で通用しなくては意味がない。
訓練と同時にやる方法もあったが、今回は学習後のモデルを検証する。

```python
model.evaluate(test_x,test_y)
```

<img width="533" alt="スクリーンショット 2020-04-23 18.31.02.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/625181/64509fda-075b-b55c-7367-ee4e10b2063c.png">

[損失,正答率]になっている。
testデータでも同様の結果が得られていることがわかる。

#終わりに
今回は俗にいう畳み込みニューラルネットワークをできるだけ簡単な形で実装した。ただ、画像サイズが28x28なので実は全て全結合層でも問題なく学習できるので、そのモデルも実装して結果を見てみると面白いかもしれない。全結合だと$O(n^4)$でコンピュータの資材を使ってしまうので大きなデータ(100x100も多分むり？)を扱えないが畳み込みではカーネルの分しか変数を持たないため問題なく1024x1024でも動く(ギリギリ)。

次回は生成モデルを扱おうと思う。とりあえず通常のGANをできるだけ理解しやすいコードで実装する。