クルマの写真を「ベンツか？BMWか？」見分けるモデルをkerasで作ってみた


# やりたいこと

* クルマの写真を「ベンツ」と「BMW」に分類できるモデルを作りたい。
* 学習用画像の取り込みから、モデルの作成と学習、モデルの保存、検証まで一連の流れを試す。
* 精度の向上は別途。


# 前提

* 学習用画像は収集済であること（必要に応じてスクレイピング等を実装してください）。
* 下記をざっくり理解していること。

### 環境構築
https://qiita.com/kenichiro-yamato/items/ec944220321a58310f1f

### 画像判定のチュートリアル
https://qiita.com/kenichiro-yamato/items/b64c70882473904600bf

### kerasのConv2の話
https://qiita.com/kenichiro-yamato/items/60affeb7ca9f67c87a17

# 実装（サンプルコード）

### cnn_car_benz_bmw.py

```python
import keras
from keras.datasets import cifar10
from keras.models import Sequential
from keras.layers import Dense, Dropout, Activation, Flatten
from keras.layers import Conv2D, MaxPooling2D
from PIL import Image
import numpy as np
import glob
import os

num_classes = 2
im_rows = 32
im_cols = 32
in_shape = (im_rows, im_cols, 3)

X = []
Y = []

# for Benz
for f in glob.glob('/share/images/benz/*.jpg'):
    fname = os.path.split(f)[1]
    file_path = '/share/images/benz/' + fname
    im = np.asarray(Image.open(file_path))
    X.append(im)
    label = [0] # zero is benz
    Y.append(label)

# for Bmw
for f in glob.glob('/share/images/bmw/*.jpg'):
    fname = os.path.split(f)[1]
    file_path = '/share/images/bmw/' + fname
    im = np.asarray(Image.open(file_path))
    X.append(im)
    label = [1] # one is bmw.
    Y.append(label)

X = np.array(X)
Y = np.array(Y)
print(X.shape)
print(Y.shape)

# Images
X_train = X.astype('float32') / 255

# Labels
Y_train = keras.utils.to_categorical(Y, num_classes)

model = Sequential()
model.add(Conv2D(32, (3, 3), padding='same', input_shape=in_shape))
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))

model.add(Conv2D(64, (3, 3), padding='same'))
model.add(Activation('relu'))
model.add(Conv2D(64, (3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))

model.add(Flatten())
model.add(Dense(512))
model.add(Activation('relu'))
model.add(Dropout(0.5))
model.add(Dense(num_classes))
model.add(Activation('softmax'))

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

hist = model.fit(X_train, Y_train, batch_size=32, epochs=50, verbose=1)

save_model_name = 'yamato_benz_or_bmw_model'
model.save(save_model_name)
```


# 実装サンプルコードの解説

### インポート

```python
import keras
from keras.datasets import cifar10
from keras.models import Sequential
from keras.layers import Dense, Dropout, Activation, Flatten
from keras.layers import Conv2D, MaxPooling2D
from PIL import Image
import numpy as np
import glob
import os
```

### 学習データ準備

下記のように、学習用のjpg画像ファイル一式を、任意のディレクトリに置いておく。
すべての画像ファイルは、形式はjpgで、width:32px, height:32px にリサイズしておく。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75451/df604171-0278-74f0-2e4e-2ce2e9a6ffd9.png)


```
/share/images/benz/1.jpg
/share/images/benz/2.jpg
  :

/share/images/bmw/1.jpg
/share/images/bmw/2.jpg
  :
```

### 学習データの読み込み

```python
num_classes = 2
im_rows = 32
im_cols = 32
in_shape = (im_rows, im_cols, 3)

X = []
Y = []

# for Benz
for f in glob.glob('/share/images/benz/*.jpg'):
    fname = os.path.split(f)[1]
    file_path = '/share/images/benz/' + fname
    im = np.asarray(Image.open(file_path))
    X.append(im)
    label = [0] # zero is benz
    Y.append(label)

# for Bmw
for f in glob.glob('/share/images/bmw/*.jpg'):
    fname = os.path.split(f)[1]
    file_path = '/share/images/bmw/' + fname
    im = np.asarray(Image.open(file_path))
    X.append(im)
    label = [1] # one is bmw.
    Y.append(label)

X = np.array(X)
Y = np.array(Y)
print(X.shape)
print(Y.shape)

```

 * X = [] には学習用の画像データを、Y = [] には学習用のラベルデータを積んでいく。
 * num_classes には、クラスの分類数を指定する。今回は benz と bmw の二種類なので、2となる。
 * im_rows = 32 と im_cols = 32 は、学習データのjpg画像の縦横ピクセル数を指定する。学習データのjpg画像は、あらかじめこの縦横ピクセルにリサイズされていることが前提となる。


print(X.shape), print(Y.shape) の結果は、

```
(2102, 32, 32, 3)
(2102, 1)
```
となる。ここで2101は画像の枚数である（今回はBenzとBMWで約1000枚ずつ用意したので）。32はピクセル数、3はRGBを意味する。



## 学習

```python
# Images
X_train = X.astype('float32') / 255

# Labels
Y_train = keras.utils.to_categorical(Y, num_classes)

model = Sequential()
model.add(Conv2D(32, (3, 3), padding='same', input_shape=in_shape))
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))

model.add(Conv2D(64, (3, 3), padding='same'))
model.add(Activation('relu'))
model.add(Conv2D(64, (3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))

model.add(Flatten())
model.add(Dense(512))
model.add(Activation('relu'))
model.add(Dropout(0.5))
model.add(Dense(num_classes))
model.add(Activation('softmax'))

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

hist = model.fit(X_train, Y_train, batch_size=32, epochs=50, verbose=1)
```



## モデル保存

```python
save_model_name = 'yamato_benz_or_bmw_model'
model.save(save_model_name)
```











# 判定させる

学習済モデルを使って、写真を判定できるか確認する。

### モデル

学習済モデルは
yamato_benz_or_bmw_model
で保存済みとする。

```
ll yamato_benz_or_bmw_model/
total 244
drwxr-xr-x  4 root root     59 Nov 27 05:37 ./
drwxrwxrwx. 6 root root   4096 Nov 27 05:54 ../
drwxr-xr-x  2 root root      6 Nov 27 05:37 assets/
-rw-r--r--  1 root root 239774 Nov 27 05:37 saved_model.pb
drwxr-xr-x  2 root root     66 Nov 27 05:37 variables/
```

### ラベル

 * 0 : ベンツ
 * 1 : BMW


### 実装

```python
from tensorflow import keras
from keras.models import Sequential
from keras.layers import Dense, Activation
import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf
from PIL import Image

save_model_name = 'yamato_benz_or_bmw_model'
model = tf.keras.models.load_model(save_model_name)

LABELS = ["benz","bmw"]

input_file_name = 'input.jpg'

im = np.array(Image.open(input_file_name))
im = im / 255.0
input_array = np.array([im])
pred = model.predict(input_array)
print("---------- pred  ----------")
print(pred)
print("---------- argmax ----------")
print(np.argmax(pred))

print("---------- result ----------")
for i, label in enumerate(LABELS):
    print('socre of {label:10}: {score:0.3f}' . format(label=label, score=pred[0,i]))
```

判定したい画像を

 * jpg
 * width:32px, height:32px

にて、input.jpg として保存しておく。

```python
input_file_name = 'input.jpg'
```

で読み込む。


# 結果

下記のように「ベンツなら0」「BMWなら1」として、スコアが高いほうを返す。
かつ、それぞれの確率を返す。

```
---------- pred  ----------
[[0.35300204 0.6469979 ]]
---------- argmax ----------
1
---------- result ----------
socre of benz      : 0.353
socre of bmw       : 0.647
```

この例では「BMW」と判定しているが、スコアが 0.647 であることを考えると、
実際は「どちらかよくわからない」つまり「判定できていない」ということになる。
あとは、判定の正確性を高めるために、ひたすらモデルを学習させていく。