今月中頃(2019.3.16), Urban Data Challenge 2018のファイナルが行われ, CODE for YAMATOKORIYAMAが金魚愛(AI)育成プロジェクトとして取り組んでいる「Kingyo AI Navi」が, アイデア部門の金賞[1]を受賞した.
CODE for YAMATOKORIYAMAでは, 今年度このアイデアをアプリ化したいと考えており, LINEの枠組みを利用したアプリ化の検討を開始した.
関連記事:
・「Kingyo AI Navi」のアプリ化を考える (1)
・「Kingyo AI Navi」のアプリ化を考える (2)
・「Kingyo AI Navi」のアプリ化を考える (3)
・「Kingyo AI Navi」のアプリ化を考える (4)
・「Kingyo AI Navi」のアプリ化を考える (5)
・「Kingyo AI Navi」のアプリ化を考える (6)
1. 概要
「Kingyo AI Navi」をLINEのMessaging API + サービスで実現すべく, 今回はLINE Botから金魚の画像を送信して種類を識別し, 関連情報を返す部分の基本的な動作を試した.
サービス側は, 金魚種別の識別に前回[2]記載したCloud AutoML Vitionを利用するので, GCP(Google Cloud Platform)の枠組みで構築することにした.
[構成図]
① LINEアプリからLINE Botサーバへ金魚の画像を送信する.
② LINE Botサーバは, Messaging API経由で, Kingyo AI NaviサービスのWebhook URLをコールする.
③ Kingyo AI Naviサービスは, 画像データをAutoML Vision APIに送信し, 金魚の分類を行う.
④ Kingyo AI Naviサービスは, AutoML Visionから分類結果を受信する.
⑤ LINE Botサーバは, Messaging API経由で, Kingyo AI Naviサービスから応答(金魚の種類, 関連情報URL等)を受信する.
⑥ LINE Botサーバは, 受信した応答をLINEアプリに送信する.
2. サービス開発
2.1 LINE側設定
LINEのMessaging APIを利用する.
LINEのMessaging APIを使ったLINE Botの作成については, ネット上に多くの記事[3][4]があるので, そちらを参照のこと.
あと, LINE DevelopersでChannelにWebhook URLを登録する際に, 「SSLのみ対応」ということでドメイン名およびSSL証明書が必要となり, とりあえず以下のサービスを利用した.
・ドメイン名
無料でドメイン名を取得できるfreenomを利用. 12カ月まで無料.
・SSL証明書
無料でSSL証明書を発行してくれるLet's Encryptを利用. 3カ月まで無料.
2.2 サーバ側開発環境
サーバ構築等に詳しくないので, あえて勉強のためGAE(Google App Engine)でなくGCE(Google Compute Engine)を使用してみた.
[開発環境]
サーバ:GCE (f1-micro; usリージョン)
OS:Ubuntu 18.04 LTS
開発言語:Python 3.6
GCEのインスタンス生成等については, こちらもネット上に多くの記事[4]があるので, そちらを参照のこと.
2. 3 アプリ実装
今回サーバ側アプリは, python + flaskで実装した.
まずは, LINE Messaging APIからのイベントを処理する部分を実装する.
(1) LINE Messaging APIがWebhookすると, callback()関数が呼び出される. ここでは, MessageEventがTextMessageかImageMessageかを判断し, 処理を振り分ける.
(2) handle_text_message()関数で, 送信されたテキストをそのまま返す.
(3) handle_image_message()関数で, 画像データを識別する処理を呼び, 識別結果をCarouselTemplateを使って応答メッセージを返す.
[コード]
import os import ssl from argparse import ArgumentParser import datetime import settings import automl import pandas as pd # Import from Flask Module from flask import Flask, request, abort # Import from Line Bot SDK from linebot import ( LineBotApi, WebhookHandler ) from linebot.exceptions import ( InvalidSignatureError ) from linebot.models import ( MessageEvent, ImageMessage, TextMessage, TextSendMessage, CarouselColumn, CarouselTemplate, TemplateSendMessage ) context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2) context.load_cert_chain('/etc/letsencrypt/live/{Domain Name}/fullchain.pem', '/etc/letsencrypt/live/{Domain Name}/privkey.pem') # Instance of Flask app = Flask(__name__) # Message App ID line_bot_api = LineBotApi(os.environ['LINE_CHANNEL_ACCESS_TOKEN']) handler = WebhookHandler(os.environ['LINE_CHANNEL_SECRET']) category = pd.read_csv('./resources/kingyo.csv', index_col=0) @app.route("/callback", methods=['POST']) def callback(): print('callback : ', datetime.datetime.today()) 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 as e: print('InvalidSignatureError : ', e) abort(400) return 'OK' # Handle text message @handler.add(MessageEvent, message=TextMessage) def handle_text_message(event): print('handle_text_message : ', datetime.datetime.today()) line_bot_api.reply_message( event.reply_token, TextSendMessage(text=event.message.text) ) # Handle image message @handler.add(MessageEvent, message=ImageMessage) def handle_image_message(event): print('handle_image_message : ', datetime.datetime.today()) message_content = line_bot_api.get_message_content(event.message.id) try: image_info = automl.get_info_by_automl(message_content.content) if isinstance(image_info, str): messages = [ TextSendMessage(text=image_info) ] elif isinstance(image_info, list): columns = [] for res in image_info: label = res['Label'] column = CarouselColumn( thumbnail_image_url = category.loc[label, 'url'], title = '種類:{}'.format(category.loc[label, 'name']), text = '確信度:{:.3f}'.format(res['Score']), actions = [ {"type": "uri", "label": "関連サイト", "uri": "https://www.kingyoen.com/"} ] ) columns.append(column) messages = TemplateSendMessage( alt_text = 'template', template = CarouselTemplate(columns=columns, imageSize='contain'), ) print('reply message : ', datetime.datetime.today()) reply_message(event, messages) except Exception as e: import traceback traceback.print_exc() reply_message(event, TextSendMessage(text='何か調子が悪いなー.')) def reply_message(event, messages): line_bot_api.reply_message( event.reply_token, messages = messages, ) if __name__ == '__main__': arg_parser = ArgumentParser( usage = 'Usage: python ' + __file__ + ' [--help]' ) arg_parser.add_argument('-d', '--debug', default=False, help='debug') options = arg_parser.parse_args() app.run(host=os.environ['HOST_ADDRESS'], port=os.environ['HTTPS_PORT'], ssl_contex\ t=context, threaded=True, debug=options.debug)
次に, Cloud AutoML Visionを呼び出す処理を実装する.
(1) setup_automl_vision()関数で, Cloud AutoMLを使うための準備を行う.
(2) get_info_by_automl()関数で, 画像データをCloud AutoML Visionに送信して金魚識別を行い, 結果を返す.
[コード]
import os import settings from google.cloud import automl_v1beta1 as automl SCORE_THRESHOLD = '0.2' # init AutoML Vision def setup_automl_vision(): automl_client = automl.AutoMlClient() prediction_client = automl.PredictionServiceClient() model_id = automl_client.model_path(os.environ['PROJECT_ID'], os.environ['COMPUTE_REGION'], os.environ['MODEL_ID']) print(prediction_client, automl_client, model_id) return prediction_client, model_id prediction_client, model_id = setup_automl_vision() # predict the image using AutoML Vision def get_info_by_automl(image): payload = {"image": {"image_bytes" : image}} params = {"score_threshold" : SCORE_THRESHOLD} try: response = prediction_client.predict(model_id, payload, params) results_info = [] for result in response.payload: print('Predicted class name: {}'.format(result.display_name)) print('Predicted class score: {}'.format(result.classification.score)) info = { "Label" : result.display_name, "Score" : result.classification.score} results_info.append(info) if len(results_info) > 0: return results_info else: return '私もよく判りましぇーん.' except: import traceback traceback.print_exc() return '何か調子悪いなー.'
[動作例]
LINE画面から, 金魚の写真を撮って送信すると, 識別結果に応じた応答が表示される.
「関連サイト」をタッチすると, そのサイトが表示される.
課題:
画像を送信して応答で結果が表示されるまでに, 約5秒かかっている.
そこで, サーバ側の処理時間をちょっと測定してみたところ, どうもCloud AutoML Visionでの金魚識別に3秒程度かかっている感じ.
もしかしたら, 実際にサービス動かす際にはCloud AutoML Visionを使わず, サーバ側で独自に金魚識別をやらなければいけないかも....
次回は, できればLINE Botからテキストを送信すると, それに応じて対話を行う仕組みをDialogflowでも利用して試してみたいと思う. (現状はEcho)
ただ, 何か対話タスクを決めないと応答内容(対話シナリオ)を準備できないので, どうしようかな~?
----
参照URL:
[1] UDC2018審査結果 | アーバンデータチャレンジ
[2] 「Kingyo AI Navi」のアプリ化を考える (1)
[3] ボットを作成する | LINE Developers
[4] LINE Messaging API を使ってLINEにメッセージ送信/メッセージ返信する
[4] VM インスタンスの作成と起動 | Compute Engine ドキュメント | Google Cloud
|