ABEJA Platform + Cloud Functions + LINE Botで機械学習アプリをつくる

## はじめに

本記事は、以下の手順をまとめた記事です。
- ABEJA Platform + Google Cloud Functions + LINE Botでサーバーレスな機械学習アプリをつくる

## システム構成
- LINE Botに画像を送信する
- LINE Messaging API からの HTTPリクエスト（webhook）をCloud Functionsで受ける
- Cloud Functionsから、ABEJA Platform上にデプロイされた画像分類の機械学習APIにHTTPリクエストを送り、画像のクラスの予測結果を取得する
- 予測結果をLINEに返す

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/376869/7d276633-2964-787c-f799-cdd2c209166e.png" width=80%>

## 事前準備
### 機械学習モデルの学習・デプロイ
ABEJA Platform上に機械学習モデルをデプロイします。ABEJA Platformのテンプレートの機能を使うことで、ノンプログラミングで機械学習モデルを学習・デプロイすることができます。

今回は、以下の記事の手順に沿って作成した、花の画像の分類モデルをサンプルとして使用します。

（参考）
ABEJA Platformのテンプレートを使用して、ノンプログラミングで機械学習モデルを学習する
https://qiita.com/yushin_n/items/6852ad042913617d8892

ABEJA Platformのテンプレートを使用して、ノンプログラミングで機械学習モデルをデプロイする
https://qiita.com/yushin_n/items/fb338ca9bd3c685ad691

デプロイメントの画面から、デプロイしたモデルのエンドポイントを控えておきます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/376869/fc80cfd9-8e31-466c-c8c6-066d2ff3d554.png" width=80%>

### LINE Developersのチャネル作成
LINE Messaging APIを使ってボットを作成するため、まずLINE Developersコンソールでチャネルを作成します。

（参考）
Messaging APIを利用するには
https://developers.line.biz/ja/docs/messaging-api/getting-started/

チャネルを作成した後、「チャネル基本設定」に記載されている`Channel Secret`と`アクセストークン`を控えておきます。

## Google Cloud Functionsのファンクションの作成
[Google Cloud Functions](https://cloud.google.com/functions/)のコンソールからプロジェクトとファンクションを作成します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/376869/77231d48-b754-8a2a-a2d0-cad8e462e018.png" width=70%>

Inline editorを選択して、RuntimeにはPython 3.7を指定します。
その後、`main.py`にソースコードを書きます。

ソースコードは、[line-bot-sdk-pythonのサンプル](https://github.com/line/line-bot-sdk-python/blob/master/examples/flask-echo/app.py)を参考にしました。

```python:main.py

import os
import io
import sys
import requests
import flask
import linebot
import linebot.exceptions
import linebot.models
import googletrans

# set API endpoint
endpoint = os.getenv('ABEJA_PLATFORM_API_ENDPOINT', None)

# set ABEJA Platform credential
user_id = os.getenv('ABEJA_PLATFORM_USER_ID', None)
personal_access_token = os.getenv('ABEJA_PLATFORM_USER_TOKEN', None)

credential = {
    'user_id': user_id,
    'personal_access_token': personal_access_token
}

# get channel_secret and channel_access_token from your environment variable
channel_secret = os.getenv('LINE_CHANNEL_SECRET', None)
channel_access_token = os.getenv('LINE_CHANNEL_ACCESS_TOKEN', None)

if channel_secret is None:
    print('Specify LINE_CHANNEL_SECRET as environment variable.')
    sys.exit(1)
if channel_access_token is None:
    print('Specify LINE_CHANNEL_ACCESS_TOKEN as environment variable.')
    sys.exit(1)

line_bot_api = linebot.LineBotApi(channel_access_token)
parser = linebot.WebhookParser(channel_secret)


def main(request):
    # get X-Line-Signature header value
    signature = request.headers['X-Line-Signature']

    # get request body as text
    body = request.get_data(as_text=True)

    try:
        # parse webhook body
        events = parser.parse(body, signature)
        for event in events:
            # initialize reply message
            text = ''
            
            # if message is TextMessage, then ask for image
            if event.message.type == 'text':
                text = u'画像を送ってください！'

            # if message is ImageMessage, then predict
            if event.message.type == 'image':
                message_id = event.message.id
                message_content = line_bot_api.get_message_content(message_id)
                img_io = io.BytesIO(message_content.content)
                content_type = 'image/jpeg'

                # post image to api endpoint for prediction
                res = requests.post(endpoint, data=img_io, headers={'Content-Type': content_type},
                                    auth=(user_id, personal_access_token))
                result = res.json()
                
                # translate english label to japanese
                label_en = result['result'][0]['label']
                translator = googletrans.Translator()
                label_ja = translator.translate(label_en.lower(), dest='ja')
                
                prob = result['result'][0]['probability']
                
                # set reply message
                text = u'{}%の確率で、{}です！'.format(int(prob*100), label_ja.text)

            line_bot_api.reply_message(
                event.reply_token,
                linebot.models.TextSendMessage(text=text))

    except linebot.exceptions.InvalidSignatureError:
        flask.abort(400)

    return 'OK'
```

requirements.txtに`line-bot-sdk`と`googletrans`を追加します。

```text:requirements.txt
# Function dependencies, for example:
# package>=version
line-bot-sdk
googletrans
```

環境変数に、以下を指定します。
- `LINE_CHANNEL_SECRET`（LINEのChannel Secret）
- `LINE_CHANNEL_ACCESS_TOKEN`（LINEのアクセストークン）
- `ABEJA_PLATFORM_USER_ID`（ABEJA PlatformのユーザーID）
- `ABEJA_PLATFORM_USER_TOKEN`（ABEJA Platformのアクセストークン）
- `ABEJA_PLATFORM_API_ENDPOINT`（デプロイしたモデルのエンドポイント）

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/376869/481ce8e9-f796-5e0a-981d-eea259f5a314.png" width=60%>

ファンクションをデプロイした後、TriggerのURLをLINE Developersのチャネルの`Webhook URL`に登録します。これで準備完了です。

*`接続確認`をクリックすると「Webhookが無効なHTTPステータスコードを返しました（期待されるステータスコードは200です）」と出てしまいましたが、実際は接続できていました。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/376869/fb2a2627-c0a3-113b-f8b9-dfa744fd8e5f.png" width=90%>

## LINEでの画像の送信

設定したとおり、チャネルにテキストを送信すると「画像を送ってください！」というメッセージが返ってきます。

また、画像を送信すると、画像のクラスの予測結果とクラス所属確率が返ってきました！

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/376869/f5bd7e31-58e7-0d71-015a-19f5908986b4.png" width=50%>

## まとめ
本記事では、ABEJA Platform + Google Cloud Functions + LINE Botでサーバーレスな機械学習アプリをつくる手順をまとめました。今回は、5種類の花（デイジー、ローズ、タンポポ、ヒマワリ、チューリップ）を分類する機械学習モデルを使いましたが、HTTPリクエストを送る機械学習APIを変えることで、色々なアプリケーションをつくることができますね :dog:

## 参考
ABEJA Platform + AWS Lambda（Node.js） + LINE Botの事例もあります。
https://qiita.com/peisuke/items/f46e46a98692c3490f15

ABEJA Platformは、トライアルも提供しています。気になられた方は、是非、お気軽にお問い合わせください。また、フォーラムもありますので、是非、ご活用ください。

ABEJA Platformに関するお問い合わせ
https://abejainc.com/platform/ja/contact/

ABEJA Platform Forum
https://forums.abeja.io/