LUISを使ってChatbotに日本語を理解させる

2018年1月26日(金)
樋口 勝一
連載6回目となる今回は、マイクロソフトが提供する言語解析AIを用いて、Chatbotに日本語を「理解」させてみる。

ChatbotにLUISを実装する

LUISアプリケーションが完成したところで、ChatbotからLUISを利用する方法を紹介しましょう。

LUISアプリケーションの発行

まずは、Azureの「Cognitive Services」にLUISを利用するためのAPIを作成します。Azureにサインインし、「新規」-「Cognitive Services」からAPIを作成します。作成するAPIのタイプは「Language Understanding(LUIS)」を選択します。

Azure Cognitive ServicesにAPIを作成(1)

Azure Cognitive ServicesにAPIを作成(1)

Keysを選択して、2つあるキーのうち、どちらか一方を取得しておきます。

Azure Cognitive ServicesにAPIを作成(2)

Azure Cognitive ServicesにAPIを作成(2)

次に、LUISの管理画面に戻り、LUISアプリケーションを外部から呼び出して利用できるように発行を行います。画面上のメニューから「PUBLISH」を選択します。

  • Publish to : Production
  • Timezone : Tokyo
  • North America Regions

この設定で「Add Key」をクリックします。表示されたダイアログで、「Tenant name」はLUISアプリケーションのApp Idを、「Key」はAzureで作成した「Cognitive Services API」の名前をそれぞれ選択します。その後「Add Key」をクリックして確定します。

キーの割り当て

キーの割り当て

管理画面を確認すると、Azureで作成したCognitive Services APIのKeyが自動的に読み込まれています。

最後に忘れずに「Publish to production slot」をクリックして、アプリケーションの発行を行います。

APIとの接続が成功しているか確認してみましょう。画面下部の「Chatbot201707_LUIS」の「Endpoint」のURLをクリックします。

Endpointの確認

Endpointの確認

URLの最後に「ビーフカレーをください」と日本語で追加して参照します。成功していれば、json形式のデータが表示されます。queryが「ビーフカレーをください」の場合に、LUISによって「intent=Order」「entity=ビーフカレー」のように言語解析されています。LUISアプリケーションとCognitive Services APIが、適切に接続できていることが確認できました。

Endpointのテスト結果

Endpointのテスト結果

Chatbot201707への実装

AIが利用できる環境が整ったところで、開発中の「Chatbot201707」にLUISの機能を実装してみましょう。C#からLUISを呼び出すときに必要となるのが、LUISアプリケーションの「Application ID」とAzure Cognitive Services APIの「Key」となります。「Application ID」は管理画面の「SETTINGS」から確認できます。

Application IDの確認

Application IDの確認

Azure Cognitive Services APIの「Key」は管理画面の「PUBLISH」にある、「Chatbot201707_LUIS」の「Key string」から2つのKeyが確認できます。いずれか一方だけで構いません。

Keyの確認

Keyの確認

Visual Studioで「Chatbot201707」を開き、Dialogsフォルダーに、空白のクラスファイル「ChatDialogs.cs」を追加します。ダイアログの中からLUISを呼び出します。

リスト1:ChatDialog.cs

01using System;
02using System.Threading.Tasks;
03using Microsoft.Bot.Builder.Dialogs;
04using Microsoft.Bot.Builder.Luis;
05using Microsoft.Bot.Builder.Luis.Models;
06 
07namespace Chatbot201707.Dialogs
08{
09    [LuisModel("取得した Application ID", "取得した Key")]
10    [Serializable]
11    public class ChatDialog : LuisDialog<string>
12    {
13        [LuisIntent("")]
14        public async Task None(IDialogContext context, LuisResult result)
15        {
16            await context.PostAsync($"申し訳ありません注文内容がわかりません。");
17            context.Wait(MessageReceived);
18        }
19 
20        [LuisIntent("Order")]
21        public async Task GetOrder(IDialogContext context, LuisResult result)
22        {
23            EntityRecommendation entity = result.Entities[0];
24            string order = entity.Entity;
25            context.Done(order);
26        }
27    }
28}

個別に注意すべきポイントを見ていきましょう。

参照設定

ダイアログを利用するので「Microsoft.Bot.Builder.Dialogs」を追加し、LUIS呼び出しに必要な「Microsoft.Bot.Builder.Luis」と「Microsoft.Bot.Builder.Luis.Models」を追加しておきます。

[LuisModel("取得した Application ID", "取得した Key")]

LUIS呼び出しに必要な「Application ID」と「Key」を設定します。

[Serializable]

Chatbotではお約束なので忘れずに。

public class ChatDialog : LuisDialog<string>

普通のダイアログと異なり、「LuisDialog」インターフェースを実装します。ダイアログの戻り値はユーザーが入力した文字とするのでstring型を指定しておきます。

[LuisIntent("")]

LUISアプリケーションの実行結果として、Intentが空の場合は、該当するIntentがなかったということで、エラー処理としてメッセージを表示します。

[LuisIntent("Order")]

「Order」Intentに該当した場合の処理を記述します。

EntityRecommendation entity = result.Entities[0]

今回のサンプルではEntityは一つだけですが、設定内容によっては複数返ってくることがあります。そこで、配列の一つとしてEntityを受け取ります。

string order = entity.Entity

注文内容からEntityにマッチした文字列(ビーフカレー)を取得します。

context.Done(order)

ダイアログを終了して、Rootダイアログに戻ります。戻り値として注文内容を返します。

続いて、ChatDialogsを呼び出すために、RootDialogを修正します。

リスト2:RootDialog.cs

001using System;
002using System.Threading;
003using System.Threading.Tasks;
004using Microsoft.Bot.Builder.Dialogs;
005using Microsoft.Bot.Connector;
006using System.Collections.Generic;
007 
008namespace Chatbot201707.Dialogs
009{
010    [Serializable]
011    public class RootDialog : IDialog<object>
012    {
013        private List<string> menuList = new List<string>() { "ランチコース", "カレー", "ドリンク", "デザート", "終了" };
014 
015        public Task StartAsync(IDialogContext context)
016        {
017            context.Wait(HelloMessage);
018            return Task.CompletedTask;
019        }
020 
021        private async Task HelloMessage(IDialogContext context, IAwaitable<object> result)
022        {
023            await context.PostAsync($@"いらっしゃいませ!ご注文を伺います。
024 
025(メニューをご覧になる場合は「メニュー」と入力してください。)");
026 
027            context.Wait(MessageReceivedAsync);
028        }
029 
030        private  async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
031        {
032           var activity = await result;
033            switch (activity.Type)
034            {
035                case ActivityTypes.Message:
036                    var message = activity as IMessageActivity;
037                    switch (activity.Text)                    {
038                        case "メニュー":
039                            MenuMessage(context);
040                            break;
041                        default:
042                            await context.Forward(new ChatDialog(), ChatResumeAfterDialog, message, CancellationToken.None);
043                            break;
044                    }
045                    break;
046            }
047        }
048 
049        private void MenuMessage(IDialogContext context)
050        {
051            PromptDialog.Choice(context, SelectDialog, menuList, "ランチメニューをお選びください。");
052        }
053 
054        private async Task SelectDialog(IDialogContext context, IAwaitable<object> result)
055        {
056            var selectedMenu = await result;
057            switch (selectedMenu)
058            {
059                case "ランチコース":
060                    context.Call(new LunchDialog(), LunchResumeAfterDialog);
061                    break;
062                case "カレー":
063                    context.Call(new CurryDialog(), CurryResumeAfterDialog);
064                    break;
065                case "ドリンク":
066                    context.Call(new DrinkDialog(), DrinkResumeAfterDialog);
067                    break;
068                case "デザート":
069                    context.Call(new DessertDialog(), DessertResumeAfterDialog);
070                    break;
071                case "終了":
072                    await context.PostAsync("ご注文を承りました。");
073                    context.Wait(HelloMessage);
074                    break;
075            }
076        }
077 
078        private async Task LunchResumeAfterDialog(IDialogContext context, IAwaitable<string> result)
079        {
080            var lunch = await result;
081            await context.PostAsync($"ランチコースは {lunch} ですね。");
082            MenuMessage(context);
083        }
084 
085        private async Task CurryResumeAfterDialog(IDialogContext context, IAwaitable<CurryFormQuery> result)
086        {
087            var curry = await result;
088            var topping = "";
089 
090            for (int i = 0; i < curry.Topping.Count; i++)
091            {
092                topping += " " + curry.Topping[i];
093            }
094 
095            await context.PostAsync($@"カレーは {curry.Curry.ToString()} ですね。
096 
097ライスは {curry.Rice.ToString()} ですね。
098 
099サイズは {curry.Size.ToString()} ですね。
100 
101トッピングは {topping} ですね。
102 
103");
104            MenuMessage(context);
105        }
106 
107        private async Task DrinkResumeAfterDialog(IDialogContext context, IAwaitable<string> result)
108        {
109            var drink = await result;
110            await context.PostAsync($"お飲み物は {drink} ですね。");
111            MenuMessage(context);
112        }
113 
114        private async Task DessertResumeAfterDialog(IDialogContext context, IAwaitable<string> result)
115        {
116            var dessert = await result;
117            await context.PostAsync($"デザートは {dessert} ですね。");
118            MenuMessage(context);
119        }
120 
121        private async Task ChatResumeAfterDialog(IDialogContext context, IAwaitable<string> result)
122        {
123            var chat = await result;
124            await context.PostAsync($"ご注文は {chat} ですね。");
125            MenuMessage(context);
126        }
127    }
128}

修正箇所だけピックアップしています。ここでも注意すべきポイントをチェックしていきましょう。

HelloMessage

これまではユーザーから入力があった場合、最初にPromptDialogでメニューを表示していましたが、LUISを利用するために、直接注文を入力するか、メニューを表示するかを選択できるようにしています。

MessageReceivedAsync

ユーザーから入力された文字列が「メニュー」の場合は、これまで通りMenuMessage メソッドを呼び出してPromptDialogでメニューを表示します。

await context.Forward(new ChatDialog(), ...)

その他の文字列の場合はChatDialogを呼び出します。ChatDialogが終了した時には、ChatResumeAfterDialogメソッドを実行します。ChatDialogにはmessageでユーザーからの入力文字列を渡しています。

ChatResumeAfterDialog

ChatDialogからの戻り値=カレーの注文を受け取り、確認メッセージを表示します。

エミュレーターで動作を確認してみましょう。

エミュレーター上で動作を確認する

エミュレーター上で動作を確認する

ユーザーから直接入力された文字列に対して、LUISの言語解析によって注文内容を識別しているのが確認できました。

以上、Chatbotに言語解析のAIを搭載することで、Chatbotをさらに成長させることができました。今回のサンプルは、LUISの使い方としてはとてもシンプルなものですが、ここから様々な機能追加をすることで、より使いやすいChatbotとして育てることができます。AzureのCognitive Services にはこの他にも、画像認識や、音声認識など様々なAIが提供されています。引き続きこれらのAIを実装して、Chatbotを成長させていきましょう。

GMOインターネット株式会社 Windowsソリューション チーフエグゼクティブ

GMOインターネットでWindowsのサービス開発運用に関わって16年、数年単位で進化し続けるMicrosoftのWindowsは新しもの好きにはたまらない製品です。自動販売機に見たことのないジュースがあれば、迷わすボタンを押します。そんなチャレンジが僕の人生を明るく、楽しくしてくれています。

お名前.com デスクトップクラウド
http://www.onamae-desktop.com/

お名前.com VPS Hyper-V
http://www.onamae-server.com/vps/hyperv/

連載バックナンバー

Web開発技術解説
第10回

Chatbotから話しかける(プロアクティブメッセージの送信)

2018/5/18
連載10回目となる今回は、Chatbotから先に話しかけるための手順を紹介します。
Web開発技術解説
第9回

Chatbotで画像コミュニケーションを実現する

2018/5/14
連載9回目となる今回は、Chatbotとの文字でのやり取りに画像を追加する方法を解説します。
Web開発技術解説
第8回

FacebookでChatbotを公開する

2018/4/17
連載8回目となる今回は、FacebookのMessengerを介してChatbotとやり取りする方法を解説します。

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています