kerasでの画像認識

#はじめに
Kerasを使って画像分類プログラムを作成します。
シュークリームとエクレアの分類にチャレンジしてみます。
実行環境は以下のものです

* python                    3.5.6
* Keras                  2.2.0 
* tensorflow             1.5.
* matplotlib             2.2.2    
* numpy                  1.15.2 
* opencv                    3.4.2  


#画像の収集
僕のスマホの中だけでも20~30枚はあったのですがさすがにそれだけでは足りないのでスクレイピングで画像を集めることにしました。

###flickrAPIでのスクレイピング
画像を集めるにあたって一枚一枚見ながら保存していくのは時間がかかりそうだったのでflickrAPIを使って手っ取り早く集めることにしました。
下の記事を参考にしながら集めました。
[Flickrを使って画像ファイルをダウンロードする](http://ykubot.com/2017/11/05/flickr-api/)

```flickr.py
key = "xxxxxxxx"
secret = "xxxxxx"

```
###画像の水増し
学習用の画像を
・反転
・膨張
・縮小
・ぼかし
　　　等をして画像の枚数を増やしていきます。

#cream.ipynb
###インポート　　
```bunrui.py
import os
import cv2
import numpy as np
import matplotlib.pyplot as plt
from keras.utils.np_utils import to_categorical
from keras.layers import Dense, Dropout, Flatten, Input
from keras.applications.vgg16 import VGG16
from keras.models import Model, Sequential
from keras import optimizers

```
必要なライブラリのインポート
今回はVGG16を使います。

###listdir関数でディレクトリとファイルの一覧を取得する
```cream.ipynb
path_cream = os.listdir('./cream_puff')
path_eclair = os.listdir('./eclair')
path_cream_puff_test = os.listdir('./cream_puff_test')
path_eclair_test = os.listdir('./eclair_test')
```
###リサイズ
```
input_tensor = Input(shape=(50, 50, 3))
```
となっているので、各画像を(50,50,3)にリサイズする

```
for i in range(len(path_cream)):
    img = cv2.imread('cream_puff/' + path_cream[i])
    if(type(img) != type(None)):
        img = cv2.resize(img ,(50,50))
        img_cream.append(img)
```
のようにする。
img = cv2.resize(img, (50,50))
の時にエラーが続き、imreadの出力がNoneとなっているものまでリサイズいようとしている可能性がありこれを画像を読み込めなかった際にリサイズおよびappendをしないという方法で回避します。
if(type(img) != type(None)):
のところがそれにあたります。

###出力層のチャンネル数と活性化関数
2値分類問題ですので、出力層として
top_model.add(Dense(2, activation='softmax'))
を追加します。

```
top_model.add(Dense(2, activation='softmax')
```

###シュークリームかエクレアを判定する関数

```
def cream(img):
    img = cv2.resize(img, (50, 50))
    pred = np.argmax(model.predict(np.array([img])))
    if pred == 0:
        return 'これはシュークリームです'
    else:
        return 'これはエクレアです'

```

###モデルの定義

```
top_model = Sequential()
```
モデルを定義します。

###層の追加

```
top_model.add(Flatten(input_shape=vgg16.output_shape[1:]))
top_model.add(Dense(256, activation='relu'))
top_model.add(Dropout(0.5))
```

###学習

```
model.fit(X_train, y_train, batch_size=100, epochs=20, validation_data=(X_test, y_test))
```
###予測

```
for i in range(10):
    img = cv2.imread('cream_puff_test/' + path_cream_puff_test[i])
    if(type(img) != type(None)):
        plt.imshow(img)
        plt.show()
        print(cream(img))    
```
cream_puff_testファイルの最初から10枚を予想します。

#cream.ipynb全文

```
import os
import cv2
import numpy as np
import matplotlib.pyplot as plt
from keras.utils.np_utils import to_categorical
from keras.layers import Dense, Dropout, Flatten, Input
from keras.applications.vgg16 import VGG16
from keras.models import Model, Sequential
from keras import optimizers

number = 100

path_cream = os.listdir('./cream_puff')
path_eclair = os.listdir('./eclair')
path_cream_puff_test = os.listdir('./cream_puff_test')
path_eclair_test = os.listdir('./eclair_test')

img_cream = []
img_eclair = []
img_cream_puff_test = []
img_eclair_test= []

for i in range(len(path_cream)):
    img = cv2.imread('cream_puff/' + path_cream[i])
    if(type(img) != type(None)):
        img = cv2.resize(img ,(50,50))
        img_cream.append(img)

for i in range(len(path_eclair)):
    img = cv2.imread('eclair/' + path_eclair[i])
    if(type(img) != type(None)):
        img = cv2.resize(img, (50,50))
        img_eclair.append(img)
        
for i in range(len(path_cream_puff_test)):
    img = cv2.imread('cream_puff_test/' + path_cream_puff_test[i])
    if(type(img) != type(None)):
        my_img = cv2.resize(img, (50,50))
        img_cream_puff_test.append(my_img)
    
for i in range(len(path_eclair_test)):
    img = cv2.imread('eclair_test/' + path_eclair_test[i])
    if(type(img) != type(None)):
        my_img = cv2.resize(img, (50,50))
        img_eclair_test.append(my_img)
        
X = np.array(img_cream + img_eclair)
y =  np.array([0]*len(img_cream) + [1]*len(img_eclair))

X_t = np.array(img_cream_puff_test + img_eclair_test)
y_t = np.array([0]*len(img_cream_puff_test) +[1]*len(img_eclair_test))

rand_index = np.random.permutation(np.arange(len(X)))
rand_index_2 = np.random.permutation(np.arange(len(X_t)))

X = X[rand_index]
y = y[rand_index]

X_t = X_t[rand_index_2]
y_t = y_t[rand_index_2]

# データの分割
X_train = X[:int(len(X)*0.8)]
y_train = y[:int(len(y)*0.8)]
X_test = X_t[int(len(X_t)*0.8):]  
y_test = y_t[int(len(y_t)*0.8):] 

# 正解ラベルをone-hotの形にします
y_train = to_categorical(y_train)
y_test = to_categorical(y_test)

# モデルにvggを使います
input_tensor = Input(shape=(50, 50, 3))
vgg16 = VGG16(include_top=False, weights='imagenet', input_tensor=input_tensor)

# vggのoutputを受け取り、2クラス分類する層を定義します
top_model = Sequential()
top_model.add(Flatten(input_shape=vgg16.output_shape[1:]))
top_model.add(Dense(256, activation='relu'))
top_model.add(Dropout(0.5))
top_model.add(Dense(2, activation='softmax'))

# vggと、top_modelを連結します
model = Model(input=vgg16.input, output=top_model(vgg16.output))

# vggの層の重みを変更不能にします
for layer in model.layers[:15]:
    layer.trainable = False

# コンパイルします
model.compile(loss='categorical_crossentropy',
              optimizer=optimizers.SGD(lr=1e-4, momentum=0.9),
              metrics=['accuracy'])

# 学習を行います
model.fit(X_train, y_train, batch_size=100, epochs=20, validation_data=(X_test, y_test))

# 画像を一枚受け取り、シュークリームかエクレアかを判定する関数
def cream(img):
    img = cv2.resize(img, (50, 50))
    pred = np.argmax(model.predict(np.array([img])))
    if pred == 0:
        return 'これはシュークリームです'
    else:
        return 'これはエクレアです'

scores = model.evaluate(X_test, y_test, verbose=1)
print('Test loss 間違い具合:', scores[0])
print('Test accuracy 正解率:', scores[1])
    
# cream関数に写真を渡して性別を予測します
for i in range(10):
    img = cv2.imread('cream_puff_test/' + path_cream_puff_test[i])
    if(type(img) != type(None)):
        b,g,r = cv2.split(img)
        img = cv2.merge([r,g,b])
        plt.imshow(img)
        plt.show()
        print(cream(img))    
```

###実行例

```
Train on 176 samples, validate on 259 samples
Epoch 1/20
176/176 [==============================] - 43s 247ms/step - loss: 6.7329 - acc: 0.5057 - val_loss: 4.3862 - val_acc: 0.6178
Epoch 2/20
176/176 [==============================] - 43s 245ms/step - loss: 4.3240 - acc: 0.6591 - val_loss: 3.1685 - val_acc: 0.7297
Epoch 3/20
176/176 [==============================] - 43s 243ms/step - loss: 3.3006 - acc: 0.7216 - val_loss: 1.8599 - val_acc: 0.8456
Epoch 4/20
176/176 [==============================] - 43s 244ms/step - loss: 2.8053 - acc: 0.7670 - val_loss: 1.5331 - val_acc: 0.8649
Epoch 5/20
176/176 [==============================] - 44s 248ms/step - loss: 1.1428 - acc: 0.8807 - val_loss: 1.2807 - val_acc: 0.8803
Epoch 6/20
176/176 [==============================] - 43s 245ms/step - loss: 1.1941 - acc: 0.8920 - val_loss: 1.1303 - val_acc: 0.8996
Epoch 7/20
176/176 [==============================] - 44s 249ms/step - loss: 1.0682 - acc: 0.9148 - val_loss: 0.9442 - val_acc: 0.9151
Epoch 8/20
176/176 [==============================] - 43s 244ms/step - loss: 0.8357 - acc: 0.9318 - val_loss: 1.2015 - val_acc: 0.9073
Epoch 9/20
176/176 [==============================] - 43s 246ms/step - loss: 0.7434 - acc: 0.9261 - val_loss: 1.2330 - val_acc: 0.8996
Epoch 10/20
176/176 [==============================] - 43s 247ms/step - loss: 0.6037 - acc: 0.9489 - val_loss: 0.8309 - val_acc: 0.9344
Epoch 11/20
176/176 [==============================] - 43s 246ms/step - loss: 0.3860 - acc: 0.9716 - val_loss: 0.6467 - val_acc: 0.9537
Epoch 12/20
176/176 [==============================] - 44s 248ms/step - loss: 0.8019 - acc: 0.9432 - val_loss: 0.6289 - val_acc: 0.9575
Epoch 13/20
176/176 [==============================] - 49s 277ms/step - loss: 0.4585 - acc: 0.9716 - val_loss: 0.6162 - val_acc: 0.9614
Epoch 14/20
176/176 [==============================] - 48s 275ms/step - loss: 0.3828 - acc: 0.9716 - val_loss: 0.6003 - val_acc: 0.9537
Epoch 15/20
176/176 [==============================] - 45s 257ms/step - loss: 0.6700 - acc: 0.9545 - val_loss: 0.6224 - val_acc: 0.9575
Epoch 16/20
176/176 [==============================] - 46s 259ms/step - loss: 0.4346 - acc: 0.9659 - val_loss: 0.6814 - val_acc: 0.9537
Epoch 17/20
176/176 [==============================] - 42s 241ms/step - loss: 0.3981 - acc: 0.9659 - val_loss: 0.6966 - val_acc: 0.9537
Epoch 18/20
176/176 [==============================] - 42s 241ms/step - loss: 0.0916 - acc: 0.9943 - val_loss: 0.7299 - val_acc: 0.9498
Epoch 19/20
176/176 [==============================] - 42s 240ms/step - loss: 0.3536 - acc: 0.9773 - val_loss: 0.7361 - val_acc: 0.9421
Epoch 20/20
176/176 [==============================] - 43s 245ms/step - loss: 0.5549 - acc: 0.9602 - val_loss: 0.6192 - val_acc: 0.9537
259/259 [==============================] - 21s 81ms/step
```

```
Test loss 間違い具合: 0.6191888309252883
Test accuracy 正解率: 0.9536679536679536
```

![image.png](https://qiita-image-store.s3.amazonaws.com/0/361344/a4f270ed-b8d9-538a-e01e-145ba0bd2b2d.png)

```
これはシュークリームです。
```

上のような結果が得られるようになりました。

初めに100枚ずつの画像でやってみたのですが正解率はかなり低く当たらなかったのですが枚数を増やしたところ正解率が上がり当たるようになりました。
epochs=20　のところまでは数を増やせば loss の数値が順調に小さくなっていきました。