Dialogflow のインテントにおけるトレーニング フレーズの品質の評価

このチュートリアルでは、Dialogflow エージェントのインテントに指定されるトレーニング フレーズの品質を分析し評価する方法を説明します。この分析の目的は、指定されたインテントに関連のないフレーズ、もしくは他のインテントとの関連性の高いフレーズによってエージェントが混乱するのを防ぐことです。

TensorFlow Hubtf.Hub)ユニバーサル センテンス エンコーダ モジュールを使用して、トレーニング フレーズの意味の組み込みを生成するという手法を使用します。次に、同じインテントと異なるインテントの埋め込み間の類似度に基づいて、凝集度と分離度の測定を計算します。このチュートリアルでは、「混乱を招く」ようなトレーニング フレーズも示されます。埋め込み空間内において、指定したものとは異なるインテントにより近いフレーズです。

このチュートリアルのコードは Colab ノートブックにあります。この記事は、Dialogflow の基本的な知識がある読者を対象としています。Dialogflow の詳細については、Google Cloud で Dialogflow Enterprise Edition を使用して chatbot をビルド、保護、およびスケーリングする方法に関するこのマルチパート チュートリアルをご覧ください。

はじめに

Dialogflow を使用すると、自然言語の入力を処理および理解する強力な自然言語理解(NLU)エンジンを提供することで、プロダクトやサービス上に会話型インターフェースを構築できます。Dialogflow のユースケースは次のとおりです。

  • 航空会社、映画館などの予約用ボットを構築する。
  • ファストフードの配達注文のシステムを簡素化する。
  • 半自動コールセンターによって効率的なカスタマー サービスを実現する。

ユーザーの発話を処理するために複雑な会話型フローを実装することもできますが、Dialogflow は基本的に次のステップを実行します。

  1. ユーザーが「先月の請求の合計額はいくらですか?」というような質問をします。
  2. エージェントは入力値を解析し、それを bill_value_inquiry などのインテントと照合します。
  3. さらに、エージェントが「先月」などのエンティティ情報を抽出します。
  4. 抽出されたエンティティのインテントを考慮して、エージェントがフルフィルメントを呼び出してユーザーのリクエストに応答します。

Dialogflow プラットフォームの主なコンセプトを次の表に示します。

用語 説明
エージェント エージェントは、システムに統合可能な NLU モジュールと呼ぶのが最も適切です。ユーザーの入力がエージェントのインテントと一致すると、エージェントはテキストまたはユーザーによる音声リクエストをアクション可能なデータに変換します。
インテント 会話では、インテントによってユーザー入力がレスポンスにマッピングされます。各インテントで、インテントをトリガーする可能性があるユーザー発話の例(トレーニング フレーズ)、各発話から抽出される内容、および応答する方法を定義します。
エンティティ インテントによって特定のユーザー入力の背後にある動機をエージェントが理解する場合、ユーザーが話す内容から特定の情報を取り出すためにエンティティが使用されます。ユーザーのリクエストを実行するため、たとえば、番地、プロダクト名、数量と単位などが使用されます。
フルフィルメント フルフィルメントを使用すると、エージェントによって抽出されたエンティティ情報を使用して、動的なレスポンスやトリガー アクションをバックエンドでインテントごとに生成できます。

Dialogflow のコンセプトの詳細については、Dialogflow のドキュメントをご覧ください。

インテントは Dialogflow システムに不可欠です。インテントはユーザーのリクエストを実行するために、そのリクエストを適切なビジネス ロジックにリンクさせるからです。たとえば、通信サービス プロバイダ向けの Dialogflow システムでは、bill_value_inquirypay_billupgrade_contractcancel_contractadd_service などのインテントが使用される可能性があります。ただし、ユーザーの発話(テキストや音声)を適切なインテントに一致させるには、関連するトレーニング フレーズのセットを使用して、インテントをトレーニングする必要があります。たとえば、天気を尋ねるインテントのトレーニング フレーズは次のようになります。

  • 「今の天気はどうですか?」
  • 「明日のカイロの気温は?」
  • 「来週、チューリッヒに傘を持っていく必要はありますか?」

システムで複数のインテントを作成する場合、インテントに指定した一部のフレーズが混乱や誤解を招く場合があります。たとえば、信頼できる情報源として営業部門で利用されている Dialogflow エージェントがあるとします。連絡先を取得する場合、内部アカウント チーム用とお客様用の 2 つのインテントがあり、get_internal_contactsget_external_contacts の両方を呼び出すことができます。各インテントの一般的なトレーニング フレーズは次のとおりです。

  • get_internal_contacts: 「お客様 X の担当者はだれですか?」
  • get_external_contacts: 「お客様 X に連絡するにはどうすればいいですか?」

外部の連絡先を探しているユーザーから「お客様 X の連絡先」というリクエストが与えられたとします。フレーズが両方のインテントに一致するため、リクエストは Dialogflow エージェントを混乱させる可能性があります。誤ったインテントが一致した場合、ユーザー エクスペリエンスが低下します。リクエストの形式を変更することになり、これはユーザーにとって煩わしく、時間を消費するためです。

したがって、同じインテント内のフレーズはより類似させ、異なるインテント間のフレーズはより類似させないようにします。以降のチュートリアルでは、インテントごとに指定したトレーニング フレーズの品質の評価方法と、混乱を招く可能性のあるトレーニング フレーズを特定する方法について説明します。

アプローチ

このチュートリアルでは、2 つのフレーズ間の類似度を計算し、さらには、すべてのトレーニング フレーズの類似度マトリックスの計算をするというアプローチを使用します。そのマトリックスが得られると、以下を計算できます。

  • 凝集度: 同じインテント内の各フレーズペア間の平均類似値。この値は各インテントに対して計算されます。インテントの凝集度の値が高いほど、インテントのトレーニング フレーズが優れていることになります。
  • 分離度: 2 つのインテント内のトレーニング フレーズの各ペア間の平均距離。
  • 混乱を招くフレーズ: 他のインテントのトレーニング フレーズと類似度の高いトレーニング フレーズ。

2 つのフレーズ間の類似度を計算するには、各フレーズを実際の値の特徴ベクトルに変換します。これは、フレーズの意味(埋め込み)を表します。このタスクのために、このチュートリアルでは TensorFlow Hubtf.Hub)を使用します。これは、機械学習モデルの再利用可能なモジュールの公開、検出、利用のために使用されるライブラリです。これらのモジュールは、テキストや画像などから抽出された事前トレーニング済みのモデルまたは埋め込みです。利用可能なテキスト埋め込みは閲覧可能です。このチュートリアルでは、Universal Sentence Encoder(v2)モジュールを使用します。このモジュールは、テキストを 512 次元のベクトルにエンコードし、テキスト分類、意味的類似度の計算、クラスタリングなどの自然言語タスクに使用できます。

このチュートリアルでは、2 つの埋め込みベクトル間の近接度の指標としてコサイン類似度を使用します。2 つの実数値のベクトル(この例では 2 つのトレーニング フレーズから抽出された 2 つの埋め込みベクトル)を考慮し、コサイン類似度は、次の式を使用してその角度のコサインを計算します。

$$ \cos(A,B) = \frac{\sum_{i=1}^{n}A_iB_i}{\sqrt{\sum_{i=1}^{n}{A_i^2}}\sqrt{\sum_{i=1}^{n}{B_i^2}}} $$

この式で、n はベクトルの要素数です。ベクトル間の角度が小さいほど、その角度のコサイン値は大きくなり、類似度が高くなることを示します。任意の 2 つのベクトル間のコサイン類似度は、常に 0 から 1 までの値です。

図 1 は、このアプローチの概要を示しています。

インテントの凝集度と分離度の評価の概要

図 1: インテントの疑集度と分離度の評価の概要

上の図は次の処理の流れを示しています。

  1. インテントとそのトレーニング フレーズをインポートします。
  2. tf.Hub ユニバーサル センテンス エンコーダ トレーニング済みモジュールを使用して、トレーニング フレーズの埋め込みを生成します。
  3. 2 次元空間で、生成された埋め込みを可視化したものを作成します。
  4. 異なるインテント内のすべてのトレーニング フレーズ間のペア単位類似度の値を含む、埋め込みコサイン類似度マトリックスを計算します。
  5. 凝集度と分離度の指標を計算します。
  6. 混乱を招くフレーズを特定します。

目標

  • (省略可)Dialogflow エージェントを作成します。
  • トレーニング フレーズを使用してインテントをインポートします。
  • Colab ノートブックを実行してインテントの品質を評価します。

費用

このチュートリアルでは、課金対象である次の Google Cloud コンポーネントを使用します。

  • Dialogflow: Standard Edition は無料ですが、Enterprise Edition で提供されるエンタープライズ サポートは有料です。Dialogflow エージェントを作成するときにどちらのエディションを使用するかを選択できます。アカウントでは両方の Edition からのエージェントを利用できます。詳細については、Dialogflow 料金ページをご覧ください。

始める前に

  1. Google アカウントにログインします。

    Google アカウントをまだお持ちでない場合は、新しいアカウントを登録します。

  2. Cloud Console のプロジェクト セレクタページで、Cloud プロジェクトを選択または作成します。

    プロジェクト セレクタのページに移動

  3. Google Cloud プロジェクトに対して課金が有効になっていることを確認します。 プロジェクトに対して課金が有効になっていることを確認する方法を学習する

  4. Dialogflow API を有効にします。

    API を有効にする

  5. Dialogflow API を呼び出すサービス アカウントを作成します。

    サービス アカウントの作成
  6. [サービス アカウントの詳細] ダイアログで、次のスクリーンショットに示されたアカウント名と説明を入力し、[作成] をクリックします。

    サービス アカウントの詳細ダイアログのスクリーンショット
  7. Dialogflow API クライアントに役割を設定し、[続行] をクリックします。

    サービス アカウントの権限ダイアログのスクリーンショット

Colab ノートブックでチュートリアルを実施する

次のセクションでは、アプローチのセクションで説明した手順に従って、凝集度と分離度の指標を計算し、混乱を招くフレーズを特定します。

Colab ノートブックのスタートガイド

  1. Colab のノートブック(https://colab.research.google.com/drive/...)に移動します。

  2. Google ドライブにローカルコピーを作成します。

    ノートブックを Google ドライブにコピーする

  3. 必要なライブラリとモジュールをインポートする前に、Cloud Shell で、残りのチュートリアルに必要な Python ライブラリをインストールします。

    !pip install --quiet --upgrade tensorflow dialogflow scipy tensorflow-hub seaborn
    
  4. 始める前にセクションで作成した Google Cloud の PROJECT_IDSERVICE_ACCOUNT_EMAIL を設定します。

    Google Cloud PROJECT_ID と SERVICE_ACCOUNT_EMAIL を設定する

  5. セッションを認証してサービス アカウントのキーを作成します。

    auth.authenticate_user()
    !gcloud config set project {PROJECT_ID}
    !gcloud iam service-accounts keys create sa-key.json \
        --iam-account={SERVICE_ACCOUNT_EMAIL} --project={PROJECT_ID}
    

    これらのコマンドを実行すると、リンクが表示されます。

  6. リンク先でユーザー アカウントを認証します。

  7. ウェブページから認証コードをコピーして、ノートブックの [Enter verification code] フィールドに貼り付けます。

    ノートブックの **Enter verification code** フィールド

Dialogflow エージェントの設定

このチュートリアルで使用する Dialogflow エージェントをすでに所有している場合は、この手順を省略できます。ただし、エージェントがない場合や、新しいエージェントをセットアップする場合は、intents-healthcheck という名前の、エクスポートされた Dialogflow エージェントのコンテンツが含まれた ZIP ファイルをダウンロードできます。このエージェントを Dialogflow アカウントにインポートするには、次の手順を行います。

  1. インポートしたエージェントの zip ファイルをダウンロードします。

    gsutil cp gs://dialogflow-intent-health-check/intent-quality-demo.zip .
    
  2. https://dialogflow.com/ に移動します。

  3. 右上の [Go to Console] ボタンをクリックします。

  4. 左側のメニューの [Create new agent] をクリックします。

    新しいエージェントを作成する

  5. エージェント名 intents-healthcheck を入力します。

  6. [Google Project] の一覧から目的の GCP プロジェクトを選択します。

    • Google Cloud プロジェクトは、Dialogflow エージェントを 1 つのみ持つことができます。リストで Google Cloud プロジェクトが見つからない場合、エージェントはプロジェクトにすでに関連付けられています。
    • [新しいプロジェクトを作成] を選択すると、Dialogflow はエージェントと同じ名前を使用して Google Cloud プロジェクトを作成します。
  7. [作成] をクリックします。

    エージェントに関する情報の入力

  8. 左側のメニューから新しいエージェントを選択し、設定アイコンをクリックします。次に、ページの中央のメニューで [Export and Import] を選択します。

    エクスポートとインポートのダイアログ

  9. [Restore from zip] をクリックします。

    1. ステップ 1 でダウンロードした agent-backup.zip ファイルを選択します。
    2. フォームの下部にあるテキスト ボックスに「RESTORE」と入力して確定します。
    3. [復元] をクリックします。

    zip ファイルからエージェントを復元する

    エージェントを復元すると、Dialogflow によって 5 つのインテントが作成されます。

  10. 左のメニューから [Intents] を選択して、インポートしたインテントを確認します。次のインテントが表示されます。

    インポートされたインテントの確認

これ以降のチュートリアルでは、この復元されたエージェントを使用します。

Colab ノートブックのコードを理解する

次のセクションでは、ノートブックのコードを実行したときのその動作について説明します。

インテントの取得

次のコードは、fetch_intents_training_phrases メソッドを使用して Dialogflow エージェントからのインテントとそのトレーニング フレーズを取得します。このメソッドによって辞書が返されます。キーは Dialogflow エージェントで指定されたインテントであり、各値はそれぞれのエンティティのトレーニング フレーズのリストです。コード内で、project はエージェントが属しているプロジェクトを参照します。service_account_file は、ユーザーが以前に作成したファイルを参照します。

def get_intents(service_account_file, project):

    dialogflow_entity_client =  dialogflow.EntityTypesClient.from_service_account_file(service_account_file)
    parent = dialogflow_entity_client.project_agent_path(project)
    entities = list(dialogflow_entity_client.list_entity_types(parent))

    dialogflow_intents_client = dialogflow.IntentsClient.from_service_account_file(service_account_file)
    parent = dialogflow_intents_client.project_agent_path(project)
    intents = list(dialogflow_intents_client.list_intents(
        parent=parent,
        intent_view=dialogflow.enums.IntentView.INTENT_VIEW_FULL))

    entities_name_to_value = {}
    for intent in intents:
        entities_used = {entity.display_name
            for entity in intent.parameters}

        for entity in entities:
            if entity.display_name in entities_used \
                    and entity.display_name not in entities_name_to_value:
                entities_name_to_value[entity.display_name] = np.random.choice(
                    np.random.choice(entity.entities).synonyms, replace=False)

    intent_to_training_phrases = defaultdict(list)
    for intent in intents:
        for training_phrase in intent.training_phrases:
            parts = [entities_name_to_value[part.alias] if part.entity_type else part.text
                for part in training_phrase.parts]
            intent_to_training_phrases[intent.display_name].append("".join(parts))
        # Remove intents with no training phrases
        if not intent_to_training_phrases[intent.display_name]:
            del intent_to_training_phrases[intent.display_name]
    return intent_to_training_phrases
 

次のコードにより取得したインテントが検証されます。

intent_training_phrases = fetch_intents_training_phrases("sa-key.json", project_id)
for intent in intent_training_phrases:
    print("{}:{}".format(intent, len(intent_training_phrases[intent])))

fetch_intents_training_phrases メソッドは、次のリストを返します。このコード スニペットでは、デモの intents-healthcheck エージェントのインテントの後に、各インテントで利用できるトレーニング フレーズ数が表示されています。

start_conversation:4
close_conversation:5
get_internal_contacts:17
request_help:7
get_external_contacts:6

トレーニング フレーズ用の埋め込みの生成

以下のコードにより、tf.Hub ユニバーサル センテンス エンコーダのトレーニング済みモジュールがダウンロードされます。

embed_module = hub.Module("https://tfhub.dev/google/universal-sentence-encoder/2")

モジュールは最初に使用された後、ローカルのキャッシュに保存されます。

以下のコードにより、センテンスのリストを受け入れるメソッドが実装され、tf.Hub モジュールに基づいて埋め込みのリストが返されます。

def make_embeddings_fn():
    placeholder = tf.placeholder(dtype=tf.string)
    embed = embed_module(placeholder)
    session = tf.Session()
    session.run([tf.global_variables_initializer(), tf.tables_initializer()])
    def _embeddings_fn(sentences):
        computed_embeddings = session.run(
            embed, feed_dict={placeholder: sentences})
        return computed_embeddings
    return _embeddings_fn

generate_embeddings = make_embeddings_fn()

このメソッドにより、tf.Session が作成されます。また、メソッドが呼び出されるたびに、毎回ではなく、1 回のみ埋め込みモジュールが読み込まれます。

次のコードによって、インテントにトレーニング フレーズの埋め込みが生成されます。

{
    intent: {
        training_phrase': [embedding_array]
    }
}

training_phrases_with_embeddings = defaultdict(list)
for intent_name, training_phrases_list in intent_training_phrases.items():
    computed_embeddings = generate_embeddings(training_phrases_list)
    training_phrases_with_embeddings[intent_name] = dict(zip(training_phrases_list, computed_embeddings))

このコード スニペットによって、training_phrases_with_embeddings ネストされた辞書が作成されます。

次のコードによって、生成された埋め込みが検証されます。

training_phrases_with_embeddings = defaultdict(list)
for intent_name, training_phrases_list in intent_training_phrases.items():
    computed_embeddings = generate_embeddings(training_phrases_list)
    training_phrases_with_embeddings[intent_name] = dict(zip(training_phrases_list, computed_embeddings))
 

次のコード スニペットによって、start_conversation インテントの各トレーニング フレーズと、各フレーズの埋め込みベクトルの最初の 5 つの要素が表示されます。ユニバーサル センテンス エンコーダによって、トレーニング フレーズごとに 512 次元の埋め込みベクトルが生成されます。

Ciao!:[-0.03649221  0.02498418 -0.03456857  0.02827227  0.00471277]
Howdy!:[-0.02732556 -0.00821852 -0.00794602  0.06356855 -0.03726532]
Hello!:[-0.0255452   0.00690543 -0.00611844  0.05633081 -0.0142823 ]
Hi!:[-0.03227544 -0.00985429 -0.01329378  0.06012927 -0.03646606]

2 次元空間での埋め込みの可視化

次のコードによって、主成分分析を使用して主成分を計算することにより、埋め込みの次元数が 512 から 2 に削減されます。

from sklearn.decomposition import PCA
embedding_vectors = None

for intent in training_phrases_with_embeddings:
    embeddings = list(training_phrases_with_embeddings[intent].values())
    if embedding_vectors is None:
        embedding_vectors = embeddings
    else:
        embedding_vectors = np.concatenate((only_embeddings, embeddings))

pca = PCA(n_components=3)
pca.fit(embedding_vectors)

このコード スニペットは、sklearnPCA クラスを使用して、トレーニング フレーズの埋め込みの 2D 表現を生成します。

次のコードによって、次元数が削減されたフレーズの埋め込みを可視化したものが生成されます。

import matplotlib.pyplot as plt

fig = plt.figure(figsize=(15,10))
ax = fig.add_subplot(111)

legend = []

for color, intent in enumerate(training_phrases_with_embeddings):
    phrases = list(training_phrases_with_embeddings[intent].keys())
    embeddings = list(training_phrases_with_embeddings[intent].values())
    points = pca.transform(embeddings)
    xs = points[:,0]
    ys = points[:,1]
    ax.scatter(xs, ys, marker='o', s=100, c="C"+str(color))
    for i, phrase in enumerate(phrases):
        ax.annotate(phrase, (xs[i], ys[i]))
    legend.append(intent)

ax.legend(legend)
plt.show()

次の図は、可視化の結果を示しています。次元数削減によるフレーズ埋め込みの可視化

フレーズ間のペア単位類似度の計算

次のコードによって、sklearn.metrics.pairwise.cosine_similarity を使用して、トレーニング フレーズの埋め込みのペア単位のコサイン類似度が計算されます。このコードによって、ペア単位類似度の値を使用して、Dataframe similarity_df が作成されます。

from sklearn.metrics.pairwise import cosine_similarity

flatten = []
for intent in training_phrases_with_embeddings:
        for phrase in training_phrases_with_embeddings[intent]:
            flatten.append((intent, phrase, training_phrases_with_embeddings[intent][phrase]))

data = []
for i in range(len(flatten)):
    for j in range(i+1, len(flatten)):
        intent_1 = flatten[i][0]
        phrase_1 = flatten[i][1]
        embedd_1 = flatten[i][2]
        intent_2 = flatten[j][0]
        phrase_2 = flatten[j][1]
        embedd_2 = flatten[j][2]
        similarity = cosine_similarity([embedd_1], [embedd_2])[0][0]
        record = [intent_1, phrase_1, intent_2, phrase_2, similarity]
        data.append(record)

similarity_df = pd.DataFrame(data,
    columns=["Intent A", "Phrase A", "Intent B", "Phrase B", "Similarity"])

次のコードによって、サンプルの類似度レコードが表示されます。

different_intent = similarity_df['Intent A'] != similarity_df['Intent B']
display(similarity_df[different_intent].sort_values('Similarity',
ascending=False).head(5))

次のコード スニペットは、同じインテントに属さない最も類似したトレーニング フレーズを示しています。

同じインテントに属さない最も類似したトレーニング フレーズ

異なるインテントのフレーズが高い類似度の値を持つ場合は、Dialogflow エージェントを混乱させる可能性があり、ユーザーの入力が誤ったインテントにリダイレクトされる可能性があります。

インテントの凝集度と分離度の測定

次のコードによって、アプローチのセクションで説明したように、各インテントの凝集度の値が計算されます。

same_intent = similarity_df['Intent A'] == similarity_df['Intent B']
cohesion_df = pd.DataFrame(similarity_df[different_intent].groupby('Intent A', as_index=False)['Similarity'].mean())
cohesion_df.columns = ['Intent', 'Cohesion']
display(cohesion_df)

表示される結果は、各インテントに対する凝集度の値です。

各インテントの凝集度の計算

次のコードによって、アプローチのセクションで説明したように、インテント間のペア単位分離度が計算されます。

different_intent = similarity_df['Intent A'] != similarity_df['Intent B']
separation_df = pd.DataFrame(similarity_df[different_intent].groupby(['Intent A', 'Intent B'], as_index=False)['Similarity'].mean())
separation_df['Separation'] = 1 - separation_df['Similarity']
del separation_df['Similarity']
display(separation_df.sort_values('Separation'))

表示される結果は、インテント間のペア単位分離度です。

インテント間のペア単位分離度の計算

さらに改善する方法

インテントのトレーニング フレーズの質を改善するには、次のアプローチを検討します。

  • 高類似度を持つ異なるインテントのフレーズを見つけて変更または削除する。
  • 異なるインテントに属する最も類似度が高いフレーズを見つける。
  • 低凝集度のインテントでトレーニング フレーズを追加し、低分離度のインテントでトレーニング フレーズを調査する。

クリーンアップ

  1. Cloud Console で [リソースの管理] ページに移動します。

    [リソースの管理] ページに移動

  2. プロジェクト リストで、削除するプロジェクトを選択し、[削除] をクリックします。
  3. ダイアログでプロジェクト ID を入力し、[シャットダウン] をクリックしてプロジェクトを削除します。

次のステップ