2
1

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 1 year has passed since last update.

AWSでrinnaの日本語GPT-2モデルとおしゃべりするLINE Botを作る

Posted at

概要

FlaskでAIとおしゃべりするインターフェースを作るで登場した、私がファインチューニングしたrinnaの日本語GPT-2モデル(以下、もこなGPT)とおしゃべりできるLINE Botを作りました。
参考にした記事と開発中に私がつまづいたところをまとめます。
LINE_capture_711939977.244120.JPG

使用言語、ライブラリなど

  • Python 3.9
  • torch 1.13.1
  • sentencepiece 0.1.97
  • transformers 4.26.0
  • AWS SAM CLI 1.90.0
  • line-bot-sdk

使用したPCはMacbook Air(M1チップ)です。

アプリケーション全体図

linebot関数(API GatewayとLambda)がLINEとのメッセージのやり取りを担当し、mokonaGPT関数(Lambda)がユーザの入力に対する応答生成を担当します。
Qiita1.png

開発手順

mokonaGPT関数

参考:AWS SAMとHugging Face Transformersでサーバーレスな推論APIを作る

以下の記事を参考に作成したモデル(outputフォルダとして出力される)のフォルダ名をmodelに変更し、sam initにより作成されたhello_worldディレクトリ直下に配置しました。
rinnaの日本語GPT-2モデルのファインチューニングを試す

つまづいた箇所

Template.yaml

記事で言及されていた箇所に加え、Resources以下のArchitecturesx86_64からarm64に変更しました(macOSで開発しているため)。
また、記事ではMemorySizeを5000としていましたが、3000としました。

Dockerfile

TRANSFORMERS_CACHE/tmp/cache/transformersとしました。
記事のままでデプロイすると以下のエラー(CloudWatchのログ)が出たためです。

There was a problem when trying to write in your cache folder (/opt/cache/transformers). 
You should set the environment variable TRANSFORMERS_CACHE to a writable directory.

app.py

以下のようなコードにしました。

from transformers import T5Tokenizer, AutoModelForCausalLM


def lambda_handler(event, context):

    tokenizer = T5Tokenizer.from_pretrained("rinna/japanese-gpt2-medium")
    model = AutoModelForCausalLM.from_pretrained("/opt/model")
    # linebot関数から送られた文字列を受け取る
    message = event['message']
    # 応答生成
    message_input = "<s>" + message + "[SEP]"
    tokenized = tokenizer.encode(message_input, return_tensors="pt")
    output = model.generate(tokenized, do_sample=True, max_length=60, num_return_sequences=8, 
                            top_p=0.95, top_k=20, bad_words_ids=[[1], [5]])
    response = tokenizer.batch_decode(output)[0]
    # 生成された応答を整形
    response = response.split('[SEP]</s>')[1]
    response = response.replace('</s>', '')
    end = response.rfind('')
    response = response[:(end+1)]
    return response

linebot関数

参考:Lambdaでline-bot-sdk-pythonを使用してオウム返しBOTを作成する

つまづいた箇所

line-bot-sdk

作業ディレクトリ(仮にlinebotとします)でpython -m pip install line-bot-sdk -t .を実行し、line-bot-sdk の構成ファイルをダウンロードします。
しかし、botocoreurllib3の最新版に対応していないため、boto3をインポートすると以下のエラー(CloudWatchのログ)が出てしまいます。

Runtime.ImportModuleError: Unable to import module 'lambda_function': cannot import name 'DEFAULT_CIPHERS' from ‘urllib3.util.ssl_'

参考:”cannot import name 'DEFAULT_CIPHERS' from ‘urllib3.util.ssl_’"の原因

そこで、適当な作業用ディレクトリ(仮にurllibとします)を作成し、そこでpip install urllib3==1.25.11 -t .を実行してurllib3のversion 1.25.11をダウンロードします。
linebotディレクトリ内のurllib3フォルダとurllib3-2.0.4.dist-infoフォルダを削除し、urllibディレクトリ内のurllib3フォルダとurllib3-1.25.11.dist-infoに置き換えました。

lambda_function.py

以下のようなコードにしました。

import os
import sys
import json
import boto3

from linebot import (
    LineBotApi, WebhookHandler
)
from linebot.models import (
    MessageEvent, TextMessage, TextSendMessage,
)
from linebot.exceptions import (
    LineBotApiError, InvalidSignatureError
)
import logging

logger = logging.getLogger()
logger.setLevel(logging.ERROR)

channel_secret = os.getenv('LINE_CHANNEL_SECRET', None)
channel_access_token = os.getenv('LINE_CHANNEL_ACCESS_TOKEN', None)
if channel_secret is None:
    logger.error('Specify LINE_CHANNEL_SECRET as environment variable.')
    sys.exit(1)
if channel_access_token is None:
    logger.error('Specify LINE_CHANNEL_ACCESS_TOKEN as environment variable.')
    sys.exit(1)

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


def lambda_handler(event, context):
    if "x-line-signature" in event["headers"]:
        signature = event["headers"]["x-line-signature"]
    elif "X-Line-Signature" in event["headers"]:
        signature = event["headers"]["X-Line-Signature"]
    body = event["body"]
    ok_json = {"isBase64Encoded": False,
               "statusCode": 200,
               "headers": {},
               "body": ""}
    error_json = {"isBase64Encoded": False,
                  "statusCode": 500,
                  "headers": {},
                  "body": "Error"}

    @handler.add(MessageEvent, message=TextMessage)
    def message(line_event):
        text = line_event.message.text
        # mokonaGPT関数を呼び出し、ユーザがLINEから送信した文字列(text)を渡す
        payload = json.dumps({"message": text})
        response = boto3.client('lambda').invoke(
        FunctionName = 'mokonaGPT-HelloWorldFunction-********',
        InvocationType='RequestResponse',
		 Payload = payload
        )
        # mokonaGPT関数の戻り値を処理する
        body = response['Payload'].read()
        resp = body.decode('unicode-escape')
        resp = resp.replace('"', '')
        # 応答生成が上手くいかなかったら「にゃーん」と返す
        if (resp == ""):
            resp = "にゃーん"
        line_bot_api.reply_message(line_event.reply_token, TextSendMessage(text=resp))
        

    try:
        handler.handle(body, signature)
    except LineBotApiError as e:
        logger.error("Got exception from LINE Messaging API: %s\n" % e.message)
        for m in e.error.details:
            logger.error("  %s: %s" % (m.property, m.message))
        return error_json
    except InvalidSignatureError:
        return error_json

    return ok_json

関数の実行時間

mokonaGPT関数の実行に時間がかかるため、linebot関数の実行時間は長めに設定しました。
実行時間が短かった時のエラー文(CloudWatchのログ)は以下の通りでした。

Task timed out after 3.01 seconds

実行時間は「設定」タブの「一般設定」タブから変更しました。
Qiita3.png

【メモ】 IAMロールを追加する方法(mokonaGPT関数)

「設定」タブの「アクセス権限」タブにあるロール名をクリック
Qiita2.png
ページをスクロールし、「許可ポリシー」の「許可を追加」プルダウンから「インラインポリシーを作成」をクリック
Qiita4.png
「JSON」ボタンをクリックし、JSON形式で記述する
Qiita5.png

mokonaGPT関数とlinebot関数の連携について

参考:PythonであるLambda関数から別のLambda関数を呼び出す
linebot関数とmokonaGPT関数の連携箇所は以下のように記述しました。

linebot関数

# mokonaGPT関数を呼び出し、ユーザがLINEから送信した文字列(text)を渡す
payload = json.dumps({"message": text})
response = boto3.client('lambda').invoke(
        FunctionName = 'mokonaGPT-HelloWorldFunction-********',
        InvocationType='RequestResponse',
		Payload = payload
        )
    
# mokonaGPT関数の戻り値を処理する
body = response['Payload'].read()
resp = body.decode('unicode-escape')

mokonaGPT関数

# linebot関数から送られた文字列を受け取る
message = event['message']

参考:Python の Lambda 関数ハンドラー

最後に

本記事にて引用した記事に助けられ、苦節数ヶ月を経てようやくLINE Botを完成させることができました。
記事の作成者の皆様に深く御礼申し上げます。

2
1
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
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?