【ChatterBot】チャットボットが特定の応答メッセージを返すようにタグ付けをする


投稿日 2021年1月19日 >> 更新日 2023年3月1日

今回はPythonのチャットボットフレームワークであるChatterBotライブラリを使用して、タグ付けによる応答メッセージを実装していきたいと思います。

ChatterBotのチュートリアル的な実装は以下の記事をご参照ください。

このChatterBotライブラリでのタグ付けを理解するには、自動で作成されるデータベース構造を知る必要があります。

ChatterBot公式ドキュメントでは余り触れられていませんが、作成されるデータベース内には3つのテーブルがあり、「statement」テーブル、「tag」テーブル、「tag_association」テーブルとあります。

通常ボットとの会話記録は「statement」テーブル内に保存されていき、何も手を加えなければ「tag」「tag_association」テーブルの中身は空のままです。

「tag」や「tag_association」内にデータが存在していれば、「statement」内の特定のデータがタグ付けされているという事になります。

タグ付けをすることによってボットはユーザーからの入力文章に対し「そのユーザーに対する応答メッセージ」を返すことができます。

ではさっそく始めていきましょう。

実行環境&使用ライブラリ

実行環境
Windows Subsystem for Linux
Python 3.6.9
pip 9.0.1

※「ChatterBot==1.1.0」は削除されているみたいなので、別のバージョンで試してみて下さい。

使用ライブラリ ライセンス
ChatterBot==1.1.0 BSD

ChatterBotの実装

まずはChatterBotが作成するデータベース内の構造を得るために、シンプルな実装を試みます。

会話が開始されると同時にカレントディレクトリ内に「db.sqlite3」というデータベースファイルがデフォルトで作成されるので、適当なPythonファイルに以下のようなコードを記述して起動します。

# chatbot.py

from chatterbot import ChatBot

# 初期化
chatbot = ChatBot(
        name='MyBot', # ボットネーム
)

response = chatbot.get_response("Hello")
print('{} => {}'.format(chatbot.name, response))

get_responseメソッドにテキストを渡すと、与えられた入力テキストとボットの応答がデータベースに保存され「response」変数にボットの応答が返り値として格納されます。

Pythonファイルを実行してみると

$ python3 chatbot.py
MyBot => Hello

入力テキストと同じ文章が応答として返されましたが、チャットボットがトレーニング(学習)をしていない状態では入力テキストをオウム返しのように応答するだけのボットとなります。

後ほど学習をさせていきますが、まずはデータベース内の構造を確認してみます。

「ls」コマンドを叩くと「db.sqlite3」というファイルが作成されているかと思います。

$ ls
chatbot.py db.sqlite3

db.sqlite3を開いて、テーブルを確認します。


$ sqlite3 db.sqlite3
SQLite version 3.22.0 2018-01-22 18:45:57
Enter ".help" for usage hints.
sqlite>
sqlite> .tables
statement        tag              tag_association

3つのテーブルが確認できました。

select文で各テーブル内の情報を取得してみます。


sqlite>
sqlite> .header on
sqlite> .mode column
sqlite>
sqlite> select * from statement;
id          text        search_text  conversation  created_at                  in_response_to  search_in_response_to  persona
----------  ----------  -----------  ------------  --------------------------  --------------  ---------------------  ----------
1           Hello       hello                      2021-01-20 09:39:17.190649
2           Hello                                  2021-01-20 09:39:17.286474  Hello                                  bot:MyBot
sqlite>
sqlite> select * from tag; # 空
sqlite> select * from tag_association; # 空
sqlite>

「statement」テーブル内には、入力したテキストとそれに対するボットの応答テキストが保存されていますが、「tag」「tag_association」テーブル内は空です。

statementテーブル内のデータがtagによって何も紐づけがされていないので空の状態ではありますが、テキストにタグを付与するとどのようにデータが保管されるか確認してみましょう。

入力テキストにタグを付与する

ChatterBotにおける入力テキストでタグを付与するには、get_responseメソッドの「tags」引数にリストとして渡します。

# chatbot.py

response = chatbot.get_response("Hello", tags=['user_a'])

Pythonファイルを更新し実行すると「user_a」というタグとテキストが一緒にデータベースへ保存されます。

$ python3 chatbot.py
MyBot => Hello

タグの付与は複数設定することもできます。

tags引数に「user_a」と「user_b」を設定し、文章を変更します。

# chatbot.py

response = chatbot.get_response("How are you?", tags=['user_a', 'user_b'])

このファイルを実行すると先ほどと同じように設定したタグとテキストがデータベースに保存されているはずなので、データベースを確認してみます。


$ sqlite3 db.sqlite3
SQLite version 3.22.0 2018-01-22 18:45:57
Enter ".help" for usage hints.
sqlite> .header on
sqlite> .mode column
sqlite> select * from tag;
id          name
----------  ----------
1           user_a
2           user_b
sqlite>
sqlite> select * from tag_association;
tag_id      statement_id
----------  ------------
1           3
2           5
1           5
sqlite>
sqlite> select * from statement;
id          text        search_text  conversation  created_at                  in_response_to  search_in_response_to  persona
----------  ----------  -----------  ------------  --------------------------  --------------  ---------------------  ----------
1           Hello       hello                      2021-01-21 09:44:27.139777
2           Hello                                  2021-01-21 09:44:27.534256  Hello                                  bot:MyBot
3           Hello       hello                      2021-01-21 09:44:39.878055  Hello
4           Hello                                  2021-01-21 09:44:39.925743  Hello                                  bot:MyBot
5           How are yo  ADV:be AUX:                2021-01-21 09:46:28.992171  Hello
6           Hello                                  2021-01-21 09:46:29.036410  How are you?                           bot:MyBot
sqlite>

「tag」テーブルのデータを取得してみると、先ほど付与した「user_a」と「user_b」が保存されています。

user_aタグを二度程実行したと思われますが、各テキストデータのIDのように紐づけされているのでname同士の重複はしていません。

sqlite> select * from tag;
id          name
----------  ----------
1           user_a
2           user_b

「tag_association」テーブルでは、紐づけされている「tag」と「statement」のIDが保存されています。

tagテーブルのid1はstatementテーブルのid3に紐づけされているというように、ユーザーが実行した順番通りに保存されています。

sqlite> select * from tag_association;
tag_id      statement_id
----------  ------------
1           3
2           5
1           5

では「tag_association」テーブルと「statement」テーブルを結合して以下のstatementテーブル内のデータからタグ付けされているデータのみを取得してみます。

sqlite> select id, text, search_text, persona
   ...> from statement;
id          text        search_text  persona
----------  ----------  -----------  ----------
1           Hello       hello
2           Hello                    bot:MyBot
3           Hello       hello
4           Hello                    bot:MyBot
5           How are yo  ADV:be AUX:
6           Hello                    bot:MyBot

select文でjoinキーワードでテーブルを結合し、onで条件を指定します。

sqlite> select tag_id, id, text, search_text, persona
   ...> from tag_association join statement on tag_association.statement_id=statement.id;
tag_id      id          text        search_text  persona
----------  ----------  ----------  -----------  ----------
1           3           Hello       hello
2           5           How are yo  ADV:be AUX:
1           5           How are yo  ADV:be AUX:

「tag_id」の1は「user_a」、「tag_id」の2は「user_b」とそれぞれのデータを取得できました。

このようにChatterBotでは文章にタグ付けを行うことによって、タグ付けされていないデータを除外し特定のデータのみを検索アルゴリズムによって探し出してくれます。

タグが付与された文章に対しチャットボットが上図のように特定の文章を返すようにするにはトレーニング(学習)させる必要があります。

タグ付けトレーニング

ChatterBotで会話の応答文を学習させるには幾つか方法があり、1つにトレーニングクラスを使用する方法があります。

公式ドキュメントでも触れられていますが、以下のようにしてトレーニングをさせることにより学習済みの文章であればそれに答えるようになります。

from chatterbot import ChatBot
from chatterbot.trainers import ListTrainer

chatbot = ChatBot('MyBot')

trainer = ListTrainer(chatbot)

# 時系列で文章を渡して学習させる
trainer.train([
    "Hello",
    "Hello World",
    "Who are you?",
    "I am AI"
])

print(chatbot.get_response("Who are you?"))
# I am AI

上記は通常のトレーニングクラスによる学習ですが、タグ付けによる学習は「Statement」クラスを通して行います。

Statementクラスはユーザーなどから発せられる文章のエンティティとして一時的に保管されます。

ChatBotインスタンスのget_responseメソッドに入力文章が渡される際にも内部ではStatementクラスが使用されています。

Statementクラスは「chatterbot.conversation」モジュール内にあり、get_responseメソッドのようにインスタンスを作成できます。


from chatterbot.conversation import Statement

statement = Statement(text="Hi user_a", tags=['user_a'])

上記は学習用に作成した文章であり、データベースにはテキストの「Hi user_a」とタグの「user_a」が紐づけられて保存されるようになります。

何らかの入力文章にタグ「user_a」が与えられたときに「Hi user_a」と応答されるように設定するには「in_response_to」引数にテキストの発動条件となる入力文章を与えます。


from chatterbot.conversation import Statement

statement = Statement(text="Hi user_a", tags=['user_a'], in_response_to="Hello")

in_response_toに文章を設定することで、statementインスタンスの保存時にその文章がバイグラムペアとして「search_in_response_to」へ保存されます。

ChatterBotがデフォルトで作成するデータベース内のstatementテーブルにおいて「search_in_response_to」カラムは学習済みの文章、つまりそのデータのテキストが応答ステートメントとしての候補となります。

Statementインスタンスを学習(保存)させるには、初期化済みのChatBotインスタンスからstorageオブジェクトを呼び出して行います。


from chatterbot import ChatBot
from chatterbot.conversation import Statement

chatbot = ChatBot('MyBot') # 初期化

# 学習用のエンティティを代入
statement = Statement(text="Hi user_a", tags=['user_a'], in_response_to="Hello")

# statementインスタンスはリスト形式で渡す
chatbot.storage.create_many([statement])

create_manyメソッドに渡さる要素は、イテレーションにより処理されるのでリスト形式にして渡します。

storageオブジェクトのソースコードは以下のGitHubから確認できます。

タグ付けによる文章を学習させることができまたので、次はトレーニングクラスも踏まえた学習と合わせてチャットボットがタグ有りかタグ無しを区別する実装を行っていきたいと思います。

ユーザーを区別するチャットボットの実装

ユーザーを区別するチャットボットとして、登録されているユーザーか未登録ユーザーかでボットの応答メッセージを変える実装をしていきます。

予め「user_a」と「user_b」はタグ付けにより登録済みだとして、それ以外は未登録ユーザーとして定めます。

そしてここでは2つのファイルに分けて実装していきます。

1つ目はトレーニング用のファイル、2つ目は会話を実行するファイルとして行います。

トレーニング用のファイルは「training.py」として作成します。

# training.py

from chatterbot import ChatBot
from chatterbot.conversation import Statement
from chatterbot.trainers import ListTrainer

chatbot = ChatBot(name='MyBot')

trainer = ListTrainer(chatbot)

# 未登録ユーザーに受け答えする文章を学習
trainer.train([
    "Hello",
    "Hello World",
    "Who are you?",
    "I am AI"
])

# 登録ユーザーに受け答えする文章をリストに格納
statements = [
    Statement(text="Hi user_a", tags=['user_a'], in_response_to='Hello'),
    Statement(text="Hi user_b", tags=['user_b'], in_response_to='Hello'),
    Statement(text="It ’s the assistant ’s AI.", tags=['user_a', 'user_b'], in_response_to="Who are you?")
]

# Statementインスタンスの入ったリストを渡して学習開始
chatbot.storage.create_many(statements)

上記は未登録ユーザーの応答としてトレーニングクラスによる学習を行い、登録ユーザーの応答では各Statementインスタンスにより作成したリスト形式の変数をcreate_manyメソッドにより学習しています。

では会話を実行するプロセスを「chatbot.py」として作成します。

# chatbot.py

from chatterbot import ChatBot

chatbot = ChatBot(
        name='MyBot',
        default_response=["I do not understand"], # 応答が見つからなかった場合に返す文章
        read_only=False, # デフォルト値
)

# ユーザー名を入力
user_name = input('What is your name?: ')
print('Welcome to ChatBot!')
print('+-----------------+')

if user_name in ['user_a', 'user_b']:
    # 登録ユーザーの処理
    while True:
        try:
            input_data = input(user_name + ': ')
            response = chatbot.get_response(
                    input_data,
                    tags=[user_name],
                    # 検索時のフィルターとしてユーザ名の入ったタグを設定します。
                    additional_response_selection_parameters={
                        'tags': [user_name]
                    }
            )
            print('{}: {}'.format(chatbot.name, response))
            print('-------------------\n')
        except(KeyboardInterrupt, EOFError, SystemExit):
            break
else:
    # 未登録ユーザーの処理
    while True:
        try:
            input_data = input('Not User: ')
            response = chatbot.get_response(input_data)
            print('{}: {}'.format(chatbot.name, response))
            print('-------------------\n')
        except(KeyboardInterrupt, EOFError, SystemExit):
            break

上記の上から説明していくと、ChatBotオブジェクトでは様々なパラメータ引数を設定することでき、以下のように設定しています。

# ChatBotオブジェクト

chatbot = ChatBot(
        name='MyBot',
        default_response=["I do not understand"], # 応答が見つからなかった場合に返す文章
        read_only=False, # デフォルト値
)

「default_response」はユーザーの入力文章に対して応答メッセージが見つからなかった際に応答させる文章として設定することができます。

リスト形式となっていますが[文章1, 文章2]とすることでランダムに応答として返されます。

「read_only」は会話を逐次保存するかしないかを設定できます。

デフォルトでは「False」となっていて、逐次会話が保存されるようになっていますがチャットボットが学習しているわけではありません。

次に「登録ユーザーの処理」において設定したget_responseメソッド引数「additional_response_selection_parameters」です。

# 登録ユーザーの処理

response = chatbot.get_response(
    input_data,
    tags=[user_name],
    # 検索時のフィルターとしてユーザ名の入ったタグを設定します。
    additional_response_selection_parameters={
        'tags': [user_name]
    }
)

additional_response_selection_parametersは検索時のフィルターとして使用されます。

検索が行われるソースコードは以下ファイルのfilterメソッドで確認することができます。

準備は整ったので、既存のデータベースを削除したのち「training.py」で学習を行い「chatbot.py」で会話を実行します。

# db削除
$ rm db.sqlite3

# ボットの学習
$ python3 training.py
List Trainer: [####################] 100%

# 会話開始
$ python3 chatbot.py
What is your name?:

最初にユーザー名を尋ねられるので任意の名前を入力します。

ここでは「user_b」を入力して会話を始めてみます。

What is your name?: user_b
Welcome to ChatBot!
+-----------------+
user_b: Hello
MyBot: Hi user_b
-------------------

user_b: Who are you?
MyBot: It ’s the assistant ’s AI.
-------------------

ボットは登録ユーザーを区別して応答していることが分かります。

次に未登録ユーザーでの会話を実行してみます。

「Ctrl」+「C」でWhileループを抜けて再度実行します。

$ python3 chatbot.py
What is your name?:
Welcome to ChatBot!
+-----------------+
Not User: Hello
MyBot: Hello World
-------------------

Not User: Who are you?
MyBot: I am AI
-------------------

未登録ユーザー(Not User)ではトレーニングクラスにより学習した応答メッセージが返されている事が分かります。

ChatBotオブジェクトで設定したdefault_responseパラメータにより他の文章の受け答えは「I do not understand」となってしまいますが、会話内容は逐次保存されていくのでその記録を元に応答用メッセージを考えて学習させることもできるかと思います。

タグ付けをすることによって、特定の応答を出力できたり応答メッセージを共有できるので非常に便利です。

ChatterBotの日本語設定に関しては以下の記事で実装しているのでご参照してみて下さい。

今回は「ユーザー」を例に実装してみましたが、カテゴリカルにタグを設定して分野別に応答を返すボットが開発できたら面白そうです。

それでは以上となります。

最後までご覧いただきありがとうございました。