About
・CNNでラーメンの味分類
・多クラス分類(塩、しょうゆ、みそ)
Point!
二値分類とのコードの違いは何か? --- (見出し:"前処理、ジェネレーター"へ)
精度が上がらない理由 --- (見出し : "考察"へ)
コードの解説について
・多クラス分類特有の部分のみ解説
・二値分類と同じコードは過去記事で解説あり(赤ちゃんの表情分類)
https://qiita.com/Phoeboooo/items/2c7457d1bfba514e2dc8
データ
・train : 塩 -- 2167枚 しょうゆ -- 2301枚 みそ -- 2613枚
・validation : 塩 -- 360枚 しょうゆ -- 364枚 みそ -- 410枚
・test : 塩 -- 331枚 しょうゆ -- 338枚 みそ -- 409枚
モデル
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がsoftmax (二値分類では"sigmoid")
・最後のDense層のニューロン数がクラス数(3)
前処理・ジェネレーター
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が"categorical" (二値分類では"binary")
Found 7081 images belonging to 3 classes.
Found 1134 images belonging to 3 classes.
学習
history = model.fit_generator(
train_generator,
steps_per_epoch=354,
epochs=20,
validation_data=validation_generator,
validation_steps=56)
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
テスト
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
モデルをシンプルにしたら・・・
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
学習結果
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%ぐらいから精度が上がらない
考察
おそらく、分類の基準となったのはスープの色
しょうゆラーメンの中でも色に幅がある(塩、みそでも同様)
→ その幅を理解させるにはデータが少なすぎる
データの質、量が結果に大きく関わる
データの量と精度の比較検証については下記の記事で
https://qiita.com/Phoeboooo/items/f450cefc70fe1bf788c8