編集部注:本記事は過去に CodeIQ MAGAZINE にて掲載されたものと同一内容となります
2017年はChatbotイヤー!
オリジナルのBotの作成に挑戦してみた方も多いのでは?
私も公開されているAPIドキュメントやサンプルプログラムを見れば、シンプルな応対Botなら簡単に作ることができます。
今回は、そのもう一歩先。さらにステップアップして、より柔軟性の高い、まるで人間と会話しているかのようなBotの作り方を学んでいきましょう!
ダメBotはどうしたら賢くなれるのか?
※本企画はLINE株式会社の提供で、CodeIQ MAGAZINEが制作したものです。
こんにちは、池澤あやかです。
エンジニア界隈では、昨年ごろからChatbotが流行ってますよね。私もこのブームに乗じていくつかBotを作ってみたのですが、我ながら自作したBotには少し使いづらさを感じています。
あらかじめ決められたワードに反応するようなBotになってしまっているため、柔軟性に欠けているといいますか。
Microsoftのりんなちゃんみたいな賢いBotを、とはいかないまでも、もう少し賢いBotを作りたいものです。
このダメBotはどうすれば賢くなるのでしょうか……。
今回教えてくださるのは、今まで数え切れないほどBotを作成してきた株式会社トレタのCTOにして、Botマスターの増井雄一郎さん。
前回長谷部さんに教えていただいたLINE Messaging APIに引き続き、今回は私が書いてきたシンプルな応答Botプログラムをベースにして、増井さんにコードレビューをしていただきつつ、より良いBotの作り方を学んでいきたいと思います!
池澤謹製!乙女心解説BOTとは
「池澤さんが作ってきてくれたBotは、どんなBotなんですか?」
「『乙女心解説BOT』という、女の子の気持ちを代弁してくれるBotです。これは女子あるあるだと思うんですけど、『今の私の気持ちを空気を読んで察してくれ』という含みを込めた発言をすることあるじゃないですか。そこをド直球に解説してくれるBotです」
「Botを入れたグループ内で、例えば『大丈夫?』と発言すると、このような乙女心解説が出てきます」
「(笑)」
「『疲れた』なんて発言すると、乙女心解説がこのように補足してくれます」
「うざい(笑)」
「喧嘩をしている時なんかに、『お互い大人なんだし』という発言をした際には、Botがこのように補足してくれます」
「池澤さんの心の闇が…」
「プログラムとしては、Rubyの軽量Webアプリケーション用フレームワークを使っています。LINEにテキスト送られると、ルート /line/callback にコールバックが発生して、用意していたAnalyzeTextクラスの中で分析するという流れです」
post '/line/callback' do
body = request.body.read
signature = request.env['HTTP_X_LINE_SIGNATURE']
unless client.validate_signature(body, signature)
error 400 do 'Bad Request' end
end
events = client.parse_events_from(body)
events.each do |event|
case event
when Line::Bot::Event::Message
case event.type
when Line::Bot::Event::MessageType::Text
analyze = AnalyzeText.new(event.message['text'])
result = analyze.result
message = {
type: 'text',
text: result
}
client.reply_message(event['replyToken'], message)
end
end
end
end
「AnalyzeTextクラスでは、とあるセンテンスパターンと一致するところがあった場合に、あらかじめ決めておいた答えを返すというシンプルな構造になっています」
「んー、シンプルな応対Botのプログラムとしては、基本的には問題ないですね。ただ、こうした方が今後の拡張性が高いなと感じるところはいくつかあるので、レビューしていきましょうか!」
人間の持つ「あいまいさ」を生むアルゴリズムの作り方
「まず大きいところで言うと、今の池澤さんのプログラムは、Caseで書いていますよね。これを配列に置き換えるというのは、Bot作成ではよくやる手です。こんな感じで」
@@patterns = [
Pattern.new(
/どうしたの?|大丈夫?/,
[ "乙女心解説:", "めんどくさいな" ]
),
PatternWithRandom.new(
/疲れた/,
[ "乙女心解説:", "今日は何もできない" ]
),
# ....
]
「Patternクラスはこんな感じで準備しておきます」
class Pattern
attr_reader :matcher, :response
def initialize(matcher, response)
@matcher, @response = matcher, response
end
# パターンにマッチするか?
def match?(text)
@matcher.match(text) != nil
end
end
▲AnalyzeTextクラス内にパターンマッチ用のクラスを作ります。
「えー、配列!?なぜですか?」
「配列で書いたほうが、Caseで書くよりも一覧性が高いというのはもちろんなんですけど、一番の良さはパターンごとにアルゴリズムを含めることができるということです」
「例えば、パターン一致でも、毎回反応しないほうが人間らしかったりするじゃないですか。そうした時に、PatternWithRandomというクラスに、10回に1回だけマッチするようなアルゴリズムを書けばいいわけです」
# 1/10の確率で応答するパターン
class PatternWithRandom < Pattern
# 1/10でマッチする
def match?(text)
rand(10) == 0 && super
end
end
▲randで乱数を発生させ、10回に1度の確率で発動するようにする。
「確かに!Caseだと、こういったアルゴリズムを含んだものは書きにくいかも!」
「プログラムの場合、Patternを継承したクラスをつくることで、いろんなパターンに対応することができますよ」
環境依存はなるべく避けること
「他にこうしたほうがいいとか、何かありますか?」
「あとは細かいところなんだけど、改行コードはメッセンジャーやOSによって違うから、パターンの中にを含めるのは得策ではないですね」
「パターン定義の外で改行コードを加えることで、さまざまなOSに簡単に対応できます。環境依存のものへの対応は慎重に!」
「うっ、気をつけよう…」
さらにBotの柔軟性を高めるには、データベースを連携させよう!
「さらに柔軟に対応するには、どうすればいいでしょうか?」
「会話のステート管理を行ったり、気軽にパターンを増やせるように、パターンを管理するためのWebアプリをつくったりといろいろ考えられますね。これをするには、Botプログラムをデータベースに接続する必要がありますが」
「特定のキーワードに反応して、感情点数をあげていっても面白いと思います。僕も昔社内向けに、『考えます』や『頑張れ!』などのワードに反応して感情点数があがっていき、ある閾値を超えるといきなりブチ切れるというBotを作りました」
「感情まで再現できるとかなりリアルなBotになりそうですね(笑)」
「実際に、新しくチャットに入ってきた外部の人にBotが急にブチ切れて、外部の人が平謝りするという事件も起きるくらいのリアルさです」
「ひどい」
「その後、外部の人に迷惑かけれないということで、このBotはクビになっちゃいました(泣)」
「手動でパターンを増やしまくるか、裏に人工知能を入れるとかしないと、柔軟にBotに対応してもらうのは大変なのかなって半分諦めてたんですけど、意外にいろいろやり方があることが分かりました!」
「Botってグループに入れるとみんなでワイワイ楽しめるのがいいですよね。私のBotも会社の皆から意見をもらって、どんどん賢くなっていきました。チャットは周りからのフィードバックもすぐ聞けるし、モチベーション作りやすいので、いろいろ試しながら作ると楽しいですよ」
今回の題材となったBotのプログラムはこちらから
・BotのGitHubへ
・添削前と添削後のdiffはこちらから
次回は、データベースと連携することで、Chatbotの反応をより人間らしくしてみることに挑戦したいと思います。お楽しみに!
※本記事はITエンジニア向け年収提示型スカウト「moffers(モファーズ)」からの提供記事です。
オリジナル版 初回掲載日 2017年9月26日