Lineチャットボットの製作

AIを載せたLineチャットボットを作成しました。
下記IDまたはQRコードから友達追加できます。

@321kkegz

画像1

チャットボット概要

今回製作したチャットボットはこのような感じになっています。

画像2

おはように対し、おはよう! と返す。
こんにちはに対し、こんにちは! と返す。
これ以外の文に関してはオウム返しをする。
画像を送るとそれがなんであるか返す。
友達追加してくれた人の情報とお礼のコメントを返す。

以上のことができます。
画像に関してはCifer10というデータセットを用いて学習を行っているので10種類の判別ができます。

コード編

今回は以下の流れで製作しています。
1:学習とモデルの保存
2:メインのコード

・学習とモデルの保存について

このようなコードを書きました。

from keras import optimizers
from keras.applications.vgg16 import VGG16
from keras.datasets import cifar10
from keras.layers import Dense, Dropout, Flatten, Input
from keras.models import Model, Sequential
from keras.utils.np_utils import to_categorical

(X_train, y_train), (X_test, y_test) = cifar10.load_data()

X_train = X_train[:100]
X_test = X_test[:100]
y_train = to_categorical(y_train)[:100]
y_test = to_categorical(y_test)[:100]

input_tensor = Input(shape=(32, 32, 3))

vgg16 = VGG16(include_top=False, weights='imagenet', input_tensor=input_tensor)

top_model = Sequential()
top_model.add(Flatten(input_shape=vgg16.output_shape[1:]))
top_model.add(Dense(256, activation="sigmoid"))
top_model.add(Dropout(0.5))
top_model.add(Dense(10, activation="softmax"))

model = Model(inputs=vgg16.input, outputs=top_model(vgg16.output))

for layer in model.layers[:15]:
   layer.trainable = False

#model.load_weights("my_model.h5")

model.compile(loss='categorical_crossentropy',
             optimizer=optimizers.SGD(lr=1e-4, momentum=0.9),
             metrics=['accuracy'])

model.fit(X_train, y_train, batch_size=64, epochs=10)

score = model.evaluate(X_test, y_test, verbose=0)
print('Test loss:', score[0])
print('Test accuracy:', score[1])

model.save('./ファイル名.h5', include_optimizer=False)

転移学習を用いてモデルを作成しています。
Cifer10は非常に多くの画像を含むデータセットであるため、学習の時間がかかります。
なので訓練データ、テストデータの数をそれぞれ100とし時間を削減しています。

これで学習済みモデルの用意ができました。

・メインのコード

ここからはメインのコードをみていきます。
ディレクトリ構造は以下のようになっています。

.
├── Procfile
├── main.py
├── my_model.h5
├── requirements.txt
└── runtime.txt

・main.pyについて

import os
import errno
import tempfile
from flask import Flask, request, abort
from linebot import LineBotApi, WebhookHandler
from linebot.exceptions import InvalidSignatureError
from linebot.models import MessageEvent, TextMessage, ImageMessage, TextSendMessage, FollowEvent
from tensorflow.keras.models import Sequential, load_model
from tensorflow.keras.preprocessing import image
import tensorflow.compat.v1 as tf
import numpy as np
import cProfile

app = Flask(__name__)

line_bot_api = LineBotApi('アクセストークン') 
handler = WebhookHandler('チャンネルシークレット') 

static_tmp_path = os.path.join(os.path.dirname(__file__), 'static', 'tmp')
try:
   os.makedirs(static_tmp_path)
except OSError as exc:
   if exc.errno == errno.EEXIST and os.path.isdir(static_tmp_path):
       pass
   else:
       raise

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

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

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

   return 'OK'

@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
   text = event.message.text

   if text == 'おはよう':
       line_bot_api.reply_message(
           event.reply_token,
           TextSendMessage(text='おはよう!'))
   elif text == 'こんにちは':
       line_bot_api.reply_message(
           event.reply_token,
           TextSendMessage(text='こんにちは!'))
   else:
       line_bot_api.reply_message(
           event.reply_token,
           TextSendMessage(text=event.message.text))

@handler.add(FollowEvent)
def handle_follow(event):
   profile = line_bot_api.get_profile(event.source.user_id)
   line_bot_api.push_message("ユーザーID",
       TextSendMessage(text="表示名:{}\nユーザID:{}\n画像のURL:{}\nステータスメッセージ:{}".format(profile.display_name, profile.user_id, profile.picture_url, profile.status_message)))

   line_bot_api.reply_message(
       event.reply_token, TextSendMessage(text='友達追加ありがとうございます'))

class_label = ["飛行機","自動車","鳥","猫","鹿","犬","蛙","馬","船","トラック"]
model = load_model('my_model.h5')
@handler.add(MessageEvent, message=ImageMessage)
def handle_content_message(event):
   message_content = line_bot_api.get_message_content(event.message.id)
   with tempfile.NamedTemporaryFile(dir=static_tmp_path, prefix="jp" + '-', delete=False) as tf:
       for chunk in message_content.iter_content():
           tf.write(chunk)
           tempfile_path = tf.name

   dist_path = tempfile_path + '.' + "jpg"
   dist_name = os.path.basename(dist_path)
   os.rename(tempfile_path, dist_path)

   filepath = os.path.join('static', 'tmp', dist_name)

   img = image.load_img(filepath, target_size=(32,32))
   img = image.img_to_array(img)
   data = np.array([img])
   result = model.predict(data)
   predicted = result.argmax()
   pred_answer = "これは" + class_label[predicted] + "です"

   line_bot_api.reply_message(event.reply_token,TextSendMessage(text= pred_answer))

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

・Procfile、runtime.txt、requirements.txtについて。

<Procfile>

web: python main.py

<runtime.txt>

python-3.7.7

<requirements.txt>

absl-py==0.9.0
bleach==3.1.5
click==7.1.2
certifi==2020.4.5.2
chardet==3.0.4
Flask==1.1.2
future==0.18.0
gast==0.3.3
grpcio==1.30.0
#gunicorn==19.7.1
h5py==2.10.0
html5lib==1.0.1
itsdangerous==1.1.0
idna==2.9
Jinja2==2.11.2
Keras==2.4.3
Markdown==3.2.2
MarkupSafe==1.1.1
numpy==1.18.1
oauthlib==3.1.0
pillow==7.1.2
protobuf==3.12.2
PyYAML==5.3.1
requests==2.23.0
scipy==1.4.1
six==1.15.0
tensorboard==2.3.0
tensorflow==2.3.0
termcolor==1.1.0
urllib3==1.25.9
Werkzeug==1.0.1
line-bot-sdk==1.17.0

・改善点/コメント

このラインボットでははじめに書いたようなことしかできません。
Line Massaging APIについて学習を進め、もっとたくさんの機能を実装していきたいと思います。

また、このボットはなぜか時間で反応しなくなります。
webhookを検証すると動くようになるのですが、常時動くように改善していければと思います。


この記事が気に入ったらサポートをしてみませんか?