Python for NLP: ルールベースのチャットボットを作成する

Python for NLPの連載は今回で12回目です。

前回は、PythonのGensimライブラリの様々な機能について簡単に説明しました。

これまで、この連載では、NLTK、SpaCy、Gensim、StanfordCoreNLP、Pattern、TextBlobなど、よく使われるNLPライブラリのほとんどを取り上げました。

この記事では、どのようなNLPライブラリも探索するつもりはありません。

むしろ、テニスというスポーツに関するユーザーの質問に答えることができる、非常にシンプルなルールベースのチャットボットを開発する予定です。

しかし、実際にコーディングを始める前に、まず、チャットボットとは何か、どのように使われるのかを簡単に説明しましょう。

チャットボットとは?

チャットボットとは、テキスト、音声、またはグラフィカルユーザーインターフェースを介してユーザーの問い合わせに答えることができる会話型エージェントのことです。

簡単に言うと、チャットボットは、あらゆるトピックについてユーザーとチャットできるソフトウェア・アプリケーションです。

チャットボットは大きく2つのタイプに分類されます。

タスク指向型チャットボットと汎用型チャットボットです。

タスク指向のチャットボットは、特定のタスクを実行するために設計されています。

例えば、タスク指向のチャットボットは、列車の予約やピザの配達に関する問い合わせに答えることができます。

また、個人的な医療セラピストや個人アシスタントとして働くこともできます。

一方、汎用のチャットボットは、ユーザーと自由な議論をすることができます。

また、ハイブリッドチャットボットと呼ばれる第3のタイプのチャットボットは、タスク指向とオープンエンドの両方のディスカッションをユーザーと行うことができます。

チャットボット開発のための###アプローチ

チャットボットの開発アプローチは、ルールベースのチャットボットと学習ベースのチャットボットの2つに分類されます。

学習型チャットボット

学習型チャットボットは、機械学習の手法とデータセットを用いて、ユーザーの問い合わせに対する応答を生成するために学習するタイプのチャットボットです。

学習型チャットボットはさらに、検索型チャットボットと生成型チャットボットの2つに分類されます。

検索ベースのチャットボットは、ユーザーのクエリに対して特定のレスポンスを選択するように学習する。

一方、生成型チャットボットは、その場で応答を生成するように学習する。

学習ベースのチャットボットの主な利点の1つは、ユーザーの様々な問い合わせに柔軟に対応できることです。

レスポンスが常に正しいとは限らないが、学習型チャットボットは、あらゆるタイプのユーザーの問い合わせに答えることができる。

これらのチャットボットの主な欠点は、学習させるために膨大な時間とデータを必要とする可能性があることです。

ルールベース型チャットボット

ルールベースのチャットボットは、学習ベースのチャットボットに比べて、かなりわかりやすいのが特徴です。

特定のルールのセットがあります。

ユーザーのクエリーがいずれかのルールにマッチすれば、クエリーの答えが生成され、そうでなければ、ユーザーのクエリーの答えが存在しないことがユーザーに通知されます。

ルールベースのチャットボットの利点の1つは、常に正確な結果を得られることです。

しかし、欠点としては、拡張性がないことです。

回答を増やすには、新たなルールを定義する必要があります。

次のセクションでは、テニスというスポーツに関する簡単なユーザーの問い合わせに答える、ルールベースのチャットボットの作成方法を説明します。

Pythonによるルールベースチャットボットの開発

これから開発するチャットボットは、とてもシンプルなものになります。

まず、テニスというスポーツに関する多くの情報を含むコーパスが必要です。

このようなコーパスは、テニスに関するWikipediaの記事をスクレイピングすることで作成します。

次に、このコーパスに対していくつかの前処理を行い、コーパスを文に分割する。

ユーザがクエリを入力すると、そのクエリはベクトル化される。

また、コーパスに含まれる全ての文は対応するベクトル化された形に変換される。

次に、ユーザー入力のベクトルと最も高いコサイン類似度を持つ文が、ユーザー入力に対する応答として選択される。

以上の手順で、チャットボットを開発します。

必須ライブラリのインポート

import nltk
import numpy as np
import random
import string


import bs4 as bs
import urllib.request
import re


Wikipediaのデータを解析するために、Beautifulsoup4ライブラリを使用する予定です。

さらに、Pythonの正規表現ライブラリである re を使って、テキストの前処理を行います。

コーパスの作成

先に述べたように、テニスに関するWikipediaの記事を利用してコーパスを作成する。

以下のスクリプトはWikipediaの記事を取得し、記事のテキストからすべての段落を抽出する。

最後に、処理を容易にするために、テキストを小文字に変換する。

raw_html = urllib.request.urlopen('https://en.wikipedia.org/wiki/Tennis')
raw_html = raw_html.read()


article_html = bs.BeautifulSoup(raw_html, 'lxml')


article_paragraphs = article_html.find_all('p')


article_text = ''


for para in article_paragraphs:
    article_text += para.text


article_text = article_text.lower()


テキスト前処理とヘルパー機能

次に、テキストから特殊文字や空白をすべて取り除くために、テキストの前処理を行う必要があります。

次の正規表現がそれを行う。

article_text = re.sub(r'[[0-9]*]', ' ', article_text)
article_text = re.sub(r's+', ' ', article_text)


ユーザー入力のcosine類似度は実際に各文章と比較されるので、テキストを文と単語に分割する必要がある。

以下のスクリプトを実行する。

article_sentences = nltk.sent_tokenize(article_text)
article_words = nltk.word_tokenize(article_text)


最後に、ユーザー入力のテキストから句読点を取り除き、テキストをレマタイズするヘルパー関数を作成する必要があります。

レンマタイゼーションとは、単語をその根源的な形に還元することである。

例えば、”ate “はeat、”throwing “はthrow、”worse “はbadとなる。

次のコードを実行してみてください。

wnlemmatizer = nltk.stem.WordNetLemmatizer()


def perform_lemmatization(tokens):
    return [wnlemmatizer.lemmatize(token) for token in tokens]


punctuation_removal = dict((ord(punctuation), None) for punctuation in string.punctuation)


def get_processed_text(document):
    return perform_lemmatization(nltk.word_tokenize(document.lower().translate(punctuation_removal)))


上記のスクリプトでは、まずNTLKライブラリから WordNetLemmatizer のインスタンスを作成する。

次に、関数 perform_lemmatization を定義する。

この関数は単語のリストを入力とし、対応するレンマタイザーのリストをレンマタイズする。

punctuation_removalリストは、渡されたテキストから句読点を削除する。

最後に、get_processed_text` メソッドは、文を入力として受け取り、トークン化し、レンマタイズし、そして文から句読点を削除します。

挨拶への対応

ルールベースのチャットボットを開発するため、ユーザー入力の種類によって異なる方法で処理する必要があります。

例えば、挨拶については、専用の関数を定義します。

挨拶を処理するために、greeting_inputsgreeting_outputs という二つのリストを作成します。

ユーザが挨拶を入力すると、 greetings_inputs リストからその挨拶を検索し、見つかった場合は greeting_outputs リストからランダムに返答を選択するようにする。

次のスクリプトを見てください。

greeting_inputs = ("hey", "good morning", "good evening", "morning", "evening", "hi", "whatsup")
greeting_responses = ["hey", "hey hows you?", "*nods*", "hello, how you doing", "hello", "Welcome, I am good and you"]


def generate_greeting_response(greeting):
    for token in greeting.split():
        if token.lower() in greeting_inputs:
            return random.choice(greeting_responses)


ここでは、generate_greeting_response() メソッドが挨拶メッセージの検証とそれに対応するレスポンスの生成を基本的に担っています。

ユーザーからの問い合わせに対応する

先に述べたように、応答は入力文をベクトル化したものとコーパス中の文とのコサイン類似度に基づいて生成される。

以下のスクリプトは TfidfVectorizercosine_similarity 関数をインポートしている。

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity


これでテニスに関連するユーザークエリに対する応答を生成するために必要な準備はすべて整った。

ユーザーの入力を受け取り、ユーザー入力の cosine 類似度を求め、コーパスの文と比較するメソッドを作成します。

次のスクリプトを見よ。

def generate_response(user_input):
    tennisrobo_response = ''
    article_sentences.append(user_input)


word_vectorizer = TfidfVectorizer(tokenizer=get_processed_text, stop_words='english')
    all_word_vectors = word_vectorizer.fit_transform(article_sentences)
    similar_vector_values = cosine_similarity(all_word_vectors[-1], all_word_vectors)
    similar_sentence_number = similar_vector_values.argsort()[0][-2]


matched_vector = similar_vector_values.flatten()
    matched_vector.sort()
    vector_matched = matched_vector[-2]


if vector_matched == 0:
        tennisrobo_response = tennisrobo_response + "I am sorry, I could not understand you"
        return tennisrobo_response
    else:
        tennisrobo_response = tennisrobo_response + article_sentences[similar_sentence_number]
        return tennisrobo_response


generate_response()メソッドはユーザー入力を1つのパラメータとして受け付けることが分かる。

次に、空の文字列tennisrobo_response` を定義する。

次に、ユーザーからの入力を既存の文のリストに追加します。

word_vectorizer = TfidfVectorizer(tokenizer=get_processed_text, stop_words='english')
all_word_vectors = word_vectorizer.fit_transform(article_sentences)


tfidfvectorizer` を初期化し、入力文と一緒にコーパスに含まれる全ての文を対応するベクトル化された形式に変換する。

次の行では

similar_vector_values = cosine_similarity(all_word_vectors[-1], all_word_vectors)


cosine_similarity関数を用いて、all_word_vectors` リストの最後の項目(これは最後に追加されたので、実際にはユーザー入力の単語ベクトル)とコーパスの全文の単語ベクトルとの間のコサイン類似度を求めている。

次に、次の行で

similar_sentence_number = similar_vector_values.argsort()[0][-2]


ベクトルの余弦類似度を含むリストをソートすると、リストの最後から2番目の項目が実際にユーザー入力と最も高い余弦を持つことになる(ソートの後)。

最後の項目はユーザー入力そのものであるため、これは選択しない。

最後に、取得したcosine類似度を平坦化し、類似度が0になるかどうかをチェックする。

もしマッチしたベクトルのcos類似度が0であれば、我々のクエリに答えがなかったことを意味する。

この場合、我々は単にユーザークエリを理解していないと表示する。

そうでない場合、cosine similarityが0でなければ、コーパスの中に入力と似た文が見つかったことを意味する。

ーーーチャットボットとチャットする

最後のステップとして、先ほど設計したチャットボットとチャットするための関数を作成する必要があります。

そのために、ユーザーが “Bye “と入力するまで実行し続けるヘルパー関数を作成します。

以下のスクリプトをご覧ください。

この後にコードの説明があります。

continue_dialogue = True
print("Hello, I am your friend TennisRobo. You can ask me any question regarding tennis:")
while(continue_dialogue == True):
    human_text = input()
    human_text = human_text.lower()
    if human_text != 'bye':
        if human_text == 'thanks' or human_text == 'thank you very much' or human_text == 'thank you':
            continue_dialogue = False
            print("TennisRobo: Most welcome")
        else:
            if generate_greeting_response(human_text) != None:
                print("TennisRobo: " + generate_greeting_response(human_text))
            else:
                print("TennisRobo: ", end="")
                print(generate_response(human_text))
                article_sentences.remove(human_text)
    else:
        continue_dialogue = False
        print("TennisRobo: Good bye and take care of yourself...")


上のスクリプトでは、まずフラグ continue_dialogue を true にセットしています。

その後、ユーザーに対して何か入力を求めるウェルカムメッセージを表示します。

次に、continue_dialogue フラグが true になるまで実行し続ける while ループを初期化します。

ループの中では、ユーザーからの入力を受け取り、それを小文字に変換しています。

ユーザー入力は human_text 変数に格納される。

もし、ユーザーが “bye” という単語を入力した場合、continue_dialogue が false に設定され、ユーザーに対してさようならのメッセージが表示される。

一方、入力テキストが “bye” と等しくない場合、入力に “thanks” や “thank you” などの単語が含まれているかどうかがチェックされる。

そのような単語が見つかった場合、”Most welcome “という返答が生成される。

そうでない場合、ユーザー入力が None でなければ、 generate_response メソッドが呼ばれ、前のセクションで説明したように、コサイン類似度に基づいてユーザーの応答を取得する。

応答が生成されると、ユーザー入力はコーパスの一部とならないように、文のコレクションから削除される。

このプロセスはユーザーが “bye “と入力するまで続けられる。

このタイプのチャットボットがなぜルールベースのチャットボットと呼ばれるのか、おわかりいただけると思います。

従うべきルールがたくさんあり、もしチャットボットにもっと機能を追加したければ、もっとルールを追加しなければなりません。

チャットボットのスクリプトの出力は、このようになります。

上の画像で、「roger federer」と入力したところ、生成されたレスポンスは以下の通りです。

however it must be noted that both rod laver and ken rosewall also won major pro slam tournaments on all three surfaces (grass, clay, wood) rosewall in 1963 and laver in 1967. more recently, roger federer is considered by many observers to have the most "complete" game in modern tennis."


この応答は正確ではないかもしれませんが、それでも意味があります。

この記事の目的は、完璧なチャットボットを開発することではなく、ルールベースのチャットボットの動作原理を説明することであることは重要です。

結論

チャットボットは、人間と様々な種類の会話をする会話エージェントです。

チャットボットは、個人アシスタントからチケット予約システム、生理療法士に至るまで、生活のさまざまな層でその居場所を見つけつつあります。

人間の代わりにチャットボットを利用することは、実は非常にコスト効率が良いのです。

しかし、人間と同等の効率を持つチャットボットを開発するのは、非常に複雑です。

今回は、コサイン類似度を利用した簡単なルールベースのチャットボットを開発する方法を紹介します。

次回は、他の自然言語処理の分野を探ります。

タイトルとURLをコピーしました