簡単画像判別AIをつくる[綾鷹AI]

##前書き
春休みに入り、特にやることがないので前から気になっていた[記事](https://qiita.com/tomo_20180402/items/e8c55bdca648f4877188)を元に、リアルタイムに表示するAIアプリを作ろうと考えていた。
苦しめられながらも完成させるまでの道筋を備忘録として残す。

ちなみに現在AppleSiliconM1 ではKerasが対応していない為、MacBookPro2014で作業した
モデル構築の際、140枚×10種類＝1400枚の学習は5分程度で終了した。


参考
[画像認識で「綾鷹を選ばせる」AIを作る](https://qiita.com/tomo_20180402/items/e8c55bdca648f4877188)
[機械学習入門者が一日でシェルティー判定AIを作成してみた](https://qiita.com/maitake9116/items/df733df5e608f8616d14)

#環境を作る

##仮想環境の構築

<details><summary>2年前の俺へ</summary><div>

```terminal
$ cd
$ mkdir python_project  # python_projectファイルを作る
$ cd python_project
$ python3 -m venv env1  # python_projectファイルの中にenv1(仮想環境設定ファイル)を作る
$ source /'Path'/env1/bin/activate  # 仮想環境を有効化にする
```
(env) (base)　  ← こんなのがついてたら成功

ターミナルを開いてターミナルにファイルをドラック＆ペーストしてみたら
Path(ファイルがどこに保存されているかの道筋)が簡単にわかる
</div></details>

```terminal
$ python3 -m venv env1  
$ source /'Path'/env1/bin/activate  
```

##ライブラリのインストール



KerasはTensorflowに含まれたのでインストールは必要ない

```terminal
$ pip install tensorflow
$ pip install numpy
$ pip install matplotlib
$ pip install pillow

```


以上で環境設定は完了
それ以外にもVScodeを使ったが説明がめんどくさいので割愛


# 構成

```
└─AyatakaAI
   ├─data
   │ ├─images
   │ │ ├─original              : 元画像
   │ │ │ ├─Ayataka             : 綾鷹の写真データ集
   │ │ │ ├─16Tea               : 十六茶の写真データ集
   │ │ │ └─ほか8種類,多いので割愛
   │ │ ├─extended              : 加工して水増しした画像
   │ │ │ ├─Ayataka             : 水増しした綾鷹の写真データ集
   │ │ │ ├─16Tea               : 水増しした十六茶の写真データ集
   │ │ │ └─ほか8種類,多いので割愛
   │ │ └─testData              : AI の動作確認用の画像
   │ │   ├─test1.jpg           : 新しく撮った綾鷹の写真データ
   │ │   └─test2.jpg           : お茶を2本並べてみた写真データ
   │ ├─train_test_data         : extended の画像を使って作成した教師／テストデータ
   │ │ └─data.npy              
   │ └─model                   : 作成したモデル
   │   ├─Training_and_validation_accuracy.png
   │   ├─Training_and_validation_loss.png
   │   ├─model_predict.hdf5
   │   └─model_predict.json
   ├─img_duplicator            : 画像水増しデータ作成
   │ └─main.py
   ├─train_test_data_generator : 教師／テストデータ作成
   │ └─main.py
   ├─model_generator           : モデル生成
   │ └─main.py
   └─ai                        : 画像判定
     └─main.py
```

<details><summary>事前に用意するフォルダは</summary><div>

```
└─AyatakaAI
   ├─data
   │ ├─images
   │ │ ├─original              : 元画像
   │ │ │ ├─Ayataka             : 綾鷹の写真データ集
   │ │ │ ├─16Tea               : 十六茶の写真データ集
   │ │ │ └─ほか8種類,多いので割愛
   │ │ └─test                  : AI の動作確認用の画像
   │ │   ├─test1.jpg           : 新しく撮った綾鷹の写真データ
   │ │   └─test2.jpg           : お茶を2本並べてみた写真データ
   │ ├─train_test_data         : extended の画像を使って作成した教師／テストデータ          
   │ └─model                   : 作成したモデル
   ├─img_duplicator            : 画像水増しデータ作成
   │ └─main.py
   ├─train_test_data_generator : 教師／テストデータ作成
   │ └─main.py
   ├─model_generator           : モデル生成
   │ └─main.py
   └─ai                        : 画像判定
     └─main.py
```
</div></details>

お茶の写真はそれぞれ40枚スクエアで撮影した。


#写真を水増しする
40枚では学習するには少ない為、Keras の ImageDataGenerator で画像の解像度を変えたり、回転させたりと水増しを行う

```img_duplicator/main.py
# -- coding: utf-8 --
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers

import os
import glob
import numpy as np

CATEGORIES = [
    u'16Tea',
    u'AYATAKA!!',
    u'BarleyTea_Tsurube',
    u'Halows_barlyTea',
    u'Halows_Ocha',
    u'Halows_OolongTea',
    u'Halows_RoastedGreenTea',
    u'OiOcha',
    u'RichGreenTea',
    u'RichRoastedGreenTea']

# 画像サイズ
IMG_SIZE = 150

# ImageDataGeneratorを定義
DATA_GENERATOR = keras.preprocessing.image.ImageDataGenerator(horizontal_flip=0.3, zoom_range=0.1)

#画像をまとめているファイルまでのパス
IMG_ROOT_DIR = '/../AyatakaAI_py/data/images'

for idx, category in enumerate(CATEGORIES):  #idxにはインデックス番号、categoryには'16Tea'などが入る
    # コピー元
    img_dir = os.path.join(IMG_ROOT_DIR, 'original', category)
    # コピー先
    out_dir = os.path.join(IMG_ROOT_DIR, 'extended', category)
    os.makedirs(out_dir, exist_ok=True)

    files = glob.glob(os.path.join(img_dir, '*.JPG'))
    for i, file in enumerate(files):
        img = keras.preprocessing.image.load_img(file)
        img = img.resize((IMG_SIZE, IMG_SIZE))
        x = keras.preprocessing.image.img_to_array(img)
        x = np.expand_dims(x, axis=0)
        g = DATA_GENERATOR.flow(x, batch_size=1, save_to_dir=out_dir, save_prefix='img', save_format='jpg')
        for i in range(10):
            batch = g.next()
    print(u'{} : ファイル数は {} 件です。'.format(category, len(os.listdir(out_dir))))

```

```
$ cd /../img_dumplicator
$ python main.py
```

#教師データを作成
大量の画像を用意したので教師データを作成。
全データの 2 割ほどをテストデータにまわす。
作成した教師／テストデータは保存。






```train_test_data_generator/main.py
# -*- coding: utf-8 -*-

from PIL import Image
import os, glob
import numpy as np
import random, math
from sklearn.model_selection import train_test_split
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers

# 画像が保存されているルートディレクトリのパス
IMG_ROOT_DIR = '/../AyatakaAI_py/data/images/extended'
# カテゴリ
CATEGORIES = [
    u'16Tea',
    u'AYATAKA!!',
    u'BarleyTea_Tsurube',
    u'Halows_barlyTea',
    u'Halows_Ocha',
    u'Halows_OolongTea',
    u'Halows_RoastedGreenTea',
    u'OiOcha',
    u'RichGreenTea',
    u'RichRoastedGreenTea']

# 密度
DENSE_SIZE = len(CATEGORIES)
# 画像サイズ
IMG_SIZE = 150
# 画像データ
X = []
# カテゴリデータ
Y = []
# 教師データ
X_TRAIN = []
Y_TRAIN = []
# テストデータ
X_TEST = []
Y_TEST = []
# データ保存先
TRAIN_TEST_DATA = '/../AyatakaAI_py/data/train_test_data/data.npy'


# カテゴリごとに処理する
for idx, category in enumerate(CATEGORIES):
    # 各ラベルの画像ディレクトリ
    image_dir = os.path.join(IMG_ROOT_DIR, category)
    files = glob.glob(os.path.join(image_dir, '*.jpg'))
    for f in files:
        # 各画像をリサイズしてデータに変換する
        img = Image.open(f)
        img = img.convert('RGB')
        img = img.resize((IMG_SIZE, IMG_SIZE))
        data = np.asarray(img)
        X.append(data)
        Y.append(idx)

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

# 正規化
X = X.astype('float32') /255
Y = keras.utils.to_categorical(Y, DENSE_SIZE)

# 教師データとテストデータを分ける
X_TRAIN, X_TEST, Y_TRAIN, Y_TEST = train_test_split(X, Y, test_size=0.20)

# 教師／テストデータを保存する
np.save(TRAIN_TEST_DATA, (X_TRAIN, X_TEST, Y_TRAIN, Y_TEST))
print(u'教師／テストデータの作成が完了しました。: {}'.format(TRAIN_TEST_DATA))

```


```
$ cd /../train_test_data_generator
$ python main.py
```

#モデル構築
教師／テストデータを用意できた。次はモデルを構築。
構築したモデルは保存。

```model_generator/main.py
# -*- coding: utf-8 -*-

#モデルの構築
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers

import numpy as np
import matplotlib.pyplot as plt
import os

# カテゴリ
CATEGORIES = [
    u'16Tea',
    u'AYATAKA!!',
    u'BarleyTea_Tsurube',
    u'Halows_barlyTea',
    u'Halows_Ocha',
    u'Halows_OolongTea',
    u'Halows_RoastedGreenTea',
    u'OiOcha',
    u'RichGreenTea',
    u'RichRoastedGreenTea']

# 密度
DENSE_SIZE = len(CATEGORIES)
# 画像サイズ
IMG_SIZE = 150
INPUT_SHAPE = (IMG_SIZE, IMG_SIZE,3)
# 教師データ
X_TRAIN = []
Y_TRAIN = []
# テストデータ
X_TEST = []
Y_TEST = []
# データ保存先
TRAIN_TEST_DATA = '/../AyatakaAI_py/data/train_test_data/data.npy'
# モデル保存先
MODEL_ROOT_DIR = '/../AyatakaAI_py/data/model/'


# ----- モデル構築 ----- #
model = keras.models.Sequential()
model.add(layers.Conv2D(32,(3,3),activation="relu",input_shape=INPUT_SHAPE))
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(layers.Flatten())
model.add(layers.Dropout(0.5))
model.add(layers.Dense(512,activation="relu"))
model.add(layers.Dense(DENSE_SIZE,activation="sigmoid"))

#モデル構成の確認
model.summary()
# ----- /モデル構築 ----- #

# ----- モデルコンパイル ----- #
model.compile(loss="binary_crossentropy",
              optimizer=keras.optimizers.RMSprop(lr=1e-4),
              metrics=["acc"])
# ----- /モデル構築 ----- #

# ----- モデル学習 ----- #
# 教師データとテストデータを読み込む
X_TRAIN, X_TEST, Y_TRAIN, Y_TEST = np.load(TRAIN_TEST_DATA, allow_pickle=True)
model = model.fit(X_TRAIN,
                  Y_TRAIN,
                  epochs=10,
                  batch_size=6,
                  validation_data=(X_TEST, Y_TEST))
# ----- /モデル学習 ----- #

# ----- 学習結果プロット ----- #
acc = model.history['acc']
val_acc = model.history['val_acc']
loss = model.history['loss']
val_loss = model.history['val_loss']

epochs = range(len(acc))

plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()
plt.savefig(os.path.join(MODEL_ROOT_DIR, 'Training_and_validation_accuracy.png'))

plt.figure()

plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()
plt.savefig(os.path.join(MODEL_ROOT_DIR, 'Training_and_validation_loss.png'))
# ----- /学習結果プロット ----- #

# ----- モデル保存 ----- #
# モデル保存
json_string = model.model.to_json()
open(os.path.join(MODEL_ROOT_DIR, 'model_predict.json'), 'w').write(json_string)

#重み保存
model.model.save_weights(os.path.join(MODEL_ROOT_DIR, 'model_predict.hdf5'))
# ----- /モデル保存 ----- #

```

```
$ cd /../model_generator
$ python main.py
```

#写真を判定

```ai/main.py
# -*- coding: utf-8 -*-

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers

import numpy as np
import sys
import os

# モデル保存先
MODEL_ROOT_DIR = '/../AyatakaAI_py/data/model/'
MODEL_PATH = os.path.join(MODEL_ROOT_DIR, 'model_predict.json')
WEIGHT_PATH = os.path.join(MODEL_ROOT_DIR, 'model_predict.hdf5')
# カテゴリ
CATEGORIES = [
    u'16Tea',
    u'AYATAKA!!',
    u'BarleyTea_Tsurube',
    u'Halows_barlyTea',
    u'Halows_Ocha',
    u'Halows_OolongTea',
    u'Halows_RoastedGreenTea',
    u'OiOcha',
    u'RichGreenTea',
    u'RichRoastedGreenTea']

CATEGORIES_NAME = [
    u'十六茶',
    u'綾鷹',
    u'鶴瓶麦茶',
    u'ハローズ_麦茶',
    u'ハローズ_お茶',
    u'ハローズ_烏龍茶',
    u'ハローズ_ほうじ茶',
    u'おーいお茶',
    u'生茶',
    u'生ほうじ茶']

# 画像サイズ
IMG_SIZE = 150
INPUT_SHAPE = (IMG_SIZE, IMG_SIZE,3)

# モデルを読み込む
model = keras.models.model_from_json(open(MODEL_PATH).read())
model.load_weights(WEIGHT_PATH)

# 入力引数から画像を読み込む
args = sys.argv
img = keras.preprocessing.image.load_img(args[1], target_size=INPUT_SHAPE)
x = keras.preprocessing.image.img_to_array(img)
x = np.expand_dims(x, axis=0)

# モデルで予測する
features = model.predict(x)
print("確率：")
for i in range(0, 10):
    print(str(CATEGORIES_NAME[i]) + ' ： ' + str(features[0][i]))
print("----------------------------------------------")
print("計算結果")
if np.argmax(features[0]) == 1:
    print(u'選ばれたのは綾鷹でした。')
else:
    print(u'綾鷹ではなく' + str(CATEGORIES_NAME[np.argmax(features[0])]) + 'が選ばれました。')
print("----------------------------------------------")

```

```
$ cd /../ai
$ python main.py '../AyatakaAI/data/images/testData/test1.jpg'
```