MLP、CNNを使って物体認識を行う〜精度を向上させる（その２） / Kaggle CIFAR-10

[前回の記事](https://qiita.com/kame_yang/items/7d2bdba27da38722c2cb)では、CIFAR-10を題材にMLPのModelの構築と、パラメーターチューニングを行い、0.53049〜というスコア（Accuracy）を得ることができました。

今回はCNNを使いさらなる精度向上を目指してみたいと思います。


# Model作成その③　CNN
データを準備するコードはこちら。
前回からreshapeを少し変えています。

```python
# データのインポート
import numpy as np
from tensorflow.keras.datasets import cifar10
from tensorflow.keras.utils import to_categorical

(X_train, y_train), (X_test, y_test) = cifar10.load_data()
X_train, X_test = X_train.reshape(-1, 32, 32, 3), X_test.reshape(-1, 32, 32, 3)
X_train, X_test = X_train.astype('float32'), X_test.astype('float32')
X_train, X_test = X_train/255.0, X_test/255.0
y_train, y_test = to_categorical(y_train), to_categorical(y_test)
```

Modelのコードです。
チューニングはこの後行いますので、シンプルに畳み込み層とプーリング層を1層ずつだけ実装しました。

```python
# ニューラルネットワーク構築（CNN）
import time
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Dropout, Conv2D, MaxPooling2D, Flatten
from tensorflow.keras import optimizers

# Sequentialオブジェクト
model = Sequential()

# 畳み込み層
model.add(Conv2D(filters=64, kernel_size=(5,5), padding='same', input_shape=(32, 32, 3), activation='relu'))

# プーリング層
model.add(MaxPooling2D(pool_size=(2,2)))

# Flatten層
model.add(Flatten())

# 出力層
model.add(Dense(10, activation='softmax'))

# コンパイル
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])

# 学習を行う（GPU：オン）
start = time.time()
history = model.fit(
    X_train,
    y_train,
    epochs=20,
    batch_size=128,
    validation_data=(X_test, y_test)
)
end = time.time()

# 結果
score = model.evaluate(X_test, y_test, verbose=1)

print('accuracy:', score[1])
print('loss    :', score[0])
print('所要時間 :',end-start,'sec')

```

```
accuracy: 0.6527000069618225
loss    : 1.1159632205963135
所要時間 : 43.79876708984375 sec
```

チューニング後のMLPよりも10%以上精度を上げられていますね！
CNNの強力さをシンプルに実感します。


# Model作成その④　CNNパラメーターチューニング
前回同様hyperoptを使ってパラメーターチューニングを行います。
チューニング対象は畳み込み層のfilter、kernel_size、プーリング層／ドロップアウト層の配置の有無と、以降追加する層の数、そしてそのユニット数ならびに活性化関数……うん、ほぼ全部ですね。

データ作成／Model作成の関数です。


```python
from hyperopt import hp
from hyperopt import Trials, tpe
from hyperas import optim
from hyperas.distributions import choice, uniform

def create_data():
    import numpy as np
    import time
    from tensorflow.keras.datasets import cifar10
    from tensorflow.keras.models import Sequential
    from tensorflow.keras.layers import Dense, Dropout, Conv2D, MaxPooling2D, Flatten
    from tensorflow.keras import optimizers
    from tensorflow.keras.utils import to_categorical

    (X_train, y_train), (X_test, y_test) = cifar10.load_data()
    X_train, X_test = X_train.reshape(-1, 32, 32, 3), X_test.reshape(-1, 32, 32, 3)
    X_train, X_test = X_train.astype('float32'), X_test.astype('float32')
    X_train, X_test = X_train/255.0, X_test/255.0
    y_train, y_test = to_categorical(y_train), to_categorical(y_test)
    
    return X_train, X_test, y_train, y_test

def create_model(X_train, X_test):
    
    # Sequentialオブジェクト
    model = Sequential()
    
    # 畳み込み層（第一層）
    model.add(
        Conv2D(
            filters={{choice([64, 128])}},
            kernel_size={{choice([(3,3), (5,5), (7,7)])}},
            padding='same',
            input_shape=(32, 32, 3),
            activation={{choice(['tanh', 'relu'])}}
        )
    )
    # プーリング層
    if {{choice([0, 1])}} == 0:
        pass
    else:
        model.add(MaxPooling2D(pool_size=(2,2)))
    # ドロップアウト
    if {{choice([0, 1])}} == 0:
        pass
    else:
        model.add(Dropout({{quniform(0.2, 0.6, 0.1)}}))
    
    # 畳み込み層（第二層）
    model.add(
        Conv2D(
            filters={{choice([32, 64])}},
            kernel_size={{choice([(3,3), (5,5), (7,7)])}},
            padding='same',
            input_shape=(32, 32, 3),
            activation={{choice(['tanh', 'relu'])}}
        )
    )
    # プーリング層
    if {{choice([0, 1])}} == 0:
        pass
    else:
        model.add(MaxPooling2D(pool_size=(2,2)))
    # ドロップアウト
    if {{choice([0, 1])}} == 0:
        pass
    else:
        model.add(Dropout({{quniform(0.2, 0.6, 0.1)}}))
    
    # 畳み込み層（第三層）
    model.add(
        Conv2D(
            filters={{choice([32, 64])}},
            kernel_size={{choice([(3,3), (5,5), (7,7)])}},
            padding='same',
            input_shape=(32, 32, 3),
            activation={{choice(['tanh', 'relu'])}}
        )
    )
    # プーリング層
    if {{choice([0, 1])}} == 0:
        pass
    else:
        model.add(MaxPooling2D(pool_size=(2,2)))
    # ドロップアウト
    if {{choice([0, 1])}} == 0:
        pass
    else:
        model.add(Dropout({{quniform(0.2, 0.6, 0.1)}}))
    
    # Flatten層
    model.add(Flatten())
    
    # 以降
    if {{choice([0, 1, 2])}} == 0:
        pass
    elif {{choice([0, 1, 2])}} == 1:
        model.add(Dense({{choice([400, 500, 600])}},activation={{choice(['tanh', 'relu'])}}))
        model.add(Dropout({{quniform(0.2, 0.6, 0.1)}}))
    elif {{choice([0, 1, 2])}} == 2:
        model.add(Dense({{choice([400, 500, 600])}},activation={{choice(['tanh', 'relu'])}}))
        model.add(Dropout({{quniform(0.2, 0.6, 0.1)}}))
        model.add(Dense({{choice([100, 200, 300])}},activation={{choice(['tanh', 'relu'])}}))
        model.add(Dropout({{quniform(0.2, 0.6, 0.1)}}))
    
    # 出力層
    model.add(Dense(10, activation='softmax'))

    model.compile(loss='categorical_crossentropy',optimizer={{choice(['adam', 'rmsprop'])}},metrics=['accuracy'])

    # 学習を行う（GPU：オン）
    history = model.fit(
        X_train,
        y_train,
        epochs=30,
        batch_size={{choice([128, 256])}},
        validation_data=(X_test, y_test),
        verbose=0
    )
    val_acc = np.amax(history.history['val_accuracy'])
    return  {'loss': -val_acc, 'status':STATUS_OK, 'model':model}
```

探索を実行するコードは基本前回と同じですが、検証回数を増やしています。

```python
# 探索実行
best_run, best_model = optim.minimize(
                model=create_model, 
                data=create_data, 
                algo=tpe.suggest, 
                max_evals=50, 
                eval_space=True, 
                notebook_name='__notebook_source__', 
                trials=Trials()
)

# 結果の確認
X_train, X_test, y_train, y_test = create_data()
score = best_model.evaluate(X_test, y_test)

print('accuracy:', score[1])
print('loss    :', score[0])
```

```
100%|██████████| 50/50 [2:27:28<00:00, 176.98s/trial, best loss: -0.805400013923645]   
accuracy: 0.8014000058174133
loss    : 0.6208286881446838
```

無事、これまでで一番高いスコアが得られました。

参考までにチューニング後のModelの構造も見てみることとします。

```python
# Model構造の表示
best_model.summary()
```

```
Model: "sequential_42"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_126 (Conv2D)          (None, 32, 32, 64)        1792      
_________________________________________________________________
conv2d_127 (Conv2D)          (None, 32, 32, 64)        102464    
_________________________________________________________________
max_pooling2d_71 (MaxPooling (None, 16, 16, 64)        0         
_________________________________________________________________
conv2d_128 (Conv2D)          (None, 16, 16, 64)        200768    
_________________________________________________________________
max_pooling2d_72 (MaxPooling (None, 8, 8, 64)          0         
_________________________________________________________________
dropout_94 (Dropout)         (None, 8, 8, 64)          0         
_________________________________________________________________
flatten_42 (Flatten)         (None, 4096)              0         
_________________________________________________________________
dense_75 (Dense)             (None, 500)               2048500   
_________________________________________________________________
dropout_95 (Dropout)         (None, 500)               0         
_________________________________________________________________
dense_76 (Dense)             (None, 10)                5010      
=================================================================
Total params: 2,358,534
Trainable params: 2,358,534
Non-trainable params: 0
_________________________________________________________________
```


# まとめ
各Modelでのスコアは以下の通りです。

| Model             | スコア   |
|:------------------|:-------|
| MLP（チューニングなし）  | 0.4948999881744385  |
| MLP（チューニングあり）  | 0.5304999947547913  |
| CNN（チューニングなし）  | 0.6527000069618225  |
| CNN（チューニングあり）  | 0.8014000058174133  |

良いですね、工夫をしていくにつれてしっかりスコアも伸びています。
~~記事としてもまとめやすいね！~~

ちなみにこのスコアでSubmitすると、Leaderboard上は50位＋くらいの成績です。
全体でも230名強しかいないのでアレですが、上位20%までもう一押し、ってところでしょうか。

分かりきっていたことですが、この程度の規模感のデータでもパラメーターチューニングを頑張ろうとするとそれなりに時間を喰いますね。
CNN（チューニングあり）では2時間半以上掛かっているので、その間に貯まってる家事でも済ませちゃいましょう。

ここから更にスコアを伸ばすとすれば、まだパラメーターのチューニングや層の配置に工夫の余地はあるかと思います。
割と適当に決めてしまったので、時間があるのであればグリッドサーチ等も試して見るべきでしょうか。

あとはやはりアンサンブルか、学習率の制御も一つのアプローチかと思います。
ただこの辺りは自分自身も勉強不足なところも多いので、他のコンペに参加しつつ知見を深めていければと考えています。