cifar10のkerasでの分類にチャレンジ

#はじめに
pythonのkerasを用いて機械学習の練習を行う際に必ずといっていほど目にするcifar10とmnist。今回は機械学習の第一歩としてcifar10に着目して取り組んでみます。また、これを見てくれた機械学習にこれから取り組みたい人にとって１つでも参考になればと思います。

###目次
・使ったもの
・データセットの入手、作成
・ネットワークを構築
・学習させてみる

###使ったもの
・numpy (毎度おなじみのやつ, 保存に使っただけで使う必要なし)
・keras(tensorfow?)
なければ、conda install keras でインストールを。

###データセットの入手、作成
まずはデータセットを手に入れます。今回はCIFAR-10データセットを使用しました。CIFAR-10とはkerasに同梱されており、32×32ピクセルのカラー画像が60000枚存在します。これらの画像はすべて、10種類のいずれかに分けられます。

早速読み込んでいきます。

```python
from keras.datasets import cifar10 
(x_train, y_train), (x_test, y_test) = cifar10.load_data() 
```
次に、このデータを使いやすいように加工を施していきます。

```python 
from keras.utils import to_categorical 
x_train = x_train.astype('float32') / 255 
x_test = x_test.astype('float32') / 255 
y_train = to_categorical(y_train, 10)
y_test = to_categorical(y_test, 10)
```
ざっくりいうと画像データは1から255の整数で構成され、入力値が-1から1の間にあると嬉しいので255で割り、to_categoricalで、y_train, y_testをone-hotエンコーディングの形状にしています。
私はいろいろな場合で試したかったので、この４つのデータをnp.savezで保存してしまいました。

###one-hot エンコーディングとは
ざっくりいうと、分類する種類の数の長さを持ったベクトルで、ある要素が１でそれ以外はすべて０であるベクトルに変換することを指します。ロードした段階でのy_train、y_testを例えば、

```python
print(y_train[4])
```
と実行して覗いてみると、[1]が表示されます。この数字を上記のベクトルに変換すると、
[0, 1, 0, 0, 0, 0, 0, 0, 0]となります。
他に、y_trainの画像が5に分類される場合は
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0]となります。


###ネットワークを構築
いよいよ本題、ネットワークの構築に取り掛かかります。個人的になれているfunctional APIという手法で構築しました。

```python 
from keras.layers import Dense, Flatten, Conv2D, BatchNormalization,
Dropout, LeakyReLU, Input
from keras.models import Model 

class Network():
    def __init__(self, input_dim, filter, kernel, stride, use_batch, use_dropout, output_dim, lr):
        self.input_dim = input_dim 
        self.filter = filter
        self.kernel = kernel
        self.stride = stride 
        self.n_layers = len(filter)
        self.use_batch = use_batch
        self.use_dropout = use_dropout
        self.lr = lr 
        
        self.crate()

    def create(self):
        input_layer = Input(shape=self.input_dim)
        x = input_layer
        for i in range(self.n_layers):
           conv = Conv2D(filters=self.filter[i], kernel_size=self.kernel[i], strides=self.stride[i], padding='same')
           x = conv(x)
           if self.use_batch:
              x = BatchNormalization(momentum=self.use_batch)(x)
           x = LeakyReLU()(x)
           if self.use_dropout:
              x = Dropout(rate=0.5)(x)
         x = Flatten()(x)
         x = Dense(units=self.output_dim, activation='sigmoid')(x)
         output_layer = x
         self.model = Model(input_layer, output_layer)
```
あとでいろいろ値を変えて学習させたかったのでNetworkというクラスを定義。

###説明
####Input
その名の通り、データが入力される層です。データの形をshapeとして代入します。
####Conv2D
　画像を取り扱う際によく用いられる畳み込み層と呼ばれるものです。画像の一部の値とフィルタの値を乗算しその値を返す、これを画像全体に渡るまで繰り返すことでその画像の特徴をうまく抽出することができるというものです。

####BatchNormalization 
　ニューラルネットワークを訓練する際、重みが大きくなりすぎるとやがて勾配爆発というものを引き起こします。それを防ぐための層だと簡単に理解すればいいと思います。（というか自分もそれくらいしかまだ理解が追いついていない、、）

####LeakyReLU
活性化関数を通す層。ReLUだと０より小さい値をすべて０と返しますが、LeakyReLUは負の値を返します。これにより、負の値を生かすことができます。

####Dropout 
この層は、過剰に学習させすぎるのを防ぐためにいくつかのノードを選択して出力を０にします。

####Flatten
入力されてきたものの次元を１にします。

####Conv2Dの各変数について
input_dimとは、その名の通り最初に入力される次元です。今回の場合は(32, 32, 3)となります。(32, 32)は画像のサイズ、３番目の３はチャンネルの個数というものを表し、今回は(赤、緑、青）の個数となります。（白黒画像だと１？）
filtersはフィルターの数を表しており、kernel_sizeはその大きさです。sridesはフィルターを移動させる大きさです。

####おおざっぱな流れ
まとめると、画像データを学習させる際によく用いられるConv2Dという畳み込み層、バッチ正規化を施すBatchNomalization、LeakyReLUという活性化関数、Dropout層を繰り返し、Flattenでならして最後にDense層を通すというもの。
最後に入力層、出力層でmodelを定義して完成です。

```python 
from keras.optimizers import Adam

   def compile(self, lr):
      self.model.compile(optimizer=Adam(lr=self.lr), loss='categorical_crossentropy', metrics=['accuracy'])

   def train(self, data1, data2, batch_size, epochs):
      self.model.fit(data1, data2, batch_size=batch_size, epochs=epochs, shuffle='True')
```
ここから訓練していきます。

###学習させてみる
```python 
indim = (32, 32, 3)
fil = [32, 64, 64, 32]
ker = [3, 3, 3, 3]
stri = [1, 2, 1, 2]
usebatch = 0.25
usedrop = True 
Lr = 0.0005 
Epochs = 10 
Batchsize = 32

NETWORK = Network(input_dim=indim, filters=fil, kernel=ker, stride=stri, use_batch=usebatch, use_dropout=usedrop, lr=Lr)
NETWORK.model.compile(lr=Lr)
NETWORK.model.train(data1=x_train, data2=y_train, batch_size=Batchsize, epochs=Epochs)
```
いざ実行してみます。
![Screenshot from 2022-01-14 11-15-12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2499154/c4d3cefe-9a3a-432f-95d5-043ac5708658.png)

accuracyは62％ほど。

　個人的にバッチ正規化のイメージがまだあまりうまくつかめていないので、今後の課題にしたいところです。あとはフィルターやストライドの数値をどのようにすれば精度を上げられるのか、今後の課題です。

これがこれが気象の解析において活用できるときが来るのかはわかりませんが、可能性はなくはないと思うのでなにか考えてみたいところですね。

##参考文献
[生成Deep Learning] 
・David foster 著　
・松田晃一、小沼千絵　訳
・オライリージャパン
・2020年発行