LoginSignup
9
6

More than 3 years have passed since last update.

Python × Flask × Tensorflow.Keras 猫の品種を予測するWebアプリ

Posted at

はじめに

Webアプリ作成初挑戦、Qiita初投稿ですm(_ _)m

参考にした投稿
- 【AI×ねこ】猫の品種を当ててみる
- Python × Flask × PyTorch 数字認識Webアプリのお手軽構築

環境

  • Mac
  • Visual Code
  • Python3.7.5

作りたいもの

 猫画像を送信したら品種を予測してくれるWebアプリ
 (Tensolflowの転移学習モデルInception-v3を用いて猫の品種を予測して
  近い品種上位3位を確率を添えて教えてくれるやつ)
 やりたいこと3.png

環境構築

ファイル構成

 ├── path/to/cat/image
 │   └── (学習データ 猫画像)
 ├── cat_list.txt(猫の品種リスト)
 ├── image_process.py
 ├── model.h5(save_model.pyの出力)
 ├── save_model.py(転移モデルを再学習する関数)
 ├── sever.py
 ├── static
 │   └── (入力した画像が保存される)
 └── templates
     └── index.html

importしたモジュールとバージョン

  • Flask 1.1.1
  • numpy 1.18.1
  • pandas 1.0.1
  • tensorflow 2.1.0
  • scikit-learn 0.22.2.post1
  • opencv-python 4.2.0.32
  • Pillow 7.0.0

機械学習モデル構築

モデル構築のイメージ

モデル.png

猫画像データセットを用意

The Oxford-IIIT Pet DatasetのDownloadsのDatasetから得ました
犬の画像も含まれています

モデル構築

Inception-v3を再学習させてmodel.h5を作ります
学習データは200枚/クラス
クラスは以下の12個
- Abyssinian
- Bengal
- Birman
- Bombay
- British_Shorthair
- Egyptian_Mau
- Maine_Coon
- Persian
- Ragdoll
- Russian_Blue
- Siamese
- Sphynx

事前にモデル構築をします

save_model.py
import glob
import numpy as np
import pandas as pd
#kerasのload_imgには手動でpillowのinstall必要
from tensorflow.keras.preprocessing.image import load_img, img_to_array
from tensorflow.keras.applications.inception_v3 import InceptionV3, preprocess_input
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Dense
from sklearn.model_selection import train_test_split

x_size = 299
y_size = 299
kind_label = []
cat_img = []

# 識別する品種のリスト
cat_list = ['Abyssinian','Bengal','Birman','Bombay','British_Shorthair','Egyptian_Mau','Maine_Coon','Persian'
            ,'Ragdoll','Russian_Blue','Siamese','Sphynx']
f = open('cat_list.txt', 'w')
for x in cat_list:
    f.write(str(x) + "\n")
f.close()

#データセットのロード
for cat_kind in cat_list:
    file_list = glob.glob(f'path/to/cat/images/{cat_kind}*.jpg')
    #print(f"kind: {cat_kind}, num of images: {len(file_list)}")
    for cat_file in file_list:
        # img_path = file
        img = load_img(cat_file, target_size=(x_size, y_size))
        x = img_to_array(img)
        x = preprocess_input(x)
        cat_img.append(x)
        kind_label.append(cat_kind) 

#品種ラベルをダミー化
Y_dummy = pd.get_dummies(kind_label)

X_train, X_test, y_train, y_test = train_test_split(
    cat_img, Y_dummy, test_size=0.2, random_state=42)

# model読み込み
model = InceptionV3(weights='imagenet')

# 中間層を出力するモデル
intermediate_layer_model = Model(inputs=model.input, outputs=model.layers[311].output)

# Denseレイヤーを接続
x = intermediate_layer_model.output
x = Dense(1024, activation='relu')(x)
predictions = Dense(len(cat_list), activation='softmax')(x)

# 転移学習モデル
transfer_model = Model(inputs=intermediate_layer_model.input, outputs=predictions)

# 一旦全レイヤーをフリーズ
for layer in transfer_model.layers:
    layer.trainable = False

# 最終段のDenseだけ再学習する
transfer_model.layers[312].trainable = True
transfer_model.layers[313].trainable = True

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

#転移学習
transfer_model.fit(np.array(X_train), np.array(y_train), epochs=10,
                    validation_data=(np.array(X_test), np.array(y_test)))

#精度確認用に出力(必要に応じて)
loss, acc = transfer_model.evaluate(np.array(X_test), np.array(y_test))
print('Loss {}, Accuracy {}'.format(loss, acc))

transfer_model.save("./model.h5")

Webアプリ

①saver.py
- モデル読み込み
- 入力データ読み込み
- 品種予測(②image_process.py)
- 入力データと予測結果表示(③index.html)

sever.py
from flask import Flask, render_template, request
from tensorflow.keras.preprocessing.image import load_img
from tensorflow.keras.models import load_model
import numpy as np
from image_process import examine_cat_breeds
from datetime import datetime
import os
import cv2
import pandas as pd

app = Flask(__name__)

# モデル(model.h5)とクラスのリスト(cat_list)を読み込み
model = load_model('model.h5')
cat_list = []
with open('cat_list.txt') as f:
    cat_list = [s.strip() for s in f.readlines()]
print('= = cat_list = =')
print(cat_list)


@app.route("/", methods=["GET","POST"])
def upload_file():
    if request.method == "GET":
        return render_template("index.html")

    if request.method == "POST":
        # アプロードされたファイルをいったん保存する
        f = request.files["file"]
        filepath = "./static/" + datetime.now().strftime("%Y%m%d%H%M%S") + ".png"
        f.save(filepath)
        # 画像ファイルを読み込む
        # 画像ファイルをリサイズ
        input_img = load_img(filepath, target_size=(299, 299))
        # 猫の種別を調べる関数の実行
        result = examine_cat_breeds(input_img, model, cat_list)
        print("result")
        print(result)

        no1_cat = result[0,0]
        no2_cat = result[1,0]
        no3_cat = result[2,0]

        no1_cat_pred = result[0,1]
        no2_cat_pred = result[1,1]
        no3_cat_pred = result[2,1]

        return render_template("index.html", filepath=filepath, 
        no1_cat=no1_cat, no2_cat=no2_cat, no3_cat=no3_cat,
        no1_cat_pred=no1_cat_pred, no2_cat_pred=no2_cat_pred, no3_cat_pred=no3_cat_pred)

if __name__ == '__main__':
    app.debug = True
    app.run(host='localhost', port=5000)

②image_process.py
引数は
- 入力データ(予測したい猫画像)
- モデル(猫モデル)
- クラスリスト(猫の品種リスト)
返り値は予測された品種上位3位と確率が格納された行列

image_process.py
from tensorflow.keras.preprocessing.image import img_to_array
import numpy as np
from tensorflow.keras.applications.inception_v3 import preprocess_input


def examine_cat_breeds(image, model, cat_list):
    # 行列に変換
    img_array = img_to_array(image)
    # 3dim->4dim
    img_dims = np.expand_dims(img_array, axis=0)
    # Predict class(preds:クラスごとの確率が格納された12×1行列)
    preds = model.predict(preprocess_input(img_dims))
    preds_reshape = preds.reshape(-1,preds.shape[0])
    # cat_list(リスト)を12×1行列に変換
    cat_array = np.array(cat_list).reshape(len(cat_list),-1)
    # 確率高い順にソートする
    preds_sort = preds_reshape[np.argsort(preds_reshape[:, 0])[::-1]]
    # 確率の降順に合わせて猫の順番も変える
    cat_sort = cat_array[np.argsort(preds_reshape[:, 0])[::-1]]
    # preds_reshape と cat_arrayを結合
    set_result = np.concatenate([cat_sort, preds_sort], 1)
    return set_result[0:3, :]

③index.html
綺麗に実装できませんでした(^_^;)

index.html
<!DOCTYPE html>
<html>
   <body>
      {% if no1_cat %}
         <img src="{{filepath}} " border="1"> <br>
         予測結果<br>
         {{no1_cat}}:{{no1_cat_pred}}<br>
         {{no2_cat}}:{{no2_cat_pred}}<br>
         {{no3_cat}}:{{no3_cat_pred}}<br>
         <hr>
      {% endif %}
         ファイルを選択して送信してください<br>
         <form action = "./" method = "POST" 
            enctype = "multipart/form-data">
            <input type = "file" name = "file" />
            <input type = "submit"/>
         </form>
   </body>
</html>

実行

python sever.py+Enterで実行して
http://localhost:5000/にアクセスするすると以下が表示される

スクリーンショット 2020-03-22 23.37.19.png

かわいい猫画像を送信すると品種予測結果が表示されます

スクリーンショット 2020-03-22 23.31.36.png

まとめ

そういえばめちゃくちゃかわいい地域猫はキジ白っぽいんですよね
今回学習したクラスにいない…(^_^;)

キジ白など日本に多い品種の学習データを集める手法を探してみようかな
スクレイピングというやつ?

スマホアプリ化したいなぁ

9
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
6