3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

EfficientNetを使って簡単なWebアプリとLine Botを作ってみた

Last updated at Posted at 2021-04-06

はじめに

しばらくディープラーニングを使った画像分類や画像認識モデルを触っていなかったので、リハビリを兼ねて、
犬の画像からその犬種を推測するWEBアプリケーションを作りました。

コードはここにあります。
https://github.com/xcnkx/dogs_line_bot

LineのAPIを使ってline bot化とwebアプリ化をしています。

やったこと

1. 画像分類モデルの学習
2. LineのAPIを使ってLine bot化
3. WEB UIを作成
4. Herokuを使ってデプロイ (poetry ver.)

1. 画像分類モデルの学習

モデルについて

予測モデルは最近kaggleでもbaselineとしてとりあえず使われることが多いEfficientNetを使いました。
これを選んだ理由は、自分の記憶上ではCNNモデルの論文とかはInceptionV3で止まっていたのでEfficientNetをとりあえず使ってみたかった。

詳細

    Open In Colab

2. LineのAPIを使ってLine bot化

携帯で犬の写真を撮ってLINEでボットに送ると犬種を教えてくれるようにするまでがゴール。
簡単なアプリなので使用したフレームワークはflask。

コード解説

import os
import sys
import glob
from io import BytesIO
from tensorflow.keras.models import load_model
from tensorflow.keras.preprocessing.image import img_to_array
import efficientnet.tfkeras  # NOQA

from PIL import Image

from werkzeug.utils import secure_filename
from flask import Flask, request, abort, render_template
from linebot import LineBotApi, WebhookHandler
from linebot.exceptions import InvalidSignatureError
from linebot.models import (
    MessageEvent,
    TextMessage,
    TextSendMessage,
    ImageMessage,
)

必要なライブラリをインポートする。
学習済みのEfficientNetをloadするのにimport efficientnet.tfkerasが必要になるので忘れずに。

app = Flask(__name__)
file_path = "/images"
# 環境変数からchannel_secret・channel_access_tokenを取得
channel_secret = os.environ["DOG_BOT_CHANNEL_SECRET"]
channel_access_token = os.environ["DOG_BOT_CHANNEL_ACCESS_TOKEN"]
app.secret_key = __name__

if channel_secret is None:
    print("Specify CHANNEL_SECRET as environment variable.")
    sys.exit(1)
if channel_access_token is None:
    print("Specify CHANNEL_ACCESS_TOKEN as environment variable.")
    sys.exit(1)

line_bot_api = LineBotApi(channel_access_token)
handler = WebhookHandler(channel_secret)

LineのAPIをのchannel_secret・channel_access_tokenを環境変数から取得。
そしてline_bot_api, handlerをインスタンス化
どちらもLine developersの管理画面から確認ができます。
詳しくは -> https://www.casleyconsulting.co.jp/blog/engineer/3028/

# load model
model = load_model("./model/efficient_net_model.h5")


classes = [
    "Chihuahua",
    "Japanese_spaniel",
    "Maltese_dog",
    "Pekinese",
    "Shih-Tzu",
    "Blenheim_spaniel",
#    (省略)
    "African_hunting_dog",
]

学習済みのモデルをロード。またラベルをclassesリストに持つようにする

次は予測に必要な関数を定義

def predict(img: Image):
    img = img.resize((224, 224), Image.NEAREST)
    x = img_to_array(img)
    x /= 255
    result = model.predict(x.reshape([-1, 224, 224, 3]))
    predicted = result.argmax()
    proba = result[0, predicted] * 100
    return classes[predicted], "{:.1f}".format(proba)


UPLOAD_FOLDER = "./static/images/cache"
ALLOWED_EXTENSIONS = set(["png", "jpg", "jpeg", "gif"])


def allowed_file(filename):
    return "." in filename and filename.rsplit(".", 1)[1].lower() in ALLOWED_EXTENSIONS


def remove_glob(pathname, recursive=True):
    for p in glob.glob(pathname, recursive=recursive):
        if os.path.isfile(p):
            os.remove(p)

predict

flaskでのルーティングの設定と、各イベントに対してのレスポンスを書いています。

@app.route("/", methods=["GET", "POST"])
def upload_file():
    if request.method == "POST":
        if "file" not in request.files:
            alert = "ファイルがありません"
            return render_template("index.html", alert=alert)

        file = request.files["file"]
        if file.filename == "":
            alert = "ファイルがありません"
            return render_template("index.html", alert=alert)

        if file and allowed_file(file.filename):
            remove_glob("./cache/*")
            img = BytesIO(file.stream.read())
            img = Image.open(img)
            pred, proba = predict(img)
            pred_answer = f"この犬は{proba}%の確率で[{pred}]ですね!"

            file_name = secure_filename(file.filename)
            img.save(os.path.join(UPLOAD_FOLDER, file_name))

            return render_template(
                "index.html", answer=pred_answer, file_name=file_name
            )

    return render_template("index.html", answer="")


@app.route("/callback", methods=["POST"])
def callback():
    # get X-Line-Signature header value
    signature = request.headers["X-Line-Signature"]

    # get request body as text
    body = request.get_data(as_text=True)
    app.logger.info("Request body: " + body)

    # handle webhook body
    try:
        handler.handle(body, signature)
    except InvalidSignatureError:
        abort(400)

    return "OK"


@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
    messages = [
        TextSendMessage(text="犬の画像を送ってみて!品種当てちゃうぞ!"),
    ]

    line_bot_api.reply_message(event.reply_token, messages)


@handler.add(MessageEvent, message=ImageMessage)
def handle_image(event):
    message_id = event.message.id
    message_content = line_bot_api.get_message_content(message_id)
    image = BytesIO(message_content.content)

    image = Image.open(image)
    pred, proba = predict(image)

    line_bot_api.reply_message(
        event.reply_token,
        TextSendMessage(text=f"この犬は{proba}%の確率で[{pred}]ですね!"),
    )

アプリの起動


if __name__ == "__main__":
    port = int(os.environ.get("PORT", 5000))
    app.run(host="0.0.0.0", port=port)

3. Web UIを作成

flaskではrender_templateを使うことでtemplatesフォルダに入っているHTMLをrenderすることができ、さらにパラメータも渡せます。

上記のコードでの例

return render_template(
                "index.html", answer=pred_answer, file_name=file_name
            )

view側(HTML側)はjinja2というPython用のテンプレートエンジンをつかっているのでこういう風に書ける。
またCSSはbootstrapを使っています。

<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Dog Recognition bot web app</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css"
        integrity="sha384-GJzZqFGwb1QTTN6wy59ffF1BuGJpLSa9DkKMp0DgiMDm4iYMj70gZWKYbI706tWS" crossorigin="anonymous">
    <link rel="icon" href="../static/images/pet_robot_dog.png" />
</head>

<body class="bg-secondary">
    <div class="d-flex p-3 flex-column align-items-center">
        <div class="card text-white bg-dark text-center mb-3" style="max-width: 36rem;">
            <div class="card-header">
                <h2><img class="img-thum" src="../static/images/pet_robot_dog.png" alt="dog bot" height="100">
                    Dog Recognition Bot </h2>
            </div>
            <div class="card-body">
                <h3 class="card-title"> 犬の画像を送ってください!</h3>
                <h4 class="card-title"> AIが犬種を推測します</h4>
            </div>
            <div class="card-body">
                <form method="POST" enctype="multipart/form-data">
                    <input class="file_choose" type="file" name="file">
                    <input class="btn btn-primary btn" value="submit!" type="submit">
                </form>
            </div>
            <div class="card-body">
                {% if answer %}
                <div>
                    <img src="../static/images/cache/{{ file_name }}" alt="uploaded_image" class="img-fluid">
                </div>
                <div class="alert alert-primary" role="alert">
                    <div class="answer">{{answer}}</div>
                </div>
                {% endif %}
                {% if alert %}
                <div class="alert alert-danger" role="alert">
                    <div class="answer">{{alert}}</div>
                </div>
                {% endif %}
                </div>
            <footer>
                <small>&copy; github.com/xcnkx</small>
            </footer>
        </div>
</body>
</html>

4. Herokuを使ってデプロイ (poetry ver.)

Herokuでアプリをデプロイするにはまずアカウントを作成する必要があります。そのあとherokuでアプリを作って自分のgithubと連携させます。
ここらへんの話はよくあるものなので詳しくは -> https://qiita.com/suigin/items/0deb9451f45e351acf92#line-bot%E3%82%92heroku%E3%81%ABdeploy
ここではpoetryを使っているpythonアプリをどうやってHerokuにデプロイをするかを書きます。

pythonの仮想環境管理ツールとして自分は普段poetryを使っているのですが、poetryで管理しているpythonのプロジェクトをherokuにデプロイしようとすると普通はできません。 herokuはrequirements.txt(pipenv)を使ってpythonのパッケージをインストールをしているからです。

しかし、時代はpoetryなのでどうにかしてherokuにデプロイしたいと思い方法を探したところなんとある方法見つけました。

それはズバリ、 https://github.com/moneymeets/python-poetry-buildpack を使うことでした!
pyproject.tomlからrequirements.txtを自動で生成してくれるherokuの公式kビルドパックです。

これはherokuのGUIからでもインストールをして使うことも出来ますが、ターミナルから以下のようにインストールできます。

heroku buildpacks:clear
heroku buildpacks:add https://github.com/moneymeets/python-poetry-buildpack.git
heroku buildpacks:add heroku/python

そのあとは

git push heroku master:master

でherokuにデプロイできると思います。

## デプロイでハマったこと

  • herokuで使えるpythonのversionが指定されているので確認しないとエラーでます
  • tensorflow をそのままインストールすると使用メモリが500MBを超えるのでtensorflow-cpuをインストールしないといけません
  • またEfficientNetB4以上を使用するとweightが大きすぎてメモリに乗らないのでモデルのサイズの調整が必要

おわりに

久々に画像分類モデルとkerasを触ったので楽しかった。またEfficientNetの良さ(学習の速さと精度)がわかり、kaggleでとりあえずEfficientNetをbaselineとして使う人の気持ちもわかりました。
また気になるモデルや論文があれば、このアプリを使って実装していきたいです。

またQiitaで記事を書く時はこまめに保存をするように心がけましょう。途中でなぜか自動保存が動いてなく、実はこの記事を書くのが2回目です、、、、、

3
3
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
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?