初めてPythonで動くモノを作ってみた結果（画像認識）



##1はじめに
こんにちは、Aidemy研修生のMagicchicです。皆さんはプログラミングというものに対してどのようなイメージを持っていますか？私がプログラミングに触れてから2ヶ月ほど経ちますが難しいという感触は抜けません。しかしそれ以上にエラーを改善して動かすことができると嬉しいです。今回はそんな初学者がpythonを用いて初めて動くモノを作ってみた結果を共有したいと思います。
##2画像認識とは
最近よく聞くカメラの顔認証や工場での不良品検知など、画像認識という技術が活躍しています。写真から動物の種類を判定したりできるようにアプリでも似てる芸能人診断みたいなのもあります。こういった、高度な画像認識を実現している技術がCNN（畳み込みニューラルネットワーク）です。畳み込みニューラルネットワーク（Convolution Neural　Network)とは、AIが画像分析を行うための学習手法の１つで、一部が見えにくくなっているような画像でも解析することができます。略してCNNとよばれることもあります。

畳み込み層とプーリング層という２つの層を含む構造の順伝播型のネットワークで、特徴として、それぞれの層の間に生物の脳の視覚野に関する脳科学の知見にヒントを得た、「局所受容野」「重み共有」という結合をもっています。

”多層構造”に加え、工夫された２つの隠れ層という”構造”が組み込まれたニューラルネットワークといえます。

分析する画像が入力層に読み込まれた後、このデータをくまなくスキャンし、データの特徴（勾配、凹凸など）を抽出するために使われるのがフィルタです。抽出された特徴データは畳み込み層に送られ、そこで更に特徴の凝縮されたデータが作成されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/916857/6ea8ce0b-32f2-1019-2496-f7dfbd2968cb.png)


そして、そのCNNを誰もが簡単に利用できるようにしてくれたライブラリが、Kerasです。もし画像認識プログラムを作成するならば、KerasでCNNを作成するのが近道でしょう。
###kerasとは
Kerasは，Pythonで書かれた，TensorFlowまたはTheano上で実行可能な高水準のニューラルネットワークライブラリです． Kerasは，迅速な実験を可能にすることに重点を置いて開発されました． 可能な限り遅れなくアイデアから結果に進められることは，良い研究をする上で重要です．(keras公式ドキュメントより）


##3導入と手順
今回設定したテーマとして車種の判定をしようと思いました。私は車というものに対して全くの無知です。よく車を見るだけで車種を言い当てられる人を見るとすごいなと思います。対抗するというわけではありませんが機械学習で同じようなことができたらいいなと思いました。対象車種はトヨタの国産高級車から3つピックアップ

---

大まかな手順は以下の通りです。
1.画像収集
2.データを変換、その後学習データにnpyで保存
3.データを増やす
4.学習モデル、評価関数の構築
5.結果

##4説明
ここから先は上で示した手順に沿って書きます。
今回はicrawlerを用いて収集しました。

```
pip install icrawler
```

これをターミナルで実行してまずはicrawlerをインストールします。
その後テキストエディタにて

```cpp
from icrawler.builtin import BingImageCrawler
crawler = BingImageCrawler(storage={"root_dir": "toyotacentury"})
crawler.crawl(keyword="toyotacentury", max_num=100)

from icrawler.builtin import BingImageCrawler
crawler = BingImageCrawler(storage={"root_dir": "toyotacrown"})
crawler.crawl(keyword="toyotacrown", max_num=120)

from icrawler.builtin import BingImageCrawler
crawler = BingImageCrawler(storage={"root_dir": "toyotamarkx"})
crawler.crawl(keyword="toyotamarkx", max_num=120)
```
---
次に集めたデータのうちデータとして使えなさそうなもの（関係のないものやはっきりとしていないもの）を目視で削除した結果それぞれ80枚になりました。それらをnpyで保存します。



```cpp
from PIL import Image
import os, glob
import numpy as np
import sklearn
from sklearn import model_selection

classes = ["toyotacentury", "toyotacrown", "toyotamarkx"]
num_classes = len(classes)
image_size = 100

# 画像の読み込み、numpyの配列に変換
X = []
Y = []
for index, classlabel in enumerate(classes):
    photos_dir = "./" + classlabel
    files = glob.glob(photos_dir + "/*.jpg")
    for i, file in enumerate(files):
        if i >= 93: break
        image = Image.open(file)
        image = image.convert("RGB")
        image = image.resize((image_size, image_size))
        data = np.asarray(image)
        X.append(data)
        Y.append(index)
#listからnumpyに変換
X = np.array(X)
Y = np.array(Y)
#データを学習用と評価用に分割する
X_train, X_test, y_train, y_test = model_selection.train_test_split(X, Y)
xy = (X_train, X_test, y_train, y_test)
np.save("./toyotacar.npy", xy)
```


---
続いてデータを増やす作業です。現在の枚数では足りないはずなので対象のフォルダ内の画像を増加させます。

```cpp
import os
import glob
import numpy as np
from keras.preprocessing.image import ImageDataGenerator,load_img, img_to_array, array_to_img

# 画像を拡張する関数
def draw_images(generator, x, dir_name, index):
    save_name = 'extened-' + str(index)
    g = generator.flow(x, batch_size=1, save_to_dir=output_dir,
                       save_prefix=save_name, save_format='jpeg')

    # 1つの入力画像から何枚拡張するかを指定（今回は10枚）
    for i in range(10):
        bach = g.next()

# 出力先フォルダの設定
output_dir = "toyotacenturyzou"

if not(os.path.exists(output_dir)):
    os.mkdir(output_dir)

# 拡張する画像読み込み
images = glob.glob(os.path.join("toyotacentury", "*.jpg"))

# ImageDataGeneratorを定義
datagen = ImageDataGenerator(rotation_range=20,
                            width_shift_range=0,
                            shear_range=0,
                            height_shift_range=0,
                            zoom_range=0,
                            horizontal_flip=True,
                            fill_mode="nearest",
                            channel_shift_range=40)

# 画像拡張
for i in range(len(images)):
    img = load_img(images[i])
    img = img.resize((350,300 ))
    x = img_to_array(img)
    x = np.expand_dims(x, axis=0)
    draw_images(datagen, x, output_dir, i)

import os
import glob
import numpy as np
from keras.preprocessing.image import ImageDataGenerator,load_img, img_to_array, array_to_img

# 画像を拡張する関数
def draw_images(generator, x, dir_name, index):
    save_name = 'extened-' + str(index)
    g = generator.flow(x, batch_size=1, save_to_dir=output_dir,
                       save_prefix=save_name, save_format='jpeg')

    # 1つの入力画像から何枚拡張するかを指定（今回は10枚）
    for i in range(10):
        bach = g.next()

# 出力先フォルダの設定
output_dir = "toyotacrownzou"

if not(os.path.exists(output_dir)):
    os.mkdir(output_dir)

# 拡張する画像読み込み
images = glob.glob(os.path.join("toyotacrown", "*.jpg"))

# ImageDataGeneratorを定義
datagen = ImageDataGenerator(rotation_range=20,
                            width_shift_range=0,
                            shear_range=0,
                            height_shift_range=0,
                            zoom_range=0,
                            horizontal_flip=True,
                            fill_mode="nearest",
                            channel_shift_range=40)

# 画像拡張
for i in range(len(images)):
    img = load_img(images[i])
    img = img.resize((350,300 ))
    x = img_to_array(img)
    x = np.expand_dims(x, axis=0)
    draw_images(datagen, x, output_dir, i)


import os
import glob
import numpy as np
from keras.preprocessing.image import ImageDataGenerator,load_img, img_to_array, array_to_img

# 画像を拡張する関数
def draw_images(generator, x, dir_name, index):
    save_name = 'extened-' + str(index)
    g = generator.flow(x, batch_size=1, save_to_dir=output_dir,
                       save_prefix=save_name, save_format='jpeg')

    # 1つの入力画像から何枚拡張するかを指定（今回は10枚）
    for i in range(10):
        bach = g.next()

# 出力先フォルダの設定
output_dir = "toyotamarkxzou"

if not(os.path.exists(output_dir)):
    os.mkdir(output_dir)

# 拡張する画像読み込み
images = glob.glob(os.path.join("toyotamarkx", "*.jpg"))

# ImageDataGeneratorを定義
datagen = ImageDataGenerator(rotation_range=20,
                            width_shift_range=0,
                            shear_range=0,
                            height_shift_range=0,
                            zoom_range=0,
                            horizontal_flip=True,
                            fill_mode="nearest",
                            channel_shift_range=40)

# 画像拡張
for i in range(len(images)):
    img = load_img(images[i])
    img = img.resize((350,300 ))
    x = img_to_array(img)
    x = np.expand_dims(x, axis=0)
    draw_images(datagen, x, output_dir, i)
```

この作業により元あった画像数の10倍、800枚まで増加しました。

---

####それでは最後に学習モデル、評価関数の構築に移ります。
最初に注意しておきたい部分としてメイン関数の定義をする際にnp.loadで()内にデータの指定をするとエラーが発生するかも知れません。そこでallow_pickleオプションを指定すると改善されると思います。
以下リンク参照
(https://qiita.com/ytkj/items/ee6e1125476883923db8)

```cpp
from keras.models import Sequential
from keras.layers import Conv2D, MaxPooling2D
from keras.layers import Activation, Dropout, Flatten, Dense
from keras.utils import np_utils
import keras
import numpy as np
from keras.optimizers import RMSprop

classes = ["toyotacentury", "toyotacrown", "toyotamarkx"]
num_classes = len(classes)
image_size = 100

# メイン関数の定義

def main():
    X_train, X_test, y_train, y_test = np.load("./toyotacar.npy", allow_pickle=True)#ファイルからデータを配列に読み込む
    X_train = X_train.astype("float") / 256#データを正規化
    X_test = X_test.astype("float") / 256
    y_train = np_utils.to_categorical(y_train, num_classes)
    y_test = np_utils.to_categorical(y_test, num_classes)

#トレーニング関数と評価関数の呼び出し
    model = model_train(X_train, y_train)
    model_eval(model, X_test, y_test)
    
def model_train(X, y):
    model = Sequential()
    model.add(Conv2D(32,(3,3), padding='same',input_shape=X.shape[1:]))
    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,(2,2), padding='same'))
    model.add(Activation('relu'))
    model.add(Conv2D(64,(3,3)))
    model.add(Activation('relu'))
    model.add(MaxPooling2D(pool_size=(3,3)))
    model.add(Dropout(0.25))

    model.add(Flatten())
    model.add(Dense(512))
    model.add(Activation('relu'))
    model.add(Dropout(0.5))
    model.add(Dense(3))
    model.add(Activation('softmax'))
#最適化の処理
    opt = keras.optimizers.RMSprop(lr=0.0001, decay=1e-6)
#正解と推定値の誤差が小さくなるようにする
    model.compile(loss='categorical_crossentropy',optimizer=opt,metrics=['accuracy'])
    model.fit(X, y, batch_size=20, epochs=75)

    # モデルの保存
    model.save('./toyota_cnn.h5')

    return model

def model_eval(model, X, y):
    scores = model.evaluate(X, y, verbose=1)
    print('Test Loss: ', scores[0])
    print('Test Accuracy: ', scores[1])

if __name__ == "__main__":
    main()
```

実行をした際にAttributeErrorが発生するかもしれません(私だけかも知れませんが）、下記のリンクにて原因を詳しく説明していたので添付しておきます。
(https://ja.stackoverflow.com/questions/48286/python%e3%81%a7attributeerror)

実行結果は以下のようになりました。

```
Test Loss:  2.74328875541687
Test Accuracy:  0.4833333194255829
```

##5結果の考察と今後の展望

今回の実行結果ではaccuracyは50%を下回る正確性を欠いた状況、lossは0から大きくかけ離れた数値になりました。数値上昇のため画像の切り抜きや枚数増加、epoch数を変動させ最適解を見つけたいと思います。

特に「車」というテーマ上、形の大きな枠組みは大して特徴差はありません。accuracyを1.0、lossをoにできる限り近づけた後、画像を受け取り、判定する関数を定義して予測の実行を行いたいと思います。
#####今後の作成コードの参考予定リンク
(https://qiita.com/kenichiro-yamato/items/b64c70882473904600bf)


##参考

(https://qiita.com/kazama0119/items/ede4732d21fe00085eb6)
(https://qiita.com/keimoriyama/items/846a3462a92c8c5661ff)
(https://qiita.com/keimoriyama/items/7b09d7c1797fcee6a2b0)
(https://udemy.benesse.co.jp/data-science/ai/convolution-neural-network.html)
(https://dev.classmethod.jp/articles/introduction-keras-deeplearning/)