KerasでCIFAR-10の画像分類をやる【①基礎】

**対象読者**：CNNを用いた画像分類の基本知識があり、Kerasを用いて実装をしたいと思っている人

# CIFAR-10データセットとは
CIFAR-10データセットは、32x32のカラー画像6万枚からなり、10クラス、各クラス6千枚の画像から構成されています。また、学習画像は5万枚、テスト画像は1万枚あります。

（参考：[CIFAR-10 and CIFAR-100 datasets](https://www.cs.toronto.edu/~kriz/cifar.html)）

データセットの画像の一例を以下に示します。
![example_cifar10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/304572/92e325f8-77d3-4153-b600-af17dae5dada.png)


# 実装環境
Google Colaboratoryの無料版を用いています。
Google ColaboratoryはGoogleが提供するJupyter notebook環境です。
また、Pythonのバージョンは3.7
Kerasのバージョンは2.1です。

# 実装
## 必要なモジュールのインポート
```python:
import numpy as np
import matplotlib.pyplot as plt

from keras.datasets import cifar10
from keras.utils.np_utils import to_categorical
from keras.models import Sequential
from keras.layers import Conv2D
from keras.layers import MaxPool2D
from keras.layers import Dense,Activation,Dropout,Flatten
from keras.layers import BatchNormalization
```

## データセットの読込と前処理
```python:
# CIFAR-10データセットをダウンロード
(x_train,y_train), (x_test,y_test) = cifar10.load_data()

# 画像を0~1の範囲で正規化
x_train = x_train.astype('float32')/255.0
x_test = x_test.astype('float32')/255.0

# 正解ラベルをOne-Hotベクトルに変換
y_train = to_categorical(y_train,10)
y_test = to_categorical(y_test,10)

print(x_train.shape, y_train.shape)
```

```terminal:出力
(50000, 32, 32, 3) (50000, 10)
```

## モデルの構築
Kerasではモデルの記述方法として、いくつかありますが、今回は一番簡単なSequentialで記述します。

今回のモデルのアーキテクチャ（構成）は、CNN-BN-ReLU-CNN-BN-ReLU-Poolをブロックとしたものを2つ繋げ、その後全結合層に接続しています。
モデルの損失関数には、多クラス分類であるため、交差エントロピーを採用しています。
また、最適化関数にはAdamを採用しています。最適化関数には他にSGDやRMSpropなどがありますが、経験的にAdamがおすすめです。
```python:
#モデルの構築
model = Sequential()

model.add(Conv2D(32,(3,3),padding='same',input_shape=(32,32,3)))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(Conv2D(32,(3,3),padding='same'))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(MaxPool2D(pool_size=(2,2)))

model.add(Conv2D(64,(3,3),padding='same'))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(Conv2D(64,(3,3),padding='same'))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(MaxPool2D(pool_size=(2,2)))

model.add(Flatten())
model.add(Dense(512))
model.add(Activation('relu'))
model.add(Dropout(0.5))
model.add(Dense(10,activation='softmax'))

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

#モデルの表示
model.summary()
```

```terminal:出力画面
Model: "sequential"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 conv2d (Conv2D)             (None, 32, 32, 32)        896       
                                                                 
 batch_normalization (BatchN  (None, 32, 32, 32)       128       
 ormalization)                                                   
                                                                 
 activation (Activation)     (None, 32, 32, 32)        0         
                                                                 
 conv2d_1 (Conv2D)           (None, 32, 32, 32)        9248      
                                                                 
 batch_normalization_1 (Batc  (None, 32, 32, 32)       128       
 hNormalization)                                                 
                                                                 
 activation_1 (Activation)   (None, 32, 32, 32)        0         
                                                                 
 max_pooling2d (MaxPooling2D  (None, 16, 16, 32)       0         
 )                                                               
                                                                 
 conv2d_2 (Conv2D)           (None, 16, 16, 64)        18496     
                                                                 
 batch_normalization_2 (Batc  (None, 16, 16, 64)       256       
 hNormalization)                                                 
                                                                 
 activation_2 (Activation)   (None, 16, 16, 64)        0         
                                                                 
 conv2d_3 (Conv2D)           (None, 16, 16, 64)        36928     
                                                                 
 batch_normalization_3 (Batc  (None, 16, 16, 64)       256       
 hNormalization)                                                 
                                                                 
 activation_3 (Activation)   (None, 16, 16, 64)        0         
                                                                 
 max_pooling2d_1 (MaxPooling  (None, 8, 8, 64)         0         
 2D)                                                             
                                                                 
 flatten (Flatten)           (None, 4096)              0         
                                                                 
 dense (Dense)               (None, 512)               2097664   
                                                                 
 activation_4 (Activation)   (None, 512)               0         
                                                                 
 dropout (Dropout)           (None, 512)               0         
                                                                 
 dense_1 (Dense)             (None, 10)                5130      
                                                                 
=================================================================
Total params: 2,169,130
Trainable params: 2,168,746
Non-trainable params: 384
_________________________________________________________________
```


## モデルの学習
```python:
# モデルの学習
history = model.fit(x_train,y_train,
                    batch_size=128, # バッチサイズを128に設定
                    epochs=10, # エポック数を10に設定
                    verbose=1, #進捗状況をプログレスバーで表示
                    validation_split=0.1) # 訓練データの1割を検証データとして扱う
```

```terminal:出力画面
Epoch 1/10
352/352 [==============================] - 17s 16ms/step - loss: 1.8556 - accuracy: 0.3420 - val_loss: 2.1067 - val_accuracy: 0.2288
Epoch 2/10
352/352 [==============================] - 5s 14ms/step - loss: 1.3924 - accuracy: 0.4909 - val_loss: 1.2452 - val_accuracy: 0.5358
Epoch 3/10
352/352 [==============================] - 5s 15ms/step - loss: 1.2152 - accuracy: 0.5603 - val_loss: 1.1623 - val_accuracy: 0.5936
Epoch 4/10
352/352 [==============================] - 5s 15ms/step - loss: 1.0994 - accuracy: 0.6016 - val_loss: 0.9856 - val_accuracy: 0.6396
Epoch 5/10
352/352 [==============================] - 5s 15ms/step - loss: 1.0258 - accuracy: 0.6324 - val_loss: 0.8176 - val_accuracy: 0.7106
Epoch 6/10
352/352 [==============================] - 6s 17ms/step - loss: 0.9742 - accuracy: 0.6509 - val_loss: 0.8058 - val_accuracy: 0.7172
Epoch 7/10
352/352 [==============================] - 5s 14ms/step - loss: 0.9245 - accuracy: 0.6684 - val_loss: 0.8620 - val_accuracy: 0.7020
Epoch 8/10
352/352 [==============================] - 5s 14ms/step - loss: 0.8831 - accuracy: 0.6843 - val_loss: 1.0213 - val_accuracy: 0.6504
Epoch 9/10
352/352 [==============================] - 5s 14ms/step - loss: 0.8554 - accuracy: 0.6920 - val_loss: 0.8039 - val_accuracy: 0.7184
Epoch 10/10
352/352 [==============================] - 5s 14ms/step - loss: 0.8128 - accuracy: 0.7072 - val_loss: 0.7428 - val_accuracy: 0.7446
```

### 学習結果の表示
```python:
# エポックごとの正解率とlossの取得
acc = history.history['accuracy']
val_acc = history.history['val_accuracy']
loss = history.history['loss']
val_loss = history.history['val_loss']

# エポック数の取得
epochs = range(len(acc))

# 正解率の表示
plt.plot(epochs, acc, label='acc')
plt.plot(epochs, val_acc, label='val_acc')
plt.title('Training and validation accuracy')
plt.legend()
plt.show()

# lossの表示
plt.plot(epochs, loss, label='loss')
plt.plot(epochs, val_loss, label='val_loss')
plt.title('Training and validation loss')
plt.legend()
plt.show()
```
![accuracy_and_loss.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/304572/2924e5a3-a7dd-1fb2-1f38-065f99aabfd8.png)
エポック数が増えるにつれ、正解率は上がり、lossの値は小さくなっています。
今回は学習のエポック数10に設定しましたが、もっと学習させればより性能が上がることが期待できます。


## テストデータを用いたモデルの評価
```python:
# 評価
loss,acc = model.evaluate(x_test,y_test,verbose=0)
print('Test loss:',loss)
print('Test accuracy:',acc)
```

```terminal:出力画面
Test loss: 0.7661172151565552
Test accuracy: 0.7469000220298767
```
分類精度は正解率77%となりました。

## ソースコード全体
```python:
import numpy as np
import matplotlib.pyplot as plt

from keras.datasets import cifar10
from keras.utils.np_utils import to_categorical
from keras.models import Sequential
from keras.layers import Conv2D
from keras.layers import MaxPool2D
from keras.layers import Dense,Activation,Dropout,Flatten
from keras.layers import BatchNormalization

# CIFAR-10データセットをダウンロード
(x_train,y_train), (x_test,y_test) = cifar10.load_data()

# 画像を0~1の範囲で正規化
x_train = x_train.astype('float32')/255.0
x_test = x_test.astype('float32')/255.0

# 正解ラベルをOne-Hot表現に変換
y_train = to_categorical(y_train,10)
y_test = to_categorical(y_test,10)


#モデルを構築
model = Sequential()

model.add(Conv2D(32,(3,3),padding='same',input_shape=(32,32,3)))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(Conv2D(32,(3,3),padding='same'))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(MaxPool2D(pool_size=(2,2)))

model.add(Conv2D(64,(3,3),padding='same'))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(Conv2D(64,(3,3),padding='same'))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(MaxPool2D(pool_size=(2,2)))

model.add(Flatten())
model.add(Dense(512))
model.add(Activation('relu'))
model.add(Dropout(0.5))
model.add(Dense(10,activation='softmax'))

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

#モデルの表示
model.summary()


# モデルの学習
history = model.fit(x_train,y_train,
                    batch_size=128, # バッチサイズを128に設定
                    epochs=10, # エポック数を10に設定
                    verbose=1, #進捗状況をプログレスバーで表示
                    validation_split=0.1) # 訓練データの1割を検証データとして扱う


# エポックごとの正解率とlossの取得
acc = history.history['accuracy']
val_acc = history.history['val_accuracy']
loss = history.history['loss']
val_loss = history.history['val_loss']

# エポック数の取得
epochs = range(len(acc))

# 正解率の表示
plt.plot(epochs, acc, label='acc')
plt.plot(epochs, val_acc, label='val_acc')
plt.title('Training and validation accuracy')
plt.legend()
plt.show()

# lossの表示
plt.plot(epochs, loss, label='loss')
plt.plot(epochs, val_loss, label='val_loss')
plt.title('Training and validation loss')
plt.legend()
plt.show()


# 評価
loss,acc = model.evaluate(x_test,y_test,verbose=0)
print('Test loss:',loss)
print('Test accuracy:',acc)
```

---
### 関連記事
*   KerasでCIFAR-10の画像分類をやる【②モデルの保存と読み込み】