[Keras / CNN]  多クラス画像分類 --- ラーメンの味分類

#About
・CNNでラーメンの味分類
・多クラス分類（塩、しょうゆ、みそ）

#Point!
二値分類とのコードの違いは何か？　--- (見出し："前処理、ジェネレーター"へ）
精度が上がらない理由　　　　　　　--- (見出し : "考察"へ)

#コードの解説について
・多クラス分類特有の部分のみ解説
・二値分類と同じコードは過去記事で解説あり（赤ちゃんの表情分類）
https://qiita.com/Phoeboooo/items/2c7457d1bfba514e2dc8

#データ
・train :　塩 -- 2167枚　　しょうゆ -- 2301枚　みそ -- 2613枚
・validation :　塩 -- 360枚　　しょうゆ -- 364枚　みそ -- 410枚
・test : 塩 -- 331枚　　しょうゆ -- 338枚　みそ -- 409枚

#モデル
```python

from keras import layers
from keras import models


model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation='relu',
                        input_shape=(150, 150, 3)))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Dropout(0.2))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Dropout(0.2))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Dropout(0.2))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Dropout(0.2))
model.add(layers.Flatten())
model.add(layers.Dense(512, activation='relu'))
model.add(layers.Dropout(0.2))
model.add(layers.Dense(3, activation='softmax'))

from keras import optimizers

model.compile(loss='categorical_crossentropy',
              optimizer=optimizers.RMSprop(lr=1e-4),
              metrics=['acc'])

```
>
Total params: 3,454,147
Trainable params: 3,454,147
Non-trainable params: 0


・もちろん上記のモデルがベストではないはず
・activationが<b>softmax</b>　(二値分類では"sigmoid")
・最後のDense層のニューロン数がクラス数(3)


#前処理・ジェネレーター
```python

from keras.preprocessing.image import ImageDataGenerator

train_dir = 'Downloads/ramen/train'
validation_dir = 'Downloads/ramen/validation'

# rescaled by 1./255
train_datagen = ImageDataGenerator(rescale=1./255)
test_datagen = ImageDataGenerator(rescale=1./255)

train_generator = train_datagen.flow_from_directory(
        train_dir,     # directory
        target_size=(150, 150),     # input shape (resized to 150x150)
        batch_size=20,
        class_mode='categorical')    # categorical labels

validation_generator = test_datagen.flow_from_directory(
        validation_dir,
        target_size=(150, 150),
        batch_size=20,
        class_mode='categorical')

```
・二値分類との違い
→　class_modeが<b>"categorical"</b>　(二値分類では"binary")

>
Found 7081 images belonging to 3 classes.
Found 1134 images belonging to 3 classes.

#学習
```python

history = model.fit_generator(
      train_generator,
      steps_per_epoch=354,
      epochs=20,
      validation_data=validation_generator,
      validation_steps=56)
```

```python

Epoch 1/20
354/354 [==============================] - 606s 2s/step - loss: 1.0235 - acc: 0.4818 - val_loss: 1.0156 - val_acc: 0.4875
Epoch 2/20
354/354 [==============================] - 554s 2s/step - loss: 0.9353 - acc: 0.5576 - val_loss: 0.9883 - val_acc: 0.5116
Epoch 3/20
354/354 [==============================] - 557s 2s/step - loss: 0.8976 - acc: 0.5866 - val_loss: 0.9701 - val_acc: 0.5304
.
.
.
Epoch 18/20
354/354 [==============================] - 549s 2s/step - loss: 0.5529 - acc: 0.7716 - val_loss: 0.6864 - val_acc: 0.7071
Epoch 19/20
354/354 [==============================] - 549s 2s/step - loss: 0.5524 - acc: 0.7720 - val_loss: 0.6935 - val_acc: 0.7089
Epoch 20/20
354/354 [==============================] - 581s 2s/step - loss: 0.5374 - acc: 0.7826 - val_loss: 0.7253 - val_acc: 0.7000

```

#テスト

```python

from keras.models import load_model
from keras.preprocessing.image import ImageDataGenerator
model = load_model('ramen')

test_dir = 'downloads/ramen/test'
test_datagen = ImageDataGenerator(rescale=1./255)
test_generator = test_datagen.flow_from_directory(
    test_dir,
    target_size=(150,150),
    batch_size=20,
    class_mode='categorical')

test_loss, test_acc = model.evaluate_generator(test_generator, steps=54)
print('test loss:', test_loss)
print('test acc:', test_acc)

```
>
Found 1078 images belonging to 3 classes.
test loss: 0.6364766670069579
test acc: 0.7346938798732793

#モデルをシンプルにしたら・・・
```python

model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation='relu',
                        input_shape=(150, 150, 3)))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(GlobalAveragePooling2D())
model.add(layers.Dense(32, activation='relu'))
model.add(layers.Dense(3, activation='softmax'))

```
>
Total params: 245,059
Trainable params: 245,059
Non-trainable params: 0

##学習結果
```python

Epoch 1/30
354/354 [==============================] - 564s 2s/step - loss: 0.6535 - acc: 0.7249 - val_loss: 0.6807 - val_acc: 0.7116
Epoch 2/30
354/354 [==============================] - 557s 2s/step - loss: 0.6487 - acc: 0.7261 - val_loss: 0.6944 - val_acc: 0.7018
Epoch 3/30
354/354 [==============================] - 636s 2s/step - loss: 0.6383 - acc: 0.7301 - val_loss: 0.7004 - val_acc: 0.7143
Epoch 4/30
354/354 [==============================] - 627s 2s/step - loss: 0.6417 - acc: 0.7271 - val_loss: 0.6961 - val_acc: 0.7018
Epoch 5/30
354/354 [==============================] - 538s 2s/step - loss: 0.6432 - acc: 0.7340 - val_loss: 0.7023 - val_acc: 0.7045
```
51~55エポック目での学習結果
70%ぐらいから精度が上がらない

#考察
おそらく、分類の基準となったのはスープの色
しょうゆラーメンの中でも色に幅がある（塩、みそでも同様）
→　その幅を理解させるにはデータが少なすぎる

<b>データの質、量が結果に大きく関わる</b>
データの量と精度の比較検証については下記の記事で
https://qiita.com/Phoeboooo/items/f450cefc70fe1bf788c8